vendor: 918,632 bytes, lines 1-23853
1/*! ableplayer V5.0.0 - with DOMPurify included. Console logs enabled, for development */ 2 3(function (global, factory) { 4 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) : 5 typeof define === 'function' && define.amd ? define(['jquery'], factory) : 6 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AblePlayer = factory(global.jQuery)); 7})(this, (function ($) { 'use strict'; 8 9 /*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */ 10 11 const { 12 entries, 13 setPrototypeOf, 14 isFrozen, 15 getPrototypeOf, 16 getOwnPropertyDescriptor 17 } = Object; 18 let { 19 freeze, 20 seal, 21 create 22 } = Object; // eslint-disable-line import/no-mutable-exports 23 let { 24 apply, 25 construct 26 } = typeof Reflect !== 'undefined' && Reflect; 27 if (!freeze) { 28 freeze = function freeze(x) { 29 return x; 30 }; 31 } 32 if (!seal) { 33 seal = function seal(x) { 34 return x; 35 }; 36 } 37 if (!apply) { 38 apply = function apply(func, thisArg) { 39 for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { 40 args[_key - 2] = arguments[_key]; 41 } 42 return func.apply(thisArg, args); 43 }; 44 } 45 if (!construct) { 46 construct = function construct(Func) { 47 for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 48 args[_key2 - 1] = arguments[_key2]; 49 } 50 return new Func(...args); 51 }; 52 } 53 const arrayForEach = unapply(Array.prototype.forEach); 54 const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf); 55 const arrayPop = unapply(Array.prototype.pop); 56 const arrayPush = unapply(Array.prototype.push); 57 const arraySplice = unapply(Array.prototype.splice); 58 const stringToLowerCase = unapply(String.prototype.toLowerCase); 59 const stringToString = unapply(String.prototype.toString); 60 const stringMatch = unapply(String.prototype.match); 61 const stringReplace = unapply(String.prototype.replace); 62 const stringIndexOf = unapply(String.prototype.indexOf); 63 const stringTrim = unapply(String.prototype.trim); 64 const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty); 65 const regExpTest = unapply(RegExp.prototype.test); 66 const typeErrorCreate = unconstruct(TypeError); 67 /** 68 * Creates a new function that calls the given function with a specified thisArg and arguments. 69 * 70 * @param func - The function to be wrapped and called. 71 * @returns A new function that calls the given function with a specified thisArg and arguments. 72 */ 73 function unapply(func) { 74 return function (thisArg) { 75 if (thisArg instanceof RegExp) { 76 thisArg.lastIndex = 0; 77 } 78 for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { 79 args[_key3 - 1] = arguments[_key3]; 80 } 81 return apply(func, thisArg, args); 82 }; 83 } 84 /** 85 * Creates a new function that constructs an instance of the given constructor function with the provided arguments. 86 * 87 * @param func - The constructor function to be wrapped and called. 88 * @returns A new function that constructs an instance of the given constructor function with the provided arguments. 89 */ 90 function unconstruct(Func) { 91 return function () { 92 for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 93 args[_key4] = arguments[_key4]; 94 } 95 return construct(Func, args); 96 }; 97 } 98 /** 99 * Add properties to a lookup table 100 * 101 * @param set - The set to which elements will be added. 102 * @param array - The array containing elements to be added to the set. 103 * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set. 104 * @returns The modified set with added elements. 105 */ 106 function addToSet(set, array) { 107 let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase; 108 if (setPrototypeOf) { 109 // Make 'in' and truthy checks like Boolean(set.constructor) 110 // independent of any properties defined on Object.prototype. 111 // Prevent prototype setters from intercepting set as a this value. 112 setPrototypeOf(set, null); 113 } 114 let l = array.length; 115 while (l--) { 116 let element = array[l]; 117 if (typeof element === 'string') { 118 const lcElement = transformCaseFunc(element); 119 if (lcElement !== element) { 120 // Config presets (e.g. tags.js, attrs.js) are immutable. 121 if (!isFrozen(array)) { 122 array[l] = lcElement; 123 } 124 element = lcElement; 125 } 126 } 127 set[element] = true; 128 } 129 return set; 130 } 131 /** 132 * Clean up an array to harden against CSPP 133 * 134 * @param array - The array to be cleaned. 135 * @returns The cleaned version of the array 136 */ 137 function cleanArray(array) { 138 for (let index = 0; index < array.length; index++) { 139 const isPropertyExist = objectHasOwnProperty(array, index); 140 if (!isPropertyExist) { 141 array[index] = null; 142 } 143 } 144 return array; 145 } 146 /** 147 * Shallow clone an object 148 * 149 * @param object - The object to be cloned. 150 * @returns A new object that copies the original. 151 */ 152 function clone(object) { 153 const newObject = create(null); 154 for (const [property, value] of entries(object)) { 155 const isPropertyExist = objectHasOwnProperty(object, property); 156 if (isPropertyExist) { 157 if (Array.isArray(value)) { 158 newObject[property] = cleanArray(value); 159 } else if (value && typeof value === 'object' && value.constructor === Object) { 160 newObject[property] = clone(value); 161 } else { 162 newObject[property] = value; 163 } 164 } 165 } 166 return newObject; 167 } 168 /** 169 * This method automatically checks if the prop is function or getter and behaves accordingly. 170 * 171 * @param object - The object to look up the getter function in its prototype chain. 172 * @param prop - The property name for which to find the getter function. 173 * @returns The getter function found in the prototype chain or a fallback function. 174 */ 175 function lookupGetter(object, prop) { 176 while (object !== null) { 177 const desc = getOwnPropertyDescriptor(object, prop); 178 if (desc) { 179 if (desc.get) { 180 return unapply(desc.get); 181 } 182 if (typeof desc.value === 'function') { 183 return unapply(desc.value); 184 } 185 } 186 object = getPrototypeOf(object); 187 } 188 function fallbackValue() { 189 return null; 190 } 191 return fallbackValue; 192 } 193 194 const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); 195 const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); 196 const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); 197 // List of SVG elements that are disallowed by default. 198 // We still need to know them so that we can do namespace 199 // checks properly in case one wants to add them to 200 // allow-list. 201 const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); 202 const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']); 203 // Similarly to SVG, we want to know all MathML elements, 204 // even those that we disallow by default. 205 const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); 206 const text = freeze(['#text']); 207 208 const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']); 209 const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']); 210 const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); 211 const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); 212 213 // eslint-disable-next-line unicorn/better-regex 214 const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode 215 const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); 216 const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex 217 const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape 218 const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape 219 const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape 220 ); 221 const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); 222 const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex 223 ); 224 const DOCTYPE_NAME = seal(/^html$/i); 225 const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i); 226 227 var EXPRESSIONS = /*#__PURE__*/Object.freeze({ 228 __proto__: null, 229 ARIA_ATTR: ARIA_ATTR, 230 ATTR_WHITESPACE: ATTR_WHITESPACE, 231 CUSTOM_ELEMENT: CUSTOM_ELEMENT, 232 DATA_ATTR: DATA_ATTR, 233 DOCTYPE_NAME: DOCTYPE_NAME, 234 ERB_EXPR: ERB_EXPR, 235 IS_ALLOWED_URI: IS_ALLOWED_URI, 236 IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA, 237 MUSTACHE_EXPR: MUSTACHE_EXPR, 238 TMPLIT_EXPR: TMPLIT_EXPR 239 }); 240 241 /* eslint-disable @typescript-eslint/indent */ 242 // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType 243 const NODE_TYPE = { 244 element: 1, 245 text: 3, 246 // Deprecated 247 progressingInstruction: 7, 248 comment: 8, 249 document: 9}; 250 const getGlobal = function getGlobal() { 251 return typeof window === 'undefined' ? null : window; 252 }; 253 /** 254 * Creates a no-op policy for internal use only. 255 * Don't export this function outside this module! 256 * @param trustedTypes The policy factory. 257 * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). 258 * @return The policy created (or null, if Trusted Types 259 * are not supported or creating the policy failed). 260 */ 261 const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) { 262 if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') { 263 return null; 264 } 265 // Allow the callers to control the unique policy name 266 // by adding a data-tt-policy-suffix to the script element with the DOMPurify. 267 // Policy creation with duplicate names throws in Trusted Types. 268 let suffix = null; 269 const ATTR_NAME = 'data-tt-policy-suffix'; 270 if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) { 271 suffix = purifyHostElement.getAttribute(ATTR_NAME); 272 } 273 const policyName = 'dompurify' + (suffix ? '#' + suffix : ''); 274 try { 275 return trustedTypes.createPolicy(policyName, { 276 createHTML(html) { 277 return html; 278 }, 279 createScriptURL(scriptUrl) { 280 return scriptUrl; 281 } 282 }); 283 } catch (_) { 284 // Policy creation failed (most likely another DOMPurify script has 285 // already run). Skip creating the policy, as this will only cause errors 286 // if TT are enforced. 287 console.warn('TrustedTypes policy ' + policyName + ' could not be created.'); 288 return null; 289 } 290 }; 291 const _createHooksMap = function _createHooksMap() { 292 return { 293 afterSanitizeAttributes: [], 294 afterSanitizeElements: [], 295 afterSanitizeShadowDOM: [], 296 beforeSanitizeAttributes: [], 297 beforeSanitizeElements: [], 298 beforeSanitizeShadowDOM: [], 299 uponSanitizeAttribute: [], 300 uponSanitizeElement: [], 301 uponSanitizeShadowNode: [] 302 }; 303 }; 304 function createDOMPurify() { 305 let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); 306 const DOMPurify = root => createDOMPurify(root); 307 DOMPurify.version = '3.4.0'; 308 DOMPurify.removed = []; 309 if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) { 310 // Not running in a browser, provide a factory function 311 // so that you can pass your own Window 312 DOMPurify.isSupported = false; 313 return DOMPurify; 314 } 315 let { 316 document 317 } = window; 318 const originalDocument = document; 319 const currentScript = originalDocument.currentScript; 320 const { 321 DocumentFragment, 322 HTMLTemplateElement, 323 Node, 324 Element, 325 NodeFilter, 326 NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap, 327 HTMLFormElement, 328 DOMParser, 329 trustedTypes 330 } = window; 331 const ElementPrototype = Element.prototype; 332 const cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); 333 const remove = lookupGetter(ElementPrototype, 'remove'); 334 const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); 335 const getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); 336 const getParentNode = lookupGetter(ElementPrototype, 'parentNode'); 337 // As per issue #47, the web-components registry is inherited by a 338 // new document created via createHTMLDocument. As per the spec 339 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) 340 // a new empty registry is used when creating a template contents owner 341 // document, so we use that as our parent document to ensure nothing 342 // is inherited. 343 if (typeof HTMLTemplateElement === 'function') { 344 const template = document.createElement('template'); 345 if (template.content && template.content.ownerDocument) { 346 document = template.content.ownerDocument; 347 } 348 } 349 let trustedTypesPolicy; 350 let emptyHTML = ''; 351 const { 352 implementation, 353 createNodeIterator, 354 createDocumentFragment, 355 getElementsByTagName 356 } = document; 357 const { 358 importNode 359 } = originalDocument; 360 let hooks = _createHooksMap(); 361 /** 362 * Expose whether this browser supports running the full DOMPurify. 363 */ 364 DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined; 365 const { 366 MUSTACHE_EXPR, 367 ERB_EXPR, 368 TMPLIT_EXPR, 369 DATA_ATTR, 370 ARIA_ATTR, 371 IS_SCRIPT_OR_DATA, 372 ATTR_WHITESPACE, 373 CUSTOM_ELEMENT 374 } = EXPRESSIONS; 375 let { 376 IS_ALLOWED_URI: IS_ALLOWED_URI$1 377 } = EXPRESSIONS; 378 /** 379 * We consider the elements and attributes below to be safe. Ideally 380 * don't add any new ones but feel free to remove unwanted ones. 381 */ 382 /* allowed element names */ 383 let ALLOWED_TAGS = null; 384 const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]); 385 /* Allowed attribute names */ 386 let ALLOWED_ATTR = null; 387 const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]); 388 /* 389 * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements. 390 * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) 391 * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) 392 * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. 393 */ 394 let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, { 395 tagNameCheck: { 396 writable: true, 397 configurable: false, 398 enumerable: true, 399 value: null 400 }, 401 attributeNameCheck: { 402 writable: true, 403 configurable: false, 404 enumerable: true, 405 value: null 406 }, 407 allowCustomizedBuiltInElements: { 408 writable: true, 409 configurable: false, 410 enumerable: true, 411 value: false 412 } 413 })); 414 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ 415 let FORBID_TAGS = null; 416 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ 417 let FORBID_ATTR = null; 418 /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */ 419 const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, { 420 tagCheck: { 421 writable: true, 422 configurable: false, 423 enumerable: true, 424 value: null 425 }, 426 attributeCheck: { 427 writable: true, 428 configurable: false, 429 enumerable: true, 430 value: null 431 } 432 })); 433 /* Decide if ARIA attributes are okay */ 434 let ALLOW_ARIA_ATTR = true; 435 /* Decide if custom data attributes are okay */ 436 let ALLOW_DATA_ATTR = true; 437 /* Decide if unknown protocols are okay */ 438 let ALLOW_UNKNOWN_PROTOCOLS = false; 439 /* Decide if self-closing tags in attributes are allowed. 440 * Usually removed due to a mXSS issue in jQuery 3.0 */ 441 let ALLOW_SELF_CLOSE_IN_ATTR = true; 442 /* Output should be safe for common template engines. 443 * This means, DOMPurify removes data attributes, mustaches and ERB 444 */ 445 let SAFE_FOR_TEMPLATES = false; 446 /* Output should be safe even for XML used within HTML and alike. 447 * This means, DOMPurify removes comments when containing risky content. 448 */ 449 let SAFE_FOR_XML = true; 450 /* Decide if document with <html>... should be returned */ 451 let WHOLE_DOCUMENT = false; 452 /* Track whether config is already set on this instance of DOMPurify. */ 453 let SET_CONFIG = false; 454 /* Decide if all elements (e.g. style, script) must be children of 455 * document.body. By default, browsers might move them to document.head */ 456 let FORCE_BODY = false; 457 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html 458 * string (or a TrustedHTML object if Trusted Types are supported). 459 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead 460 */ 461 let RETURN_DOM = false; 462 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html 463 * string (or a TrustedHTML object if Trusted Types are supported) */ 464 let RETURN_DOM_FRAGMENT = false; 465 /* Try to return a Trusted Type object instead of a string, return a string in 466 * case Trusted Types are not supported */ 467 let RETURN_TRUSTED_TYPE = false; 468 /* Output should be free from DOM clobbering attacks? 469 * This sanitizes markups named with colliding, clobberable built-in DOM APIs. 470 */ 471 let SANITIZE_DOM = true; 472 /* Achieve full DOM Clobbering protection by isolating the namespace of named 473 * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. 474 * 475 * HTML/DOM spec rules that enable DOM Clobbering: 476 * - Named Access on Window (§7.3.3) 477 * - DOM Tree Accessors (§3.1.5) 478 * - Form Element Parent-Child Relations (§4.10.3) 479 * - Iframe srcdoc / Nested WindowProxies (§4.8.5) 480 * - HTMLCollection (§4.2.10.2) 481 * 482 * Namespace isolation is implemented by prefixing `id` and `name` attributes 483 * with a constant string, i.e., `user-content-` 484 */ 485 let SANITIZE_NAMED_PROPS = false; 486 const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; 487 /* Keep element content when removing element? */ 488 let KEEP_CONTENT = true; 489 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead 490 * of importing it into a new Document and returning a sanitized copy */ 491 let IN_PLACE = false; 492 /* Allow usage of profiles like html, svg and mathMl */ 493 let USE_PROFILES = {}; 494 /* Tags to ignore content of when KEEP_CONTENT is true */ 495 let FORBID_CONTENTS = null; 496 const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); 497 /* Tags that are safe for data: URIs */ 498 let DATA_URI_TAGS = null; 499 const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); 500 /* Attributes safe for values like "javascript:" */ 501 let URI_SAFE_ATTRIBUTES = null; 502 const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); 503 const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; 504 const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; 505 const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; 506 /* Document namespace */ 507 let NAMESPACE = HTML_NAMESPACE; 508 let IS_EMPTY_INPUT = false; 509 /* Allowed XHTML+XML namespaces */ 510 let ALLOWED_NAMESPACES = null; 511 const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); 512 let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); 513 let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']); 514 // Certain elements are allowed in both SVG and HTML 515 // namespace. We need to specify them explicitly 516 // so that they don't get erroneously deleted from 517 // HTML namespace. 518 const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); 519 /* Parsing of strict XHTML documents */ 520 let PARSER_MEDIA_TYPE = null; 521 const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; 522 const DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; 523 let transformCaseFunc = null; 524 /* Keep a reference to config to pass to hooks */ 525 let CONFIG = null; 526 /* Ideally, do not touch anything below this line */ 527 /* ______________________________________________ */ 528 const formElement = document.createElement('form'); 529 const isRegexOrFunction = function isRegexOrFunction(testValue) { 530 return testValue instanceof RegExp || testValue instanceof Function; 531 }; 532 /** 533 * _parseConfig 534 * 535 * @param cfg optional config literal 536 */ 537 // eslint-disable-next-line complexity 538 const _parseConfig = function _parseConfig() { 539 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 540 if (CONFIG && CONFIG === cfg) { 541 return; 542 } 543 /* Shield configuration object from tampering */ 544 if (!cfg || typeof cfg !== 'object') { 545 cfg = {}; 546 } 547 /* Shield configuration object from prototype pollution */ 548 cfg = clone(cfg); 549 PARSER_MEDIA_TYPE = 550 // eslint-disable-next-line unicorn/prefer-includes 551 SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE; 552 // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. 553 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; 554 /* Set configuration parameters */ 555 ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; 556 ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; 557 ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; 558 URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES; 559 DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS; 560 FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; 561 FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({}); 562 FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({}); 563 USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false; 564 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true 565 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true 566 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false 567 ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true 568 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false 569 SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true 570 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false 571 RETURN_DOM = cfg.RETURN_DOM || false; // Default false 572 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false 573 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false 574 FORCE_BODY = cfg.FORCE_BODY || false; // Default false 575 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true 576 SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false 577 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true 578 IN_PLACE = cfg.IN_PLACE || false; // Default false 579 IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI; 580 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; 581 MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS; 582 HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS; 583 CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null); 584 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { 585 CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; 586 } 587 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) { 588 CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; 589 } 590 if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') { 591 CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; 592 } 593 if (SAFE_FOR_TEMPLATES) { 594 ALLOW_DATA_ATTR = false; 595 } 596 if (RETURN_DOM_FRAGMENT) { 597 RETURN_DOM = true; 598 } 599 /* Parse profile info */ 600 if (USE_PROFILES) { 601 ALLOWED_TAGS = addToSet({}, text); 602 ALLOWED_ATTR = create(null); 603 if (USE_PROFILES.html === true) { 604 addToSet(ALLOWED_TAGS, html$1); 605 addToSet(ALLOWED_ATTR, html); 606 } 607 if (USE_PROFILES.svg === true) { 608 addToSet(ALLOWED_TAGS, svg$1); 609 addToSet(ALLOWED_ATTR, svg); 610 addToSet(ALLOWED_ATTR, xml); 611 } 612 if (USE_PROFILES.svgFilters === true) { 613 addToSet(ALLOWED_TAGS, svgFilters); 614 addToSet(ALLOWED_ATTR, svg); 615 addToSet(ALLOWED_ATTR, xml); 616 } 617 if (USE_PROFILES.mathMl === true) { 618 addToSet(ALLOWED_TAGS, mathMl$1); 619 addToSet(ALLOWED_ATTR, mathMl); 620 addToSet(ALLOWED_ATTR, xml); 621 } 622 } 623 /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent 624 * leaking across calls when switching from function to array config */ 625 EXTRA_ELEMENT_HANDLING.tagCheck = null; 626 EXTRA_ELEMENT_HANDLING.attributeCheck = null; 627 /* Merge configuration parameters */ 628 if (cfg.ADD_TAGS) { 629 if (typeof cfg.ADD_TAGS === 'function') { 630 EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS; 631 } else { 632 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { 633 ALLOWED_TAGS = clone(ALLOWED_TAGS); 634 } 635 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); 636 } 637 } 638 if (cfg.ADD_ATTR) { 639 if (typeof cfg.ADD_ATTR === 'function') { 640 EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR; 641 } else { 642 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { 643 ALLOWED_ATTR = clone(ALLOWED_ATTR); 644 } 645 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); 646 } 647 } 648 if (cfg.ADD_URI_SAFE_ATTR) { 649 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); 650 } 651 if (cfg.FORBID_CONTENTS) { 652 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { 653 FORBID_CONTENTS = clone(FORBID_CONTENTS); 654 } 655 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); 656 } 657 if (cfg.ADD_FORBID_CONTENTS) { 658 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { 659 FORBID_CONTENTS = clone(FORBID_CONTENTS); 660 } 661 addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc); 662 } 663 /* Add #text in case KEEP_CONTENT is set to true */ 664 if (KEEP_CONTENT) { 665 ALLOWED_TAGS['#text'] = true; 666 } 667 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ 668 if (WHOLE_DOCUMENT) { 669 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); 670 } 671 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ 672 if (ALLOWED_TAGS.table) { 673 addToSet(ALLOWED_TAGS, ['tbody']); 674 delete FORBID_TAGS.tbody; 675 } 676 if (cfg.TRUSTED_TYPES_POLICY) { 677 if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') { 678 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); 679 } 680 if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') { 681 throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); 682 } 683 // Overwrite existing TrustedTypes policy. 684 trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; 685 // Sign local variables required by `sanitize`. 686 emptyHTML = trustedTypesPolicy.createHTML(''); 687 } else { 688 // Uninitialized policy, attempt to initialize the internal dompurify policy. 689 if (trustedTypesPolicy === undefined) { 690 trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript); 691 } 692 // If creating the internal policy succeeded sign internal variables. 693 if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') { 694 emptyHTML = trustedTypesPolicy.createHTML(''); 695 } 696 } 697 // Prevent further manipulation of configuration. 698 // Not available in IE8, Safari 5, etc. 699 if (freeze) { 700 freeze(cfg); 701 } 702 CONFIG = cfg; 703 }; 704 /* Keep track of all possible SVG and MathML tags 705 * so that we can perform the namespace checks 706 * correctly. */ 707 const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]); 708 const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]); 709 /** 710 * @param element a DOM element whose namespace is being checked 711 * @returns Return false if the element has a 712 * namespace that a spec-compliant parser would never 713 * return. Return true otherwise. 714 */ 715 const _checkValidNamespace = function _checkValidNamespace(element) { 716 let parent = getParentNode(element); 717 // In JSDOM, if we're inside shadow DOM, then parentNode 718 // can be null. We just simulate parent in this case. 719 if (!parent || !parent.tagName) { 720 parent = { 721 namespaceURI: NAMESPACE, 722 tagName: 'template' 723 }; 724 } 725 const tagName = stringToLowerCase(element.tagName); 726 const parentTagName = stringToLowerCase(parent.tagName); 727 if (!ALLOWED_NAMESPACES[element.namespaceURI]) { 728 return false; 729 } 730 if (element.namespaceURI === SVG_NAMESPACE) { 731 // The only way to switch from HTML namespace to SVG 732 // is via <svg>. If it happens via any other tag, then 733 // it should be killed. 734 if (parent.namespaceURI === HTML_NAMESPACE) { 735 return tagName === 'svg'; 736 } 737 // The only way to switch from MathML to SVG is via` 738 // svg if parent is either <annotation-xml> or MathML 739 // text integration points. 740 if (parent.namespaceURI === MATHML_NAMESPACE) { 741 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); 742 } 743 // We only allow elements that are defined in SVG 744 // spec. All others are disallowed in SVG namespace. 745 return Boolean(ALL_SVG_TAGS[tagName]); 746 } 747 if (element.namespaceURI === MATHML_NAMESPACE) { 748 // The only way to switch from HTML namespace to MathML 749 // is via <math>. If it happens via any other tag, then 750 // it should be killed. 751 if (parent.namespaceURI === HTML_NAMESPACE) { 752 return tagName === 'math'; 753 } 754 // The only way to switch from SVG to MathML is via 755 // <math> and HTML integration points 756 if (parent.namespaceURI === SVG_NAMESPACE) { 757 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; 758 } 759 // We only allow elements that are defined in MathML 760 // spec. All others are disallowed in MathML namespace. 761 return Boolean(ALL_MATHML_TAGS[tagName]); 762 } 763 if (element.namespaceURI === HTML_NAMESPACE) { 764 // The only way to switch from SVG to HTML is via 765 // HTML integration points, and from MathML to HTML 766 // is via MathML text integration points 767 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) { 768 return false; 769 } 770 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { 771 return false; 772 } 773 // We disallow tags that are specific for MathML 774 // or SVG and should never appear in HTML namespace 775 return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); 776 } 777 // For XHTML and XML documents that support custom namespaces 778 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { 779 return true; 780 } 781 // The code should never reach this place (this means 782 // that the element somehow got namespace that is not 783 // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). 784 // Return false just in case. 785 return false; 786 }; 787 /** 788 * _forceRemove 789 * 790 * @param node a DOM node 791 */ 792 const _forceRemove = function _forceRemove(node) { 793 arrayPush(DOMPurify.removed, { 794 element: node 795 }); 796 try { 797 // eslint-disable-next-line unicorn/prefer-dom-node-remove 798 getParentNode(node).removeChild(node); 799 } catch (_) { 800 remove(node); 801 } 802 }; 803 /** 804 * _removeAttribute 805 * 806 * @param name an Attribute name 807 * @param element a DOM node 808 */ 809 const _removeAttribute = function _removeAttribute(name, element) { 810 try { 811 arrayPush(DOMPurify.removed, { 812 attribute: element.getAttributeNode(name), 813 from: element 814 }); 815 } catch (_) { 816 arrayPush(DOMPurify.removed, { 817 attribute: null, 818 from: element 819 }); 820 } 821 element.removeAttribute(name); 822 // We void attribute values for unremovable "is" attributes 823 if (name === 'is') { 824 if (RETURN_DOM || RETURN_DOM_FRAGMENT) { 825 try { 826 _forceRemove(element); 827 } catch (_) {} 828 } else { 829 try { 830 element.setAttribute(name, ''); 831 } catch (_) {} 832 } 833 } 834 }; 835 /** 836 * _initDocument 837 * 838 * @param dirty - a string of dirty markup 839 * @return a DOM, filled with the dirty markup 840 */ 841 const _initDocument = function _initDocument(dirty) { 842 /* Create a HTML document */ 843 let doc = null; 844 let leadingWhitespace = null; 845 if (FORCE_BODY) { 846 dirty = '<remove></remove>' + dirty; 847 } else { 848 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ 849 const matches = stringMatch(dirty, /^[\r\n\t ]+/); 850 leadingWhitespace = matches && matches[0]; 851 } 852 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) { 853 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) 854 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>'; 855 } 856 const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty; 857 /* 858 * Use the DOMParser API by default, fallback later if needs be 859 * DOMParser not work for svg when has multiple root element. 860 */ 861 if (NAMESPACE === HTML_NAMESPACE) { 862 try { 863 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); 864 } catch (_) {} 865 } 866 /* Use createHTMLDocument in case DOMParser is not available */ 867 if (!doc || !doc.documentElement) { 868 doc = implementation.createDocument(NAMESPACE, 'template', null); 869 try { 870 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload; 871 } catch (_) { 872 // Syntax error if dirtyPayload is invalid xml 873 } 874 } 875 const body = doc.body || doc.documentElement; 876 if (dirty && leadingWhitespace) { 877 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); 878 } 879 /* Work on whole document or just its body */ 880 if (NAMESPACE === HTML_NAMESPACE) { 881 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; 882 } 883 return WHOLE_DOCUMENT ? doc.documentElement : body; 884 }; 885 /** 886 * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. 887 * 888 * @param root The root element or node to start traversing on. 889 * @return The created NodeIterator 890 */ 891 const _createNodeIterator = function _createNodeIterator(root) { 892 return createNodeIterator.call(root.ownerDocument || root, root, 893 // eslint-disable-next-line no-bitwise 894 NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null); 895 }; 896 /** 897 * _isClobbered 898 * 899 * @param element element to check for clobbering attacks 900 * @return true if clobbered, false if safe 901 */ 902 const _isClobbered = function _isClobbered(element) { 903 return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function'); 904 }; 905 /** 906 * Checks whether the given object is a DOM node. 907 * 908 * @param value object to check whether it's a DOM node 909 * @return true is object is a DOM node 910 */ 911 const _isNode = function _isNode(value) { 912 return typeof Node === 'function' && value instanceof Node; 913 }; 914 function _executeHooks(hooks, currentNode, data) { 915 arrayForEach(hooks, hook => { 916 hook.call(DOMPurify, currentNode, data, CONFIG); 917 }); 918 } 919 /** 920 * _sanitizeElements 921 * 922 * @protect nodeName 923 * @protect textContent 924 * @protect removeChild 925 * @param currentNode to check for permission to exist 926 * @return true if node was killed, false if left alive 927 */ 928 const _sanitizeElements = function _sanitizeElements(currentNode) { 929 let content = null; 930 /* Execute a hook if present */ 931 _executeHooks(hooks.beforeSanitizeElements, currentNode, null); 932 /* Check if element is clobbered or can clobber */ 933 if (_isClobbered(currentNode)) { 934 _forceRemove(currentNode); 935 return true; 936 } 937 /* Now let's check the element's type and name */ 938 const tagName = transformCaseFunc(currentNode.nodeName); 939 /* Execute a hook if present */ 940 _executeHooks(hooks.uponSanitizeElement, currentNode, { 941 tagName, 942 allowedTags: ALLOWED_TAGS 943 }); 944 /* Detect mXSS attempts abusing namespace confusion */ 945 if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) { 946 _forceRemove(currentNode); 947 return true; 948 } 949 /* Remove risky CSS construction leading to mXSS */ 950 if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) { 951 _forceRemove(currentNode); 952 return true; 953 } 954 /* Remove any occurrence of processing instructions */ 955 if (currentNode.nodeType === NODE_TYPE.progressingInstruction) { 956 _forceRemove(currentNode); 957 return true; 958 } 959 /* Remove any kind of possibly harmful comments */ 960 if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) { 961 _forceRemove(currentNode); 962 return true; 963 } 964 /* Remove element if anything forbids its presence */ 965 if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) { 966 /* Check if we have a custom element to handle */ 967 if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) { 968 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) { 969 return false; 970 } 971 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) { 972 return false; 973 } 974 } 975 /* Keep content except for bad-listed elements */ 976 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { 977 const parentNode = getParentNode(currentNode) || currentNode.parentNode; 978 const childNodes = getChildNodes(currentNode) || currentNode.childNodes; 979 if (childNodes && parentNode) { 980 const childCount = childNodes.length; 981 for (let i = childCount - 1; i >= 0; --i) { 982 const childClone = cloneNode(childNodes[i], true); 983 childClone.__removalCount = (currentNode.__removalCount || 0) + 1; 984 parentNode.insertBefore(childClone, getNextSibling(currentNode)); 985 } 986 } 987 } 988 _forceRemove(currentNode); 989 return true; 990 } 991 /* Check whether element has a valid namespace */ 992 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { 993 _forceRemove(currentNode); 994 return true; 995 } 996 /* Make sure that older browsers don't get fallback-tag mXSS */ 997 if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) { 998 _forceRemove(currentNode); 999 return true; 1000 } 1001 /* Sanitize element content to be template-safe */ 1002 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) { 1003 /* Get the element's text content */ 1004 content = currentNode.textContent; 1005 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { 1006 content = stringReplace(content, expr, ' '); 1007 }); 1008 if (currentNode.textContent !== content) { 1009 arrayPush(DOMPurify.removed, { 1010 element: currentNode.cloneNode() 1011 }); 1012 currentNode.textContent = content; 1013 } 1014 } 1015 /* Execute a hook if present */ 1016 _executeHooks(hooks.afterSanitizeElements, currentNode, null); 1017 return false; 1018 }; 1019 /** 1020 * _isValidAttribute 1021 * 1022 * @param lcTag Lowercase tag name of containing element. 1023 * @param lcName Lowercase attribute name. 1024 * @param value Attribute value. 1025 * @return Returns true if `value` is valid, otherwise false. 1026 */ 1027 // eslint-disable-next-line complexity 1028 const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { 1029 /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */ 1030 if (FORBID_ATTR[lcName]) { 1031 return false; 1032 } 1033 /* Make sure attribute cannot clobber */ 1034 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { 1035 return false; 1036 } 1037 /* Allow valid data-* attributes: At least one character after "-" 1038 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) 1039 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) 1040 We don't need to check the value; it's always URI safe. */ 1041 if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { 1042 if ( 1043 // First condition does a very basic check if a) it's basically a valid custom element tagname AND 1044 // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck 1045 // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck 1046 _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) || 1047 // Alternative, second condition checks if it's an `is`-attribute, AND 1048 // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck 1049 lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { 1050 return false; 1051 } 1052 /* Check value is safe. First, is attr inert? If so, is safe */ 1053 } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) { 1054 return false; 1055 } else ; 1056 return true; 1057 }; 1058 /** 1059 * _isBasicCustomElement 1060 * checks if at least one dash is included in tagName, and it's not the first char 1061 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name 1062 * 1063 * @param tagName name of the tag of the node to sanitize 1064 * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false. 1065 */ 1066 const _isBasicCustomElement = function _isBasicCustomElement(tagName) { 1067 return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT); 1068 }; 1069 /** 1070 * _sanitizeAttributes 1071 * 1072 * @protect attributes 1073 * @protect nodeName 1074 * @protect removeAttribute 1075 * @protect setAttribute 1076 * 1077 * @param currentNode to sanitize 1078 */ 1079 const _sanitizeAttributes = function _sanitizeAttributes(currentNode) { 1080 /* Execute a hook if present */ 1081 _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null); 1082 const { 1083 attributes 1084 } = currentNode; 1085 /* Check if we have attributes; if not we might have a text node */ 1086 if (!attributes || _isClobbered(currentNode)) { 1087 return; 1088 } 1089 const hookEvent = { 1090 attrName: '', 1091 attrValue: '', 1092 keepAttr: true, 1093 allowedAttributes: ALLOWED_ATTR, 1094 forceKeepAttr: undefined 1095 }; 1096 let l = attributes.length; 1097 /* Go backwards over all attributes; safely remove bad ones */ 1098 while (l--) { 1099 const attr = attributes[l]; 1100 const { 1101 name, 1102 namespaceURI, 1103 value: attrValue 1104 } = attr; 1105 const lcName = transformCaseFunc(name); 1106 const initValue = attrValue; 1107 let value = name === 'value' ? initValue : stringTrim(initValue); 1108 /* Execute a hook if present */ 1109 hookEvent.attrName = lcName; 1110 hookEvent.attrValue = value; 1111 hookEvent.keepAttr = true; 1112 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set 1113 _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent); 1114 value = hookEvent.attrValue; 1115 /* Full DOM Clobbering protection via namespace isolation, 1116 * Prefix id and name attributes with `user-content-` 1117 */ 1118 if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { 1119 // Remove the attribute with this value 1120 _removeAttribute(name, currentNode); 1121 // Prefix the value and later re-create the attribute with the sanitized value 1122 value = SANITIZE_NAMED_PROPS_PREFIX + value; 1123 } 1124 /* Work around a security issue with comments inside attributes */ 1125 if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) { 1126 _removeAttribute(name, currentNode); 1127 continue; 1128 } 1129 /* Make sure we cannot easily use animated hrefs, even if animations are allowed */ 1130 if (lcName === 'attributename' && stringMatch(value, 'href')) { 1131 _removeAttribute(name, currentNode); 1132 continue; 1133 } 1134 /* Did the hooks approve of the attribute? */ 1135 if (hookEvent.forceKeepAttr) { 1136 continue; 1137 } 1138 /* Did the hooks approve of the attribute? */ 1139 if (!hookEvent.keepAttr) { 1140 _removeAttribute(name, currentNode); 1141 continue; 1142 } 1143 /* Work around a security issue in jQuery 3.0 */ 1144 if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { 1145 _removeAttribute(name, currentNode); 1146 continue; 1147 } 1148 /* Sanitize attribute content to be template-safe */ 1149 if (SAFE_FOR_TEMPLATES) { 1150 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { 1151 value = stringReplace(value, expr, ' '); 1152 }); 1153 } 1154 /* Is `value` valid for this attribute? */ 1155 const lcTag = transformCaseFunc(currentNode.nodeName); 1156 if (!_isValidAttribute(lcTag, lcName, value)) { 1157 _removeAttribute(name, currentNode); 1158 continue; 1159 } 1160 /* Handle attributes that require Trusted Types */ 1161 if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') { 1162 if (namespaceURI) ; else { 1163 switch (trustedTypes.getAttributeType(lcTag, lcName)) { 1164 case 'TrustedHTML': 1165 { 1166 value = trustedTypesPolicy.createHTML(value); 1167 break; 1168 } 1169 case 'TrustedScriptURL': 1170 { 1171 value = trustedTypesPolicy.createScriptURL(value); 1172 break; 1173 } 1174 } 1175 } 1176 } 1177 /* Handle invalid data-* attribute set by try-catching it */ 1178 if (value !== initValue) { 1179 try { 1180 if (namespaceURI) { 1181 currentNode.setAttributeNS(namespaceURI, name, value); 1182 } else { 1183 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ 1184 currentNode.setAttribute(name, value); 1185 } 1186 if (_isClobbered(currentNode)) { 1187 _forceRemove(currentNode); 1188 } else { 1189 arrayPop(DOMPurify.removed); 1190 } 1191 } catch (_) { 1192 _removeAttribute(name, currentNode); 1193 } 1194 } 1195 } 1196 /* Execute a hook if present */ 1197 _executeHooks(hooks.afterSanitizeAttributes, currentNode, null); 1198 }; 1199 /** 1200 * _sanitizeShadowDOM 1201 * 1202 * @param fragment to iterate over recursively 1203 */ 1204 const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) { 1205 let shadowNode = null; 1206 const shadowIterator = _createNodeIterator(fragment); 1207 /* Execute a hook if present */ 1208 _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null); 1209 while (shadowNode = shadowIterator.nextNode()) { 1210 /* Execute a hook if present */ 1211 _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null); 1212 /* Sanitize tags and elements */ 1213 _sanitizeElements(shadowNode); 1214 /* Check attributes next */ 1215 _sanitizeAttributes(shadowNode); 1216 /* Deep shadow DOM detected */ 1217 if (shadowNode.content instanceof DocumentFragment) { 1218 _sanitizeShadowDOM2(shadowNode.content); 1219 } 1220 } 1221 /* Execute a hook if present */ 1222 _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null); 1223 }; 1224 // eslint-disable-next-line complexity 1225 DOMPurify.sanitize = function (dirty) { 1226 let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1227 let body = null; 1228 let importedNode = null; 1229 let currentNode = null; 1230 let returnNode = null; 1231 /* Make sure we have a string to sanitize. 1232 DO NOT return early, as this will return the wrong type if 1233 the user has requested a DOM object rather than a string */ 1234 IS_EMPTY_INPUT = !dirty; 1235 if (IS_EMPTY_INPUT) { 1236 dirty = '<!-->'; 1237 } 1238 /* Stringify, in case dirty is an object */ 1239 if (typeof dirty !== 'string' && !_isNode(dirty)) { 1240 if (typeof dirty.toString === 'function') { 1241 dirty = dirty.toString(); 1242 if (typeof dirty !== 'string') { 1243 throw typeErrorCreate('dirty is not a string, aborting'); 1244 } 1245 } else { 1246 throw typeErrorCreate('toString is not a function'); 1247 } 1248 } 1249 /* Return dirty HTML if DOMPurify cannot run */ 1250 if (!DOMPurify.isSupported) { 1251 return dirty; 1252 } 1253 /* Assign config vars */ 1254 if (!SET_CONFIG) { 1255 _parseConfig(cfg); 1256 } 1257 /* Clean up removed elements */ 1258 DOMPurify.removed = []; 1259 /* Check if dirty is correctly typed for IN_PLACE */ 1260 if (typeof dirty === 'string') { 1261 IN_PLACE = false; 1262 } 1263 if (IN_PLACE) { 1264 /* Do some early pre-sanitization to avoid unsafe root nodes */ 1265 if (dirty.nodeName) { 1266 const tagName = transformCaseFunc(dirty.nodeName); 1267 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { 1268 throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place'); 1269 } 1270 } 1271 } else if (dirty instanceof Node) { 1272 /* If dirty is a DOM element, append to an empty document to avoid 1273 elements being stripped by the parser */ 1274 body = _initDocument('<!---->'); 1275 importedNode = body.ownerDocument.importNode(dirty, true); 1276 if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') { 1277 /* Node is already a body, use as is */ 1278 body = importedNode; 1279 } else if (importedNode.nodeName === 'HTML') { 1280 body = importedNode; 1281 } else { 1282 // eslint-disable-next-line unicorn/prefer-dom-node-append 1283 body.appendChild(importedNode); 1284 } 1285 } else { 1286 /* Exit directly if we have nothing to do */ 1287 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && 1288 // eslint-disable-next-line unicorn/prefer-includes 1289 dirty.indexOf('<') === -1) { 1290 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; 1291 } 1292 /* Initialize the document to work on */ 1293 body = _initDocument(dirty); 1294 /* Check we have a DOM node from the data */ 1295 if (!body) { 1296 return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; 1297 } 1298 } 1299 /* Remove first element node (ours) if FORCE_BODY is set */ 1300 if (body && FORCE_BODY) { 1301 _forceRemove(body.firstChild); 1302 } 1303 /* Get node iterator */ 1304 const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body); 1305 /* Now start iterating over the created document */ 1306 while (currentNode = nodeIterator.nextNode()) { 1307 /* Sanitize tags and elements */ 1308 _sanitizeElements(currentNode); 1309 /* Check attributes next */ 1310 _sanitizeAttributes(currentNode); 1311 /* Shadow DOM detected, sanitize it */ 1312 if (currentNode.content instanceof DocumentFragment) { 1313 _sanitizeShadowDOM2(currentNode.content); 1314 } 1315 } 1316 /* If we sanitized `dirty` in-place, return it. */ 1317 if (IN_PLACE) { 1318 return dirty; 1319 } 1320 /* Return sanitized string or DOM */ 1321 if (RETURN_DOM) { 1322 if (SAFE_FOR_TEMPLATES) { 1323 body.normalize(); 1324 let html = body.innerHTML; 1325 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { 1326 html = stringReplace(html, expr, ' '); 1327 }); 1328 body.innerHTML = html; 1329 } 1330 if (RETURN_DOM_FRAGMENT) { 1331 returnNode = createDocumentFragment.call(body.ownerDocument); 1332 while (body.firstChild) { 1333 // eslint-disable-next-line unicorn/prefer-dom-node-append 1334 returnNode.appendChild(body.firstChild); 1335 } 1336 } else { 1337 returnNode = body; 1338 } 1339 if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) { 1340 /* 1341 AdoptNode() is not used because internal state is not reset 1342 (e.g. the past names map of a HTMLFormElement), this is safe 1343 in theory but we would rather not risk another attack vector. 1344 The state that is cloned by importNode() is explicitly defined 1345 by the specs. 1346 */ 1347 returnNode = importNode.call(originalDocument, returnNode, true); 1348 } 1349 return returnNode; 1350 } 1351 let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; 1352 /* Serialize doctype if allowed */ 1353 if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { 1354 serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML; 1355 } 1356 /* Sanitize final string template-safe */ 1357 if (SAFE_FOR_TEMPLATES) { 1358 arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => { 1359 serializedHTML = stringReplace(serializedHTML, expr, ' '); 1360 }); 1361 } 1362 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; 1363 }; 1364 DOMPurify.setConfig = function () { 1365 let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 1366 _parseConfig(cfg); 1367 SET_CONFIG = true; 1368 }; 1369 DOMPurify.clearConfig = function () { 1370 CONFIG = null; 1371 SET_CONFIG = false; 1372 }; 1373 DOMPurify.isValidAttribute = function (tag, attr, value) { 1374 /* Initialize shared config vars if necessary. */ 1375 if (!CONFIG) { 1376 _parseConfig({}); 1377 } 1378 const lcTag = transformCaseFunc(tag); 1379 const lcName = transformCaseFunc(attr); 1380 return _isValidAttribute(lcTag, lcName, value); 1381 }; 1382 DOMPurify.addHook = function (entryPoint, hookFunction) { 1383 if (typeof hookFunction !== 'function') { 1384 return; 1385 } 1386 arrayPush(hooks[entryPoint], hookFunction); 1387 }; 1388 DOMPurify.removeHook = function (entryPoint, hookFunction) { 1389 if (hookFunction !== undefined) { 1390 const index = arrayLastIndexOf(hooks[entryPoint], hookFunction); 1391 return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0]; 1392 } 1393 return arrayPop(hooks[entryPoint]); 1394 }; 1395 DOMPurify.removeHooks = function (entryPoint) { 1396 hooks[entryPoint] = []; 1397 }; 1398 DOMPurify.removeAllHooks = function () { 1399 hooks = _createHooksMap(); 1400 }; 1401 return DOMPurify; 1402 } 1403 var purify = createDOMPurify(); 1404 1405 /*jslint node: true, browser: true, white: true, indent: 2, unparam: true, plusplus: true */ 1406 1407 1408 // maintain an array of Able Player instances for use globally (e.g., for keeping prefs in sync) 1409 // 5.0.0: this is now a Set to make it easier to create and destroy players 1410 const ablePlayerInstances = new Set(); 1411 1412 /** 1413 * Performs one-time setup on `window`. 1414 * 1415 * Does nothing if `window` is not available, for example in SSR. 1416 */ 1417 function ablePlayerSetupWindow() { 1418 if (typeof window === 'undefined') { 1419 console.log("`window` is undefined. Skipping one-time Able Player `window` setup."); 1420 return; 1421 } 1422 $(function () { 1423 if (typeof purify === 'undefined') { 1424 console.warn('Required dependency DOMPurify not available. Please use the full Able Player bundle which has DOMPurify built in. Or, keep using this bundle, and include DOMPurify separately.'); 1425 } 1426 1427 $('video, audio').each(function (index, element) { 1428 if ($(element).data('able-player') !== undefined) { 1429 new AblePlayer($(this),$(element)); 1430 } 1431 }); 1432 }); 1433 1434 // YouTube player support; pass ready event to jQuery so we can catch in player. 1435 window.onYouTubeIframeAPIReady = function() { 1436 AblePlayer.youTubeIframeAPIReady = true; 1437 $('body').trigger('youTubeIframeAPIReady', []); 1438 }; 1439 1440 // If there is only one player on the page, dispatch global keydown events to it 1441 // Otherwise, keydowwn events are handled locally (see event.js > handleEventListeners()) 1442 $(window).on('keydown',function(e) { 1443 if (AblePlayer.hasSingleInstance()) { 1444 const singleInstance = AblePlayer.getSingleInstance(); 1445 singleInstance.onPlayerKeyPress(e); 1446 } 1447 }); 1448 } 1449 1450 // Outdented for a simpler diff during module conversion 1451 /** 1452 * Construct the AblePlayer object. 1453 * 1454 * Able Player needs `window` to instantiate, so, skip the constructor if 1455 * you are running outside the browser (for example, SSR). 1456 * 1457 * @param object media jQuery selector or element identifying the media. 1458 */ 1459 function AblePlayer(media) { 1460 1461 if (typeof window === 'undefined') { 1462 console.warn("`window` is undefined. Able Player needs `window` to instantiate. Skip constructing Able Player if you are running outside a browser (for example, SSR)."); 1463 return; 1464 } 1465 1466 var thisObj = this; 1467 1468 this.media = media; 1469 1470 if ($(media).length === 0) { 1471 this.provideFallback(); 1472 return; 1473 } 1474 1475 // Default variables assignment 1476 // The following variables CAN be overridden with HTML attributes 1477 1478 // autoplay (Boolean; if present always resolves to true, regardless of value) 1479 if ($(media).attr('autoplay') !== undefined) { 1480 this.autoplay = true; // this value remains constant 1481 this.okToPlay = true; // this value can change dynamically 1482 } else { 1483 this.autoplay = false; 1484 this.okToPlay = false; 1485 } 1486 1487 // loop (Boolean; if present always resolves to true, regardless of value) 1488 this.loop = ($(media).attr('loop') !== undefined) ? true : false; 1489 1490 // playsinline (Boolean; if present always resolves to true, regardless of value) 1491 this.playsInline = ($(media).attr('playsinline') !== undefined) ? '1' : '0'; 1492 1493 // poster (Boolean, indicating whether media element has a poster attribute) 1494 this.hasPoster = ( $(media).attr('poster') || $(media).data('poster') ) ? true : false; 1495 1496 this.audioPoster = $(media).data('poster'); 1497 this.audioPosterAlt = $(media).data('poster-alt' ); 1498 1499 // get height and width attributes, if present 1500 // and add them to variables 1501 // Not currently used, but might be useful for resizing player 1502 this.width = $(media).attr('width') ?? 0; 1503 this.height = $(media).attr('height') ?? 0; 1504 1505 // start-time 1506 var startTime = $(media).data('start-time'); 1507 var isNumeric = ( typeof startTime === 'number' || ( typeof startTime === 'string' && startTime.trim() !== '' && ! isNaN(startTime) && isFinite( Number(startTime) ) ) ) ? true : false; 1508 this.startTime = ( startTime !== undefined && isNumeric ) ? startTime : 0; 1509 1510 // debug 1511 this.debug = ($(media).data('debug') !== undefined && $(media).data('debug') !== false) ? true : false; 1512 1513 // Volume 1514 // Range is 0 to 10. Best not to crank it to avoid overpowering screen readers 1515 this.defaultVolume = 7; 1516 if ($(media).data('volume') !== undefined && $(media).data('volume') !== "") { 1517 var volume = $(media).data('volume'); 1518 if (volume >= 0 && volume <= 10) { 1519 this.defaultVolume = volume; 1520 } 1521 } 1522 this.volume = this.defaultVolume; 1523 1524 // Optional Buttons 1525 // Buttons are added to the player controller if relevant media is present 1526 // However, in some applications it might be undesirable to show buttons 1527 // (e.g., if chapters or transcripts are provided in an external container) 1528 1529 if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) { 1530 this.useChaptersButton = false; 1531 } else { 1532 this.useChaptersButton = true; 1533 } 1534 1535 // Control whether text descriptions are read aloud 1536 // set to "false" if the sole purpose of the WebVTT descriptions file 1537 // is to integrate text description into the transcript 1538 // set to "true" to write description text to a div 1539 // This variable does *not* control the method by which description is read. 1540 // For that, see below (this.descMethod) 1541 if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { 1542 this.readDescriptionsAloud = false; 1543 } else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { 1544 // support both singular and plural spelling of attribute 1545 this.readDescriptionsAloud = false; 1546 } else { 1547 this.readDescriptionsAloud = true; 1548 } 1549 1550 // setting initial this.descVoices to an empty array 1551 // to be populated later by getBrowserVoices 1552 this.descVoices = []; 1553 1554 // Method by which text descriptions are read 1555 // valid values of data-desc-reader are: 1556 // 'brower' (default) - text-based audio description is handled by the browser, if supported 1557 // 'screenreader' - text-based audio description is always handled by screen readers 1558 // The latter may be preferable by owners of websites in languages that are not well supported 1559 // by the Web Speech API 1560 this.descReader = ($(media).data('desc-reader') == 'screenreader') ? 'screenreader' : 'browser'; 1561 1562 // Default state of captions and descriptions 1563 // This setting is overridden by user preferences, if they exist 1564 // values for data-state-captions and data-state-descriptions are 'on' or 'off' 1565 this.defaultStateCaptions = ($(media).data('state-captions') == 'off') ? 0 : 1; 1566 this.defaultStateDescriptions = ($(media).data('state-descriptions') == 'on') ? 1 : 0; 1567 1568 // Default setting for prefDescPause 1569 // Extended description (i.e., pausing during description) is on by default 1570 // but this settings give website owners control over that 1571 // since they know the nature of their videos, and whether pausing is necessary 1572 // This setting is overridden by user preferences, if they exist 1573 this.defaultDescPause = ($(media).data('desc-pause-default') == 'off') ? 0 : 1; 1574 1575 // Headings 1576 // By default, an off-screen heading is automatically added to the top of the media player 1577 // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel() 1578 // Authors can override this behavior by manually assigning a heading level using data-heading-level 1579 // Accepted values are 1-6, or 0 which indicates "no heading" 1580 // (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this) 1581 if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") { 1582 var headingLevel = $(media).data('heading-level'); 1583 if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0 1584 this.playerHeadingLevel = headingLevel; 1585 } 1586 } 1587 1588 // Transcripts 1589 // There are three types of interactive transcripts. 1590 // In descending of order of precedence (in case there are conflicting tags), they are: 1591 // 1. "manual" - A manually coded external transcript (requires data-transcript-src) 1592 // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div & a valid target element) 1593 // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button 1594 // If data-include-transcript="false", there is no "popup" transcript 1595 var transcriptDivLocation = $(media).data('transcript-div'); 1596 if ( transcriptDivLocation !== undefined && transcriptDivLocation !== "" && null !== document.getElementById( transcriptDivLocation ) ) { 1597 this.transcriptDivLocation = transcriptDivLocation; 1598 } else { 1599 this.transcriptDivLocation = null; 1600 } 1601 var includeTranscript = $(media).data('include-transcript'); 1602 this.hideTranscriptButton = ( includeTranscript !== undefined && includeTranscript === false) ? true : false; 1603 1604 this.transcriptType = null; 1605 if ($(media).data('transcript-src') !== undefined) { 1606 this.transcriptSrc = $(media).data('transcript-src'); 1607 if (this.transcriptSrcHasRequiredParts()) { 1608 this.transcriptType = 'manual'; 1609 } else { 1610 console.log('ERROR: Able Player transcript is missing required parts'); 1611 } 1612 } else if ($(media).find('track[kind="captions"],track[kind="subtitles"],track:not([kind])').length > 0) { 1613 // required tracks are present. COULD automatically generate a transcript 1614 this.transcriptType = (this.transcriptDivLocation) ? 'external' : 'popup'; 1615 } 1616 1617 // In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript 1618 // If false (default), line breaks are are removed from transcripts for a more seamless reading experience 1619 // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem 1620 this.lyricsMode = ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) ? true : false; 1621 1622 // Set Transcript Title if defined explicitly. See transcript.js. 1623 if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { 1624 this.transcriptTitle = $(media).data('transcript-title'); 1625 } 1626 1627 // Sign Language 1628 // sign language can be a modal (default) or assigned to a div on the page. 1629 var signDivLocation = $(media).data('sign-div'); 1630 if ( signDivLocation !== undefined && signDivLocation !== "" && null !== document.getElementById( signDivLocation ) ) { 1631 this.$signDivLocation = $( '#' + signDivLocation ); 1632 } else { 1633 this.$signDivLocation = null; 1634 } 1635 1636 // Captions 1637 // data-captions-position can be used to set the default captions position 1638 // this is only the default, and can be overridden by user preferences 1639 // valid values of data-captions-position are 'below' and 'overlay' 1640 this.defaultCaptionsPosition = ($(media).data('captions-position') === 'overlay') ? 'overlay' : 'below'; 1641 1642 // Chapters 1643 var chaptersDiv = $(media).data('chapters-div'); 1644 if ( chaptersDiv !== undefined && chaptersDiv !== "") { 1645 this.chaptersDivLocation = chaptersDiv; 1646 } 1647 1648 if ($(media).data('chapters-title') !== undefined) { 1649 // NOTE: empty string is valid; results in no title being displayed 1650 this.chaptersTitle = $(media).data('chapters-title'); 1651 } 1652 1653 var defaultChapter = $(media).data('chapters-default'); 1654 this.defaultChapter = ( defaultChapter !== undefined && defaultChapter !== "") ? defaultChapter : null; 1655 1656 // Slower/Faster buttons 1657 // valid values of data-speed-icons are 'animals' (default) and 'arrows' 1658 // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows 1659 this.speedIcons = ($(media).data('speed-icons') === 'arrows') ? 'arrows' : 'animals'; 1660 1661 // Seekbar 1662 // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' 1663 var seekbarScope = $(media).data('seekbar-scope'); 1664 this.seekbarScope = ( seekbarScope === 'chapter' || seekbarScope === 'chapters') ? 'chapter' : 'video'; 1665 1666 // YouTube 1667 var youTubeId = $(media).data('youtube-id'); 1668 if ( youTubeId !== undefined && youTubeId !== "") { 1669 this.youTubeId = this.getYouTubeId(youTubeId); 1670 if ( ! this.hasPoster ) { 1671 let poster = this.getYouTubePosterUrl(this.youTubeId,'640'); 1672 $(media).attr( 'poster', poster ); 1673 } 1674 } 1675 1676 var youTubeDescId = $(media).data('youtube-desc-id'); 1677 if ( youTubeDescId !== undefined && youTubeDescId !== "") { 1678 this.youTubeDescId = this.getYouTubeId(youTubeDescId); 1679 } 1680 1681 var youTubeSignId = $(media).data('youtube-sign-src'); 1682 if ( youTubeSignId !== undefined && youTubeSignId !== "") { 1683 this.youTubeSignId = this.getYouTubeId(youTubeSignId); 1684 } 1685 1686 var youTubeNoCookie = $(media).data('youtube-nocookie'); 1687 this.youTubeNoCookie = (youTubeNoCookie !== undefined && youTubeNoCookie) ? true : false; 1688 1689 // Vimeo 1690 var vimeoId = $(media).data('vimeo-id'); 1691 if ( vimeoId !== undefined && vimeoId !== "") { 1692 this.vimeoId = this.getVimeoId(vimeoId); 1693 if ( ! this.hasPoster ) { 1694 let poster = thisObj.getVimeoPosterUrl(this.vimeoId,'1200'); 1695 $(media).attr( 'poster', poster ); 1696 } 1697 } 1698 var vimeoDescId = $(media).data('vimeo-desc-id'); 1699 if ( vimeoDescId !== undefined && vimeoDescId !== "") { 1700 this.vimeoDescId = this.getVimeoId(vimeoDescId); 1701 } 1702 1703 // Skin 1704 // valid values of data-skin are: 1705 // '2020' (default as of 4.6), all buttons in one row beneath a full-width seekbar 1706 // 'legacy', two rows of controls; seekbar positioned in available space within top row 1707 this.skin = ($(media).data('skin') == 'legacy') ? 'legacy' : '2020'; 1708 1709 // Size 1710 // width of Able Player is determined using the following order of precedence: 1711 // 1. data-width attribute 1712 // 2. width attribute (for video or audio, although it is not valid HTML for audio) 1713 // 3. Intrinsic size from video (video only, determined later) 1714 if ($(media).data('width') !== undefined) { 1715 this.playerWidth = parseInt($(media).data('width')); 1716 } else if ($(media)[0].getAttribute('width')) { 1717 // NOTE: jQuery attr() returns null for all invalid HTML attributes 1718 // (e.g., width on <audio>) 1719 // but it can be acessed via JavaScript getAttribute() 1720 this.playerWidth = parseInt($(media)[0].getAttribute('width')); 1721 } else { 1722 this.playerWidth = null; 1723 } 1724 1725 var allowFullScreen = $(media).data('allow-fullscreen'); 1726 this.allowFullscreen = (allowFullScreen !== undefined && allowFullScreen === false) ? false : true; 1727 1728 // Define other variables that are used in fullscreen program flow 1729 this.clickedFullscreenButton = false; 1730 this.restoringAfterFullscreen = false; 1731 1732 // Seek interval 1733 // Number of seconds to seek forward or back with Rewind & Forward buttons 1734 // Unless specified with data-seek-interval, the default value is re-calculated in initialize.js > setSeekInterval(); 1735 // Calculation attempts to intelligently assign a reasonable interval based on media length 1736 this.defaultSeekInterval = 10; 1737 this.useFixedSeekInterval = false; // will change to true if media has valid data-seek-interval attribute 1738 if ($(media).data('seek-interval') !== undefined && $(media).data('seek-interval') !== "") { 1739 var seekInterval = $(media).data('seek-interval'); 1740 if (/^[1-9][0-9]*$/.test(seekInterval)) { // must be a whole number greater than 0 1741 this.seekInterval = seekInterval; 1742 this.useFixedSeekInterval = true; // do not override with calculuation 1743 } 1744 } 1745 1746 // Now Playing 1747 // Shows "Now Playing:" plus the title of the current track above player 1748 // Only used if there is a playlist 1749 var showNowPlaying = $(media).data('show-now-playing'); 1750 this.showNowPlaying = (showNowPlaying !== undefined && showNowPlaying === false) ? false : true; 1751 1752 // Fallback 1753 // The data-test-fallback attribute can be used to test the fallback solution in any browser 1754 var testFallback = $(media).data('test-fallback'); 1755 if ( testFallback !== undefined && testFallback !== false) { 1756 // 1: build error; 2: browser doesn't support media. 1757 this.testFallback = ( testFallback == '2' ) ? 2 : 1; 1758 } else { 1759 this.testFallback = false; 1760 } 1761 1762 // Language 1763 // Player language is determined given the following precedence: 1764 // 1. The value of data-lang on the media element, if provided and a matching translation file is available 1765 // 2. Lang attribute on <html> or <body>, if a matching translation file is available 1766 // 3. English 1767 // Final calculation occurs in translation.js > getTranslationText() 1768 var lang = $(media).data('lang'); 1769 this.lang = ( lang !== undefined && lang !== "") ? lang.toLowerCase() : null; 1770 1771 // Metadata Tracks 1772 var metaType = $(media).data('meta-type'); 1773 if ( metaType !== undefined && metaType !== "") { 1774 this.metaType = metaType; 1775 } 1776 var metaDiv = $(media).data('meta-div'); 1777 if ( metaDiv !== undefined && metaDiv !== "") { 1778 this.metaDiv = metaDiv; 1779 } 1780 1781 // Search 1782 // conducting a search requires an external div in which to write the results 1783 var searchDiv = $(media).data('search-div'); 1784 if ( searchDiv !== undefined && searchDiv !== "") { 1785 1786 this.searchDiv = searchDiv; 1787 1788 // Search term (optional; could be assigned later in a JavaScript application) 1789 var searchString = $(media).data('search'); 1790 if ( searchString !== undefined && searchString !== "") { 1791 this.searchString = searchString; 1792 } 1793 1794 // Search Language 1795 var searchLang = $(media).data('search-lang'); 1796 this.searchLang = ( searchLang !== undefined && searchLang !== "") ? searchLang : null; 1797 1798 // Search option: Ignore capitalization in search terms 1799 var searchIgnoreCaps = $(media).data('search-ignore-caps'); 1800 this.searchIgnoreCaps = ( searchIgnoreCaps !== undefined && searchIgnoreCaps !== false) ? true : false; 1801 } 1802 1803 // Hide controls when video starts playing 1804 // They will reappear again when user presses a key or moves the mouse 1805 // As of v4.0, controls are hidden automatically on playback in fullscreen mode 1806 if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) { 1807 this.hideControls = true; 1808 this.hideControlsOriginal = true; // a copy of hideControls, since the former may change if user enters full screen mode 1809 } else { 1810 this.hideControls = false; 1811 this.hideControlsOriginal = false; 1812 } 1813 1814 // Steno mode 1815 // Enable support for Able Player keyboard shortcuts in textaarea fields 1816 // so users can control the player while transcribing 1817 if ($(media).data('steno-mode') !== undefined && $(media).data('steno-mode') !== false) { 1818 this.stenoMode = true; 1819 // Add support for stenography in an iframe via data-steno-iframe-id 1820 if ($(media).data('steno-iframe-id') !== undefined && $(media).data('steno-iframe-id') !== "") { 1821 this.stenoFrameId = $(media).data('steno-iframe-id'); 1822 this.$stenoFrame = $('#' + this.stenoFrameId); 1823 if (!(this.$stenoFrame.length)) { 1824 // iframe not found 1825 this.stenoFrameId = null; 1826 this.$stenoFrame = null; 1827 } 1828 } else { 1829 this.stenoFrameId = null; 1830 this.$stenoFrame = null; 1831 } 1832 } else { 1833 this.stenoMode = false; 1834 this.stenoFrameId = null; 1835 this.$stenoFrame = null; 1836 } 1837 1838 // Define built-in variables that CANNOT be overridden with HTML attributes 1839 this.setDefaults(); 1840 1841 //////////////////////////////////////// 1842 // End assignment of default variables 1843 //////////////////////////////////////// 1844 1845 this.ableIndex = AblePlayer.nextIndex; 1846 AblePlayer.nextIndex += 1; 1847 1848 this.title = $(media).attr('title'); 1849 1850 // populate translation object with localized versions of all labels and prompts 1851 this.tt = {}; 1852 try { 1853 this.getTranslationText(); 1854 this.setup(); 1855 } catch (e) { 1856 console.warn('Error setting up translations:', e); 1857 this.provideFallback(); 1858 } 1859 1860 ablePlayerInstances.add(this); 1861 } 1862 // Index to increment every time new player is created. 1863 // 5.0.0: this is now only used to generate unique IDs. Otherwise use hasSingleInstance. 1864 AblePlayer.nextIndex = 0; 1865 1866 AblePlayer.prototype.setup = function() { 1867 1868 var thisObj = this; 1869 this.initializing = true; // will remain true until entire sequence of function calls is complete 1870 1871 this.reinitialize().then(function () { 1872 if (!thisObj.player) { 1873 // No player for this media, show last-line fallback. 1874 thisObj.provideFallback(); 1875 } else { 1876 thisObj.setupInstance().then(function () { 1877 thisObj.setupInstancePlaylist(); 1878 if (thisObj.hasPlaylist) ; else { 1879 thisObj.recreatePlayer().then(function() { 1880 thisObj.initializing = false; 1881 thisObj.playerCreated = true; // remains true until browser is refreshed 1882 }); 1883 } 1884 }); 1885 } 1886 }); 1887 }; 1888 1889 /** 1890 * Removes this player from the global instance list. 1891 * 1892 * You probably want to call this during/after removing a player from the 1893 * DOM. This avoids memory leaks, and allows the event handling to have the 1894 * correct count of how many players are actually on the page. 1895 */ 1896 AblePlayer.prototype.dispose = function () { 1897 AblePlayer.ablePlayerInstances.delete(this); 1898 1899 // Look for various dialogs tied to this instance. Elements associated 1900 // with these are appended to the body, and they need to be 1901 // `.remove()`d here. 1902 const dialogs = [ 1903 this.captionPrefsDialog, 1904 this.descPrefsDialog, 1905 this.keyboardPrefsDialog, 1906 this.transcriptPrefsDialog, 1907 this.transcriptResizeDialog, 1908 this.signResizeDialog, 1909 ]; 1910 1911 for (const dialog of dialogs) { 1912 if (!dialog) { 1913 continue; 1914 } 1915 if (dialog.modal) { 1916 dialog.modal.remove(); 1917 } 1918 if (dialog.overlay) { 1919 dialog.overlay.remove(); 1920 } 1921 } 1922 }; 1923 1924 AblePlayer.getActiveDOMElement = function () { 1925 var activeElement = document.activeElement; 1926 1927 // For shadow DOMs we need to keep digging down through the DOMs 1928 while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { 1929 activeElement = activeElement.shadowRoot.activeElement; 1930 } 1931 1932 return activeElement; 1933 }; 1934 1935 AblePlayer.localGetElementById = function(element, id) { 1936 if (element.getRootNode) { 1937 // Use getRootNode() and querySelector() where supported (for shadow DOM support) 1938 return $(element.getRootNode().querySelector('#' + id)); 1939 } else { 1940 // If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support) 1941 return $(document.getElementById(id)); 1942 } 1943 }; 1944 1945 AblePlayer.ablePlayerSetupWindow = ablePlayerSetupWindow; 1946 1947 AblePlayer.youTubeIframeAPIReady = false; 1948 AblePlayer.loadingYouTubeIframeAPI = false; 1949 1950 AblePlayer.ablePlayerInstances = ablePlayerInstances; 1951 1952 AblePlayer.hasSingleInstance = () => AblePlayer.ablePlayerInstances.size === 1; 1953 1954 AblePlayer.getSingleInstance = () => { 1955 // If there are actually more instances, this returns the first one 1956 for (const instance of AblePlayer.ablePlayerInstances) { 1957 return instance; 1958 } 1959 }; 1960 1961 function addBrowserFunctions(AblePlayer) { 1962 1963 AblePlayer.prototype.isIOS = function(version) { 1964 1965 // return true if this is iOS 1966 // if version is provided check for a particular version 1967 1968 var userAgent, iOS; 1969 1970 userAgent = navigator.userAgent.toLowerCase(); 1971 iOS = /ipad|iphone|ipod/.exec(userAgent); 1972 if (iOS) { 1973 if (typeof version !== 'undefined') { 1974 if (userAgent.indexOf('os ' + version) !== -1) { 1975 // this is the target version of iOS 1976 return true; 1977 } else { 1978 return false; 1979 } 1980 } else { 1981 // no version was specified 1982 return true; 1983 } 1984 } else { 1985 // this is not iOS 1986 return false; 1987 } 1988 }; 1989 1990 AblePlayer.prototype.browserSupportsVolume = function() { 1991 1992 // To test whether the browser supports changing the volume, 1993 // create a new audio element and try setting the volume to something other than 1. 1994 // Then, retrieve the current setting to see if it preserved it. 1995 // This doesn't work in iOS by design: https://developer.apple.com/documentation/avfoundation/avplayer/volume 1996 1997 var audio, testVolume; 1998 1999 if (this.isIOS()) { 2000 return false; 2001 } 2002 2003 testVolume = 0.9; // any value between 0.1 and 0.9 2004 audio = new Audio(); 2005 audio.volume = testVolume; 2006 2007 return ( audio.volume === testVolume ); 2008 }; 2009 2010 AblePlayer.prototype.nativeFullscreenSupported = function () { 2011 2012 return document.fullscreenEnabled || document.webkitFullscreenEnabled; 2013 }; 2014 2015 } 2016 2017 /** 2018 * @file validate.js 2019 * @description This file contains the code to validate the VTT data. 2020 */ 2021 2022 /** PRE-SANITIZED FUNCTIONS 2023 * Some of the VTT attributes need to be transformed before being sanitized by DOMPurify. 2024 * @namespace preProcessing 2025 */ 2026 var preProcessing = { 2027 /** 2028 * Transforms tags with class names separated by dots into tags with a class attribute containing space-separated class names. 2029 * @memberof preProcessing 2030 * @param {string} vttContent - The content of the VTT. 2031 * @returns {string} - The VTT content with processed tags. 2032 */ 2033 transformCSSClasses: function (vttContent) { 2034 // This function should only be passed one cue at a time. 2035 // Throw an error if the string checked is more than 1000 characters. 2036 if ( vttContent.length > 1000 ) { 2037 throw new Error( "Input too long" ); 2038 } 2039 return vttContent.replace( 2040 /<(v|c|b|i|u|lang|ruby)\.([\w.]+)([^>]*)>/g, 2041 function (_, tag, cssClasses, otherAttrs) { 2042 var classAttr = cssClasses.replace(/\./g, " "); 2043 return `<${tag} class="${classAttr}"${otherAttrs}>`; 2044 } 2045 ); 2046 }, 2047 2048 /** 2049 * Transforms <lang> tags by adding a lang attribute with the language code. 2050 * @memberof preProcessing 2051 * @param {string} content - The content with processed CSS classes. 2052 * @returns {string} - The content with <lang> tags transformed. 2053 */ 2054 transformLangTags: function (content) { 2055 return content.replace( 2056 /<lang\s+([\w-]+)([^>]*)>/g, 2057 function (_, langCode, otherAttrs) { 2058 return '<lang lang="' + langCode + '"' + otherAttrs + ">"; 2059 } 2060 ); 2061 }, 2062 2063 /** 2064 * Transforms <v> tags by extracting any non-attribute text as a `title` attribute, 2065 * retains existing attributes (except class), and preserves the class attribute if present. 2066 * Example: <v John class="foo" data-x="y"> becomes <v title="John" data-x="y" class="foo"> 2067 * 2068 * @function 2069 * @memberof preProcessing 2070 * @param {string} content - The string content containing <v> tags to process. 2071 * @returns {string} The content with <v> tags transformed to include a title attribute and preserved attributes. 2072 */ 2073 transformVTags: function (content) { 2074 return content.replace(/<v\s+([^>]*?)>/g, function (_, tagAttributes) { 2075 var classMatch = tagAttributes.match(/class="([^"]*)"/); 2076 var titleMatch = tagAttributes.match(/title="([^"]*)"/); 2077 var classAttr = classMatch ? classMatch[0] : ""; 2078 var titleAttr = titleMatch ? titleMatch[0] : ""; 2079 var otherAttributes = tagAttributes 2080 .replace(/class="[^"]*"/, "") 2081 .replace(/title="[^"]*"/, "") 2082 .trim() 2083 .split(/\s+/); 2084 2085 var attributes = []; 2086 var titleParts = []; 2087 2088 // Iterate over each token of the tag content 2089 otherAttributes.forEach(function (token) { 2090 if (token.indexOf("=") !== -1) { 2091 attributes.push(token); 2092 } else { 2093 titleParts.push(token); 2094 } 2095 }); 2096 2097 var title = ( titleParts ) ? titleParts.join(" ") : false; 2098 var newTag = "<v"; 2099 2100 if ( title && ! titleAttr ) { 2101 newTag += ' title="' + title + '"'; 2102 } 2103 2104 if ( titleAttr && ! title ) { 2105 newTag += " " + titleAttr; 2106 } 2107 2108 if (attributes.length > 0) { 2109 newTag += " " + attributes.join(" "); 2110 } 2111 2112 if (classAttr) { 2113 newTag += " " + classAttr; 2114 } 2115 2116 newTag += ">"; 2117 2118 return newTag; 2119 }); 2120 }, 2121 }; 2122 2123 /** POST-SANITIZED FUNCTIONS 2124 * After sanitizing the VTT data, some tags need to be transformed back to their original form. 2125 * @namespace postProcessing 2126 */ 2127 var postProcessing = { 2128 /** 2129 * Post-processes <c> tags by converting class attributes to dot-separated class names. 2130 * @memberof postProcessing 2131 * @param {string} vttContent - The VTT content to be processed. 2132 * @returns {string} - The VTT content with processed <c> tags. 2133 */ 2134 postprocessCTag: function (vttContent) { 2135 return vttContent.replace( 2136 /<c class="([\w\s]+)">/g, 2137 function (_, classNames) { 2138 var classes = classNames.replace(/ /g, "."); 2139 return "<c." + classes + ">"; 2140 } 2141 ); 2142 }, 2143 2144 /** 2145 * * Post-processes <v> tags by converting class attributes, no matter where found in the attribute order, to dot-separated class names. 2146 * For example, <v class="foo bar" title="John"> becomes <v.foo.bar title="John">. 2147 * Removes the class attribute and appends other attributes after the class names. 2148 * 2149 * @function 2150 * @memberof postProcessing 2151 * @param {string} vttContent - The VTT content to be processed. 2152 * @returns {string} - The VTT content with processed <v> tags. 2153 */ 2154 postprocessVTag: function (vttContent) { 2155 return vttContent.replace( 2156 /<v([^>]*)class="([\w\s]+)"([^>]*)>/g, 2157 function (_, beforeClass, classNames, afterClass) { 2158 var classes = classNames.trim().split(/\s+/).join("."); 2159 // Rebuild the tag: <v.{classes}{other attributes}> 2160 // Remove class="..." from attributes 2161 var attrs = (beforeClass + afterClass) 2162 .replace(/\s*class="[\w\s]+"/, "") 2163 .trim(); 2164 return "<v." + classes + (attrs ? " " + attrs : "") + ">"; 2165 } 2166 ); 2167 }, 2168 2169 /** 2170 * Post-processes <lang> tags by removing the lang attribute and placing the language code directly in the tag. 2171 * @memberof postProcessing 2172 * @param {string} vttContent - The VTT content to be processed. 2173 * @returns {string} - The VTT content with processed <lang> tags. 2174 */ 2175 postprocessLangTag: function (vttContent) { 2176 return vttContent.replace( 2177 /<lang lang="([\w-]+)"([^>]*)>/g, 2178 function (_, langCode, otherAttrs) { 2179 return "<lang " + langCode + otherAttrs + ">"; 2180 } 2181 ); 2182 }, 2183 }; 2184 2185 /** 2186 * Preprocesses, sanitizes and post-processes VTT content as well as other utility functions. 2187 * @namespace validate 2188 */ 2189 var validate = { 2190 /** 2191 * Sets up the VTT content before sanitizing by transforming tags. 2192 * This way DOM purify will process the tags correctly. 2193 * @memberof validate 2194 * @param {string} vttContent - The original content of the VTT. 2195 * @returns {string} - The VTT content for the next and final step of preprocessing. 2196 */ 2197 preProcessVttContent: function (vttContent) { 2198 var processedCSS = preProcessing.transformCSSClasses(vttContent); 2199 var processedLang = preProcessing.transformLangTags(processedCSS); 2200 var processedVTags = preProcessing.transformVTags(processedLang); 2201 return processedVTags; 2202 }, 2203 2204 /** 2205 * Post-processes the sanitized VTT data by converting class attributes to dot-separated class names and other transformations. 2206 * @memberof validate 2207 * @param {string} sanitizedVttContent - The sanitized VTT content to be post-processed. 2208 * @param {string} originalVttContent - The original VTT content before sanitization. 2209 * @returns {string} - The post-processed VTT content. 2210 */ 2211 postProcessVttContent: function (sanitizedVttContent, originalVttContent) { 2212 var processedCTags = postProcessing.postprocessCTag(sanitizedVttContent); 2213 var processedVTags = postProcessing.postprocessVTag(processedCTags); 2214 var processedLangTags = postProcessing.postprocessLangTag(processedVTags); 2215 2216 var arrowReplaced = processedLangTags.replace(/-->/g, "-->"); 2217 var timestampTagReplaced = arrowReplaced.replace(/<([\d:.]+)>/g, '<$1>'); 2218 2219 var finalContent = timestampTagReplaced.replace( 2220 /<\/v>/g, 2221 function (match, offset) { 2222 return originalVttContent.indexOf(match, offset) !== -1 ? match : ""; 2223 } 2224 ); 2225 2226 return finalContent; 2227 }, 2228 2229 /** 2230 * Sanitizes the VTT data by removing unwanted tags and attributes, and then post-processes it. 2231 * @memberof validate 2232 * @param {string} vttContent - The VTT content to be sanitized and post-processed. 2233 * @returns {string} - The fully processed VTT content. 2234 */ 2235 sanitizeVttContent: function (vttContent) { 2236 if (vttContent === null || vttContent === undefined) { 2237 return ""; 2238 } 2239 var preSanitizedVttContent = validate.preProcessVttContent(vttContent); 2240 2241 var config = { 2242 ALLOWED_TAGS: ["b", "i", "u", "v", "c", "lang", "ruby", "rt", "rp"], 2243 ALLOWED_ATTR: ["title", "class", "lang"], 2244 KEEP_CONTENT: true, 2245 }; 2246 2247 var sanitizedVttContent = purify.sanitize( 2248 preSanitizedVttContent, 2249 config 2250 ); 2251 2252 return validate.postProcessVttContent(sanitizedVttContent, vttContent); 2253 }, 2254 // Utility validation functions 2255 isProtocolSafe: function (url) { 2256 //creates a new URL object for analysis to check if the protocol is http or https 2257 //returns true if there is a match false otherwise 2258 try { 2259 const parsedUrl = new URL(url, window.location.origin); // Resolve relative URLs 2260 return ["http:", "https:"].includes(parsedUrl.protocol); // Allow only HTTP and HTTPS 2261 } catch (e) { 2262 return false; // Invalid URL 2263 } 2264 }, 2265 }; 2266 2267 // Events: 2268 // - startTracking(event, position) 2269 // - tracking(event, position) 2270 // - stopTracking(event, position) 2271 2272 function AccessibleSlider(div, max, bigInterval, label) { 2273 2274 // div is the host element around which the slider will be built 2275 // max is the high end of the slider scale 2276 // bigInterval is the number of steps supported by page up/page down (set to 0 if not supported) 2277 // (smallInterval, defined as nextStep below, is always set to 1) - this is the interval supported by arrow keys 2278 // label is used within an aria-label attribute to identify the slider to screen reader users 2279 2280 var thisObj, coords; 2281 2282 thisObj = this; 2283 2284 // Initialize some variables. 2285 this.position = 0; // Note: position does not change while tracking. 2286 this.tracking = false; 2287 this.trackDevice = null; // 'mouse' or 'keyboard' 2288 this.keyTrackPosition = 0; 2289 this.lastTrackPosition = 0; 2290 this.nextStep = 1; 2291 this.inertiaCount = 0; 2292 2293 this.seekbarDiv = $(div); 2294 2295 // Add divs for tracking amount of media loaded and played 2296 this.loadedDiv = $('<div></div>'); 2297 this.playedDiv = $('<div></div>'); 2298 2299 // Add a seekhead 2300 this.seekHead = $('<div>',{ 2301 'aria-orientation': 'horizontal', 2302 'class': 'able-seekbar-head' 2303 }); 2304 2305 this.seekHead.attr('tabindex', '0'); 2306 2307 // Since head is focusable, it gets the aria roles/titles. 2308 this.seekHead.attr({ 2309 'role': 'slider', 2310 'aria-label': label, 2311 'aria-valuemin': 0, 2312 'aria-valuemax': max 2313 }); 2314 2315 this.timeTooltipTimeoutId = null; 2316 this.overTooltip = false; 2317 this.timeTooltip = $('<div>'); 2318 this.seekbarDiv.append(this.timeTooltip); 2319 2320 this.timeTooltip.attr('role', 'tooltip'); 2321 this.timeTooltip.addClass('able-tooltip'); 2322 this.timeTooltip.on('mouseenter focus', function(){ 2323 thisObj.overTooltip = true; 2324 clearInterval(thisObj.timeTooltipTimeoutId); 2325 }); 2326 this.timeTooltip.on('mouseleave blur', function(){ 2327 thisObj.overTooltip = false; 2328 $(this).hide(); 2329 }); 2330 this.timeTooltip.hide(); 2331 2332 this.seekbarDiv.append(this.loadedDiv); 2333 this.seekbarDiv.append(this.playedDiv); 2334 this.seekbarDiv.append(this.seekHead); 2335 this.seekbarDiv.wrap('<div></div>'); 2336 this.wrapperDiv = this.seekbarDiv.parent(); 2337 2338 if (this.skin === 'legacy') { 2339 this.wrapperDiv.width( 100 ); 2340 this.loadedDiv.width(0); 2341 } 2342 this.wrapperDiv.addClass('able-seekbar-wrapper'); 2343 this.loadedDiv.addClass('able-seekbar-loaded'); 2344 this.playedDiv.width(0); 2345 this.playedDiv.addClass('able-seekbar-played'); 2346 2347 // Set a default duration. User can call this dynamically if duration changes. 2348 this.setDuration(max); 2349 2350 // handle seekHead events 2351 this.seekHead.on('mouseenter mouseleave mousemove mousedown mouseup focus blur touchstart touchmove touchend', function (e) { 2352 2353 coords = thisObj.pointerEventToXY(e); 2354 2355 if (e.type === 'mouseenter' || e.type === 'focus') { 2356 thisObj.overHead = true; 2357 } else if (e.type === 'mouseleave' || e.type === 'blur') { 2358 thisObj.overHead = false; 2359 if (!thisObj.overBody && thisObj.tracking && thisObj.trackDevice === 'mouse') { 2360 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2361 } 2362 } else if (e.type === 'mousemove' || e.type === 'touchmove') { 2363 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2364 thisObj.trackHeadAtPageX(coords.x); 2365 } 2366 } else if (e.type === 'mousedown' || e.type === 'touchstart') { 2367 thisObj.startTracking('mouse', thisObj.pageXToPosition(thisObj.seekHead.offset() + (thisObj.seekHead.width() / 2))); 2368 if (!thisObj.seekbarDiv.is(':focus')) { 2369 thisObj.seekbarDiv.focus(); 2370 } 2371 e.preventDefault(); 2372 } else if (e.type === 'mouseup' || e.type === 'touchend') { 2373 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2374 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2375 } 2376 } 2377 if (e.type !== 'mousemove' && e.type !== 'mousedown' && e.type !== 'mouseup' && e.type !== 'touchstart' && e.type !== 'touchend') { 2378 thisObj.refreshTooltip(); 2379 } 2380 }); 2381 2382 // handle seekbarDiv events 2383 this.seekbarDiv.on( 2384 'mouseenter mouseleave mousemove mousedown mouseup keydown keyup touchstart touchmove touchend', function (e) { 2385 2386 // Don't trigger move on right click. 2387 if ( e.button == 2 && e.type == 'mousedown' ) { 2388 return; 2389 } 2390 coords = thisObj.pointerEventToXY(e); 2391 let keyPressed = e.key; 2392 2393 if (e.type === 'mouseenter') { 2394 thisObj.overBody = true; 2395 thisObj.overBodyMousePos = { 2396 x: coords.x, 2397 y: coords.y 2398 }; 2399 } else if (e.type === 'mouseleave') { 2400 thisObj.overBody = false; 2401 thisObj.overBodyMousePos = null; 2402 if (!thisObj.overHead && thisObj.tracking && thisObj.trackDevice === 'mouse') { 2403 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2404 } 2405 } else if (e.type === 'mousemove' || e.type === 'touchmove') { 2406 thisObj.overBodyMousePos = { 2407 x: coords.x, 2408 y: coords.y 2409 }; 2410 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2411 thisObj.trackHeadAtPageX(coords.x); 2412 } 2413 } else if (e.type === 'mousedown' || e.type === 'touchstart') { 2414 thisObj.startTracking('mouse', thisObj.pageXToPosition(coords.x)); 2415 thisObj.trackHeadAtPageX(coords.x); 2416 if (!thisObj.seekHead.is(':focus')) { 2417 thisObj.seekHead.focus(); 2418 } 2419 e.preventDefault(); 2420 } else if (e.type === 'mouseup' || e.type === 'touchend') { 2421 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2422 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2423 } 2424 } else if (e.type === 'keydown') { 2425 if (e.key === 'Home') { 2426 thisObj.trackImmediatelyTo(0); 2427 } else if (e.key === 'End') { 2428 thisObj.trackImmediatelyTo(thisObj.duration); 2429 } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') { 2430 thisObj.arrowKeyDown(-1); 2431 } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') { 2432 thisObj.arrowKeyDown(1); 2433 } else if (e.key === 'PageUp' && bigInterval > 0) { 2434 thisObj.arrowKeyDown(bigInterval); 2435 } else if (e.key === 'PageDown' && bigInterval > 0) { 2436 thisObj.arrowKeyDown(-bigInterval); 2437 } else { 2438 return; 2439 } 2440 e.preventDefault(); 2441 } else if (e.type === 'keyup') { 2442 if ( keyPressed === e.key ) { 2443 if (thisObj.tracking && thisObj.trackDevice === 'keyboard') { 2444 thisObj.stopTracking(thisObj.keyTrackPosition); 2445 } 2446 e.preventDefault(); 2447 } 2448 } 2449 if (!thisObj.overTooltip && e.type !== 'mouseup' && e.type !== 'keydown' && e.type !== 'keydown') { 2450 thisObj.refreshTooltip(); 2451 } 2452 }); 2453 } 2454 2455 AccessibleSlider.prototype.arrowKeyDown = function (multiplier) { 2456 if (this.tracking && this.trackDevice === 'keyboard') { 2457 this.keyTrackPosition = this.boundPos(this.keyTrackPosition + (this.nextStep * multiplier)); 2458 this.inertiaCount += 1; 2459 if (this.inertiaCount === 20) { 2460 this.inertiaCount = 0; 2461 this.nextStep *= 2; 2462 } 2463 this.trackHeadAtPosition(this.keyTrackPosition); 2464 } else { 2465 this.nextStep = 1; 2466 this.inertiaCount = 0; 2467 this.keyTrackPosition = this.boundPos(this.position + (this.nextStep * multiplier)); 2468 this.startTracking('keyboard', this.keyTrackPosition); 2469 this.trackHeadAtPosition(this.keyTrackPosition); 2470 } 2471 }; 2472 2473 AccessibleSlider.prototype.pageXToPosition = function (pageX) { 2474 var offset = pageX - this.seekbarDiv.offset().left; 2475 var position = this.duration * (offset / this.seekbarDiv.width()); 2476 return this.boundPos(position); 2477 }; 2478 2479 AccessibleSlider.prototype.boundPos = function (position) { 2480 return Math.max(0, Math.min(position, this.duration)); 2481 }; 2482 2483 AccessibleSlider.prototype.setDuration = function (duration) { 2484 if (duration !== this.duration) { 2485 this.duration = duration; 2486 this.resetHeadLocation(); 2487 this.seekHead.attr('aria-valuemax', duration); 2488 } 2489 }; 2490 2491 // Set width of the legacy seekbar. 2492 AccessibleSlider.prototype.setWidth = function (width) { 2493 this.wrapperDiv.width(width); 2494 this.resizeDivs(); 2495 this.resetHeadLocation(); 2496 }; 2497 2498 AccessibleSlider.prototype.getWidth = function () { 2499 return this.wrapperDiv.width(); 2500 }; 2501 2502 AccessibleSlider.prototype.resizeDivs = function () { 2503 this.playedDiv.width( 100 * (this.position / this.duration) + '%' ); 2504 this.loadedDiv.width( 100 * this.buffered + '%' ); 2505 }; 2506 2507 // Stops tracking, sets the head location to the current position. 2508 AccessibleSlider.prototype.resetHeadLocation = function () { 2509 var ratio = this.position / this.duration; 2510 var center = this.seekbarDiv.width() * ratio; 2511 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 2512 2513 if (this.tracking) { 2514 this.stopTracking(this.position); 2515 } 2516 }; 2517 2518 AccessibleSlider.prototype.setPosition = function (position, updateLive) { 2519 this.position = position; 2520 this.resetHeadLocation(); 2521 if (this.overHead) { 2522 this.refreshTooltip(); 2523 } 2524 this.resizeDivs(); 2525 this.updateAriaValues(position, updateLive); 2526 }; 2527 2528 // TODO: Native HTML5 can have several buffered segments, and this actually happens quite often. Change this to display them all. 2529 AccessibleSlider.prototype.setBuffered = function (ratio) { 2530 if (!isNaN(ratio)) { 2531 this.buffered = ratio; 2532 this.redrawDivs; 2533 } 2534 }; 2535 2536 AccessibleSlider.prototype.startTracking = function (device, position) { 2537 if (!this.tracking) { 2538 this.trackDevice = device; 2539 this.tracking = true; 2540 this.seekbarDiv.trigger('startTracking', [position]); 2541 } 2542 }; 2543 2544 AccessibleSlider.prototype.stopTracking = function (position) { 2545 this.trackDevice = null; 2546 this.tracking = false; 2547 this.seekbarDiv.trigger('stopTracking', [position]); 2548 this.setPosition(position, true); 2549 }; 2550 2551 AccessibleSlider.prototype.trackHeadAtPageX = function (pageX) { 2552 var position = this.pageXToPosition(pageX); 2553 var newLeft = pageX - this.seekbarDiv.offset().left - (this.seekHead.width() / 2); 2554 newLeft = Math.max(0, Math.min(newLeft, this.seekbarDiv.width() - this.seekHead.width())); 2555 this.lastTrackPosition = position; 2556 this.seekHead.css('left', newLeft); 2557 this.reportTrackAtPosition(position); 2558 }; 2559 2560 AccessibleSlider.prototype.trackHeadAtPosition = function (position) { 2561 var ratio = position / this.duration; 2562 var center = this.seekbarDiv.width() * ratio; 2563 this.lastTrackPosition = position; 2564 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 2565 this.reportTrackAtPosition(position); 2566 }; 2567 2568 AccessibleSlider.prototype.reportTrackAtPosition = function (position) { 2569 this.seekbarDiv.trigger('tracking', [position]); 2570 this.updateAriaValues(position, true); 2571 }; 2572 2573 AccessibleSlider.prototype.updateAriaValues = function (position, updateLive) { 2574 // TODO: Localize, move to another function. 2575 var pHours = Math.floor(position / 3600); 2576 var pMinutes = Math.floor((position % 3600) / 60); 2577 var pSeconds = Math.floor(position % 60); 2578 2579 var pHourWord = pHours === 1 ? 'hour' : 'hours'; 2580 var pMinuteWord = pMinutes === 1 ? 'minute' : 'minutes'; 2581 var pSecondWord = pSeconds === 1 ? 'second' : 'seconds'; 2582 2583 var descriptionText; 2584 if (pHours > 0) { 2585 descriptionText = pHours + 2586 ' ' + pHourWord + 2587 ', ' + pMinutes + 2588 ' ' + pMinuteWord + 2589 ', ' + pSeconds + 2590 ' ' + pSecondWord; 2591 } else if (pMinutes > 0) { 2592 descriptionText = pMinutes + 2593 ' ' + pMinuteWord + 2594 ', ' + pSeconds + 2595 ' ' + pSecondWord; 2596 } else { 2597 descriptionText = pSeconds + ' ' + pSecondWord; 2598 } 2599 2600 /* Comment to stop live region from generating or being used. */ 2601 if (!this.liveAriaRegion) { 2602 this.liveAriaRegion = $('<span>', { 2603 'class': 'able-offscreen', 2604 'aria-live': 'polite' 2605 }); 2606 this.wrapperDiv.append(this.liveAriaRegion); 2607 } 2608 if (updateLive && (this.liveAriaRegion.text() !== descriptionText)) { 2609 this.liveAriaRegion.text(descriptionText); 2610 } 2611 2612 // Uncomment the following lines to use aria values instead of separate live region. 2613 this.seekHead.attr('aria-valuetext', descriptionText); 2614 this.seekHead.attr('aria-valuenow', Math.floor(position).toString()); 2615 }; 2616 2617 AccessibleSlider.prototype.trackImmediatelyTo = function (position) { 2618 this.startTracking('keyboard', position); 2619 this.trackHeadAtPosition(position); 2620 this.keyTrackPosition = position; 2621 }; 2622 2623 AccessibleSlider.prototype.refreshTooltip = function () { 2624 if (this.overHead) { 2625 this.timeTooltip.show(); 2626 if (this.tracking) { 2627 this.timeTooltip.text(this.positionToStr(this.lastTrackPosition)); 2628 } else { 2629 this.timeTooltip.text(this.positionToStr(this.position)); 2630 } 2631 this.setTooltipPosition(this.seekHead.position().left + (this.seekHead.width() / 2)); 2632 } else if (this.overBody && this.overBodyMousePos) { 2633 this.timeTooltip.show(); 2634 this.timeTooltip.text(this.positionToStr(this.pageXToPosition(this.overBodyMousePos.x))); 2635 this.setTooltipPosition(this.overBodyMousePos.x - this.seekbarDiv.offset().left); 2636 } else { 2637 2638 clearTimeout(this.timeTooltipTimeoutId); 2639 var _this = this; 2640 this.timeTooltipTimeoutId = setTimeout(function() { 2641 // give user a half second move cursor over tooltip 2642 _this.timeTooltip.hide(); 2643 }, 500); 2644 } 2645 }; 2646 2647 AccessibleSlider.prototype.hideSliderTooltips = function () { 2648 this.overHead = false; 2649 this.overBody = false; 2650 this.timeTooltip.hide(); 2651 }; 2652 2653 AccessibleSlider.prototype.setTooltipPosition = function (x) { 2654 this.timeTooltip.css({ 2655 left: x - (this.timeTooltip.width() / 2) - 10, 2656 bottom: this.seekHead.height() 2657 }); 2658 }; 2659 2660 AccessibleSlider.prototype.positionToStr = function (seconds) { 2661 2662 // same logic as misc.js > formatSecondsAsColonTime() 2663 var dHours = Math.floor(seconds / 3600); 2664 var dMinutes = Math.floor(seconds / 60) % 60; 2665 var dSeconds = Math.floor(seconds % 60); 2666 if (dSeconds < 10) { 2667 dSeconds = '0' + dSeconds; 2668 } 2669 if (dHours > 0) { 2670 if (dMinutes < 10) { 2671 dMinutes = '0' + dMinutes; 2672 } 2673 return dHours + ':' + dMinutes + ':' + dSeconds; 2674 } else { 2675 return dMinutes + ':' + dSeconds; 2676 } 2677 }; 2678 2679 AccessibleSlider.prototype.pointerEventToXY = function(e) { 2680 2681 // returns array of coordinates x and y in response to both mouse and touch events 2682 // for mouse events, this comes from e.pageX and e.pageY 2683 // for touch events, it's a bit more complicated 2684 var out = {x:0, y:0}; 2685 if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') { 2686 var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]; 2687 out.x = touch.pageX; 2688 out.y = touch.pageY; 2689 } else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover'|| e.type=='mouseout' || e.type=='mouseenter' || e.type=='mouseleave') { 2690 out.x = e.pageX; 2691 out.y = e.pageY; 2692 } 2693 return out; 2694 }; 2695 2696 function addBuildplayerFunctions(AblePlayer) { 2697 AblePlayer.prototype.injectPlayerCode = function() { 2698 2699 // create and inject surrounding HTML structure 2700 // If iOS & video: 2701 // iOS does not support any of the player's functionality - everything plays in its own player 2702 // Therefore, AblePlayer is not loaded & all functionality is disabled 2703 // (this all determined. If this is iOS && video, this function is never called) 2704 2705 var captionsContainer; 2706 // Wrappers, from inner to outer: 2707 // $mediaContainer - contains the original media element 2708 // $ableDiv - contains the media player and all its objects (e.g., captions, controls, descriptions) 2709 // $ableWrapper - contains additional widgets (e.g., transcript window, sign window) 2710 this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent(); 2711 this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent(); 2712 this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent(); 2713 this.$ableWrapper.addClass('able-skin-' + this.skin); 2714 2715 if (this.mediaType === 'video') { 2716 // youtube adds its own big play button 2717 // don't show ours *unless* video has a poster attribute 2718 // (which obstructs the YouTube poster & big play button) 2719 if (this.player !== 'youtube' || this.hasPoster) { 2720 this.injectBigPlayButton(); 2721 } 2722 } 2723 2724 // add container that captions or description will be appended to 2725 // Note: new Jquery object must be assigned _after_ wrap, hence the temp captionsContainer variable 2726 captionsContainer = $('<div>'); 2727 if (this.mediaType === 'video') { 2728 captionsContainer.addClass('able-vidcap-container'); 2729 } else if (this.mediaType === 'audio') { 2730 captionsContainer.addClass('able-audcap-container'); 2731 // hide this by default. It will be shown if captions are available 2732 captionsContainer.addClass('captions-off'); 2733 } 2734 2735 this.injectPlayerControlArea(); // this may need to be injected after captions??? 2736 this.$captionsContainer = this.$mediaContainer.wrap(captionsContainer).parent(); 2737 this.injectAlert(this.$ableDiv); 2738 this.injectPlaylist(); 2739 this.injectAudioPoster(); 2740 // Do this last, as it should be prepended to the top of this.$ableDiv 2741 // after everything else has prepended 2742 this.injectOffscreenHeading(); 2743 }; 2744 2745 AblePlayer.prototype.injectAudioPoster = function() { 2746 if ( this.mediaType === 'audio' && this.hasPoster ) { 2747 const audioPoster = purify.sanitize(this.audioPoster); 2748 const audioPosterAlt = purify.sanitize(this.audioPosterAlt); 2749 let audioPosterImg = document.createElement( 'img' ); 2750 audioPosterImg.setAttribute( 'src', audioPoster ); 2751 audioPosterImg.setAttribute( 'alt', audioPosterAlt ); 2752 this.$audioWrapper = this.$playerDiv.wrap( '<div class="able-audio-wrapper">' ).parent(); 2753 this.$audioWrapper.prepend( audioPosterImg ); 2754 } 2755 }; 2756 2757 AblePlayer.prototype.injectOffscreenHeading = function () { 2758 2759 // Inject an offscreen heading to the media container. 2760 // If heading hasn't already been manually defined via data-heading-level, 2761 // automatically assign a level that is one level deeper than the closest parent heading 2762 // as determined by getNextHeadingLevel() 2763 var headingType; 2764 if (this.playerHeadingLevel == '0') ; else { 2765 if (typeof this.playerHeadingLevel === 'undefined') { 2766 this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6 2767 } 2768 headingType = 'h' + this.playerHeadingLevel.toString(); 2769 this.$headingDiv = $('<' + headingType + '>'); 2770 this.$ableDiv.prepend(this.$headingDiv); 2771 this.$headingDiv.addClass('able-offscreen'); 2772 this.$headingDiv.text( this.translate( 'playerHeading', 'Media player' ) ); 2773 } 2774 }; 2775 2776 AblePlayer.prototype.injectBigPlayButton = function () { 2777 2778 var thisObj = this; 2779 2780 this.$bigPlayButton = $('<button>', { 2781 'class': 'able-big-play-button', 2782 'aria-hidden': false, 2783 'aria-label': this.translate( 'play', 'Play' ), 2784 'type': 'button', 2785 'tabindex': 0 2786 }); 2787 2788 this.getIcon( this.$bigPlayButton, 'play' ); 2789 2790 this.$bigPlayButton.on( 'click', function () { 2791 thisObj.handlePlay(); 2792 }); 2793 2794 this.$mediaContainer.append(this.$bigPlayButton); 2795 }; 2796 2797 AblePlayer.prototype.injectPlayerControlArea = function () { 2798 2799 this.$playerDiv = $('<div>', { 2800 'class' : 'able-player', 2801 'role' : 'region', 2802 'aria-label' : ( 'audio' === this.mediaType ) ? this.translate( 'audioPlayer', 'audio player' ) : this.translate( 'videoPlayer', 'video player' ) 2803 }); 2804 this.$playerDiv.addClass('able-' + this.mediaType); 2805 if (this.hasPlaylist && this.showNowPlaying) { 2806 this.$nowPlayingDiv = $('<div>',{ 2807 'class' : 'able-now-playing', 2808 'aria-live' : 'assertive', 2809 'aria-atomic': 'true' 2810 }); 2811 } 2812 this.$controllerDiv = $('<div>',{ 2813 'class' : 'able-controller' 2814 }); 2815 this.$controllerDiv.addClass('able-' + this.iconColor + '-controls'); 2816 2817 this.$statusBarDiv = $('<div>',{ 2818 'class' : 'able-status-bar' 2819 }); 2820 this.$timer = $('<span>',{ 2821 'class' : 'able-timer' 2822 }); 2823 this.$elapsedTimeContainer = $('<span>',{ 2824 'class': 'able-elapsedTime', 2825 text: '0:00' 2826 }); 2827 this.$durationContainer = $('<span>',{ 2828 'class': 'able-duration' 2829 }); 2830 this.$durationSeparator = $('<span>',{ 2831 'class': 'able-timer-separator', 2832 'text': ' / ' 2833 }); 2834 this.$timer.append(this.$elapsedTimeContainer).append(this.$durationSeparator).append(this.$durationContainer); 2835 2836 this.$speed = $('<span>',{ 2837 'class' : 'able-speed', 2838 'aria-live' : 'assertive', 2839 'aria-atomic' : 'true' 2840 }).text(this.translate( 'speed', 'Speed' ) + ': 1x'); 2841 2842 this.$status = $('<span>',{ 2843 'class' : 'able-status', 2844 'aria-live' : 'polite' 2845 }); 2846 2847 // Put everything together. 2848 this.$statusBarDiv.append(this.$timer, this.$speed, this.$status); 2849 if (this.showNowPlaying) { 2850 this.$playerDiv.append(this.$nowPlayingDiv, this.$controllerDiv, this.$statusBarDiv); 2851 } else { 2852 this.$playerDiv.append(this.$controllerDiv, this.$statusBarDiv); 2853 } 2854 2855 if (this.mediaType === 'video') { 2856 // the player controls go after the media & captions 2857 this.$ableDiv.append(this.$playerDiv); 2858 } else { 2859 // the player controls go before the media & captions 2860 this.$ableDiv.prepend(this.$playerDiv); 2861 } 2862 }; 2863 2864 AblePlayer.prototype.injectTextDescriptionArea = function () { 2865 2866 // create a div for writing description text 2867 this.$descDiv = $('<div>',{ 2868 'class': 'able-descriptions' 2869 }); 2870 // Add ARIA so description will be announced by screen readers 2871 // Later (in description.js > showDescription()), 2872 // if browser supports Web Speech API and this.descMethod === 'browser' 2873 // these attributes will be removed 2874 this.$descDiv.attr({ 2875 'aria-live': 'assertive', 2876 'aria-atomic': 'true' 2877 }); 2878 // Start off with description hidden. 2879 // It will be exposed conditionally within description.js > initDescription() 2880 this.$descDiv.hide(); 2881 this.$ableDiv.append(this.$descDiv); 2882 }; 2883 2884 AblePlayer.prototype.getDefaultWidth = function(which) { 2885 let viewportMaxwidth = window.innerWidth; 2886 // return default width of resizable elements 2887 // these values are somewhat arbitrary, but seem to result in good usability 2888 // if users disagree, they can resize (and resposition) them 2889 if (which === 'transcript') { 2890 return ( viewportMaxwidth <= 450 ) ? viewportMaxwidth : 450; 2891 } else if (which === 'sign') { 2892 return ( viewportMaxwidth <= 400 ) ? viewportMaxwidth : 400; 2893 } 2894 }; 2895 2896 /** 2897 * Reposition draggable windows when switched into fullscreen. 2898 * 2899 * @param {string} which 'transcript' or 'sign'. 2900 */ 2901 AblePlayer.prototype.rePositionDraggableWindow = function (which) { 2902 2903 let preferences, $window; 2904 preferences = this.getPref(); 2905 $window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow; 2906 console.log( $window ); 2907 if ( which === 'transcript' && $window ) { 2908 if (typeof preferences.transcript !== 'undefined') { 2909 this.prevTranscriptPosition = preferences.transcript; 2910 } 2911 $window.css({ 2912 'top': 0, 2913 'left': 0 2914 }); 2915 } else if ( 'sign' === which && $window ) { 2916 if (typeof preferences.sign !== 'undefined') { 2917 this.prevSignPosition = preferences.sign; 2918 } 2919 $window.css({ 2920 'top': 0, 2921 'right': 0, 2922 'left': 'auto' 2923 }); 2924 } 2925 }; 2926 2927 AblePlayer.prototype.positionDraggableWindow = function (which, width) { 2928 2929 // which is either 'transcript' or 'sign' 2930 var preferences, preferencePos, $window, windowPos, viewportWidth, windowWidth; 2931 2932 preferences = this.getPref(); 2933 $window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow; 2934 if ( ! $window ) { 2935 return; 2936 } 2937 if (which === 'transcript') { 2938 if (typeof preferences.transcript !== 'undefined') { 2939 preferencePos = preferences.transcript; 2940 } 2941 if ( this.prevTranscriptPosition ) { 2942 preferencePos = this.prevTranscriptPosition; 2943 this.prevTranscriptPosition = false; 2944 } 2945 } else if (which === 'sign') { 2946 if (typeof preferences.sign !== 'undefined') { 2947 preferencePos = preferences.sign; 2948 } 2949 if ( this.prevSignPosition ) { 2950 preferencePos = this.prevSignPosition; 2951 this.prevSignPosition = false; 2952 } 2953 } 2954 if (typeof preferencePos !== 'undefined' && !($.isEmptyObject(preferencePos))) { 2955 // position window using stored values from preferences 2956 $window.css({ 2957 'position': preferencePos['position'], 2958 'width': preferencePos['width'], 2959 'z-index': preferencePos['zindex'] 2960 }); 2961 if (preferencePos['position'] === 'absolute') { 2962 $window.css({ 2963 'top': preferencePos['top'], 2964 'left': preferencePos['left'] 2965 }); 2966 // Check whether the window is above the top of the viewport. 2967 let topPosition = $window.offset().top; 2968 let leftPosition = $window.offset().left; 2969 viewportWidth = window.innerWidth; 2970 windowWidth = $window.width(); 2971 if ( topPosition < 0 ) { 2972 $window.css({ 2973 'top': preferencePos['top'] - topPosition 2974 }); 2975 } 2976 // If draggable window is off screen to the left. 2977 if ( leftPosition < 0 && ! this.restoringAfterFullscreen ) { 2978 console.log( leftPosition ); 2979 $window.css({ 2980 'left': preferencePos['left'] - leftPosition 2981 }); 2982 } 2983 // If draggable window is off screen to the right. 2984 if ( viewportWidth - leftPosition < 30 ) { 2985 $window.css({ 2986 'left': viewportWidth - windowWidth 2987 }); 2988 } 2989 } 2990 // since preferences are not page-specific, z-index needs may vary across different pages 2991 this.updateZIndex(which); 2992 } else { 2993 // position window using default values 2994 windowPos = this.getOptimumPosition(which, width); 2995 if (typeof width === 'undefined') { 2996 width = this.getDefaultWidth(which); 2997 } 2998 $window.css({ 2999 'position': windowPos[0], 3000 'width': width, 3001 'z-index': windowPos[3] 3002 }); 3003 if (windowPos[0] === 'absolute') { 3004 $window.css({ 3005 'top': windowPos[1] + 'px', 3006 'left': windowPos[2] + 'px', 3007 }); 3008 } 3009 } 3010 }; 3011 3012 AblePlayer.prototype.getOptimumPosition = function (targetWindow, targetWidth) { 3013 3014 // returns optimum position for targetWindow, as an array with the following structure: 3015 // 0 - CSS position ('absolute' or 'relative') 3016 // 1 - top 3017 // 2 - left 3018 // 3 - zindex (if not default) 3019 // targetWindow is either 'transcript' or 'sign' 3020 // if there is room to the right of the player, position element there 3021 // else if there is room the left of the player, position element there 3022 // else position element beneath player 3023 3024 var gap, position, ableWidth, ableOffset, ableLeft, windowWidth, otherWindowWidth; 3025 3026 if (typeof targetWidth === 'undefined') { 3027 targetWidth = this.getDefaultWidth(targetWindow); 3028 } 3029 3030 gap = 5; // number of pixels to preserve between Able Player objects 3031 position = []; // position, top, left 3032 3033 ableWidth = this.$ableDiv.width(); 3034 ableOffset = this.$ableDiv.offset(); 3035 ableLeft = ableOffset.left; 3036 windowWidth = $(window).width(); 3037 otherWindowWidth = 0; // width of other visiable draggable windows will be added to this 3038 3039 if (targetWindow === 'transcript') { 3040 // If placing the transcript window, check position of sign window first. 3041 if (typeof this.$signWindow !== 'undefined' && (this.$signWindow.is(':visible'))) { 3042 otherWindowWidth = this.$signWindow.width() + gap; 3043 } 3044 } else if (targetWindow === 'sign') { 3045 // If placing the sign window, check position of transcript window first. 3046 if (typeof this.$transcriptArea !== 'undefined' && (this.$transcriptArea.is(':visible'))) { 3047 otherWindowWidth = this.$transcriptArea.width() + gap; 3048 } 3049 } 3050 if (targetWidth < (windowWidth - (ableLeft + ableWidth + gap + otherWindowWidth))) { 3051 // there's room to the left of $ableDiv 3052 position[0] = 'absolute'; 3053 position[1] = 0; 3054 position[2] = ableWidth + otherWindowWidth + gap; 3055 } else if (targetWidth + gap < ableLeft) { 3056 // there's room to the right of $ableDiv 3057 position[0] = 'absolute'; 3058 position[1] = 0; 3059 position[2] = ableLeft - targetWidth - gap; 3060 } else { 3061 // position element below $ableDiv 3062 position[0] = 'relative'; 3063 // no need to define top, left, or z-index 3064 } 3065 return position; 3066 }; 3067 3068 AblePlayer.prototype.injectAlert = function ($container) { 3069 // inject two alerts, one visible for all users and one for screen reader users only 3070 this.$alertBox = $('<div role="alert"></div>'); 3071 this.$alertBox.addClass('able-alert'); 3072 this.$alertBox.hide(); 3073 3074 var $alertText = $( '<span></span>' ); 3075 $alertText.appendTo(this.$alertBox); 3076 3077 var $alertDismiss = $('<button type="button"></button>' ); 3078 $alertDismiss.attr( 'aria-label', this.translate( 'dismissButton', 'Dismiss' ) ); 3079 $alertDismiss.text( 'Ã' ); 3080 $alertDismiss.appendTo(this.$alertBox); 3081 3082 $alertDismiss.on( 'click', function(e) { 3083 $(this).parent('div').hide(); 3084 }); 3085 3086 this.$alertBox.appendTo($container); 3087 3088 if ( ! this.$srAlertBox ) { 3089 this.$srAlertBox = $('<div role="alert"></div>'); 3090 this.$srAlertBox.addClass('able-screenreader-alert'); 3091 this.$srAlertBox.appendTo($container); 3092 } 3093 }; 3094 3095 AblePlayer.prototype.injectPlaylist = function () { 3096 3097 if (this.playlistEmbed === true) { 3098 // move playlist into player, immediately before statusBarDiv 3099 var playlistClone = this.$playlistDom.clone(); 3100 playlistClone.insertBefore(this.$statusBarDiv); 3101 // Update to the new playlist copy. 3102 this.$playlist = playlistClone.find('li'); 3103 } 3104 }; 3105 3106 AblePlayer.prototype.createPopup = function (which, tracks) { 3107 3108 // Create popup menu and append to player 3109 // 'which' parameter is either 'captions', 'chapters', 'prefs', 'transcript-window' or 'sign-window' 3110 // 'tracks', if provided, is a list of tracks to be used as menu items 3111 3112 var thisObj, $menu, includeMenuItem, i, $menuItem, prefCat, whichPref, hasDefault, track, 3113 windowOptions, $thisItem, $prevItem, $nextItem, hasDescription, hasTranscript; 3114 3115 thisObj = this; 3116 3117 $menu = $('<ul>',{ 3118 'id': this.mediaId + '-' + which + '-menu', 3119 'class': 'able-popup', 3120 'role': 'menu' 3121 }).hide(); 3122 3123 if (which === 'captions') { 3124 $menu.addClass('able-popup-captions'); 3125 } 3126 3127 // Populate menu with menu items 3128 if (which === 'prefs') { 3129 if (this.prefCats.length > 1) { 3130 for (i = 0; i < this.prefCats.length; i++) { 3131 prefCat = this.prefCats[i]; 3132 hasDescription = ( thisObj.hasDescTracks || thisObj.hasOpenDesc || thisObj.hasClosedDesc ) ? true : false; 3133 hasTranscript = ( thisObj.transcriptType === null ) ? false : true; 3134 3135 // If this player does not have descriptions or transcripts, do not output that option preferences. 3136 if ( prefCat === 'descriptions' && ! hasDescription || prefCat === 'transcript' && ! hasTranscript ) { 3137 continue; 3138 } 3139 $menuItem = $('<li></li>',{ 3140 'role': 'menuitem', 3141 'tabindex': '-1' 3142 }); 3143 if (prefCat === 'captions') { 3144 $menuItem.text( this.translate( 'prefMenuCaptions', 'Captions' ) ); 3145 } else if (prefCat === 'descriptions') { 3146 $menuItem.text( this.translate( 'prefMenuDescriptions', 'Descriptions' ) ); 3147 } else if (prefCat === 'keyboard') { 3148 $menuItem.text( this.translate( 'prefMenuKeyboard', 'Keyboard' ) ); 3149 } else if (prefCat === 'transcript') { 3150 $menuItem.text( this.translate( 'prefMenuTranscript', 'Transcript' ) ); 3151 } 3152 $menuItem.on('click',function() { 3153 whichPref = $(this).text(); 3154 thisObj.showingPrefsDialog = true; 3155 thisObj.setFullscreen(false); 3156 if (whichPref === thisObj.translate( 'prefMenuCaptions', 'Captions' ) ) { 3157 thisObj.captionPrefsDialog.show(); 3158 } else if (whichPref === thisObj.translate( 'prefMenuDescriptions', 'Descriptions' ) ) { 3159 thisObj.descPrefsDialog.show(); 3160 } else if (whichPref === thisObj.translate( 'prefMenuKeyboard', 'Keyboard' ) ) { 3161 thisObj.keyboardPrefsDialog.show(); 3162 } else if (whichPref === thisObj.translate( 'prefMenuTranscript', 'Transcript' ) ) { 3163 thisObj.transcriptPrefsDialog.show(); 3164 } 3165 thisObj.closePopups(); 3166 thisObj.showingPrefsDialog = false; 3167 }); 3168 $menu.append($menuItem); 3169 } 3170 this.$prefsButton.attr('data-prefs-popup','menu'); 3171 } else if (this.prefCats.length == 1) { 3172 // only 1 category, so don't create a popup menu. 3173 // Instead, open dialog directly when user clicks Prefs button 3174 this.$prefsButton.attr('data-prefs-popup',this.prefCats[0]); 3175 } 3176 } else if (which === 'captions' || which === 'chapters') { 3177 hasDefault = false; 3178 for (i = 0; i < tracks.length; i++) { 3179 track = tracks[i]; 3180 if (which === 'captions' && this.player === 'html5' && typeof track.cues === 'undefined') { 3181 includeMenuItem = false; 3182 } else { 3183 includeMenuItem = true; 3184 } 3185 if (includeMenuItem) { 3186 $menuItem = $('<li></li>',{ 3187 'role': 'menuitemradio', 3188 'tabindex': '-1', 3189 'lang': track.language 3190 }); 3191 if (track.def && this.prefCaptions == 1) { 3192 $menuItem.attr('aria-checked','true'); 3193 hasDefault = true; 3194 } else { 3195 $menuItem.attr('aria-checked','false'); 3196 } 3197 // Get a label using track data 3198 if (which == 'captions') { 3199 $menuItem.text(track.label); 3200 $menuItem.on('click',this.getCaptionClickFunction(track)); 3201 } else if (which == 'chapters') { 3202 $menuItem.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start)); 3203 $menuItem.on('click',this.getChapterClickFunction(track.start)); 3204 } 3205 $menu.append($menuItem); 3206 } 3207 } 3208 if (which === 'captions') { 3209 // add a 'captions off' menu item 3210 $menuItem = $('<li></li>',{ 3211 'role': 'menuitemradio', 3212 'tabindex': '-1', 3213 }).text( this.translate( 'captionsOff', 'Captions off' ) ); 3214 if (this.prefCaptions === 0) { 3215 $menuItem.attr('aria-checked','true'); 3216 hasDefault = true; 3217 } else { 3218 $menuItem.attr('aria-checked','false'); 3219 } 3220 $menuItem.on('click',this.getCaptionOffFunction()); 3221 $menu.append($menuItem); 3222 } 3223 } else if (which === 'transcript-window' || which === 'sign-window') { 3224 windowOptions = []; 3225 windowOptions.push({ 3226 'name': 'move', 3227 'label': this.translate( 'windowMove', 'Move' ) 3228 }); 3229 windowOptions.push({ 3230 'name': 'resize', 3231 'label': this.translate( 'windowResize', 'Resize' ) 3232 }); 3233 windowOptions.push({ 3234 'name': 'close', 3235 'label': this.translate( 'closeButtonLabel', 'Close' ) 3236 }); 3237 for (i = 0; i < windowOptions.length; i++) { 3238 $menuItem = $('<li></li>',{ 3239 'role': 'menuitem', 3240 'tabindex': '-1', 3241 'data-choice': windowOptions[i].name 3242 }); 3243 $menuItem.text(windowOptions[i].label); 3244 $menuItem.on('click',function(e) { 3245 e.stopPropagation(); 3246 if (typeof e.button !== 'undefined' && e.button !== 0) { 3247 // this was a mouse click (if click is triggered by keyboard, e.button is undefined) 3248 // and the button was not a left click (left click = 0) 3249 // therefore, ignore this click 3250 return false; 3251 } 3252 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 3253 thisObj.windowMenuClickRegistered = true; 3254 thisObj.handleMenuChoice(which.substring(0, which.indexOf('-')), $(this).attr('data-choice'), e); 3255 } 3256 }); 3257 $menu.append($menuItem); 3258 } 3259 } 3260 // assign default item, if there isn't one already 3261 if (which === 'captions' && !hasDefault) { 3262 // check the menu item associated with the default language 3263 // as determined in control.js > syncTrackLanguages() 3264 if ($menu.find('li[lang=' + this.captionLang + ']')) { 3265 // a track exists for the default language. Check that item in the menu 3266 $menu.find('li[lang=' + this.captionLang + ']').attr('aria-checked','true'); 3267 } else { 3268 // check the last item (captions off) 3269 $menu.find('li').last().attr('aria-checked','true'); 3270 } 3271 } else if (which === 'chapters') { 3272 if ($menu.find('li:contains("' + this.defaultChapter + '")')) { 3273 $menu.find('li:contains("' + this.defaultChapter + '")').attr('aria-checked','true').addClass('able-focus'); 3274 } else { 3275 $menu.find('li').first().attr('aria-checked','true').addClass('able-focus'); 3276 } 3277 } 3278 // add keyboard handlers for navigating within popups 3279 $menu.on('keydown',function (e) { 3280 3281 $thisItem = $(this).find('li:focus'); 3282 if ($thisItem.is(':first-child')) { 3283 // this is the first item in the menu 3284 $prevItem = $(this).find('li').last(); // wrap to bottom 3285 $nextItem = $thisItem.next(); 3286 } else if ($thisItem.is(':last-child')) { 3287 // this is the last Item 3288 $prevItem = $thisItem.prev(); 3289 $nextItem = $(this).find('li').first(); // wrap to top 3290 } else { 3291 $prevItem = $thisItem.prev(); 3292 $nextItem = $thisItem.next(); 3293 } 3294 if (e.key === 'Tab') { 3295 if (e.shiftKey) { 3296 $thisItem.removeClass('able-focus'); 3297 $prevItem.trigger('focus').addClass('able-focus'); 3298 } else { 3299 $thisItem.removeClass('able-focus'); 3300 $nextItem.trigger('focus').addClass('able-focus'); 3301 } 3302 } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { 3303 $thisItem.removeClass('able-focus'); 3304 $nextItem.trigger('focus').addClass('able-focus'); 3305 } else if (e.key == 'ArrowUp' || e.key === 'ArrowLeft') { 3306 $thisItem.removeClass('able-focus'); 3307 $prevItem.trigger('focus').addClass('able-focus'); 3308 } else if (e.key === ' ' || e.key === 'Enter') { 3309 $thisItem.trigger( 'click' ); 3310 } else if (e.key === 'Escape') { 3311 $thisItem.removeClass('able-focus'); 3312 thisObj.closePopups(); 3313 e.stopPropagation; 3314 } 3315 e.preventDefault(); 3316 }); 3317 this.$controllerDiv.append($menu); 3318 return $menu; 3319 }; 3320 3321 AblePlayer.prototype.closePopups = function () { 3322 3323 var thisObj = this; 3324 3325 if (this.chaptersPopup && this.chaptersPopup.is(':visible')) { 3326 this.chaptersPopup.hide(); 3327 this.$chaptersButton.attr('aria-expanded','false').trigger('focus'); 3328 } 3329 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 3330 this.captionsPopup.hide(); 3331 this.$ccButton.attr('aria-expanded', 'false'); 3332 this.waitThenFocus(this.$ccButton); 3333 } 3334 if (this.prefsPopup && this.prefsPopup.is(':visible') && !this.hidingPopup) { 3335 this.hidingPopup = true; // stopgap to prevent popup from re-opening again on keypress 3336 this.prefsPopup.hide(); 3337 // restore menu items to their original state 3338 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3339 this.$prefsButton.attr('aria-expanded', 'false'); 3340 if (!this.showingPrefsDialog) { 3341 this.waitThenFocus(thisObj.$prefsButton); 3342 } 3343 // wait briefly, then reset hidingPopup 3344 setTimeout(function() { 3345 thisObj.hidingPopup = false; 3346 },100); 3347 } 3348 if (this.$volumeSlider && this.$volumeSlider.is(':visible')) { 3349 this.$volumeSlider.hide().attr('aria-hidden','true'); 3350 this.$volumeButton.attr('aria-expanded', 'false').trigger('focus'); 3351 } 3352 if (this.$transcriptPopup && this.$transcriptPopup.is(':visible')) { 3353 this.hidingPopup = true; 3354 this.$transcriptPopup.hide(); 3355 // restore menu items to their original state 3356 this.$transcriptPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3357 this.$transcriptPopupButton.attr('aria-expanded','false').trigger('focus'); 3358 // wait briefly, then reset hidingPopup 3359 setTimeout(function() { 3360 thisObj.hidingPopup = false; 3361 },100); 3362 } 3363 if (this.$signPopup && this.$signPopup.is(':visible')) { 3364 this.$signPopup.hide(); 3365 // restore menu items to their original state 3366 this.$signPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3367 this.$signPopupButton.attr('aria-expanded','false').trigger('focus'); 3368 } 3369 }; 3370 3371 AblePlayer.prototype.setupPopups = function (which) { 3372 3373 // Create and fill in the popup menu forms for various controls. 3374 // parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters') 3375 // If which is undefined, automatically setup 'captions', 'chapters', and 'prefs' popups 3376 // However, only setup 'transcript-window' and 'sign-window' popups if passed as value of which 3377 var popups, i, tracks; 3378 3379 popups = []; 3380 if (typeof which === 'undefined') { 3381 popups.push('prefs'); 3382 } 3383 3384 if (which === 'captions' || (typeof which === 'undefined')) { 3385 if (this.captions.length > 0) { 3386 popups.push('captions'); 3387 } 3388 } 3389 if (which === 'chapters' || (typeof which === 'undefined')) { 3390 if (this.chapters.length > 0 && this.useChaptersButton) { 3391 popups.push('chapters'); 3392 } 3393 } 3394 if (which === 'transcript-window' && this.transcriptType === 'popup') { 3395 popups.push('transcript-window'); 3396 } 3397 if (which === 'sign-window' && this.hasSignLanguage) { 3398 popups.push('sign-window'); 3399 } 3400 if (popups.length > 0) { 3401 for (i=0; i<popups.length; i++) { 3402 var popup = popups[i]; 3403 if (popup == 'prefs') { 3404 this.prefsPopup = this.createPopup('prefs'); 3405 } else if (popup == 'captions') { 3406 if (typeof this.captionsPopup === 'undefined' || !this.captionsPopup) { 3407 this.captionsPopup = this.createPopup('captions',this.captions); 3408 } 3409 } else if (popup == 'chapters') { 3410 if (this.selectedChapters) { 3411 tracks = this.selectedChapters.cues; 3412 } else if (this.chapters.length >= 1) { 3413 tracks = this.chapters[0].cues; 3414 } else { 3415 tracks = []; 3416 } 3417 if (typeof this.chaptersPopup === 'undefined' || !this.chaptersPopup) { 3418 this.chaptersPopup = this.createPopup('chapters',tracks); 3419 } 3420 } else if (popup == 'transcript-window') { 3421 return this.createPopup('transcript-window'); 3422 } else if (popup == 'sign-window') { 3423 return this.createPopup('sign-window'); 3424 } 3425 } 3426 } 3427 }; 3428 3429 AblePlayer.prototype.provideFallback = function() { 3430 3431 // provide fallback in case of a critical error building the player 3432 // to test, set data-test-fallback to either of the following values: 3433 // 1 = emulate failure to build Able Player 3434 // 2 = emulate browser that doesn't support HTML5 media 3435 3436 var i, $fallback; 3437 3438 if (this.usingFallback) { 3439 // fallback has already been implemented. 3440 // stopgap to prevent this function from executing twice on the same media element 3441 return; 3442 } else { 3443 this.usingFallback = true; 3444 } 3445 3446 if (!this.testFallback) { 3447 // this is not a test. 3448 // an actual error has resulted in this function being called. 3449 // use scenario 1 3450 this.testFallback = 1; 3451 } 3452 3453 if (typeof this.$media === 'undefined') { 3454 // this function has been called prior to initialize.js > reinitialize() 3455 // before doing anything, need to create the jQuery media object 3456 this.$media = $(this.media); 3457 } 3458 3459 // get/assign an id for the media element 3460 if (this.$media.attr('id')) { 3461 this.mediaId = this.$media.attr('id'); 3462 } else { 3463 this.mediaId = 'media' + Math.floor(Math.random() * 1000000000).toString(); 3464 } 3465 3466 // check whether element has nested fallback content 3467 this.hasFallback = false; 3468 if (this.$media.children().length) { 3469 i = 0; 3470 while (i < this.$media.children().length && !this.hasFallback) { 3471 if (!(this.$media.children()[i].tagName === 'SOURCE' || 3472 this.$media.children()[i].tagName === 'TRACK')) { 3473 // this element is something other than <source> or <track> 3474 this.hasFallback = true; 3475 } 3476 i++; 3477 } 3478 } 3479 if (!this.hasFallback) { 3480 // the HTML code does not include any nested fallback content 3481 // inject our own 3482 // NOTE: this message is not translated, since fallback may be needed 3483 // due to an error loading the translation file 3484 // This will only be needed on very rare occasions, so English is ok. 3485 $fallback = $('<p>').text('Media player unavailable.'); 3486 this.$media.append($fallback); 3487 } 3488 3489 // get height and width attributes, if present 3490 // and add them to a style attribute 3491 if (this.$media.attr('width')) { 3492 this.$media.css('width',this.$media.attr('width') + 'px'); 3493 } 3494 if (this.$media.attr('height')) { 3495 this.$media.css('height',this.$media.attr('height') + 'px'); 3496 } 3497 // Remove data-able-player attribute 3498 this.$media.removeAttr('data-able-player'); 3499 3500 // Add controls attribute (so browser will add its own controls) 3501 this.$media.prop('controls',true); 3502 3503 if (this.testFallback == 2) { 3504 3505 // emulate browser failure to support HTML5 media by changing the media tag name 3506 // browsers should display the supported content that's nested inside 3507 $(this.$media).replaceWith($('<foobar id="foobar-' + this.mediaId + '">')); 3508 this.$newFallbackElement = $('#foobar-' + this.mediaId); 3509 3510 // append all children from the original media 3511 if (this.$media.children().length) { 3512 i = this.$media.children().length - 1; 3513 while (i >= 0) { 3514 this.$newFallbackElement.prepend($(this.$media.children()[i])); 3515 i--; 3516 } 3517 } 3518 if (!this.hasFallback) { 3519 // inject our own fallback content, defined above 3520 this.$newFallbackElement.append($fallback); 3521 } 3522 } else { 3523 console.warn("Able Player encountered a problem, falling back to browser's HTML5 player."); 3524 } 3525 return; 3526 }; 3527 3528 AblePlayer.prototype.calculateControlLayout = function () { 3529 3530 // Calculates the layout for controls based on media and options. 3531 // Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin) 3532 // Keys are the following order: 3533 // 0 = Top left 3534 // 1 = Top right 3535 // 2 = Bottom left (legacy skin only) 3536 // 3 = Bottom right (legacy skin only) 3537 // Each key contains an array of control names to put in that section. 3538 3539 var controlLayout, playbackSupported, numA11yButtons; 3540 3541 controlLayout = []; 3542 controlLayout[0] = []; 3543 controlLayout[1] = []; 3544 if (this.skin === 'legacy') { 3545 controlLayout[2] = []; 3546 controlLayout[3] = []; 3547 } 3548 3549 controlLayout[0].push('play'); 3550 controlLayout[0].push('restart'); 3551 controlLayout[0].push('rewind'); 3552 controlLayout[0].push('forward'); 3553 3554 if (this.skin === 'legacy') { 3555 controlLayout[1].push('seek'); 3556 } 3557 3558 if (this.hasPlaylist) { 3559 if (this.skin === 'legacy') { 3560 controlLayout[0].push('previous'); 3561 controlLayout[0].push('next'); 3562 } else { 3563 controlLayout[0].push('previous'); 3564 controlLayout[0].push('next'); 3565 } 3566 } 3567 3568 if (this.isPlaybackRateSupported()) { 3569 playbackSupported = true; 3570 if (this.skin === 'legacy') { 3571 controlLayout[2].push('slower'); 3572 controlLayout[2].push('faster'); 3573 } 3574 } else { 3575 playbackSupported = false; 3576 } 3577 3578 numA11yButtons = 0; 3579 if (this.hasCaptions) { 3580 numA11yButtons++; 3581 if (this.skin === 'legacy') { 3582 controlLayout[2].push('captions'); 3583 } else { 3584 controlLayout[1].push('captions'); 3585 } 3586 } 3587 if (this.hasSignLanguage) { 3588 numA11yButtons++; 3589 if (this.skin === 'legacy') { 3590 controlLayout[2].push('sign'); 3591 } else { 3592 controlLayout[1].push('sign'); 3593 } 3594 } 3595 if (this.mediaType === 'video') { 3596 if (this.hasOpenDesc || this.hasClosedDesc) { 3597 numA11yButtons++; 3598 if (this.skin === 'legacy') { 3599 controlLayout[2].push('descriptions'); 3600 } else { 3601 controlLayout[1].push('descriptions'); 3602 } 3603 } 3604 } 3605 if (this.transcriptType !== null && !(this.hideTranscriptButton)) { 3606 numA11yButtons++; 3607 if (this.skin === 'legacy') { 3608 controlLayout[2].push('transcript'); 3609 } else { 3610 controlLayout[1].push('transcript'); 3611 } 3612 } 3613 if (this.hasChapters && this.useChaptersButton) { 3614 numA11yButtons++; 3615 if (this.skin === 'legacy') { 3616 controlLayout[2].push('chapters'); 3617 } else { 3618 controlLayout[1].push('chapters'); 3619 } 3620 } 3621 3622 if (this.skin == '2020' && numA11yButtons > 0) { 3623 controlLayout[1].push('pipe'); 3624 } 3625 3626 if (playbackSupported && this.skin === '2020') { 3627 controlLayout[1].push('faster'); 3628 controlLayout[1].push('slower'); 3629 controlLayout[1].push('pipe'); 3630 } 3631 3632 if (this.skin === 'legacy') { 3633 controlLayout[3].push('preferences'); 3634 } else { 3635 controlLayout[1].push('preferences'); 3636 } 3637 3638 if (this.mediaType === 'video' && this.allowFullscreen && this.nativeFullscreenSupported() ) { 3639 if (this.skin === 'legacy') { 3640 controlLayout[3].push('fullscreen'); 3641 } else { 3642 controlLayout[1].push('fullscreen'); 3643 } 3644 } 3645 3646 if (this.browserSupportsVolume()) { 3647 this.volumeButton = 'volume-' + this.getVolumeName(this.volume); 3648 if (this.skin === 'legacy') { 3649 controlLayout[1].push('volume'); 3650 } else { 3651 controlLayout[1].push('volume'); 3652 } 3653 } else { 3654 this.volume = false; 3655 } 3656 return controlLayout; 3657 }; 3658 3659 AblePlayer.prototype.addControls = function() { 3660 3661 // determine which controls to show based on several factors: 3662 // mediaType (audio vs video) 3663 // availability of tracks (e.g., for closed captions & audio description) 3664 // browser support (e.g., for sliders and speedButtons) 3665 // user preferences (???) 3666 // some controls are aligned on the left, and others on the right 3667 3668 var thisObj, controlLayout, numSections, 3669 i, j, controls, $controllerSpan, $sliderDiv, sliderLabel, $pipe, control, 3670 buttonTitle, $newButton, buttonText, position, buttonHeight, 3671 buttonWidth, buttonSide, controllerWidth, tooltipId, tooltipY, tooltipX, 3672 tooltipWidth, tooltipStyle, tooltip, tooltipTimerId, captionLabel, popupMenuId; 3673 3674 thisObj = this; 3675 3676 // Initialize the layout into the this.controlLayout variable. 3677 controlLayout = this.calculateControlLayout(); 3678 numSections = controlLayout.length; 3679 3680 // add an empty div to serve as a tooltip 3681 tooltipId = this.mediaId + '-tooltip'; 3682 this.$tooltipDiv = $('<div>',{ 3683 'id': tooltipId, 3684 'class': 'able-tooltip' 3685 }).hide(); 3686 this.$controllerDiv.append(this.$tooltipDiv); 3687 3688 if (this.skin == '2020') { 3689 // add a full-width seek bar 3690 $sliderDiv = $('<div class="able-seekbar"></div>'); 3691 sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' ); 3692 this.$controllerDiv.append($sliderDiv); 3693 this.seekBar = new AccessibleSlider($sliderDiv, this.duration, this.seekInterval, sliderLabel ); 3694 } 3695 3696 // add a full-width seek bar 3697 let $controlRow = $('<div class="able-control-row"></div>'); 3698 this.$controllerDiv.append($controlRow); 3699 3700 for (i = 0; i < numSections; i++) { 3701 controls = controlLayout[i]; 3702 if ((i % 2) === 0) { // even keys on the left 3703 $controllerSpan = $('<div>',{ 3704 'class': 'able-left-controls' 3705 }); 3706 } else { // odd keys on the right 3707 $controllerSpan = $('<div>',{ 3708 'class': 'able-right-controls' 3709 }); 3710 } 3711 $controlRow.append($controllerSpan); 3712 3713 for (j=0; j<controls.length; j++) { 3714 control = controls[j]; 3715 if (control === 'seek') { 3716 $sliderDiv = $('<div class="able-seekbar"></div>'); 3717 sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' ); 3718 $controllerSpan.append($sliderDiv); 3719 if (typeof this.duration === 'undefined' || this.duration === 0) { 3720 // set arbitrary starting duration, and change it when duration is known 3721 this.duration = 60; 3722 // also set elapsed to 0 3723 this.elapsed = 0; 3724 } 3725 this.seekBar = new AccessibleSlider( $sliderDiv, this.duration, this.seekInterval, sliderLabel ); 3726 } else if (control === 'pipe') { 3727 $pipe = $('<span>', { 3728 'aria-hidden': 'true', 3729 'class': 'able-pipe', 3730 }); 3731 $pipe.append('|'); 3732 $controllerSpan.append($pipe); 3733 } else { 3734 // this control is a button 3735 buttonTitle = this.getButtonTitle(control); 3736 3737 // Buttons consist of a <div role="button"> with an <svg> inside. 3738 // We add aria-label to the button (but not title) 3739 // This has been thoroughly tested and works well in all screen reader/browser combinations 3740 // See https://github.com/ableplayer/ableplayer/issues/81 3741 3742 // NOTE: Changed from <button> to <div role="button" as of 4.2.18 3743 // because <button> elements are rendered poorly in high contrast mode 3744 // in some OS/browser/plugin combinations 3745 3746 // In 5.0.0, icons are always SVG, so the font & image icon edge cases are removed. 3747 $newButton = $('<div>',{ 3748 'role': 'button', 3749 'tabindex': '0', 3750 'class': 'able-button-handler-' + control 3751 }); 3752 3753 if (control === 'volume' || control === 'preferences' || control === 'captions') { 3754 if (control == 'preferences') { 3755 this.prefCats = this.getPreferencesGroups(); 3756 if (this.prefCats.length > 1) { 3757 // Prefs button will trigger a menu 3758 popupMenuId = this.mediaId + '-prefs-menu'; 3759 $newButton.attr({ 3760 'aria-controls': popupMenuId, 3761 'aria-haspopup': 'menu', 3762 'aria-expanded': 'false' 3763 }); 3764 } else if (this.prefCats.length === 1) { 3765 // Prefs button will trigger a dialog 3766 $newButton.attr({ 3767 'aria-haspopup': 'dialog' 3768 }); 3769 } 3770 } else if (control === 'volume') { 3771 popupMenuId = this.mediaId + '-volume-slider'; 3772 // volume slider popup is not a menu or a dialog 3773 // therefore, using aria-expanded rather than aria-haspopup to communicate properties/state 3774 $newButton.attr({ 3775 'aria-controls': popupMenuId, 3776 'aria-expanded': 'false' 3777 }); 3778 } else if (control === 'captions' && this.captions) { 3779 if (this.captions.length > 1) { 3780 $newButton.attr('aria-expanded', 'false'); 3781 } else { 3782 $newButton.attr('aria-pressed', 'false'); 3783 } 3784 } 3785 } 3786 var getControl = control; 3787 if ( control === 'faster' && this.speedIcons === 'animals' ) { 3788 getControl = 'rabbit'; 3789 } 3790 if ( control === 'slower' && this.speedIcons === 'animals' ) { 3791 getControl = 'turtle'; 3792 } 3793 if ( control === 'volume' ) { 3794 this.getIcon( $newButton, this.volumeButton ); 3795 } else { 3796 if ( 'fullscreen' === getControl ) { 3797 getControl = ( this.fullscreen ) ? 'fullscreen-collapse' : 'fullscreen-expand'; 3798 } 3799 this.getIcon( $newButton, getControl ); 3800 } 3801 3802 this.setText($newButton,buttonTitle); 3803 // add an event listener that displays a tooltip on mouseenter or focus 3804 $newButton.on('mouseenter focus',function(e) { 3805 3806 // when entering a new tooltip, we can forget about hiding the previous tooltip. 3807 // since the same tooltip div is used, it's location just changes. 3808 clearTimeout(tooltipTimerId); 3809 3810 buttonText = $(this).attr('aria-label'); 3811 // get position of this button 3812 position = $(this).position(); 3813 buttonHeight = $(this).height(); 3814 buttonWidth = $(this).width(); 3815 // position() is expressed using top and left (of button); 3816 // add right (of button) too, for convenience 3817 controllerWidth = thisObj.$controllerDiv.width(); 3818 position.right = controllerWidth - position.left - buttonWidth; 3819 3820 // The following formula positions tooltip below the button 3821 // which allows the tooltip to be hoverable as per WCAG 2.x SC 1.4.13 3822 // without obstructing the seekbar 3823 tooltipY = position.top + buttonHeight + 5; 3824 3825 if ($(this).parent().hasClass('able-right-controls')) { 3826 // this control is on the right side 3827 buttonSide = 'right'; 3828 } else { 3829 // this control is on the left side 3830 buttonSide = 'left'; 3831 } 3832 // populate tooltip, then calculate its width before showing it 3833 tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).width(); 3834 // center the tooltip horizontally over the button 3835 if (buttonSide == 'left') { 3836 tooltipX = position.left - tooltipWidth/2; 3837 if (tooltipX < 0) { 3838 // tooltip would exceed the bounds of the player. Adjust. 3839 tooltipX = 2; 3840 } 3841 tooltipStyle = { 3842 left: tooltipX + 'px', 3843 right: '', 3844 top: tooltipY + 'px' 3845 }; 3846 } else { 3847 tooltipX = position.right - tooltipWidth/2; 3848 if (tooltipX < 0) { 3849 // tooltip would exceed the bounds of the player. Adjust. 3850 tooltipX = 2; 3851 } 3852 tooltipStyle = { 3853 left: '', 3854 right: tooltipX + 'px', 3855 top: tooltipY + 'px' 3856 }; 3857 } 3858 tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).css(tooltipStyle); 3859 thisObj.showTooltip(tooltip); 3860 $(this).on('mouseleave blur',function() { 3861 3862 // (keep the tooltip visible if user hovers over it) 3863 // This causes unwanted side effects if tooltips are positioned above the buttons 3864 // as the persistent tooltip obstructs the seekbar, 3865 // blocking users from being able to move a pointer from a button to the seekbar 3866 // This limitation was addressed in 4.4.49 by moving the tooltip below the buttons 3867 3868 // clear existing timeout before reassigning variable 3869 clearTimeout(tooltipTimerId); 3870 tooltipTimerId = setTimeout(function() { 3871 // give the user a half second to move cursor to tooltip before removing 3872 // see https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus#hoverable 3873 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 3874 }, 500); 3875 3876 thisObj.$tooltipDiv.on('mouseenter focus', function() { 3877 clearTimeout(tooltipTimerId); 3878 }); 3879 3880 thisObj.$tooltipDiv.on('mouseleave blur', function() { 3881 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 3882 }); 3883 3884 }); 3885 }); 3886 3887 if (control === 'captions') { 3888 if (!this.prefCaptions || this.prefCaptions !== 1) { 3889 // captions are available, but user has them turned off 3890 if (this.captions.length > 1) { 3891 captionLabel = this.translate( 'captions', 'Captions' ); 3892 } else { 3893 captionLabel = this.translate( 'showCaptions', 'Show captions' ); 3894 } 3895 $newButton.addClass('buttonOff').attr('title',captionLabel); 3896 $newButton.attr('aria-pressed', 'false'); 3897 } 3898 } else if (control === 'descriptions') { 3899 if (!this.prefDesc || this.prefDesc !== 1) { 3900 // user prefer non-audio described version 3901 // Therefore, load media without description 3902 // Description can be toggled on later with this button 3903 $newButton.addClass('buttonOff').attr( 'title', this.translate( 'turnOnDescriptions', 'Turn on descriptions' ) ); 3904 } 3905 } 3906 3907 $controllerSpan.append($newButton); 3908 3909 // create variables of buttons that are referenced throughout the AblePlayer object 3910 if (control === 'play') { 3911 this.$playpauseButton = $newButton; 3912 } else if (control == 'previous') { 3913 this.$prevButton = $newButton; 3914 // if player is being rebuilt because user clicked the Prev button 3915 // return focus to that (newly built) button 3916 if (this.buttonWithFocus == 'previous') { 3917 this.$prevButton.trigger('focus'); 3918 this.buttonWithFocus = null; 3919 } 3920 } else if (control == 'next') { 3921 this.$nextButton = $newButton; 3922 // if player is being rebuilt because user clicked the Next button 3923 // return focus to that (newly built) button 3924 if (this.buttonWithFocus == 'next') { 3925 this.$nextButton.trigger('focus'); 3926 this.buttonWithFocus = null; 3927 } 3928 } else if (control === 'captions') { 3929 this.$ccButton = $newButton; 3930 } else if (control === 'sign') { 3931 this.$signButton = $newButton; 3932 // gray out sign button if sign language window is not active 3933 if (!(this.$signWindow.is(':visible'))) { 3934 this.$signButton.addClass('buttonOff'); 3935 } 3936 } else if (control === 'descriptions') { 3937 this.$descButton = $newButton; 3938 // button will be enabled or disabled in description.js > initDescription() 3939 } else if (control === 'mute') { 3940 this.$muteButton = $newButton; 3941 } else if (control === 'transcript') { 3942 this.$transcriptButton = $newButton; 3943 // gray out transcript button if transcript is not active 3944 if (!(this.$transcriptDiv.is(':visible'))) { 3945 this.$transcriptButton.addClass('buttonOff').attr( 'title', this.translate( 'showTranscript', 'Show transcript' ) ); 3946 } 3947 } else if (control === 'fullscreen') { 3948 this.$fullscreenButton = $newButton; 3949 } else if (control === 'chapters') { 3950 this.$chaptersButton = $newButton; 3951 } else if (control === 'preferences') { 3952 this.$prefsButton = $newButton; 3953 } else if (control === 'volume') { 3954 this.$volumeButton = $newButton; 3955 } 3956 } 3957 if (control === 'volume') { 3958 // in addition to the volume button, add a hidden slider 3959 this.addVolumeSlider($controllerSpan); 3960 } 3961 } 3962 if ((i % 2) == 1) { 3963 this.$controllerDiv.append('<div class="ableplayer-clear"></div>'); 3964 } 3965 } 3966 3967 if (typeof this.$captionsDiv !== 'undefined') { 3968 // stylize captions based on user prefs 3969 this.stylizeCaptions(this.$captionsDiv); 3970 } 3971 if (typeof this.$descDiv !== 'undefined') { 3972 // stylize descriptions based on user's caption prefs 3973 this.stylizeCaptions(this.$descDiv); 3974 } 3975 3976 // combine left and right controls arrays for future reference 3977 this.controls = []; 3978 for (var sec in controlLayout) if (Object.hasOwn(controlLayout, sec)) { 3979 this.controls = this.controls.concat(controlLayout[sec]); 3980 } 3981 3982 // Update state-based display of controls. 3983 this.refreshControls(); 3984 }; 3985 3986 AblePlayer.prototype.cuePlaylistItem = function(sourceIndex) { 3987 3988 // Move to a new item in a playlist. 3989 // NOTE: Swapping source for audio description is handled elsewhere; 3990 // see description.js > swapDescription() 3991 3992 var $newItem, prevPlayer, newPlayer, itemTitle, itemLang, nowPlayingSpan; 3993 3994 var thisObj = this; 3995 3996 prevPlayer = this.player; 3997 3998 if (this.initializing) ; else { 3999 if (this.playerCreated) { 4000 // remove the old 4001 this.deletePlayer('playlist'); 4002 } 4003 } 4004 4005 // set swappingSrc; needs to be true within recreatePlayer(), called below 4006 this.swappingSrc = true; 4007 4008 // if a new playlist item is being requested, and playback has already started, 4009 // it should be ok to play automatically, regardless of how it was requested 4010 if (this.startedPlaying) { 4011 this.okToPlay = true; 4012 } else { 4013 this.okToPlay = false; 4014 } 4015 4016 // We are no longer loading the previous media source 4017 // Only now, as a new source is requested, is it safe to reset this var 4018 // It will be reset to true when media.load() is called 4019 this.loadingMedia = false; 4020 4021 // Determine appropriate player to play this media 4022 $newItem = this.$playlist.eq(sourceIndex); 4023 this.playlistIndex = sourceIndex; 4024 if (this.hasAttr($newItem,'data-youtube-id')) { 4025 this.youTubeId = this.getYouTubeId($newItem.attr('data-youtube-id')); 4026 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 4027 this.youTubeDescId = this.getYouTubeId($newItem.attr('data-youtube-desc-id')); 4028 } 4029 newPlayer = 'youtube'; 4030 } else if (this.hasAttr($newItem,'data-vimeo-id')) { 4031 this.vimeoId = this.getVimeoId($newItem.attr('data-vimeo-id')); 4032 if (this.hasAttr($newItem,'data-vimeo-desc-id')) { 4033 this.vimeoDescId = this.getVimeoId($newItem.attr('data-vimeo-desc-id')); 4034 } 4035 newPlayer = 'vimeo'; 4036 } else { 4037 newPlayer = 'html5'; 4038 } 4039 if (newPlayer === 'youtube') { 4040 if (prevPlayer === 'html5') { 4041 // pause and hide the previous media 4042 if (this.playing) { 4043 this.pauseMedia(); 4044 } 4045 this.$media.hide(); 4046 } 4047 } else { 4048 // the new player is not youtube 4049 this.youTubeId = false; 4050 if (prevPlayer === 'youtube') { 4051 // unhide the media element 4052 this.$media.show(); 4053 } 4054 } 4055 this.player = newPlayer; 4056 4057 // remove source and track elements from previous playlist item 4058 this.$media.empty(); 4059 4060 // transfer media attributes from playlist to media element 4061 if (this.hasAttr($newItem,'data-poster')) { 4062 this.$media.attr('poster',$newItem.attr('data-poster')); 4063 } 4064 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 4065 this.$media.attr('data-youtube-desc-id',$newItem.attr('data-youtube-desc-id')); 4066 } 4067 if (this.youTubeId) { 4068 this.$media.attr('data-youtube-id',$newItem.attr('data-youtube-id')); 4069 } 4070 4071 // add new <source> elements from playlist data 4072 var $sourceSpans = $newItem.children('span.able-source'); 4073 if ($sourceSpans.length) { 4074 $sourceSpans.each(function() { 4075 const $this = $(this); 4076 4077 // Check if the required data-src attribute exists 4078 if (thisObj.hasAttr($this, "data-src")) { 4079 const sanitizedSrc = purify.sanitize($this.attr("data-src")); 4080 4081 // Validate the protocol of the sanitized URL 4082 if (validate.isProtocolSafe(sanitizedSrc)) { 4083 // Create a new <source> element with the sanitized src 4084 const $newSource = $("<source>", { src: sanitizedSrc }); 4085 4086 // List of optional attributes to sanitize and add 4087 const optionalAttributes = [ 4088 "data-type", 4089 "data-desc-src", 4090 "data-sign-src", 4091 ]; 4092 4093 // Process optional attributes 4094 optionalAttributes.forEach((attr) => { 4095 if (thisObj.hasAttr($this, attr)) { 4096 const attrValue = $this.attr(attr); // Get the attribute value 4097 const sanitizedValue = purify.sanitize(attrValue); // Sanitize the value 4098 4099 // If the attribute ends with "-src", validate the protocol 4100 if (attr.endsWith("-src") && validate.isProtocolSafe(sanitizedValue)) { 4101 $newSource.attr(attr, sanitizedValue); // Add the sanitized and validated attribute 4102 } else if (!attr.endsWith("-src")) { 4103 $newSource.attr(attr, sanitizedValue); // Add sanitized value for non-src attributes 4104 } 4105 } 4106 }); 4107 4108 // Append the new <source> element to the media object 4109 thisObj.$media.append($newSource); 4110 } 4111 } 4112 }); 4113 } 4114 4115 // add new <track> elements from playlist data 4116 var $trackSpans = $newItem.children('span.able-track'); 4117 if ($trackSpans.length) { 4118 // for each element in $trackSpans, create a new <track> element 4119 $trackSpans.each(function() { 4120 const $this = $(this); 4121 if (thisObj.hasAttr($this, "data-src") && thisObj.hasAttr($this, "data-kind") && thisObj.hasAttr($this, "data-srclang")) { 4122 // all required attributes are present 4123 const sanitizedSrc = purify.sanitize($this.attr("data-src")); 4124 // Validate the protocol of the sanitized URL 4125 if (validate.isProtocolSafe(sanitizedSrc)) { 4126 // Create a new <track> element with the sanitized src 4127 const $newTrack = $("<track>", { 4128 src: sanitizedSrc, 4129 kind: $this.attr("data-kind"), 4130 srclang: $this.attr("data-srclang"), 4131 }); 4132 // List of optional attributes to sanitize and add 4133 const optionalAttributes = [ 4134 "data-label", 4135 "data-desc", 4136 "data-default", 4137 ]; 4138 optionalAttributes.forEach((attr) => { 4139 if (thisObj.hasAttr($this, attr)) { 4140 $newTrack.attr(attr, purify.sanitize($this.attr(attr))); 4141 } 4142 }); 4143 // Append the new <track> element to the media object 4144 thisObj.$media.append($newTrack); 4145 } 4146 } 4147 }); 4148 } 4149 4150 itemTitle = purify.sanitize( $newItem.text() ); 4151 if (this.hasAttr($newItem,'lang')) { 4152 itemLang = $newItem.attr('lang'); 4153 } 4154 // Update relevant arrays 4155 this.$sources = this.$media.find('source'); 4156 4157 // recreate player, informed by new attributes and track elements 4158 if (this.recreatingPlayer) { 4159 // stopgap to prevent multiple firings of recreatePlayer() 4160 return; 4161 } 4162 this.recreatePlayer().then(function() { 4163 4164 // update playlist to indicate which item is playing 4165 thisObj.$playlist.removeClass('able-current') 4166 .children('button').removeAttr('aria-current'); 4167 thisObj.$playlist.eq(sourceIndex).addClass('able-current') 4168 .children('button').attr('aria-current','true'); 4169 4170 // update Now Playing div 4171 if (thisObj.showNowPlaying === true) { 4172 if (typeof thisObj.$nowPlayingDiv !== 'undefined') { 4173 nowPlayingSpan = $('<span>'); 4174 if (typeof itemLang !== 'undefined') { 4175 nowPlayingSpan.attr('lang',itemLang); 4176 } 4177 nowPlayingSpan.html('<span>' + thisObj.translate( 'selectedTrack', 'Selected Track' ) + ':</span>' + itemTitle); 4178 thisObj.$nowPlayingDiv.html(nowPlayingSpan); 4179 } 4180 } 4181 4182 // if thisObj.swappingSrc is true, media will autoplay when ready 4183 if (thisObj.initializing) { // this is the first track - user hasn't pressed play yet 4184 thisObj.swappingSrc = false; 4185 } else { 4186 if (thisObj.player === 'html5') { 4187 if (!thisObj.loadingMedia) { 4188 thisObj.media.load(); 4189 thisObj.loadingMedia = true; 4190 } 4191 } else if (thisObj.player === 'youtube') { 4192 thisObj.okToPlay = true; 4193 } 4194 } 4195 thisObj.initializing = false; 4196 thisObj.playerCreated = true; // remains true until browser is refreshed 4197 }); 4198 }; 4199 4200 AblePlayer.prototype.deletePlayer = function(context) { 4201 4202 // remove player components that need to be rebuilt 4203 // after swapping media sources that have different durations 4204 // or explicitly declared data-desc attributes 4205 4206 // Context is one of the following: 4207 // playlist - called from cuePlaylistItem() 4208 // swap-desc-html - called from swapDescription with this.player == 'html' 4209 // swap-desc-youtube - called from swapDescription with this.player == 'youtube' 4210 // swap-desc-vimeo - called from swapDescription with this.player == 'vimeo' 4211 4212 if (this.player === 'youtube' && this.youTubePlayer) { 4213 this.youTubePlayer.destroy(); 4214 } 4215 4216 if (this.player === 'vimeo' && this.vimeoPlayer) { 4217 this.vimeoPlayer.destroy(); 4218 } 4219 4220 // Empty elements that will be rebuilt 4221 this.$controllerDiv.empty(); 4222 // this.$statusBarDiv.empty(); 4223 // this.$timer.empty(); 4224 this.$elapsedTimeContainer.empty().text('0:00'); // span.able-elapsedTime 4225 this.$durationContainer.empty(); // span.able-duration 4226 4227 // Remove popup windows and modal dialogs; these too will be rebuilt 4228 if (this.$signWindow) { 4229 this.$signWindow.remove(); 4230 } 4231 if (this.$transcriptArea) { 4232 this.$transcriptArea.remove(); 4233 } 4234 $('.able-modal-dialog').remove(); 4235 4236 // Remove caption and description wrappers 4237 if (this.$captionsWrapper) { 4238 this.$captionsWrapper.remove(); 4239 } 4240 if (this.$descDiv) { 4241 this.$descDiv.remove(); 4242 } 4243 4244 // reset key variables 4245 this.hasCaptions = false; 4246 this.hasChapters = false; 4247 this.hasDescTracks = false; 4248 this.hasOpenDesc = false; 4249 this.hasClosedDesc = false; 4250 4251 this.captionsPopup = null; 4252 this.chaptersPopup = null; 4253 this.transcriptType = null; 4254 4255 this.playerDeleted = true; // will reset to false in recreatePlayer() 4256 }; 4257 4258 AblePlayer.prototype.getButtonTitle = function(control) { 4259 4260 if (control === 'playpause') { 4261 return this.translate( 'play', 'Play' ); 4262 } else if (control === 'play') { 4263 return this.translate( 'play', 'Play' ); 4264 } else if (control === 'pause') { 4265 return this.translate( 'pause', 'Pause' ); 4266 } else if (control === 'restart') { 4267 return this.translate( 'restart', 'Restart' ); 4268 } else if (control === 'previous') { 4269 return this.translate( 'prevTrack', 'Previous track' ); 4270 } else if (control === 'next') { 4271 return this.translate( 'nextTrack', 'Next track' ); 4272 } else if (control === 'rewind') { 4273 return this.translate( 'rewind', 'Rewind' ); 4274 } else if (control === 'forward') { 4275 return this.translate( 'forward', 'Forward' ); 4276 } else if (control === 'captions') { 4277 if (this.captions.length > 1) { 4278 return this.translate( 'captions', 'Captions' ); 4279 } else { 4280 return (this.captionsOn) ? this.translate( 'hideCaptions', 'Hide captions' ) : this.translate( 'showCaptions', 'Show captions' ); 4281 } 4282 } else if (control === 'descriptions') { 4283 return (this.descOn) ? this.translate( 'turnOffDescriptions', 'Turn off descriptions' ) : this.translate( 'turnOnDescriptions', 'Turn on descriptions' ); 4284 } else if (control === 'transcript') { 4285 return (this.$transcriptDiv.is(':visible')) ? this.translate( 'hideTranscript', 'Hide transcript' ) : this.translate( 'showTranscript', 'Show transcript' ); 4286 } else if (control === 'chapters') { 4287 return this.translate( 'chapters', 'Chapters' ); 4288 } else if (control === 'sign') { 4289 return this.translate( 'sign', 'Sign language' ); 4290 } else if (control === 'volume') { 4291 return this.translate( 'volume', 'Volume' ); 4292 } else if (control === 'faster') { 4293 return this.translate( 'faster', 'Faster' ); 4294 } else if (control === 'slower') { 4295 return this.translate( 'slower', 'Slower' ); 4296 } else if (control === 'preferences') { 4297 return this.translate( 'preferences', 'Preferences' ); 4298 } else if (control === 'fullscreen') { 4299 return ( !this.fullscreen ) ? this.translate( 'enterFullScreen', 'Enter full screen' ) : this.translate( 'exitFullScreen', 'Exit full screen' ); 4300 } else { 4301 // there should be no other controls, but just in case: 4302 // return the name of the control with first letter in upper case 4303 // ultimately will need to get a translated label from this.tt 4304 if (this.debug) { 4305 console.log('Found an untranslated label: ' + control); 4306 } 4307 return this.capitalizeFirstLetter( control ); 4308 } 4309 }; 4310 } 4311 4312 function addCaptionFunctions(AblePlayer) { 4313 AblePlayer.prototype.updateCaption = function (time) { 4314 if ( 4315 !this.usingYouTubeCaptions && 4316 !this.usingVimeoCaptions && 4317 typeof this.$captionsWrapper !== "undefined" 4318 ) { 4319 if (this.captionsOn) { 4320 this.$captionsWrapper.show(); 4321 if (typeof time !== "undefined") { 4322 this.showCaptions(time); 4323 } 4324 } else if (this.$captionsWrapper) { 4325 this.$captionsWrapper.hide(); 4326 this.prefCaptions = 0; 4327 } 4328 } 4329 }; 4330 4331 AblePlayer.prototype.updateCaptionsMenu = function (lang) { 4332 // uncheck all previous menu items 4333 this.captionsPopup.find("li").attr("aria-checked", "false"); 4334 if (typeof lang === "undefined") { 4335 // check the last menu item (captions off) 4336 this.captionsPopup.find("li").last().attr("aria-checked", "true"); 4337 } else { 4338 // check the newly selected lang 4339 this.captionsPopup 4340 .find("li[lang=" + lang + "]") 4341 .attr("aria-checked", "true"); 4342 } 4343 }; 4344 4345 AblePlayer.prototype.getCaptionClickFunction = function (track) { 4346 // Returns the function used when a caption is clicked in the captions menu. 4347 // Not called if user clicks "Captions off". Instead, that triggers getCaptionOffFunction() 4348 4349 var thisObj = this; 4350 return function () { 4351 thisObj.selectedCaptions = track; 4352 thisObj.captionLang = track.language; 4353 thisObj.currentCaption = -1; 4354 if (thisObj.usingYouTubeCaptions) { 4355 if (thisObj.captionsOn) { 4356 // Two things must be true in order for setOption() to work: 4357 // The YouTube caption module must be loaded 4358 // and the video must have started playing 4359 if ( 4360 thisObj.youTubePlayer.getOptions("captions") && 4361 thisObj.startedPlaying 4362 ) { 4363 thisObj.youTubePlayer.setOption("captions", "track", { 4364 languageCode: thisObj.captionLang, 4365 }); 4366 } else { 4367 // the two conditions were not met 4368 // try again to set the language after onApiChange event is triggered 4369 // meanwhile, the following variable will hold the value 4370 thisObj.captionLangPending = thisObj.captionLang; 4371 } 4372 } else { 4373 if (thisObj.youTubePlayer.getOptions("captions")) { 4374 thisObj.youTubePlayer.setOption("captions", "track", { 4375 languageCode: thisObj.captionLang, 4376 }); 4377 } else { 4378 thisObj.youTubePlayer.loadModule("captions"); 4379 thisObj.captionLangPending = thisObj.captionLang; 4380 } 4381 } 4382 } else if (thisObj.usingVimeoCaptions) { 4383 thisObj.vimeoPlayer 4384 .enableTextTrack(thisObj.captionLang) 4385 .catch(function (error) { 4386 switch (error.name) { 4387 case 'InvalidTrackLanguageError': 4388 // There is no text track for the specified language 4389 console.log(`No Vimeo text track is available in the specified language (${thisObj.captionLang})`); 4390 break; 4391 case 'InvalidTrackError': 4392 // There is no such text track 4393 console.log('No Vimeo text track is available'); 4394 break; 4395 default: 4396 // some other error occurred 4397 console.log('Error enabling Vimeo text track'); 4398 break; 4399 } 4400 }); 4401 } else { 4402 // using local track elements for captions/subtitles 4403 thisObj.syncTrackLanguages("captions", thisObj.captionLang); 4404 if (!thisObj.swappingSrc) { 4405 thisObj.updateCaption(thisObj.elapsed); 4406 thisObj.showDescription(thisObj.elapsed); 4407 } 4408 } 4409 thisObj.captionsOn = true; 4410 // stopgap to prevent spacebar in Firefox from reopening popup 4411 // immediately after closing it (used in handleCaptionToggle()) 4412 thisObj.hidingPopup = true; 4413 thisObj.captionsPopup.hide(); 4414 thisObj.$ccButton.attr("aria-expanded", "false"); 4415 if (thisObj.mediaType === "audio") { 4416 thisObj.$captionsContainer.removeClass("captions-off"); 4417 } 4418 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 4419 // e.g., if user triggered button with Enter or mouse click, not spacebar 4420 setTimeout(function () { 4421 thisObj.hidingPopup = false; 4422 }, 100); 4423 thisObj.updateCaptionsMenu(thisObj.captionLang); 4424 thisObj.waitThenFocus(thisObj.$ccButton); 4425 4426 // save preference to cookie 4427 thisObj.prefCaptions = 1; 4428 thisObj.updatePreferences("prefCaptions"); 4429 thisObj.refreshControls("captions"); 4430 }; 4431 }; 4432 4433 // Returns the function used when the "Captions Off" button is clicked in the captions tooltip. 4434 AblePlayer.prototype.getCaptionOffFunction = function () { 4435 var thisObj = this; 4436 return function () { 4437 if (thisObj.player == "youtube") { 4438 thisObj.youTubePlayer.unloadModule("captions"); 4439 } else if (thisObj.usingVimeoCaptions) { 4440 thisObj.vimeoPlayer.disableTextTrack(); 4441 } 4442 thisObj.captionsOn = false; 4443 thisObj.currentCaption = -1; 4444 4445 if (thisObj.mediaType === "audio") { 4446 thisObj.$captionsContainer.addClass("captions-off"); 4447 } 4448 4449 // stopgap to prevent spacebar in Firefox from reopening popup 4450 // immediately after closing it (used in handleCaptionToggle()) 4451 thisObj.hidingPopup = true; 4452 thisObj.captionsPopup.hide(); 4453 thisObj.$ccButton.attr("aria-expanded", "false"); 4454 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 4455 // e.g., if user triggered button with Enter or mouse click, not spacebar 4456 setTimeout(function () { 4457 thisObj.hidingPopup = false; 4458 }, 100); 4459 thisObj.updateCaptionsMenu(); 4460 thisObj.waitThenFocus(thisObj.$ccButton); 4461 4462 // save preference to cookie 4463 thisObj.prefCaptions = 0; 4464 thisObj.updatePreferences("prefCaptions"); 4465 if (!this.swappingSrc) { 4466 thisObj.refreshControls("captions"); 4467 thisObj.updateCaption(); 4468 } 4469 }; 4470 }; 4471 4472 AblePlayer.prototype.showCaptions = function (now) { 4473 var c, thisCaption, nextCaption, captionText, announceText, announcement, availableTime, rate, cueLength, estimatedTime; 4474 var cues; 4475 if (null !== this.selectedCaptions.cues && this.selectedCaptions.cues.length) { 4476 cues = this.selectedCaptions.cues; 4477 } else if (this.captions.length >= 1) { 4478 cues = this.captions[0].cues; 4479 } else { 4480 cues = []; 4481 } 4482 for (c = 0; c < cues.length; c++) { 4483 if (cues[c].start <= now && cues[c].end > now) { 4484 thisCaption = c; 4485 nextCaption = cues[ c + 1 ]; 4486 break; 4487 } 4488 } 4489 4490 if (typeof thisCaption !== "undefined") { 4491 if (this.currentCaption !== thisCaption) { 4492 // it's time to load the new caption into the container div 4493 captionText = this.flattenCueForCaption(cues[thisCaption]).replace( /\n/g, "<br>" ); 4494 // If preference enabled to voice captions, send to synthesizer. 4495 if ( this.speechEnabled && this.prefCaptionsSpeak == 1 ) { 4496 announceText = new DOMParser().parseFromString( captionText, 'text/html' ); 4497 announcement = announceText.body.textContent || ''; 4498 availableTime = ( thisCaption ) ? nextCaption.start - cues[thisCaption].start : 0; 4499 rate = false; 4500 if ( availableTime ) { 4501 cueLength = announcement.trim().split(/\W+/).length; 4502 estimatedTime = Math.round( ( ( cueLength ) / 135 ) * 60 ); 4503 rate = ( estimatedTime / availableTime ); 4504 } 4505 // use browser's built-in speech synthesis 4506 this.announceText( 'caption', announcement, rate ); 4507 } 4508 this.$captionsDiv.html(captionText); 4509 this.currentCaption = thisCaption; 4510 if (captionText.length === 0) { 4511 // hide captionsDiv; otherwise background-color is visible due to padding 4512 this.$captionsDiv.css("display", "none"); 4513 } else { 4514 this.$captionsDiv.css("display", "inline-block"); 4515 } 4516 } 4517 } else { 4518 this.$captionsDiv.html("").css("display", "none"); 4519 this.currentCaption = -1; 4520 } 4521 }; 4522 4523 AblePlayer.prototype.flattenCueForCaption = function (cue) { 4524 // Takes a cue and returns the caption text to display 4525 // Also used for chapters 4526 4527 // Support for 'i' and 'b' tags added in 2.3.66 4528 // TODO: Add support for 'c' (class) and 'ruby' 4529 4530 // c (class): <c.myClass1.myClass2>Some text</c> 4531 // Classes can be used to modify other tags too (e.g., <v.loud>) 4532 // If <c> tag, should be rendered as a <span> 4533 4534 // ruby: http://www.w3schools.com/tags/tag_ruby.asp 4535 4536 // WebVTT also supports 'u' (underline) 4537 // I see no reason to support that in Able Player. 4538 // If it's available authors are likely to use it incorrectly 4539 // where <i> or <b> should be used instead 4540 // Here are the rare use cases where an underline is appropriate on the web: 4541 // http://html5doctor.com/u-element/ 4542 4543 var result = []; 4544 4545 var flattenComponent = function (component) { 4546 var result = [], 4547 ii; 4548 if (component.type === "string") { 4549 result.push(component.value); 4550 } else if (component.type === "v") { 4551 result.push("(" + component.value + ")"); 4552 for (ii = 0; ii < component.children.length; ii++) { 4553 result.push(flattenComponent(component.children[ii])); 4554 } 4555 } else if (component.type === "i") { 4556 result.push("<em>"); 4557 for (ii = 0; ii < component.children.length; ii++) { 4558 result.push(flattenComponent(component.children[ii])); 4559 } 4560 result.push("</em>"); 4561 } else if (component.type === "b") { 4562 result.push("<strong>"); 4563 for (ii = 0; ii < component.children.length; ii++) { 4564 result.push(flattenComponent(component.children[ii])); 4565 } 4566 result.push("</strong>"); 4567 } else { 4568 for (ii = 0; ii < component.children.length; ii++) { 4569 result.push(flattenComponent(component.children[ii])); 4570 } 4571 } 4572 return result.join(""); 4573 }; 4574 4575 if (typeof cue.components !== "undefined") { 4576 for (var ii = 0; ii < cue.components.children.length; ii++) { 4577 result.push(flattenComponent(cue.components.children[ii])); 4578 } 4579 } 4580 return result.join(""); 4581 }; 4582 4583 AblePlayer.prototype.getCaptionsOptions = function (pref) { 4584 var options = []; 4585 4586 switch (pref) { 4587 case "prefCaptionsFont": 4588 options[0] = ["serif", this.translate( 'serif', 'serif' )]; 4589 options[1] = ["sans-serif", this.translate( 'sans', 'sans-serif' )]; 4590 options[2] = ["cursive", this.translate( 'cursive', 'cursive' )]; 4591 options[3] = ["fantasy", this.translate( 'fantasy', 'fantasy' )]; 4592 options[4] = ["monospace", this.translate( 'monospace', 'monospace' )]; 4593 break; 4594 4595 case "prefCaptionsColor": 4596 case "prefCaptionsBGColor": 4597 // HTML color values must be in English 4598 options[0] = ["white", this.translate( 'white', 'white' )]; 4599 options[1] = ["yellow", this.translate( 'yellow', 'yellow' )]; 4600 options[2] = ["green", this.translate( 'green', 'green' )]; 4601 options[3] = ["cyan", this.translate( 'cyan', 'cyan' )]; 4602 options[4] = ["blue", this.translate( 'blue', 'blue' )]; 4603 options[5] = ["magenta", this.translate( 'magenta', 'magenta' )]; 4604 options[6] = ["red", this.translate( 'red', 'red' )]; 4605 options[7] = ["black", this.translate( 'black', 'black' )]; 4606 break; 4607 4608 case "prefCaptionsSize": 4609 options[0] = "75%"; 4610 options[1] = "100%"; 4611 options[2] = "125%"; 4612 options[3] = "150%"; 4613 options[4] = "200%"; 4614 break; 4615 4616 case "prefCaptionsOpacity": 4617 options[0] = "0%"; 4618 options[1] = "25%"; 4619 options[2] = "50%"; 4620 options[3] = "75%"; 4621 options[4] = "100%"; 4622 break; 4623 4624 case "prefCaptionsStyle": 4625 options[0] = this.translate( 'captionsStylePopOn', 'Pop-on' ); 4626 options[1] = this.translate( 'captionsStyleRollUp', 'Roll-up' ); 4627 break; 4628 4629 case "prefCaptionsPosition": 4630 options[0] = "overlay"; 4631 options[1] = "below"; 4632 break; 4633 4634 case "prefCaptionsSpeak": 4635 options[0] = ["0", this.translate( 'off', 'Off' ) ]; 4636 options[1] = ["1", this.translate( 'on', 'On' ) ]; 4637 break; 4638 4639 case "prefCaptionsVoice": 4640 options[0] = null; // set later. 4641 break; 4642 4643 case "prefCaptionsPitch": 4644 options[0] = null; // set later. 4645 break; 4646 4647 case "prefCaptionsRate": 4648 options[0] = null; // set later. 4649 break; 4650 4651 case "prefCaptionsVolume": 4652 options[0] = null; // set later. 4653 break; 4654 } 4655 4656 return options; 4657 }; 4658 4659 AblePlayer.prototype.translatePrefs = function (pref, value, outputFormat) { 4660 // translate current value of pref to a value supported by outputformat 4661 if (outputFormat == "youtube") { 4662 if (pref === "size") { 4663 // YouTube font sizes are a range from -1 to 3 (0 = default) 4664 switch (value) { 4665 case "75%": 4666 return -1; 4667 case "100%": 4668 return 0; 4669 case "125%": 4670 return 1; 4671 case "150%": 4672 return 2; 4673 case "200%": 4674 return 3; 4675 } 4676 } 4677 } 4678 return false; 4679 }; 4680 4681 AblePlayer.prototype.stylizeCaptions = function ($element, pref) { 4682 // $element is the jQuery element containing the captions 4683 // this function handles stylizing of the sample caption text in the Prefs dialog 4684 // plus the actual production captions 4685 // TODO: consider applying the same user prefs to visible text-based description 4686 var property, newValue, opacity; 4687 4688 if (typeof $element !== "undefined") { 4689 if (pref == "prefCaptionsPosition") { 4690 this.positionCaptions(); 4691 } else if (typeof pref !== "undefined") { 4692 // just change the one property that user just changed 4693 if (pref === "prefCaptionsFont") { 4694 property = "font-family"; 4695 } else if (pref === "prefCaptionsSize") { 4696 property = "font-size"; 4697 } else if (pref === "prefCaptionsColor") { 4698 property = "color"; 4699 } else if (pref === "prefCaptionsBGColor") { 4700 property = "background-color"; 4701 } else if (pref === "prefCaptionsOpacity") { 4702 property = "opacity"; 4703 } 4704 if (pref === "prefCaptionsOpacity") { 4705 newValue = 4706 parseFloat($("#" + this.mediaId + "_" + pref).val()) / 100.0; 4707 } else { 4708 newValue = $("#" + this.mediaId + "_" + pref).val(); 4709 } 4710 $element.css(property, newValue); 4711 } else { 4712 // no property was specified, update all styles with current saved prefs 4713 opacity = parseFloat(this.prefCaptionsOpacity) / 100.0; 4714 $element.css({ 4715 "font-family": this.prefCaptionsFont, 4716 color: this.prefCaptionsColor, 4717 "background-color": this.prefCaptionsBGColor, 4718 opacity: opacity, 4719 }); 4720 if ($element === this.$captionsDiv) { 4721 if (typeof this.$captionsDiv !== "undefined") { 4722 this.$captionsDiv.css({ 4723 "font-size": this.prefCaptionsSize, 4724 }); 4725 } 4726 } 4727 if (this.prefCaptionsPosition === "below") { 4728 // also need to add the background color to the wrapper div 4729 if (typeof this.$captionsWrapper !== "undefined") { 4730 this.$captionsWrapper.css({ 4731 "background-color": this.prefCaptionsBGColor, 4732 opacity: "1", 4733 }); 4734 } 4735 } else if (this.prefCaptionsPosition === "overlay") { 4736 // no background color for overlay wrapper, captions are displayed in-line 4737 if (typeof this.$captionsWrapper !== "undefined") { 4738 this.$captionsWrapper.css({ 4739 "background-color": "transparent", 4740 opacity: "", 4741 }); 4742 } 4743 } 4744 this.positionCaptions(); 4745 } 4746 } 4747 }; 4748 AblePlayer.prototype.positionCaptions = function (position) { 4749 // set caption position to either 'overlay' or 'below' 4750 // if position parameter was passed to this function, use that 4751 // otherwise use user preference 4752 if (typeof position === "undefined") { 4753 position = this.prefCaptionsPosition; 4754 } 4755 if (typeof this.$captionsWrapper !== "undefined") { 4756 if (position == "below") { 4757 this.$captionsWrapper 4758 .removeClass("able-captions-overlay") 4759 .addClass("able-captions-below"); 4760 // also need to update in-line styles 4761 this.$captionsWrapper.css({ 4762 "background-color": this.prefCaptionsBGColor, 4763 opacity: "1", 4764 }); 4765 } else { 4766 this.$captionsWrapper 4767 .removeClass("able-captions-below") 4768 .addClass("able-captions-overlay"); 4769 this.$captionsWrapper.css({ 4770 "background-color": "transparent", 4771 opacity: "", 4772 }); 4773 } 4774 } 4775 }; 4776 } 4777 4778 function addChaptersFunctions(AblePlayer) { 4779 4780 AblePlayer.prototype.populateChaptersDiv = function() { 4781 4782 var headingLevel, headingType, headingId, $chaptersHeading; 4783 if ( ! this.chaptersDivLocation ) { 4784 return; 4785 } 4786 if ($('#' + this.chaptersDivLocation)) { 4787 4788 this.$chaptersDiv = $('#' + this.chaptersDivLocation); 4789 this.$chaptersDiv.addClass('able-chapters-div'); 4790 4791 // empty content from previous build before starting fresh 4792 this.$chaptersDiv.empty(); 4793 4794 // add optional header 4795 if (this.chaptersTitle) { 4796 headingLevel = this.getNextHeadingLevel(this.$chaptersDiv); 4797 headingType = 'h' + headingLevel.toString(); 4798 headingId = this.mediaId + '-chapters-heading'; 4799 $chaptersHeading = $('<' + headingType + '>', { 4800 'class': 'able-chapters-heading', 4801 'id': headingId 4802 }).text(this.chaptersTitle); 4803 this.$chaptersDiv.append($chaptersHeading); 4804 } 4805 4806 this.$chaptersNav = $('<nav>'); 4807 if (this.chaptersTitle) { 4808 this.$chaptersNav.attr( 'aria-labelledby', headingId ); 4809 } else { 4810 this.$chaptersNav.attr( 'aria-label', this.translate( 'chapters', 'Chapters' ) ); 4811 } 4812 this.$chaptersDiv.append(this.$chaptersNav); 4813 4814 // populate this.$chaptersNav with a list of chapters 4815 this.updateChaptersList(); 4816 } 4817 }; 4818 4819 AblePlayer.prototype.updateChaptersList = function() { 4820 4821 var thisObj, cues, $chaptersList, c, thisChapter, 4822 $chapterItem, $chapterButton, hasDefault, 4823 getClickFunction, $clickedItem; 4824 4825 thisObj = this; 4826 4827 // TODO: Update this so it can change the chapters popup menu 4828 // currently it only works if chapters are in an external container 4829 if (!this.$chaptersNav) { 4830 return false; 4831 } 4832 4833 if (typeof this.useChapterTimes === 'undefined') { 4834 this.useChapterTimes = (this.seekbarScope === 'chapter' && this.selectedChapters.cues.length) ? true : false; 4835 } 4836 if (this.useChapterTimes) { 4837 cues = this.selectedChapters.cues; 4838 } else if (this.chapters.length >= 1) { 4839 cues = this.chapters[0].cues; 4840 } else { 4841 cues = []; 4842 } 4843 if (cues.length > 0) { 4844 $chaptersList = $('<ul>'); 4845 for (c = 0; c < cues.length; c++) { 4846 thisChapter = c; 4847 $chapterItem = $('<li></li>'); 4848 $chapterButton = $('<button>',{ 4849 'type': 'button', 4850 'val': thisChapter 4851 }).text(this.flattenCueForCaption(cues[thisChapter])); 4852 4853 // add event listeners 4854 getClickFunction = function (time) { 4855 return function () { 4856 thisObj.seekTrigger = 'chapter'; 4857 $clickedItem = $(this).closest('li'); 4858 $chaptersList = $(this).closest('ul').find('li'); 4859 $chaptersList.removeClass('able-current-chapter') 4860 .children('button').removeAttr('aria-current'); 4861 $clickedItem.addClass('able-current-chapter') 4862 .children('button').attr('aria-current','true'); 4863 // Need to updateChapter before seeking to it 4864 // Otherwise seekBar is redrawn with wrong chapterDuration and/or chapterTime 4865 thisObj.updateChapter(time); 4866 thisObj.seekTo(time); 4867 } 4868 }; 4869 $chapterButton.on('click',getClickFunction(cues[thisChapter].start)); // works with Enter too 4870 $chapterButton.on('focus',function() { 4871 $(this).closest('ul').find('li').removeClass('able-focus'); 4872 $(this).closest('li').addClass('able-focus'); 4873 }); 4874 $chapterItem.on('hover',function() { 4875 $(this).closest('ul').find('li').removeClass('able-focus'); 4876 $(this).addClass('able-focus'); 4877 }); 4878 $chapterItem.on('mouseleave',function() { 4879 $(this).removeClass('able-focus'); 4880 }); 4881 $chapterButton.on('blur',function() { 4882 $(this).closest('li').removeClass('able-focus'); 4883 }); 4884 4885 // put it all together 4886 $chapterItem.append($chapterButton); 4887 $chaptersList.append($chapterItem); 4888 if (this.defaultChapter === cues[thisChapter].id) { 4889 $chapterButton.attr('aria-current','true').parent('li').addClass('able-current-chapter'); 4890 this.currentChapter = cues[thisChapter]; 4891 hasDefault = true; 4892 } 4893 } 4894 if (!hasDefault) { 4895 // select the first chapter 4896 this.currentChapter = cues[0]; 4897 $chaptersList.find('button').first().attr('aria-current','true') 4898 .parent('li').addClass('able-current-chapter'); 4899 } 4900 this.$chaptersNav.html($chaptersList); 4901 } 4902 return false; 4903 }; 4904 4905 AblePlayer.prototype.seekToChapter = function(chapterId) { 4906 4907 // step through chapters looking for matching ID 4908 var i=0; 4909 while (i < this.selectedChapters.cues.length) { 4910 if (this.selectedChapters.cues[i].id == chapterId) { 4911 // found the target chapter! Seek to it 4912 this.seekTo(this.selectedChapters.cues[i].start); 4913 this.updateChapter(this.selectedChapters.cues[i].start); 4914 break; 4915 } 4916 i++; 4917 } 4918 }; 4919 4920 AblePlayer.prototype.updateChapter = function (now) { 4921 4922 // as time-synced chapters change during playback, track changes in current chapter 4923 if (typeof this.selectedChapters === 'undefined') { 4924 return; 4925 } 4926 4927 var chapters, i, thisChapterIndex; 4928 4929 chapters = this.selectedChapters.cues; 4930 for (i = 0; i < chapters.length; i++) { 4931 if ((chapters[i].start <= now) && (chapters[i].end > now)) { 4932 thisChapterIndex = i; 4933 break; 4934 } 4935 } 4936 if (typeof thisChapterIndex !== 'undefined') { 4937 if (this.currentChapter !== chapters[thisChapterIndex]) { 4938 // this is a new chapter 4939 this.currentChapter = chapters[thisChapterIndex]; 4940 if (this.useChapterTimes) { 4941 this.chapterDuration = this.getChapterDuration(); 4942 this.seekIntervalCalculated = false; // will be recalculated in setSeekInterval() 4943 } 4944 if (typeof this.$chaptersDiv !== 'undefined') { 4945 // chapters are listed in an external container 4946 this.$chaptersDiv.find('ul').find('li') 4947 .removeClass('able-current-chapter') 4948 .children('button').removeAttr('aria-current'); 4949 this.$chaptersDiv.find('ul').find('li').eq(thisChapterIndex) 4950 .addClass('able-current-chapter') 4951 .children('button').attr('aria-current','true'); 4952 } 4953 } 4954 } 4955 }; 4956 4957 AblePlayer.prototype.getChapterDuration = function () { 4958 4959 // called if this.seekbarScope === 'chapter' 4960 // get duration of the current chapter 4961 4962 var lastChapterIndex, chapterEnd; 4963 4964 if (typeof this.currentChapter === 'undefined') { 4965 return 0; 4966 } 4967 if (typeof this.duration === 'undefined') { 4968 return 0; 4969 } 4970 lastChapterIndex = this.selectedChapters.cues.length-1; 4971 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 4972 // this is the last chapter 4973 if (this.currentChapter.end !== this.duration) { 4974 // chapter ends before or after video ends, adjust chapter end to match video end 4975 chapterEnd = this.duration; 4976 this.currentChapter.end = this.duration; 4977 } else { 4978 chapterEnd = this.currentChapter.end; 4979 } 4980 } else { // this is not the last chapter 4981 chapterEnd = this.currentChapter.end; 4982 } 4983 return chapterEnd - this.currentChapter.start; 4984 }; 4985 4986 AblePlayer.prototype.getChapterElapsed = function () { 4987 // called if this.seekbarScope === 'chapter' 4988 // get current elapsed time, relative to the current chapter duration 4989 4990 if (typeof this.currentChapter === 'undefined') { 4991 return 0; 4992 } 4993 4994 if (this.elapsed > this.currentChapter.start) { 4995 return this.elapsed - this.currentChapter.start; 4996 } else { 4997 return 0; 4998 } 4999 }; 5000 5001 AblePlayer.prototype.convertChapterTimeToVideoTime = function (chapterTime) { 5002 5003 // chapterTime is the time within the current chapter 5004 // return the same time, relative to the entire video 5005 if (typeof this.currentChapter !== 'undefined') { 5006 var newTime = this.currentChapter.start + chapterTime; 5007 if (newTime > this.currentChapter.end) { 5008 return this.currentChapter.end; 5009 } else { 5010 return newTime; 5011 } 5012 } else { 5013 return chapterTime; 5014 } 5015 }; 5016 5017 AblePlayer.prototype.getChapterClickFunction = function (time) { 5018 5019 // Returns the function used when a chapter is clicked in the chapters menu. 5020 var thisObj = this; 5021 return function () { 5022 thisObj.seekTrigger = 'chapter'; 5023 thisObj.seekTo(time); 5024 // stopgap to prevent spacebar in Firefox from reopening popup 5025 // immediately after closing it (used in handleChapters()) 5026 thisObj.hidingPopup = true; 5027 thisObj.chaptersPopup.hide(); 5028 // Ensure stopgap gets cancelled if handleChapters() isn't called 5029 // e.g., if user triggered button with Enter or mouse click, not spacebar 5030 setTimeout(function() { 5031 thisObj.hidingPopup = false; 5032 }, 100); 5033 thisObj.$chaptersButton.trigger('focus'); 5034 } 5035 }; 5036 5037 } 5038 5039 function addControlFunctions(AblePlayer) { 5040 5041 AblePlayer.prototype.seekTo = function (newTime) { 5042 5043 var thisObj = this; 5044 5045 // define variables to be used for analytics 5046 // e.g., to measure the extent to which users seek back and forward 5047 this.seekFromTime = this.media.currentTime; 5048 this.seekToTime = newTime; 5049 5050 this.seeking = true; 5051 this.liveUpdatePending = true; 5052 5053 if (this.speakingDescription) { 5054 this.synth.cancel(); 5055 } 5056 5057 this.syncSignVideo( {'time' : this.startTime } ); 5058 5059 if (this.player === 'html5') { 5060 var seekable; 5061 5062 this.startTime = newTime; 5063 // Check HTML5 media "seekable" property to be sure media is seekable to startTime 5064 seekable = this.media.seekable; 5065 if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) { 5066 // ok to seek to startTime 5067 // canplaythrough will be triggered when seeking is complete 5068 // this.seeking will be set to false at that point 5069 this.media.currentTime = this.startTime; 5070 this.seekStatus = 'complete'; 5071 this.syncSignVideo( { 'time' : this.startTime } ); 5072 } 5073 } else if (this.player === 'youtube') { 5074 this.youTubePlayer.seekTo(newTime,true); 5075 if (newTime > 0) { 5076 if (typeof this.$posterImg !== 'undefined') { 5077 this.$posterImg.hide(); 5078 } 5079 } 5080 this.syncSignVideo( {'time' : newTime } ); 5081 } else if (this.player === 'vimeo') { 5082 this.vimeoPlayer.setCurrentTime(newTime).then(function() { 5083 // seek finished. 5084 // successful completion also fires a 'seeked' event (see event.js) 5085 thisObj.elapsed = newTime; 5086 thisObj.refreshControls('timeline'); 5087 }); 5088 } 5089 this.refreshControls('timeline'); 5090 }; 5091 5092 AblePlayer.prototype.getMediaTimes = function (duration, elapsed) { 5093 5094 // Returns an array with keys 'duration' and 'elapsed' 5095 // Vars passed to this function come courtesy of select Vimeo events 5096 // Use those if they're available. 5097 // Otherwise, will need to call the relevant media API 5098 // This function should only be called from onMediaUpdateTime() 5099 // If duration and elapsed are needed other times, use this.duration and this.elapsed 5100 5101 // both values are expressed in seconds, and all player APIs are similar: 5102 // they return a value that is rounded to the nearest second before playback begins, 5103 // then to the nearest thousandth of a second after playback begins 5104 // With HTML5 media API, some browsers are more precise (e.g., Firefox rounds to 6 decimal points) 5105 // but inconsistent (values with 9 decimal points have been sporadically observed in Safari) 5106 // For standardization, values are rounded to 6 decimal points before they're returned 5107 5108 var deferred, promise, thisObj, mediaTimes; 5109 mediaTimes = {}; 5110 5111 deferred = new this.defer(); 5112 promise = deferred.promise(); 5113 thisObj = this; 5114 if (typeof duration !== 'undefined' && typeof elapsed !== 'undefined') { 5115 mediaTimes['duration'] = duration; 5116 mediaTimes['elapsed'] = elapsed; 5117 deferred.resolve(mediaTimes); 5118 } else { 5119 this.getDuration().then(function(duration) { 5120 mediaTimes['duration'] = thisObj.roundDown(duration,6); 5121 thisObj.getElapsed().then(function(elapsed) { 5122 mediaTimes['elapsed'] = thisObj.roundDown(elapsed,6); 5123 deferred.resolve(mediaTimes); 5124 }); 5125 }); 5126 } 5127 return promise; 5128 }; 5129 5130 AblePlayer.prototype.getDuration = function () { 5131 5132 // returns duration of the current media, expressed in seconds 5133 // function is called by getMediaTimes, and return value is sanitized there 5134 var deferred, promise; 5135 5136 deferred = new this.defer(); 5137 promise = deferred.promise(); 5138 5139 if (this.player === 'vimeo') { 5140 if (this.vimeoPlayer) { 5141 this.vimeoPlayer.getDuration().then(function(duration) { 5142 if (duration === undefined || isNaN(duration) || duration === -1) { 5143 deferred.resolve(0); 5144 } else { 5145 deferred.resolve(duration); 5146 } 5147 }); 5148 } else { // vimeoPlayer hasn't been initialized yet. 5149 deferred.resolve(0); 5150 } 5151 } else { 5152 var duration; 5153 if (this.player === 'html5') { 5154 duration = this.media.duration; 5155 } else if (this.player === 'youtube') { 5156 if (this.youTubePlayerReady) { 5157 if (this.duration > 0) { 5158 // duration was already retrieved while checking for captions 5159 duration = this.duration; 5160 } else { 5161 duration = this.youTubePlayer.getDuration(); 5162 } 5163 } else { // the YouTube player hasn't initialized yet 5164 duration = 0; 5165 } 5166 } 5167 if (duration === undefined || isNaN(duration) || duration === -1) { 5168 deferred.resolve(0); 5169 } else { 5170 deferred.resolve(duration); 5171 } 5172 } 5173 return promise; 5174 }; 5175 5176 AblePlayer.prototype.getElapsed = function () { 5177 5178 // returns elapsed time of the current media, expressed in seconds 5179 // function is called by getMediaTimes, and return value is sanitized there 5180 5181 var deferred, promise; 5182 5183 deferred = new this.defer(); 5184 promise = deferred.promise(); 5185 5186 if (this.player === 'vimeo') { 5187 if (this.vimeoPlayer) { 5188 this.vimeoPlayer.getCurrentTime().then(function(elapsed) { 5189 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 5190 deferred.resolve(0); 5191 } else { 5192 deferred.resolve(elapsed); 5193 } 5194 }); 5195 } else { // vimeoPlayer hasn't been initialized yet. 5196 deferred.resolve(0); 5197 } 5198 } else { 5199 var elapsed; 5200 if (this.player === 'html5') { 5201 elapsed = this.media.currentTime; 5202 } else if (this.player === 'youtube') { 5203 if (this.youTubePlayerReady) { 5204 elapsed = this.youTubePlayer.getCurrentTime(); 5205 } else { // the YouTube player hasn't initialized yet 5206 elapsed = 0; 5207 } 5208 } 5209 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 5210 deferred.resolve(0); 5211 } else { 5212 deferred.resolve(elapsed); 5213 } 5214 } 5215 return promise; 5216 }; 5217 5218 AblePlayer.prototype.getPlayerState = function () { 5219 5220 // Returns one of the following states: 5221 // - 'stopped' - Not yet played for the first time, or otherwise reset to unplayed. 5222 // - 'ended' - Finished playing. 5223 // - 'paused' - Not playing, but not stopped or ended. 5224 // - 'buffering' - Momentarily paused to load, but will resume once data is loaded. 5225 // - 'playing' - Currently playing. 5226 5227 var deferred, promise, thisObj; 5228 deferred = new this.defer(); 5229 promise = deferred.promise(); 5230 thisObj = this; 5231 5232 if (this.player === 'html5') { 5233 if (this.media.ended) { 5234 deferred.resolve('ended'); 5235 } else if (this.media.paused) { 5236 deferred.resolve('paused'); 5237 } else if (this.media.readyState !== 4) { 5238 deferred.resolve('buffering'); 5239 } else { 5240 deferred.resolve('playing'); 5241 } 5242 } else if (this.player === 'youtube' && this.youTubePlayerReady) { 5243 var state = this.youTubePlayer.getPlayerState(); 5244 if (state === -1 || state === 5) { 5245 deferred.resolve('stopped'); 5246 } else if (state === 0) { 5247 deferred.resolve('ended'); 5248 } else if (state === 1) { 5249 deferred.resolve('playing'); 5250 } else if (state === 2) { 5251 deferred.resolve('paused'); 5252 } else if (state === 3) { 5253 deferred.resolve('buffering'); 5254 } 5255 } else if (this.player === 'vimeo' && this.vimeoPlayer) { 5256 // curiously, Vimeo's API has no getPlaying(), getBuffering(), or getState() methods 5257 // so maybe if it's neither paused nor ended, it must be playing??? 5258 this.vimeoPlayer.getPaused().then(function(paused) { 5259 if (paused) { 5260 deferred.resolve('paused'); 5261 } else { 5262 thisObj.vimeoPlayer.getEnded().then(function(ended) { 5263 if (ended) { 5264 deferred.resolve('ended'); 5265 } else { 5266 deferred.resolve('playing'); 5267 } 5268 }); 5269 } 5270 }); 5271 } 5272 return promise; 5273 }; 5274 5275 AblePlayer.prototype.isPlaybackRateSupported = function () { 5276 5277 if (this.player === 'html5') { 5278 return (this.media.playbackRate) ? true : false; 5279 } else if (this.player === 'youtube') { 5280 // Youtube supports varying playback rates per video. 5281 // Only expose controls if more than one playback rate is available. 5282 if (this.youTubePlayerReady) { 5283 return (this.youTubePlayer.getAvailablePlaybackRates().length > 1) ? true : false; 5284 } else { 5285 return false; 5286 } 5287 } else if (this.player === 'vimeo') { 5288 // since this takes longer to determine, it was set previously in initVimeoPlayer() 5289 return this.vimeoSupportsPlaybackRateChange; 5290 } 5291 }; 5292 5293 AblePlayer.prototype.setPlaybackRate = function (rate) { 5294 5295 rate = Math.max(0.5, rate); 5296 5297 if (this.hasClosedDesc && this.descMethod === 'text') { 5298 // keep speech rate in sync with playback rate even if descOn is false 5299 this.syncSpeechToPlaybackRate(rate); 5300 } 5301 5302 this.syncSignVideo( {'rate' : rate } ); 5303 5304 if (this.player === 'html5') { 5305 this.media.playbackRate = rate; 5306 } else if (this.player === 'youtube') { 5307 this.youTubePlayer.setPlaybackRate(rate); 5308 } else if (this.player === 'vimeo') { 5309 this.vimeoPlayer.setPlaybackRate(rate); 5310 } 5311 this.syncSignVideo( { 'rate' : rate } ); 5312 this.playbackRate = rate; 5313 this.$speed.text( this.translate( 'speed', 'Speed' ) + ': ' + rate.toFixed(2).toString() + 'x'); 5314 }; 5315 5316 AblePlayer.prototype.getPlaybackRate = function () { 5317 5318 if (this.player === 'html5') { 5319 return this.media.playbackRate; 5320 } else if (this.player === 'youtube' && (this.youTubePlayerReady)) { 5321 return this.youTubePlayer.getPlaybackRate(); 5322 } 5323 }; 5324 5325 AblePlayer.prototype.isPaused = function () { 5326 5327 // Note there are three player states that count as paused in this sense, 5328 // and one of them is named 'paused'. 5329 // A better name would be 'isCurrentlyNotPlayingOrBuffering' 5330 5331 if (this.player === 'vimeo') { 5332 // just rely on value of this.playing 5333 return (this.playing) ? false : true; 5334 } else { 5335 this.getPlayerState().then(function(state) { 5336 // if any of the following is true, consider the media 'paused' 5337 return state === 'paused' || state === 'stopped' || state === 'ended'; 5338 }); 5339 } 5340 }; 5341 5342 AblePlayer.prototype.syncSignVideo = function(options) { 5343 if (this.hasSignLanguage && ( this.signVideo || this.signYoutube ) ) { 5344 if (options && typeof options.time !== 'undefined') { 5345 if ( this.signVideo ) { 5346 this.signVideo.currentTime = options.time; 5347 } else { 5348 this.youTubeSignPlayer.seekTo(options.time,true); 5349 } 5350 } 5351 if (options && typeof options.rate !== 'undefined') { 5352 if ( this.signVideo ) { 5353 this.signVideo.playbackRate = options.rate; 5354 } else { 5355 this.youTubeSignPlayer.setPlaybackRate(options.rate); 5356 } 5357 } 5358 if (options && typeof options.pause !== 'undefined') { 5359 if ( this.signVideo ) { 5360 this.signVideo.pause(true); 5361 } else { 5362 this.youTubeSignPlayer.pauseVideo(); 5363 } 5364 } 5365 if (options && typeof options.play !== 'undefined') { 5366 if ( this.signVideo ) { 5367 this.signVideo.play(true); 5368 } else { 5369 this.youTubeSignPlayer.playVideo(); 5370 } 5371 } 5372 if (options && typeof options.volume !== 'undefined') { 5373 if ( this.signVideo ) { 5374 this.signVideo.volume = 0; 5375 } 5376 } 5377 } 5378 }; 5379 5380 AblePlayer.prototype.pauseMedia = function () { 5381 5382 this.syncSignVideo( { 'pause' : true } ); 5383 5384 if (this.player === 'html5') { 5385 this.media.pause(true); 5386 } else if (this.player === 'youtube') { 5387 this.youTubePlayer.pauseVideo(); 5388 } else if (this.player === 'vimeo') { 5389 this.vimeoPlayer.pause(); 5390 } 5391 }; 5392 5393 AblePlayer.prototype.playMedia = function () { 5394 5395 this.syncSignVideo( { 'play' : true } ); 5396 5397 if (this.player === 'html5') { 5398 this.media.play(true); 5399 } else if (this.player === 'youtube') { 5400 5401 this.youTubePlayer.playVideo(); 5402 if (typeof this.$posterImg !== 'undefined') { 5403 this.$posterImg.hide(); 5404 } 5405 this.stoppingYouTube = false; 5406 } else if (this.player === 'vimeo') { 5407 this.vimeoPlayer.play(); 5408 } 5409 this.startedPlaying = true; 5410 if (this.hideControls) { 5411 // wait briefly after playback begins, then hide controls 5412 this.hidingControls = true; 5413 this.invokeHideControlsTimeout(); 5414 } 5415 }; 5416 5417 AblePlayer.prototype.fadeControls = function(direction) { 5418 5419 // Visibly fade controls without hiding them from screen reader users 5420 // direction is either 'out' or 'in' 5421 5422 // After the player fades, it's replaced by an empty space 5423 // Would be better if the video and captions expanded to fill the void 5424 // replace JS animation with CSS animation in 12/2025. 5425 5426 if (direction == 'out') { 5427 // get the original height of two key components: 5428 this.$playerDiv.addClass( 'fade-out' ).removeClass( 'fade-in' ); 5429 } else if (direction == 'in') { 5430 this.$playerDiv.addClass( 'fade-in' ).removeClass( 'fade-out' ); 5431 } 5432 }; 5433 5434 AblePlayer.prototype.invokeHideControlsTimeout = function () { 5435 5436 // invoke timeout for waiting a few seconds after a mouse move or key down 5437 // before hiding controls again 5438 var thisObj = this; 5439 this.hideControlsTimeout = window.setTimeout(function() { 5440 if (typeof thisObj.playing !== 'undefined' && thisObj.playing === true && thisObj.hideControls) { 5441 thisObj.fadeControls('out'); 5442 thisObj.controlsHidden = true; 5443 } 5444 },5000); 5445 this.hideControlsTimeoutStatus = 'active'; 5446 }; 5447 5448 AblePlayer.prototype.refreshControls = function(context = 'init', duration, elapsed) { 5449 5450 // context is one of the following: 5451 // 'init' - initial build (or subsequent change that requires full rebuild) 5452 // 'timeline' - a change may effect time-related controls 5453 // 'captions' - a change may effect caption-related controls 5454 // 'descriptions' - a change may effect description-related controls 5455 // 'transcript' - a change may effect the transcript window or button 5456 // 'fullscreen' - a change has been triggered by full screen toggle 5457 // 'playpause' - a change triggered by either a 'play' or 'pause' event 5458 5459 // NOTE: context is not currently supported. 5460 // The steps in this function have too many complex interdependencies 5461 // The gains in efficiency are offset by the possibility of introducing bugs 5462 // For now, executing everything 5463 context = 'init'; 5464 5465 // duration and elapsed are passed from callback functions of Vimeo API events 5466 // duration is expressed as sss.xxx 5467 // elapsed is expressed as sss.xxx 5468 5469 var thisObj, textByState, timestamp, captionsCount, newTop, statusBarWidthBreakpoint; 5470 5471 thisObj = this; 5472 // wait until new source has loaded before refreshing controls 5473 // some critical events won't fire until playback of new media starts 5474 if ( this.swappingSrc && this.playing ) { 5475 return; 5476 } 5477 5478 if ( context === 'timeline' || context === 'init' ) { 5479 // Update timeline controls. 5480 var lastChapterIndex, displayElapsed, updateLive, widthUsed, 5481 leftControls, rightControls, seekbarWidth, buffered; 5482 // all timeline-related functionality requires duration 5483 if (typeof this.duration === 'undefined') { 5484 // wait until duration is known before proceeding with refresh 5485 return; 5486 } 5487 if (this.useChapterTimes) { 5488 this.chapterDuration = this.getChapterDuration(); 5489 this.chapterElapsed = this.getChapterElapsed(); 5490 } 5491 5492 if ( !this.useFixedSeekInterval && !this.seekIntervalCalculated && this.duration > 0) { 5493 // couldn't calculate seekInterval previously; try again. 5494 this.setSeekInterval(); 5495 } 5496 5497 if (this.seekBar) { 5498 if (this.useChapterTimes) { 5499 lastChapterIndex = this.selectedChapters.cues.length-1; 5500 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 5501 // this is the last chapter 5502 if (this.currentChapter.end !== this.duration) { 5503 // chapter ends before or after video ends 5504 // need to adjust seekbar duration to match video end 5505 this.seekBar.setDuration(this.duration - this.currentChapter.start); 5506 } else { 5507 this.seekBar.setDuration(this.chapterDuration); 5508 } 5509 } else { 5510 // this is not the last chapter 5511 this.seekBar.setDuration(this.chapterDuration); 5512 } 5513 } else if ( !(this.duration === undefined || isNaN(this.duration) || this.duration === -1) ) { 5514 this.seekBar.setDuration(this.duration); 5515 } 5516 if (!(this.seekBar.tracking)) { 5517 // Only update the aria live region if we have an update pending 5518 // (from a seek button control) or if the seekBar has focus. 5519 // We use document.activeElement instead of $(':focus') due to a strange bug: 5520 // When the seekHead element is focused, .is(':focus') is failing and $(':focus') is returning an undefined element. 5521 updateLive = this.liveUpdatePending || this.seekBar.seekHead.is($(document.activeElement)); 5522 this.liveUpdatePending = false; 5523 if (this.useChapterTimes) { 5524 this.seekBar.setPosition(this.chapterElapsed, updateLive); 5525 } else { 5526 this.seekBar.setPosition(this.elapsed, updateLive); 5527 } 5528 } 5529 5530 // When seeking, display the seek bar time instead of the actual elapsed time. 5531 if (this.seekBar.tracking) { 5532 displayElapsed = this.seekBar.lastTrackPosition; 5533 } else { 5534 displayElapsed = ( this.useChapterTimes ) ? this.chapterElapsed : this.elapsed; 5535 } 5536 } 5537 // update elapsed & duration 5538 if (typeof this.$durationContainer !== 'undefined') { 5539 if (this.useChapterTimes) { 5540 this.$durationContainer.text( this.formatSecondsAsColonTime(this.chapterDuration)); 5541 } else { 5542 this.$durationContainer.text( this.formatSecondsAsColonTime(this.duration)); 5543 } 5544 } 5545 if (typeof this.$elapsedTimeContainer !== 'undefined') { 5546 this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed)); 5547 } 5548 5549 if (this.skin === 'legacy') { 5550 // Update seekbar width. 5551 // To do this, we need to calculate the width of all buttons surrounding it. 5552 if (this.seekBar) { 5553 let controlWrapper = this.seekBar.wrapperDiv.parent().parent(); 5554 leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls'); 5555 rightControls = leftControls.next('div.able-right-controls'); 5556 widthUsed = leftControls.outerWidth(true); 5557 rightControls.children().each(function () { 5558 if ($(this).attr('role')=='button') { 5559 widthUsed += $(this).outerWidth(true) + 5; 5560 } 5561 }); 5562 if (this.fullscreen) { 5563 seekbarWidth = $(window).width() - widthUsed; 5564 } else { 5565 // seekbar is wide enough to fill the remaining space 5566 // include a 10px buffer to account for minor browser differences or custom styles. 5567 seekbarWidth = controlWrapper.width() - widthUsed - 10; 5568 } 5569 // Sometimes some minor fluctuations based on browser weirdness, so set a threshold. 5570 if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) { 5571 this.seekBar.setWidth(seekbarWidth); 5572 } 5573 } 5574 } 5575 5576 // Update buffering progress. 5577 // TODO: Currently only using the first HTML5 buffered interval, 5578 // but this fails sometimes when buffering is split into two or more intervals. 5579 if (this.player === 'html5' && this.media.buffered.length > 0) { 5580 buffered = this.media.buffered.end(0); 5581 if (this.useChapterTimes) { 5582 if (buffered > this.chapterDuration) { 5583 buffered = this.chapterDuration; 5584 } 5585 if (this.seekBar) { 5586 this.seekBar.setBuffered(buffered / this.chapterDuration); 5587 } 5588 } else if ( this.seekBar && !isNaN(buffered) ) { 5589 this.seekBar.setBuffered(buffered / duration); 5590 } 5591 } else if (this.player === 'youtube' && this.seekBar && this.youTubePlayerReady ) { 5592 this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction()); 5593 } else if (this.player === 'vimeo') ; 5594 } 5595 5596 if (context === 'descriptions' || context == 'init') { 5597 if (this.$descButton) { 5598 this.toggleButtonState( 5599 this.$descButton, 5600 this.descOn, 5601 this.translate( 'turnOffDescriptions', 'Turn off descriptions' ), 5602 this.translate( 'turnOnDescriptions', 'Turn on descriptions' ), 5603 ); 5604 } 5605 } 5606 5607 if (context === 'captions' || context == 'init') { 5608 5609 if (this.$ccButton) { 5610 5611 captionsCount = this.captions.length; 5612 if (captionsCount > 1) { 5613 this.$ccButton.attr({ 5614 'aria-haspopup': 'true', 5615 'aria-controls': this.mediaId + '-captions-menu' 5616 }); 5617 } 5618 var ariaLabelOn = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' ); 5619 var ariaLabelOff = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' ); 5620 var ariaPressed = ( captionsCount > 1 ) ? true : false; 5621 5622 this.toggleButtonState( 5623 this.$ccButton, 5624 this.captionsOn, 5625 ariaLabelOff, 5626 ariaLabelOn, 5627 ariaPressed 5628 ); 5629 } 5630 } 5631 5632 if (context === 'fullscreen' || context == 'init'){ 5633 if (this.$fullscreenButton) { 5634 if (!this.fullscreen) { 5635 this.$fullscreenButton.attr( 'aria-label', this.translate( 'enterFullScreen', 'Enter full screen' ) ); 5636 this.getIcon( this.$fullscreenButton, 'fullscreen-expand' ); 5637 } else { 5638 this.$fullscreenButton.attr('aria-label', this.translate( 'exitFullScreen', 'Exit full screen' ) ); 5639 this.getIcon( this.$fullscreenButton, 'fullscreen-collapse' ); 5640 } 5641 } 5642 } 5643 if (context === 'playpause' || context == 'init'){ 5644 if (typeof this.$bigPlayButton !== 'undefined' && typeof this.seekBar !== 'undefined') { 5645 // Choose show/hide for big play button and adjust position. 5646 if (this.paused && !this.seekBar.tracking) { 5647 if (!this.hideBigPlayButton) { 5648 this.$bigPlayButton.show(); 5649 this.$bigPlayButton.attr('aria-hidden', 'false'); 5650 } 5651 } else { 5652 this.$bigPlayButton.hide(); 5653 this.$bigPlayButton.attr('aria-hidden', 'true'); 5654 } 5655 } 5656 } 5657 5658 if (context === 'transcript' || context == 'init'){ 5659 5660 if (this.transcriptType) { 5661 // Sync checkbox and autoScrollTranscript with user preference 5662 if (this.prefAutoScrollTranscript === 1) { 5663 this.autoScrollTranscript = true; 5664 this.$autoScrollTranscriptCheckbox.prop('checked',true); 5665 } else { 5666 this.autoScrollTranscript = false; 5667 this.$autoScrollTranscriptCheckbox.prop('checked',false); 5668 } 5669 5670 // If transcript locked, scroll transcript to current highlight location. 5671 if (this.autoScrollTranscript && this.currentHighlight) { 5672 newTop = Math.floor(this.$transcriptDiv.scrollTop() + 5673 $(this.currentHighlight).position().top - 5674 (this.$transcriptDiv.height() / 2) + 5675 ($(this.currentHighlight).height() / 2)); 5676 if (newTop !== Math.floor(this.$transcriptDiv.scrollTop())) { 5677 // Set a flag to ignore the coming scroll event. 5678 // there's no other way I know of to differentiate programmatic and user-initiated scroll events. 5679 this.scrollingTranscript = true; 5680 // only scroll once after moving a highlight 5681 if (this.movingHighlight) { 5682 this.$transcriptDiv.scrollTop(newTop); 5683 this.movingHighlight = false; 5684 } 5685 } 5686 } 5687 } 5688 } 5689 5690 if (context === 'init') { 5691 5692 if (this.$chaptersButton) { 5693 this.$chaptersButton.attr({ 5694 'aria-label': this.translate( 'chapters', 'Chapters' ), 5695 'aria-haspopup': 'true', 5696 'aria-controls': this.mediaId + '-chapters-menu' 5697 }); 5698 } 5699 } 5700 5701 if (context === 'timeline' || context === 'playpause' || context === 'init') { 5702 5703 // update status 5704 textByState = { 5705 'stopped': this.translate( 'statusStopped', 'Stopped' ), 5706 'paused': this.translate( 'statusPaused', 'Paused' ), 5707 'playing': this.translate( 'statusPlaying', 'Playing' ), 5708 'buffering': this.translate( 'statusBuffering', 'Buffering' ), 5709 'ended': this.translate( 'statusEnd', 'End of track' ) 5710 }; 5711 5712 if (this.stoppingYouTube) { 5713 // stoppingYouTube is true temporarily while video is paused and seeking to 0 5714 // See notes in handleRestart() 5715 // this.stoppingYouTube will be reset when seek to 0 is finished (in event.js > onMediaUpdateTime()) 5716 if (this.$status.text() !== this.translate( 'statusStopped', 'Stopped' ) ) { 5717 this.$status.text( this.translate( 'statusStopped', 'Stopped' ) ); 5718 } 5719 this.getIcon( this.$playpauseButton, 'play' ); 5720 } else if (typeof this.$status !== 'undefined' && typeof this.seekBar !== 'undefined') { 5721 // Update the text only if it's changed since it has role="alert"; 5722 // also don't update while tracking, since this may Pause/Play the player but we don't want to send a Pause/Play update. 5723 this.getPlayerState().then(function(currentState) { 5724 if (thisObj.$status.text() !== textByState[currentState] && !thisObj.seekBar.tracking) { 5725 // Debounce updates; only update after status has stayed steadily different for a while 5726 // "A while" is defined differently depending on context 5727 if (thisObj.swappingSrc) { 5728 // this is where most of the chatter occurs (e.g., playing, paused, buffering, playing), 5729 // so set a longer wait time before writing a status message 5730 if (!thisObj.debouncingStatus) { 5731 thisObj.statusMessageThreshold = 2000; // in ms (2 seconds) 5732 } 5733 } else if (!thisObj.debouncingStatus) { 5734 // for all other contexts (e.g., users clicks Play/Pause) 5735 // user should receive more rapid feedback 5736 thisObj.statusMessageThreshold = 250; // in ms 5737 } 5738 timestamp = (new Date()).getTime(); 5739 if (!thisObj.statusDebounceStart) { 5740 thisObj.statusDebounceStart = timestamp; 5741 // Call refreshControls() again after allotted time has passed 5742 thisObj.debouncingStatus = true; 5743 thisObj.statusTimeout = setTimeout(function () { 5744 thisObj.debouncingStatus = false; 5745 thisObj.refreshControls(context); 5746 }, thisObj.statusMessageThreshold); 5747 } else if ((timestamp - thisObj.statusDebounceStart) > thisObj.statusMessageThreshold) { 5748 thisObj.$status.text(textByState[currentState]); 5749 thisObj.statusDebounceStart = null; 5750 clearTimeout(thisObj.statusTimeout); 5751 thisObj.statusTimeout = null; 5752 } 5753 } else { 5754 thisObj.statusDebounceStart = null; 5755 thisObj.debouncingStatus = false; 5756 clearTimeout(thisObj.statusTimeout); 5757 thisObj.statusTimeout = null; 5758 } 5759 // Don't change play/pause button display while using the seek bar (or if YouTube stopped) 5760 if (!thisObj.seekBar.tracking && !thisObj.stoppingYouTube) { 5761 if (currentState === 'paused' || currentState === 'stopped' || currentState === 'ended') { 5762 thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'play', 'Play' ) ); 5763 thisObj.getIcon( thisObj.$playpauseButton, 'play' ); 5764 } else { 5765 thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'pause', 'Pause' ) ); 5766 thisObj.getIcon( thisObj.$playpauseButton, 'pause' ); 5767 } 5768 } 5769 }); 5770 } 5771 } 5772 5773 // Show/hide status bar content conditionally 5774 if (!this.fullscreen) { 5775 statusBarWidthBreakpoint = 300; 5776 if (this.$statusBarDiv.width() < statusBarWidthBreakpoint) { 5777 // Player is too small for a speed span 5778 this.$statusBarDiv.find('span.able-speed').hide(); 5779 this.hidingSpeed = true; 5780 } else { 5781 if (this.hidingSpeed) { 5782 this.$statusBarDiv.find('span.able-speed').show(); 5783 this.hidingSpeed = false; 5784 } 5785 } 5786 } 5787 5788 }; 5789 5790 AblePlayer.prototype.handlePlay = function(e) { 5791 if (this.paused) { 5792 // user clicked play 5793 this.okToPlay = true; 5794 this.playMedia(); 5795 if (this.synth.paused) { 5796 // media was paused while description was speaking 5797 // resume utterance 5798 this.synth.resume(); 5799 } 5800 } else { 5801 // user clicked pause 5802 this.okToPlay = false; 5803 this.pauseMedia(); 5804 if (this.speakingDescription) { 5805 // pause the current utterance 5806 // it will resume when the user presses play 5807 this.synth.pause(); 5808 } 5809 } 5810 if (this.speechEnabled === null) { 5811 this.initSpeech('play'); 5812 } 5813 }; 5814 5815 AblePlayer.prototype.handleRestart = function() { 5816 5817 if (this.speakingDescription) { 5818 // cancel audio description 5819 this.synth.cancel(); 5820 } 5821 this.seekTo(0); 5822 }; 5823 5824 AblePlayer.prototype.handlePrevTrack = function() { 5825 5826 // currently on the first track 5827 // wrap to bottom and play the last track 5828 let newIndex = (this.playlistIndex === 0) ? this.$playlist.length - 1 : this.playlistIndex - 1; 5829 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 5830 this.cuePlaylistItem(newIndex); 5831 }; 5832 5833 AblePlayer.prototype.handleNextTrack = function() { 5834 5835 // currently on the last track 5836 // wrap to top and play the first track 5837 let newIndex = (this.playlistIndex === this.$playlist.length - 1) ? 0 : this.playlistIndex + 1; 5838 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 5839 this.cuePlaylistItem(newIndex); 5840 }; 5841 5842 AblePlayer.prototype.handleRewind = function() { 5843 5844 var targetTime; 5845 5846 targetTime = this.elapsed - this.seekInterval; 5847 if (this.useChapterTimes && (targetTime < this.currentChapter.start)) { 5848 targetTime = this.currentChapter.start; 5849 } else if (targetTime < 0) { 5850 targetTime = 0; 5851 } 5852 this.seekTo(targetTime); 5853 }; 5854 5855 AblePlayer.prototype.handleFastForward = function() { 5856 5857 var targetTime, lastChapterIndex; 5858 5859 lastChapterIndex = this.chapters.length-1; 5860 targetTime = this.elapsed + this.seekInterval; 5861 if (this.useChapterTimes) { 5862 if (this.chapters[lastChapterIndex] == this.currentChapter) { 5863 // this is the last chapter 5864 if (targetTime > this.duration || targetTime > this.currentChapter.end) { 5865 // targetTime would exceed the end of the video (or chapter) 5866 // scrub to end of whichever is earliest 5867 targetTime = Math.min(this.duration, this.currentChapter.end); 5868 } else if (this.duration % targetTime < this.seekInterval) { 5869 // nothing left but pocket change after seeking to targetTime 5870 // go ahead and seek to end of video (or chapter), whichever is earliest 5871 targetTime = Math.min(this.duration, this.currentChapter.end); 5872 } 5873 } else { 5874 // this is not the last chapter 5875 if (targetTime > this.currentChapter.end) { 5876 // targetTime would exceed the end of the chapter 5877 // scrub exactly to end of chapter 5878 targetTime = this.currentChapter.end; 5879 } 5880 } 5881 } else { 5882 // not using chapter times 5883 if (targetTime > this.duration) { 5884 targetTime = this.duration; 5885 } 5886 } 5887 this.seekTo(targetTime); 5888 }; 5889 5890 AblePlayer.prototype.handleRateIncrease = function() { 5891 this.changeRate(1); 5892 }; 5893 5894 AblePlayer.prototype.handleRateDecrease = function() { 5895 this.changeRate(-1); 5896 }; 5897 5898 // Increases or decreases playback rate, where dir is 1 or -1 indication direction. 5899 AblePlayer.prototype.changeRate = function (dir) { 5900 5901 var rates, currentRate, index, newRate, vimeoMin, vimeoMax; 5902 5903 if (this.player === 'html5') { 5904 this.setPlaybackRate(this.getPlaybackRate() + (0.25 * dir)); 5905 } else if (this.player === 'youtube') { 5906 if (this.youTubePlayerReady) { 5907 rates = this.youTubePlayer.getAvailablePlaybackRates(); 5908 currentRate = this.getPlaybackRate(); 5909 index = rates.indexOf(currentRate); 5910 if (index === -1) { 5911 console.log('ERROR: Youtube returning unknown playback rate ' + currentRate.toString()); 5912 } else { 5913 index += dir; 5914 // Can only increase or decrease rate if there's another rate available. 5915 if (index < rates.length && index >= 0) { 5916 this.setPlaybackRate(rates[index]); 5917 } 5918 } 5919 } 5920 } else if (this.player === 'vimeo') { 5921 // range is 0.5 to 2 5922 // increase/decrease in inrements of 0.5 5923 vimeoMin = 0.5; 5924 vimeoMax = 2; 5925 if (dir === 1) { 5926 newRate = (this.vimeoPlaybackRate + 0.5 <= vimeoMax) ? this.vimeoPlaybackRate + 0.5 : vimeoMax; 5927 } else if (dir === -1) { 5928 newRate = (this.vimeoPlaybackRate - 0.5 >= vimeoMin) ? this.vimeoPlaybackRate - 0.5 : vimeoMin; 5929 } 5930 this.setPlaybackRate(newRate); 5931 } 5932 }; 5933 5934 AblePlayer.prototype.handleCaptionToggle = function() { 5935 5936 var thisObj = this; 5937 var captions, ariaPressed; 5938 if (this.hidingPopup) { 5939 // stopgap to prevent spacebar in Firefox from reopening popup 5940 // immediately after closing it 5941 this.hidingPopup = false; 5942 return false; 5943 } 5944 5945 captions = (this.captions.length) ? this.captions : []; 5946 if (captions.length === 1) { 5947 // When there's only one set of captions, just do an on/off toggle. 5948 if (this.captionsOn === true) { 5949 // turn them off 5950 this.captionsOn = false; 5951 this.prefCaptions = 0; 5952 ariaPressed = false; 5953 this.updatePreferences('prefCaptions'); 5954 if (this.usingYouTubeCaptions) { 5955 this.youTubePlayer.unloadModule('captions'); 5956 } else if (this.usingVimeoCaptions) { 5957 this.vimeoPlayer.disableTextTrack(); 5958 } else { 5959 this.$captionsWrapper.hide(); 5960 } 5961 } else { 5962 // captions are off. Turn them on. 5963 this.captionsOn = true; 5964 this.prefCaptions = 1; 5965 ariaPressed = true; 5966 this.updatePreferences('prefCaptions'); 5967 if (this.usingYouTubeCaptions) { 5968 this.youTubePlayer.loadModule('captions'); 5969 } else if (this.usingVimeoCaptions) { 5970 this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) { 5971 switch (error.name) { 5972 case 'InvalidTrackLanguageError': 5973 // There is no text track for the specified language 5974 console.log(`No Vimeo text track is available in the specified language (${this.captionLang})`); 5975 break; 5976 case 'InvalidTrackError': 5977 // There is no such text track 5978 console.log('No Vimeo text track is available'); 5979 break; 5980 default: 5981 // some other error occurred 5982 console.log('Error enabling Vimeo text track'); 5983 break; 5984 } 5985 }); 5986 } else { 5987 this.$captionsWrapper.show(); 5988 } 5989 for (var i=0; i<captions.length; i++) { 5990 if (captions[i].def === true) { // this is the default language 5991 this.selectedCaptions = captions[i]; 5992 } 5993 } 5994 this.selectedCaptions = this.captions[0]; 5995 if (this.descriptions.length >= 0) { 5996 this.selectedDescriptions = this.descriptions[0]; 5997 } 5998 } 5999 } else { 6000 // there is more than one caption track. 6001 // clicking on a track is handled via caption.js > getCaptionClickFunction() 6002 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 6003 this.captionsPopup.hide(); 6004 this.hidingPopup = false; 6005 this.$ccButton.attr('aria-expanded', 'false'); 6006 this.waitThenFocus(this.$ccButton); 6007 } else { 6008 this.closePopups(); 6009 if (this.captionsPopup) { 6010 this.captionsPopup.show(); 6011 this.$ccButton.attr('aria-expanded','true'); 6012 6013 // Gives time to "register" expanded ccButton 6014 setTimeout(function() { 6015 thisObj.captionsPopup.css('top', thisObj.$ccButton.position().top - thisObj.captionsPopup.outerHeight()); 6016 thisObj.captionsPopup.css('left', thisObj.$ccButton.position().left); 6017 // Place focus on the first button (even if another button is checked) 6018 thisObj.captionsPopup.find('li').removeClass('able-focus'); 6019 thisObj.captionsPopup.find('li').first().trigger('focus').addClass('able-focus'); 6020 }, 50); 6021 } 6022 } 6023 } 6024 var ariaLabelOn = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' ); 6025 var ariaLabelOff = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' ); 6026 6027 this.toggleButtonState( 6028 this.$ccButton, 6029 this.captionsOn, 6030 ariaLabelOff, 6031 ariaLabelOn, 6032 ariaPressed 6033 ); 6034 }; 6035 6036 /** 6037 * Gives enough time for DOM changes to take effect before adjusting focus. 6038 * Helpful for allowing screen reading of elements whose state is intermittently changed. 6039 * 6040 * @param {*} $el element to focus on 6041 * @param {*} timeout optional wait time in milliseconds before focus 6042 */ 6043 AblePlayer.prototype.waitThenFocus = function($el, timeout) { 6044 6045 // Default wait time of 50 ms 6046 var _timeout = (timeout === undefined || timeout === null) ? 50 : timeout; 6047 6048 setTimeout(function() { 6049 $el.trigger('focus'); 6050 }, _timeout); 6051 }; 6052 6053 AblePlayer.prototype.handleChapters = function () { 6054 if (this.hidingPopup) { 6055 // stopgap to prevent spacebar in Firefox from reopening popup 6056 // immediately after closing it 6057 this.hidingPopup = false; 6058 return false; 6059 } 6060 if (this.chaptersPopup.is(':visible')) { 6061 this.chaptersPopup.hide(); 6062 this.hidingPopup = false; 6063 this.$chaptersButton.attr('aria-expanded','false').trigger('focus'); 6064 } else { 6065 this.closePopups(); 6066 this.chaptersPopup.show(); 6067 this.$chaptersButton.attr('aria-expanded','true'); 6068 this.chaptersPopup.css('top', this.$chaptersButton.position().top - this.chaptersPopup.outerHeight()); 6069 this.chaptersPopup.css('left', this.$chaptersButton.position().left); 6070 6071 // Highlight the current chapter, if any chapters are checked 6072 // Otherwise, place focus on the first chapter 6073 this.chaptersPopup.find('li').removeClass('able-focus'); 6074 if (this.chaptersPopup.find('li[aria-checked="true"]').length) { 6075 this.chaptersPopup.find('li[aria-checked="true"]').trigger('focus').addClass('able-focus'); 6076 } else { 6077 this.chaptersPopup.find('li').first().addClass('able-focus').attr('aria-checked','true').trigger('focus'); 6078 } 6079 } 6080 }; 6081 6082 AblePlayer.prototype.handleDescriptionToggle = function() { 6083 6084 this.descOn = !this.descOn; 6085 this.prefDesc = + this.descOn; // convert boolean to integer 6086 this.updatePreferences('prefDesc'); 6087 if (typeof this.$descDiv !== 'undefined') { 6088 if (!this.$descDiv.is(':hidden')) { 6089 this.$descDiv.hide(); 6090 } 6091 // NOTE: now showing $descDiv here if previously hidden 6092 // that's handled elsewhere, dependent on whether there's text to show 6093 } 6094 this.initDescription(); 6095 this.refreshControls('descriptions'); 6096 }; 6097 6098 AblePlayer.prototype.handlePrefsClick = function(pref) { 6099 6100 // NOTE: the prefs menu is positioned near the right edge of the player 6101 // This assumes the Prefs button is also positioned in that vicinity 6102 // (last or second-last button the right) 6103 6104 // NOTE: If previously unable to fully populate the Description dialog 6105 // because the Web Speech API failed to getVoices() 6106 // now is a good time to try again 6107 // so the Description dialog can be rebuilt before the user requests it 6108 6109 var thisObj, prefsButtonPosition, prefsMenuRight, prefsMenuLeft; 6110 6111 thisObj = this; 6112 6113 if (this.speechEnabled === null) { 6114 this.initSpeech('prefs'); 6115 } 6116 if (this.hidingPopup) { 6117 // stopgap to prevent spacebar in Firefox from reopening popup 6118 // immediately after closing it 6119 this.hidingPopup = false; 6120 return false; 6121 } 6122 if (this.prefsPopup.is(':visible')) { 6123 this.prefsPopup.hide(); 6124 this.$prefsButton.attr('aria-expanded','false'); 6125 // restore each menu item to original hidden state 6126 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 6127 if (!this.showingPrefsDialog) { 6128 this.$prefsButton.trigger('focus'); 6129 } 6130 // wait briefly, then reset hidingPopup 6131 setTimeout(function() { 6132 thisObj.hidingPopup = false; 6133 },100); 6134 } else { 6135 this.closePopups(); 6136 this.prefsPopup.show(); 6137 this.$prefsButton.attr('aria-expanded','true'); 6138 this.$prefsButton.trigger('focus'); // focus first on prefs button to announce expanded state 6139 // give time for focus on button then adjust popup settings and focus 6140 setTimeout(function() { 6141 prefsButtonPosition = thisObj.$prefsButton.position(); 6142 prefsMenuRight = thisObj.$ableDiv.width() - 5; 6143 prefsMenuLeft = prefsMenuRight - thisObj.prefsPopup.width(); 6144 thisObj.prefsPopup.css('top', prefsButtonPosition.top - thisObj.prefsPopup.outerHeight()); 6145 thisObj.prefsPopup.css('left', prefsMenuLeft); 6146 // remove prior focus and set focus on first item; also change tabindex from -1 to 0 6147 thisObj.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','0'); 6148 thisObj.prefsPopup.find('li').first().trigger('focus').addClass('able-focus'); 6149 }, 50); 6150 } 6151 }; 6152 6153 AblePlayer.prototype.handleTranscriptToggle = function () { 6154 var thisObj = this; 6155 var visible = this.$transcriptDiv.is(':visible'); 6156 if ( visible ) { 6157 this.$transcriptArea.hide(); 6158 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6159 this.prefTranscript = 0; 6160 if ( this.transcriptType === 'popup' ) { 6161 this.$transcriptButton.trigger('focus').addClass('able-focus'); 6162 // wait briefly before resetting stopgap var 6163 // otherwise the keypress used to select 'Close' will trigger the transcript button 6164 // Benchmark tests: If this is gonna happen, it typically happens in around 3ms; max 12ms 6165 // Setting timeout to 100ms is a virtual guarantee of proper functionality 6166 setTimeout(function() { 6167 thisObj.closingTranscript = false; 6168 }, 100); 6169 } 6170 } else { 6171 if ( this.transcriptType === 'popup' ) { 6172 this.positionDraggableWindow('transcript'); 6173 this.$transcriptArea.show(); 6174 // showing transcriptArea has a cascading effect of showing all content *within* transcriptArea 6175 // need to re-hide the popup menu 6176 this.$transcriptPopup.hide(); 6177 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6178 this.prefTranscript = 1; 6179 // move focus to first focusable element (window options button) 6180 this.focusNotClick = true; 6181 this.$transcriptArea.find('button').first().trigger('focus'); 6182 // wait briefly before resetting stopgap var 6183 setTimeout(function() { 6184 thisObj.focusNotClick = false; 6185 }, 100); 6186 } else { 6187 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6188 this.$transcriptArea.show(); 6189 } 6190 } 6191 this.updatePreferences('prefTranscript'); 6192 }; 6193 6194 AblePlayer.prototype.handleSignToggle = function () { 6195 6196 var thisObj = this; 6197 var visible = this.$signWindow.is(':visible'); 6198 if ( visible ) { 6199 this.$signWindow.hide(); 6200 this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) ); 6201 this.prefSign = 0; 6202 this.$signButton.trigger('focus').addClass('able-focus'); 6203 // wait briefly before resetting stopgap var 6204 // otherwise the keypress used to select 'Close' will trigger the transcript button 6205 setTimeout(function() { 6206 thisObj.closingSign = false; 6207 }, 100); 6208 } else { 6209 this.positionDraggableWindow('sign'); 6210 this.$signWindow.show(); 6211 // showing signWindow has a cascading effect of showing all content *within* signWindow 6212 // need to re-hide the popup menu 6213 this.$signPopup.hide(); 6214 this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) ); 6215 this.prefSign = 1; 6216 this.focusNotClick = true; 6217 this.$signWindow.find('button').first().trigger('focus'); 6218 // wait briefly before resetting stopgap var 6219 // otherwise the keypress used to select 'Close' will trigger the transcript button 6220 setTimeout(function() { 6221 thisObj.focusNotClick = false; 6222 }, 100); 6223 } 6224 this.updatePreferences('prefSign'); 6225 }; 6226 6227 AblePlayer.prototype.setFullscreen = function (fullscreen) { 6228 6229 if (this.fullscreen == fullscreen) { 6230 return; 6231 } 6232 var thisObj = this; 6233 var $el = this.$ableWrapper; 6234 var el = $el[0]; 6235 6236 if (this.nativeFullscreenSupported()) { 6237 // Note: many varying names for options for browser compatibility. 6238 if (fullscreen) { 6239 var scroll = { 6240 x: window.pageXOffset || 0, 6241 y: window.pageYOffset || 0 6242 }; 6243 if (this.prefTranscript === 1) { 6244 // transcript is on. Go ahead and reposition it 6245 this.rePositionDraggableWindow("transcript"); 6246 } 6247 if (this.prefSign === 1) { 6248 // sign is on. Go ahead and reposition it 6249 this.rePositionDraggableWindow("sign"); 6250 } 6251 this.scrollPosition = scroll; 6252 // Initialize fullscreen 6253 if (el.requestFullscreen) { 6254 el.requestFullscreen(); 6255 } else if (el.webkitRequestFullscreen) { 6256 el.webkitRequestFullscreen(); 6257 } 6258 this.fullscreen = true; 6259 } else { 6260 // Exit fullscreen 6261 this.restoringAfterFullscreen = true; 6262 if (document.exitFullscreen) { 6263 document.exitFullscreen(); 6264 } else if (document.webkitExitFullscreen) { 6265 document.webkitExitFullscreen(); 6266 } else if (document.webkitCancelFullscreen) { 6267 document.webkitCancelFullscreen(); 6268 } 6269 if (this.prefTranscript === 1) { 6270 // transcript is on. Go ahead and reposition it 6271 this.positionDraggableWindow("transcript"); 6272 } 6273 if (this.prefSign === 1) { 6274 // sign is on. Go ahead and reposition it 6275 this.positionDraggableWindow("sign"); 6276 } 6277 this.fullscreen = false; 6278 } 6279 } 6280 // add event handlers for changes in fullscreen mode. 6281 // Browsers natively trigger this event with the Escape key, 6282 // in addition to clicking the exit fullscreen button 6283 $(document).on('fullscreenchange webkitfullscreenchange', function(e) { 6284 // NOTE: e.type = the specific event that fired (in case needing to control for browser-specific idiosyncrasies) 6285 if (!thisObj.fullscreen) { 6286 // user has just exited full screen 6287 thisObj.restoringAfterFullscreen = true; 6288 } else if (!thisObj.clickedFullscreenButton) { 6289 // user triggered fullscreenchange without clicking fullscreen button 6290 thisObj.fullscreen = false; 6291 thisObj.restoringAfterFullscreen = true; 6292 } 6293 thisObj.resizePlayer(); 6294 thisObj.refreshControls('fullscreen'); 6295 // Reset scrollPosition after closing fullscreen. 6296 if ( thisObj.scrollPosition ) { 6297 scroll = thisObj.scrollPosition; 6298 window.scrollTo( scroll.x, scroll.y ); 6299 } 6300 // NOTE: The fullscreenchange (or browser-equivalent) event is triggered twice 6301 // when exiting fullscreen via the "Exit fullscreen" button (only once if using Escape) 6302 // Not sure why, but consequently we need to be sure thisObj.clickedFullscreenButton 6303 // continues to be true through both events 6304 // Could use a counter variable to control that (reset to false after the 2nd trigger) 6305 // However, since I don't know why it's happening, and whether it's 100% reliable 6306 // resetting clickedFullscreenButton after a timeout seems to be better approach 6307 setTimeout(function() { 6308 thisObj.clickedFullscreenButton = false; 6309 thisObj.restoringAfterFullscreen = false; 6310 },100); 6311 }); 6312 }; 6313 6314 AblePlayer.prototype.handleFullscreenToggle = function () { 6315 6316 var stillPaused = this.paused; 6317 this.setFullscreen(!this.fullscreen); 6318 if (stillPaused) { 6319 this.pauseMedia(); // when toggling fullscreen and media is just paused, keep media paused. 6320 } else if (!stillPaused) { 6321 this.playMedia(); // when toggling fullscreen and media is playing, continue playing. 6322 } 6323 // automatically hide controller in fullscreen mode 6324 // then reset back to original setting after exiting fullscreen mode 6325 if (this.fullscreen) { 6326 this.hideControls = true; 6327 if (this.playing) { 6328 // go ahead and hide the controls 6329 this.fadeControls('out'); 6330 this.controlsHidden = true; 6331 } 6332 } else { 6333 // exit fullscreen mode 6334 this.hideControls = this.hideControlsOriginal; 6335 if (!this.hideControls) { // do not hide controls 6336 if (this.controlsHidden) { 6337 this.fadeControls('in'); 6338 this.controlsHidden = false; 6339 } 6340 // if there's an active timeout to fade controls out again, clear it 6341 if (this.hideControlsTimeoutStatus === 'active') { 6342 window.clearTimeout(this.hideControlsTimeout); 6343 this.hideControlsTimeoutStatus = 'clear'; 6344 } 6345 } 6346 } 6347 // don't resizePlayer yet; that will be called in response to the window resize event 6348 }; 6349 6350 AblePlayer.prototype.handleTranscriptLockToggle = function (val) { 6351 6352 this.autoScrollTranscript = val; // val is boolean 6353 this.prefAutoScrollTranscript = +val; // convert boolean to numeric 1 or 0 for cookie 6354 this.updatePreferences('prefAutoScrollTranscript'); 6355 this.refreshControls('transcript'); 6356 }; 6357 6358 AblePlayer.prototype.getIcon = function( $button, id) { 6359 // Remove existing HTML before generating. 6360 // iconData: [0 = svg viewbox, 1 = svg path] 6361 // Font and image icon functionality was removed in 5.0.0 in favor of SVG. 6362 var iconData = this.getIconData( id ); 6363 6364 var existingIcon = $button.find( 'svg#ableplayer-' + id ); 6365 // Avoid repainting icon if there's no change. 6366 if ( existingIcon.length > 0 ) { 6367 return; 6368 } 6369 $button.find('svg').remove(); 6370 6371 // Outdented for simpler diff 6372 // Function to create SVG nodes. 6373 function getNode(n, v) { 6374 n = document.createElementNS("http://www.w3.org/2000/svg", n); 6375 for (var p in v) { 6376 n.setAttributeNS(null, p.replace(/[A-Z]/g, function(m) { 6377 return "-" + m.toLowerCase(); 6378 }), v[p]); 6379 } 6380 return n; 6381 } 6382 var icon = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); 6383 icon.setAttribute( 'focusable', 'false' ); 6384 icon.setAttribute( 'aria-hidden', 'true'); 6385 icon.setAttribute( 'viewBox', iconData[0] ); 6386 icon.setAttribute( 'id', 'ableplayer-' + id ); 6387 let path = getNode( 'path', { d: iconData[1] } ); 6388 icon.appendChild( path ); 6389 6390 $button.append( icon ); 6391 // Refresh the DOM. 6392 $button.html($button.html()); 6393 }; 6394 6395 AblePlayer.prototype.setText = function( $button, text ) { 6396 $button.attr( 'aria-label', text ); 6397 }; 6398 6399 AblePlayer.prototype.toggleButtonState = function($button, isOn, onLabel, offLabel, ariaPressed = false, ariaExpanded = false) { 6400 // isOn means "the feature is being turned on". 6401 let buttonOff = ( $button.hasClass( 'buttonOff' ) ) ? true : false; 6402 if ( buttonOff && ! isOn || ! buttonOff && isOn ) { 6403 // Only toggle state if button state does not match feature state. 6404 return; 6405 } 6406 if (! isOn) { 6407 $button.addClass('buttonOff').attr('aria-label', offLabel); 6408 if ( ariaPressed ) { 6409 $button.attr('aria-pressed', 'false'); 6410 } 6411 if ( ariaExpanded ) { 6412 $button.attr( 'aria-expanded', 'false' ); 6413 } 6414 } else { 6415 $button.removeClass('buttonOff').attr('aria-label', onLabel); 6416 if ( ariaPressed ) { 6417 $button.attr('aria-pressed', 'true'); 6418 } 6419 if ( ariaExpanded ) { 6420 $button.attr( 'aria-expanded', 'true' ); 6421 } 6422 } 6423 }; 6424 6425 AblePlayer.prototype.showTooltip = function($tooltip) { 6426 6427 $tooltip.show(); 6428 }; 6429 6430 AblePlayer.prototype.showAlert = function( msg, location = 'main' ) { 6431 6432 // location is either of the following: 6433 // 'main' (default) 6434 // 'screenreader (visibly hidden) 6435 // 'sign' (sign language window) 6436 // 'transcript' (transcript window) 6437 var thisObj, $alertBox, $parentWindow; 6438 6439 thisObj = this; 6440 $alertBox = thisObj.$alertBox; 6441 $parentWindow = thisObj.$ableDiv; 6442 if (location === 'transcript') { 6443 $parentWindow = thisObj.$transcriptArea; 6444 } else if (location === 'sign') { 6445 $parentWindow = thisObj.$signWindow; 6446 } else if (location === 'screenreader') { 6447 $alertBox = thisObj.$srAlertBox; 6448 } 6449 $alertBox.find('span').text(msg); 6450 $alertBox.appendTo($parentWindow); 6451 $alertBox.css( {'display': 'flex'} ); 6452 6453 if (location !== 'screenreader') { 6454 setTimeout( function () { 6455 $alertBox.hide(); 6456 }, 30000 ); 6457 } 6458 }; 6459 6460 AblePlayer.prototype.showedAlert = function (which) { 6461 6462 // returns true if the target alert has already been shown 6463 // useful for throttling alerts that only need to be shown once 6464 // e.g., move alerts with instructions for dragging a window 6465 if (which === 'transcript') { 6466 return this.showedTranscriptAlert ?? false; 6467 } else if (which === 'sign') { 6468 return this.showedSignAlert ?? false; 6469 } 6470 return false; 6471 }; 6472 6473 // Resizes all relevant player attributes. 6474 AblePlayer.prototype.resizePlayer = function (width, height) { 6475 6476 var captionSize, newWidth, newHeight, $iframe; 6477 6478 if (this.mediaType === 'audio') { 6479 return; 6480 } 6481 if (typeof width !== 'undefined' && typeof height !== 'undefined') { 6482 // this is being called the first time a player is initialized 6483 // width and height were collected from the HTML, YouTube, or Vimeo media API 6484 // so are reflective of the actual size of the media 6485 // use these values to calculate aspectRatio 6486 this.aspectRatio = height / width; 6487 if (this.playerWidth) { 6488 // default width is already defined via a width or data-width attribute. Use that. 6489 newWidth = this.playerWidth; 6490 if (this.playerHeight) { 6491 newHeight = this.playerHeight; 6492 } else { 6493 newHeight = Math.round(newWidth * this.aspectRatio); 6494 this.playerHeight = newHeight; 6495 } 6496 } else { 6497 // playerWidth was not defined via HTML attributes 6498 newWidth = (this.player === 'html5') ? $(window).width() : this.$ableWrapper.width(); 6499 newHeight = Math.round(newWidth * this.aspectRatio); 6500 } 6501 } else if (this.fullscreen) { 6502 this.$ableWrapper.addClass('fullscreen'); 6503 newWidth = $(window).width(); 6504 // the 5 pixel buffer is arbitrary, but results in a better fit for all browsers 6505 newHeight = $(window).height() - this.$playerDiv.outerHeight() - 5; 6506 this.positionCaptions('overlay'); 6507 } else { // not fullscreen, and not first time initializing player 6508 this.$ableWrapper.removeClass('fullscreen'); 6509 if (this.player === 'html5') { 6510 newWidth = (this.playerWidth) ? this.playerWidth : $(window).width(); 6511 } else { 6512 newWidth = this.$ableWrapper.width(); 6513 } 6514 newHeight = Math.round(newWidth * this.aspectRatio); 6515 this.positionCaptions(this.prefCaptionsPosition); 6516 } 6517 if (this.debug) { 6518 console.log('resizePlayer to ' + newWidth + 'x' + newHeight); 6519 } 6520 // Now size the player with newWidth and newHeight 6521 if (this.player === 'youtube' || this.player === 'vimeo') { 6522 $iframe = this.$ableWrapper.find('iframe'); 6523 if (this.player === 'youtube' && this.youTubePlayer) { 6524 // alternatively, YouTube API offers a method for setting the video size 6525 // this adds width and height attributes to the iframe 6526 // but might have other effects, so best to do it this way 6527 this.youTubePlayer.setSize(newWidth,newHeight); 6528 } else { 6529 // Vimeo API does not have a method for changing size of player 6530 // Therefore, need to change iframe attributes directly 6531 $iframe.attr({ 6532 'width': newWidth, 6533 'height': newHeight 6534 }); 6535 } 6536 if (this.playerWidth && this.playerHeight) { 6537 if (this.fullscreen) { 6538 // remove constraints 6539 $iframe.css({ 6540 'max-width': '', 6541 'max-height': '' 6542 }); 6543 } else { 6544 // use CSS on iframe to enforce explicitly defined size constraints 6545 $iframe.css({ 6546 'max-width': this.playerWidth + 'px', 6547 'max-height': this.playerHeight + 'px' 6548 }); 6549 } 6550 } 6551 } else if (this.player === 'html5') { 6552 if (this.fullscreen) { 6553 this.$media.attr({ 6554 'width': newWidth, 6555 'height': newHeight 6556 }); 6557 this.$ableWrapper.css({ 6558 'width': newWidth, 6559 'height': newHeight 6560 }); 6561 } else { 6562 // No constraints. Let CSS handle the positioning. 6563 this.$media.removeAttr('width height'); 6564 this.$ableWrapper.removeAttr( 'style' ); 6565 } 6566 } 6567 // Resize captions 6568 if (typeof this.$captionsDiv !== 'undefined') { 6569 6570 // Font-size is too small in full screen view 6571 // use viewport units (vw) for large viewports 6572 // % units work fine if not fullscreen 6573 // prefCaptionSize is expressed as a percentage 6574 var isSmallScreen = false; 6575 var windowWidth = window.screen.width; 6576 if ( windowWidth < 1200 ) { 6577 isSmallScreen = true; 6578 } 6579 captionSize = parseInt(this.prefCaptionsSize,10); 6580 if (this.fullscreen && ! isSmallScreen ) { 6581 captionSize = (captionSize / 100) + 'vw'; 6582 } else if ( this.fullscreen && isSmallScreen ) { 6583 captionSize = '1.2rem'; 6584 } else { 6585 captionSize = captionSize + '%'; 6586 } 6587 this.$captionsDiv.css({ 6588 'font-size': captionSize 6589 }); 6590 } 6591 this.refreshControls(); 6592 }; 6593 6594 AblePlayer.prototype.retrieveOffscreenWindow = function( which, width, height ) { 6595 6596 // check to be sure popup windows ('transcript' or 'sign') are positioned on-screen 6597 // (they sometimes disappear off-screen when entering fullscreen mode) 6598 // if off-screen, recalculate so they are back on screen 6599 6600 var window, windowPos, windowTop, windowLeft, windowRight, windowWidth, windowBottom, windowHeight; 6601 6602 if (which == 'transcript') { 6603 window = this.$transcriptArea; 6604 } else if (which == 'sign') { 6605 window = this.$signWindow; 6606 } 6607 windowWidth = window.width(); 6608 windowHeight = window.height(); 6609 windowPos = window.position(); 6610 windowTop = windowPos.top; 6611 windowLeft = windowPos.left; 6612 windowRight = windowLeft + windowWidth; 6613 windowBottom = windowTop + windowHeight; 6614 6615 if (windowTop < 0) { // off-screen to the top 6616 windowTop = 10; 6617 window.css('top',windowTop); 6618 } 6619 if (windowLeft < 0) { // off-screen to the left 6620 windowLeft = 10; 6621 window.css('left',windowLeft); 6622 } 6623 if (windowRight > width) { // off-screen to the right 6624 windowLeft = (width - 20) - windowWidth; 6625 window.css('left',windowLeft); 6626 } 6627 if (windowBottom > height) { // off-screen to the bottom 6628 windowTop = (height - 10) - windowHeight; 6629 window.css('top',windowTop); 6630 } 6631 }; 6632 6633 AblePlayer.prototype.updateZIndex = function(which) { 6634 6635 // update z-index of 'transcript' or 'sign', relative to each other 6636 // direction is always 'up' (i.e., move window to top) 6637 // windows come to the top when the user clicks on them 6638 var defHighZ, defLowZ, transcriptZ, signZ, newHighZ, newLowZ; 6639 6640 // set the default z-indexes, as defined in ableplayer.css 6641 defHighZ = 8000; // by default, assigned to the sign window 6642 defLowZ = 7000; // by default, assigned to the transcript area 6643 6644 // Previously collected the highest z-index. Removed in 4.6. 6645 // If something on the page has a higher z-index than the transcript or sign window, do we care? 6646 // Excluding it here assumes "No". Our immediate concern is with the relationship between our own components. 6647 // If we elevate our z-indexes so our content is on top, we run the risk of starting a z-index war. 6648 6649 if (typeof this.$transcriptArea === 'undefined' || typeof this.$signWindow === 'undefined' ) { 6650 // at least one of the windows doesn't exist, so there's no conflict 6651 // since z-index may have been stored to a cookie on another page, need to restore default 6652 if (typeof this.$transcriptArea !== 'undefined') { 6653 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 6654 if (transcriptZ > defLowZ) { 6655 // restore to the default 6656 this.$transcriptArea.css('z-index',defLowZ); 6657 } 6658 } else if (typeof this.$signWindow !== 'undefined') { 6659 signZ = parseInt(this.$signWindow.css('z-index')); 6660 if (signZ > defHighZ) { 6661 // restore to the default 6662 this.$signWindow.css('z-index',defHighZ); 6663 } 6664 } 6665 return false; 6666 } 6667 6668 // both windows exist 6669 6670 // get current values 6671 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 6672 signZ = parseInt(this.$signWindow.css('z-index')); 6673 6674 if (transcriptZ === signZ) { 6675 // the two windows are equal; restore defaults (the target window will be on top) 6676 newHighZ = defHighZ; 6677 newLowZ = defLowZ; 6678 } else if (transcriptZ > signZ) { 6679 if (which === 'transcript') { 6680 // transcript is already on top; nothing to do 6681 return false; 6682 } else { 6683 // swap z's 6684 newHighZ = transcriptZ; 6685 newLowZ = signZ; 6686 } 6687 } else { // signZ is greater 6688 if (which === 'sign') { 6689 // sign is already on top; nothing to do 6690 return false; 6691 } else { 6692 newHighZ = signZ; 6693 newLowZ = transcriptZ; 6694 } 6695 } 6696 // now assign the new values 6697 if (which === 'transcript') { 6698 this.$transcriptArea.css('z-index',newHighZ); 6699 this.$signWindow.css('z-index',newLowZ); 6700 } else if (which === 'sign') { 6701 this.$signWindow.css('z-index',newHighZ); 6702 this.$transcriptArea.css('z-index',newLowZ); 6703 } 6704 }; 6705 6706 AblePlayer.prototype.syncTrackLanguages = function (source, language) { 6707 6708 // this function is called when the player is built (source == 'init') 6709 // and again when user changes the language of either 'captions' or 'transcript' 6710 // It syncs the languages of chapters, descriptions, and metadata tracks 6711 // NOTE: Caption and transcript languages are somewhat independent from one another 6712 // If a user changes the caption language, the transcript follows 6713 // However, if a user changes the transcript language, this only affects the transcript 6714 // This was a group decision based on the belief that users may want a transcript 6715 // that is in a different language than the captions 6716 6717 var i, captions, descriptions, chapters, meta; 6718 6719 // Captions 6720 for (i = 0; i < this.captions.length; i++) { 6721 if (this.captions[i].language === language) { 6722 captions = this.captions[i]; 6723 } 6724 } 6725 // Chapters 6726 for (i = 0; i < this.chapters.length; i++) { 6727 if (this.chapters[i].language === language) { 6728 chapters = this.chapters[i]; 6729 } 6730 } 6731 // Descriptions 6732 for (i = 0; i < this.descriptions.length; i++) { 6733 if (this.descriptions[i].language === language) { 6734 descriptions = this.descriptions[i]; 6735 } 6736 } 6737 // Metadata 6738 for (i = 0; i < this.meta.length; i++) { 6739 if (this.meta[i].language === language) { 6740 meta = this.meta[i]; 6741 } 6742 } 6743 // regardless of source... 6744 this.transcriptLang = language; 6745 if (source === 'init' || source === 'captions') { 6746 this.captionLang = language; 6747 this.selectedCaptions = captions; 6748 this.selectedChapters = chapters; 6749 this.selectedDescriptions = descriptions; 6750 this.selectedMeta = meta; 6751 this.transcriptCaptions = captions; 6752 this.transcriptChapters = chapters; 6753 this.transcriptDescriptions = descriptions; 6754 this.updateChaptersList(); 6755 // the following was commented out in Oct/Nov 2018. 6756 // chapters popup is setup automatically when setupPopups() is called later with no param 6757 // not sure why it was included here. 6758 // this.setupPopups('chapters'); 6759 } else if (source === 'transcript') { 6760 this.transcriptCaptions = captions; 6761 this.transcriptChapters = chapters; 6762 this.transcriptDescriptions = descriptions; 6763 } 6764 if (this.selectedDescriptions) { 6765 // updating description voice to match new description language 6766 this.setDescriptionVoice(); 6767 if (this.$sampleDescDiv) { 6768 if (this.sampleText) { 6769 for (i = 0; i < this.sampleText.length; i++) { 6770 if (this.sampleText[i].lang === this.selectedDescriptions.language) { 6771 this.currentSampleText = this.sampleText[i]['text']; 6772 this.$sampleDescDiv.html(this.currentSampleText); 6773 } 6774 } 6775 } 6776 } 6777 } 6778 this.updateTranscript(); 6779 }; 6780 6781 } 6782 6783 function addDescriptionFunctions(AblePlayer) { 6784 AblePlayer.prototype.initDescription = function() { 6785 6786 // set default mode for delivering description (open vs closed) 6787 // based on availability and user preference 6788 6789 // called when player is being built, or when a user 6790 // toggles the Description button or changes a description-related preference 6791 6792 // The following variables are applicable to delivery of description: 6793 // defaultStateDescriptions == 'on' or 'off', defined by website owner (overridden by prefDesc) 6794 // prefDesc == 1 if user wants description (i.e., Description button is on); else 0 6795 // prefDescPause == 1 to pause video when description starts; else 0 6796 // prefDescVisible == 1 to visibly show text-based description area; else 0 6797 // prefDescMethod == either 'video' or 'text' (as of v4.0.10, prefDescMethod is always 'video') 6798 // descMethod is the format actually used ('video' or 'text'), regardless of user preference 6799 // hasOpenDesc == true if a described version of video is available via data-desc-src attribute 6800 // hasClosedDesc == true if a description text track is available 6801 // descOn == true if description of either type is on 6802 // readDescriptionsAloud == true if text description is to be announced audibly; otherwise false 6803 // descReader == either 'browser' or 'screenreader' 6804 6805 var deferred, promise; 6806 6807 deferred = new this.defer(); 6808 promise = deferred.promise(); 6809 6810 if (this.mediaType === 'audio') { 6811 deferred.resolve(); 6812 } 6813 6814 // check to see if there's an open-described version of this video 6815 // checks only the first source since if a described version is provided, 6816 // it must be provided for all sources 6817 this.descFile = this.$sources.first().attr('data-desc-src'); 6818 if (typeof this.descFile !== 'undefined') { 6819 this.hasOpenDesc = true; 6820 } else { 6821 // there's no open-described version via data-desc-src, 6822 // but what about data-youtube-desc-src or data-vimeo-desc-src? 6823 // if these exist, they would have been defined earlier 6824 this.hasOpenDesc = (this.youTubeDescId || this.vimeoDescId) ? true : false; 6825 } 6826 6827 // Set this.descMethod based on media availability & user preferences 6828 // no description is available for this video 6829 this.descMethod = null; 6830 if (this.hasOpenDesc && this.hasClosedDesc) { 6831 // both formats are available. User gets their preference. 6832 this.descMethod = (this.prefDescMethod) ? this.prefDescMethod : 'video'; 6833 } else if (this.hasOpenDesc) { 6834 this.descMethod = 'video'; 6835 } else if (this.hasClosedDesc) { 6836 this.descMethod = 'text'; 6837 } 6838 6839 // Set the default state of descriptions 6840 this.descOn = false; 6841 if (this.descMethod) { 6842 if (this.prefDesc === 1) { 6843 this.descOn = true; 6844 } else if (this.prefDesc === 0) { 6845 this.descOn = false; 6846 } else { 6847 // user has no prefs. Use default state. 6848 this.descOn = (this.defaultStateDescriptions === 1) ? true : false; 6849 } 6850 } 6851 6852 // If a video has text audio descriptions, inject the description area. 6853 if (typeof this.$descDiv === 'undefined' && this.hasClosedDesc ) { 6854 this.injectTextDescriptionArea(); 6855 } 6856 6857 if (this.descOn) { 6858 if (this.descMethod === 'video' && !this.usingDescribedVersion() ) { 6859 // switched from non-described to described version 6860 this.swapDescription(); 6861 } 6862 if (this.hasClosedDesc) { 6863 if (this.prefDescVisible) { 6864 // make description text visible 6865 if (typeof this.$descDiv !== 'undefined') { 6866 this.$descDiv.show(); 6867 this.$descDiv.removeClass('able-offscreen'); 6868 } 6869 } else { 6870 // keep it visible to screen readers, but hide it visibly 6871 if (typeof this.$descDiv !== 'undefined') { 6872 this.$descDiv.addClass('able-offscreen'); 6873 } 6874 } 6875 } 6876 } else { // description is off. 6877 if (this.descMethod === 'video') { // user has turned off described version of video 6878 if (this.usingDescribedVersion()) { 6879 // user was using the described verion. Swap for non-described version 6880 this.swapDescription(); 6881 } 6882 } else if (this.descMethod === 'text') { // user has turned off text description 6883 // hide description div from everyone, including screen reader users 6884 if (typeof this.$descDiv !== 'undefined') { 6885 this.$descDiv.hide(); 6886 this.$descDiv.removeClass('able-offscreen'); 6887 } 6888 } 6889 } 6890 deferred.resolve(); 6891 return promise; 6892 }; 6893 6894 AblePlayer.prototype.usingDescribedVersion = function () { 6895 6896 // Returns true if currently using audio description, false otherwise. 6897 6898 if (this.player === 'youtube') { 6899 return (this.activeYouTubeId === this.youTubeDescId); 6900 } else if (this.player === 'vimeo') { 6901 return (this.activeVimeoId === this.vimeoDescId); 6902 } else { 6903 return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src')); 6904 } 6905 }; 6906 6907 /** 6908 * Initializes speech synthesis capabilities for the player. 6909 * This method addresses browser and OS limitations that require user interaction 6910 * before speech synthesis functions become available. It handles different contexts 6911 * like initialization, playing media, accessing preferences, or announcing descriptions. 6912 * @param {string} context - The context in which the function is called ('init', 'play', 'prefs', 'desc'). 6913 */ 6914 AblePlayer.prototype.initSpeech = function (context) { 6915 var thisObj = this; 6916 6917 // Function to attempt enabling speech synthesis 6918 function attemptEnableSpeech() { 6919 var greeting = new SpeechSynthesisUtterance("\x20"); 6920 greeting.onend = function () { 6921 thisObj.getBrowserVoices(); 6922 if ( 6923 (Array.isArray(thisObj.descVoices) && thisObj.descVoices.length) || 6924 context !== "init" 6925 ) { 6926 thisObj.speechEnabled = true; 6927 } 6928 }; 6929 thisObj.synth.speak(greeting); 6930 } 6931 6932 // Function to handle the initial click and enable speech synthesis 6933 function handleInitialClick() { 6934 attemptEnableSpeech(); 6935 // Once the utterance starts, remove this specific click event listener 6936 // Ensures the event handler only runs once and cleans up after itself 6937 $(document).off("click", handleInitialClick); 6938 } 6939 6940 if (this.speechEnabled === null) { 6941 if (window.speechSynthesis) { 6942 // Browser supports speech synthesis 6943 this.synth = window.speechSynthesis; 6944 this.synth.cancel(); // Cancel any ongoing speech synthesis 6945 6946 if (context === "init") { 6947 // Attempt to enable speech synthesis directly for browsers that don't require a click 6948 attemptEnableSpeech(); 6949 // For initial setup, require a user click to enable speech synthesis 6950 // Scoping to a particular handler to avoid conflicts with other click events 6951 $(document).on("click", handleInitialClick); 6952 } else { 6953 // For other contexts, attempt to enable speech synthesis directly 6954 attemptEnableSpeech(); 6955 } 6956 } else { 6957 // Browser does not support speech synthesis 6958 this.speechEnabled = false; 6959 } 6960 } 6961 }; 6962 6963 AblePlayer.prototype.getBrowserVoices = function () { 6964 6965 // define this.descVoices array 6966 // includes only languages that match the language of the captions or player 6967 6968 var voices, trackLangs, voiceLang, preferredLang; 6969 6970 preferredLang = (this.captionLang) ? this.captionLang.substring(0,2).toLowerCase() : this.lang.substring(0,2).toLowerCase(); 6971 6972 this.descVoices = []; 6973 voices = this.synth.getVoices(); 6974 trackLangs = this.getTrackLangs(); 6975 if (voices.length > 0) { 6976 this.descVoices = []; 6977 // available languages are identified with local suffixes (e.g., en-US) 6978 for (var i=0; i<voices.length; i++) { 6979 // match only the first 2 characters of the lang code 6980 voiceLang = voices[i].lang.substring(0,2).toLowerCase(); 6981 if (voiceLang === preferredLang && (trackLangs.indexOf(voiceLang) !== -1)) { 6982 // this voice matches preferredLang 6983 // AND there's a matching description track in this language 6984 // Add this voice to final array 6985 this.descVoices.push(voices[i]); 6986 } 6987 } 6988 if (!this.descVoices.length) { 6989 // no voices available in the default language(s) 6990 // just use all voices, regardless of language 6991 this.descVoices = voices; 6992 } 6993 } 6994 return false; 6995 }; 6996 6997 AblePlayer.prototype.getTrackLangs = function () { 6998 6999 // returns an array of languages (from srclang atttributes) 7000 // in which there are description tracks 7001 // use only first two characters of the lang code 7002 var descLangs = []; 7003 if (this.tracks) { 7004 for (var i=0; i < this.tracks.length; i++) { 7005 if (this.tracks[i].kind === 'descriptions' || this.tracks[i].kind === 'captions' ) { 7006 descLangs.push(this.tracks[i].language.substring(0,2).toLowerCase()); 7007 } 7008 } 7009 } 7010 return descLangs; 7011 }; 7012 7013 AblePlayer.prototype.setDescriptionVoice = function () { 7014 7015 // set description voice on player init, or when user changes caption language 7016 // Voice is determined in the following order of precedence: 7017 // 1. User's preferred voice for this language, saved in preferences 7018 // 2. The first available voice in the array of available voices for this browser in this language 7019 7020 var preferences, voices, prefDescVoice, descVoice, descLang, prefVoiceFound; 7021 preferences = this.getPref(); 7022 prefDescVoice = (typeof preferences.voices !== 'undefined') ? this.getPrefVoice() : null; 7023 7024 this.getBrowserVoices(); 7025 this.rebuildVoicePrefsForm( '_prefDescVoice' ); 7026 this.rebuildVoicePrefsForm( '_prefCaptionsVoice' ); 7027 7028 if (this.selectedDescriptions) { 7029 descLang = this.selectedDescriptions.language; 7030 } else if (this.captionLang) { 7031 descLang = this.captionLang; 7032 } else { 7033 descLang = this.lang; 7034 } 7035 7036 if (this.synth) { 7037 voices = this.synth.getVoices(); 7038 if (voices.length > 0) { 7039 if (prefDescVoice) { 7040 // select the language that matches prefDescVoice, if it's available 7041 prefVoiceFound = false; 7042 for (var i=0; i<voices.length; i++) { 7043 // first, be sure voice is the correct language 7044 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7045 if (voices[i].name === prefDescVoice) { 7046 descVoice = voices[i].name; 7047 prefVoiceFound = true; 7048 break; 7049 } 7050 } 7051 } 7052 } 7053 if (!prefVoiceFound) { 7054 // select the first language that matches the first 2 characters of the lang code 7055 for (i=0; i<voices.length; i++) { 7056 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7057 descVoice = voices[i].name; 7058 break; 7059 } 7060 } 7061 } 7062 // make this the user's current preferred voice 7063 this.prefDescVoice = descVoice; 7064 this.prefDescVoiceLang = descLang; 7065 // select this voice in the Description Prefs dialog 7066 if (this.$voiceSelectField) { 7067 this.$voiceSelectField.val(this.prefDescVoice); 7068 } 7069 this.updatePreferences('voice'); 7070 } 7071 } 7072 }; 7073 7074 AblePlayer.prototype.swapDescription = function() { 7075 7076 // swap described and non-described source media, depending on which is playing 7077 // this function is only called in two circumstances: 7078 // 1. Swapping to described version when initializing player (based on user prefs & availability) 7079 // (playerCreated == false) 7080 // 2. User is toggling description 7081 // (playerCreated == true) 7082 7083 var thisObj, i, origSrc, descSrc; 7084 7085 thisObj = this; 7086 7087 // We are no longer loading the previous media source 7088 // Only now, as a new source is requested, is it safe to reset this var 7089 // It will be reset to true when media.load() is called 7090 this.loadingMedia = false; 7091 7092 // get element that has focus at the time swap is initiated 7093 // after player is rebuilt, focus will return to that same element 7094 // (if it exists) 7095 this.$focusedElement = $(':focus'); 7096 this.activeMedia = this.mediaId; 7097 7098 // get current time of current source, and attempt to start new video at the same time 7099 // whether this is possible will be determined after the new media source has loaded 7100 // see onMediaNewSourceLoad() 7101 if (this.elapsed > 0) { 7102 this.swapTime = this.elapsed; 7103 } else { 7104 this.swapTime = 0; 7105 } 7106 if (this.duration > 0) { 7107 this.prevDuration = this.duration; 7108 } 7109 7110 // Capture current playback state, so media can resume after source is swapped 7111 if (!this.okToPlay) { 7112 this.okToPlay = this.playing; 7113 } 7114 7115 if (this.descOn) { 7116 this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) ); 7117 } else { 7118 this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) ); 7119 } 7120 7121 if (this.player === 'html5') { 7122 7123 this.swappingSrc = true; 7124 this.paused = true; 7125 7126 if (this.usingDescribedVersion()) { 7127 // the described version is currently playing. Swap to non-described 7128 for (i=0; i < this.$sources.length; i++) { 7129 // for all <source> elements, replace src with data-orig-src 7130 origSrc = purify.sanitize( this.$sources[i].getAttribute('data-orig-src') ); 7131 if (origSrc) { 7132 this.$sources[i].setAttribute('src',origSrc); 7133 } 7134 } 7135 } else { 7136 // the non-described version is currently playing. Swap to described. 7137 for (i=0; i < this.$sources.length; i++) { 7138 // for all <source> elements, replace src with data-desc-src (if one exists) 7139 // then store original source in a new data-orig-src attribute 7140 origSrc = purify.sanitize( this.$sources[i].getAttribute('src') ); 7141 descSrc = purify.sanitize( this.$sources[i].getAttribute('data-desc-src') ); 7142 if (descSrc) { 7143 this.$sources[i].setAttribute('src',descSrc); 7144 this.$sources[i].setAttribute('data-orig-src',origSrc); 7145 } 7146 } 7147 } 7148 7149 if (this.recreatingPlayer) { 7150 // stopgap to prevent multiple firings of recreatePlayer() 7151 return; 7152 } 7153 if (this.playerCreated) { 7154 // delete old player, then recreate it with new source & tracks 7155 this.deletePlayer('swap-desc-html'); 7156 this.recreatePlayer().then(function() { 7157 if (!thisObj.loadingMedia) { 7158 thisObj.media.load(); 7159 thisObj.loadingMedia = true; 7160 } 7161 }); 7162 } 7163 } else if (this.player === 'youtube') { 7164 7165 // if the described version is currently playing, swap to non-described 7166 this.activeYouTubeId = (this.usingDescribedVersion()) ? this.youTubeId : this.youTubeDescId; 7167 7168 if (typeof this.youTubePlayer !== 'undefined') { 7169 thisObj.swappingSrc = true; 7170 if (thisObj.playing) { 7171 // loadVideoById() loads and immediately plays the new video at swapTime 7172 thisObj.youTubePlayer.loadVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 7173 } else { 7174 // cueVideoById() loads the new video and seeks to swapTime, but does not play 7175 thisObj.youTubePlayer.cueVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 7176 } 7177 } 7178 if (this.playerCreated) { 7179 this.deletePlayer('swap-desc-youtube'); 7180 } 7181 // player needs to be recreated with new source 7182 if (this.recreatingPlayer) { 7183 // stopgap to prevent multiple firings of recreatePlayer() 7184 return; 7185 } 7186 this.recreatePlayer().then(function() { 7187 // nothing to do here 7188 // next steps occur when youtube onReady event fires 7189 // see youtube.js > finalizeYoutubeInit() 7190 }); 7191 } else if (this.player === 'vimeo') { 7192 if (this.usingDescribedVersion()) { 7193 // the described version is currently playing. Swap to non-described 7194 this.activeVimeoId = this.vimeoId; 7195 this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) ); 7196 } else { 7197 // the non-described version is currently playing. Swap to described. 7198 this.activeVimeoId = this.vimeoDescId; 7199 this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) ); 7200 } 7201 if (this.playerCreated) { 7202 this.deletePlayer('swap-desc-vimeo'); 7203 } 7204 // player needs to be recreated with new source 7205 if (this.recreatingPlayer) { 7206 // stopgap to prevent multiple firings of recreatePlayer() 7207 return; 7208 } 7209 this.recreatePlayer().then(function() { 7210 // load the new video source 7211 thisObj.vimeoPlayer.loadVideo(thisObj.activeVimeoId).then(function() { 7212 if (thisObj.playing) { 7213 // video was playing when user requested an alternative version 7214 // seek to swapTime and continue playback (playback happens automatically) 7215 thisObj.vimeoPlayer.setCurrentTime(thisObj.swapTime); 7216 } else { 7217 // Vimeo autostarts immediately after video loads 7218 // The "Described" button should not trigger playback, so stop this before the user notices. 7219 thisObj.vimeoPlayer.pause(); 7220 } 7221 }); 7222 }); 7223 } 7224 }; 7225 7226 AblePlayer.prototype.showDescription = function(now) { 7227 if (!this.playing || !this.hasClosedDesc || this.swappingSrc || !this.descOn || ( this.descMethod === 'video' && !this.prefDescVisible ) ) { 7228 return; 7229 } 7230 7231 var cues, d, thisDescription, descText; 7232 7233 var flattenComponentForDescription = function (component) { 7234 var result = []; 7235 if (component.type === 'string') { 7236 result.push(component.value); 7237 } else { 7238 for (var i = 0; i < component.children.length; i++) { 7239 result.push(flattenComponentForDescription(component.children[i])); 7240 } 7241 } 7242 return result.join(''); 7243 }; 7244 cues = []; 7245 if (this.selectedDescriptions) { 7246 cues = this.selectedDescriptions.cues; 7247 } else if (this.descriptions.length >= 1) { 7248 cues = this.descriptions[0].cues; 7249 } 7250 for (d = 0; d < cues.length; d++) { 7251 if ((cues[d].start <= now) && (cues[d].end > now)) { 7252 thisDescription = d; 7253 break; 7254 } 7255 } 7256 if (typeof thisDescription !== 'undefined') { 7257 if (this.currentDescription !== thisDescription) { 7258 // temporarily remove aria-live from $status to prevent description from being interrupted 7259 this.$status.removeAttr('aria-live'); 7260 descText = flattenComponentForDescription(cues[thisDescription].components); 7261 if (this.descReader === 'screenreader') { 7262 // load the new description into the container div for screen readers to read 7263 this.$descDiv.html(descText); 7264 } else if (this.speechEnabled) { 7265 if ( 'video' !== this.descMethod ) { 7266 // use browser's built-in speech synthesis 7267 this.announceText('description',descText); 7268 } 7269 if (this.prefDescVisible) { 7270 // write description to the screen for sighted users 7271 // but remove ARIA attributes since it isn't intended to be read by screen readers 7272 this.$descDiv.html(descText).removeAttr('aria-live aria-atomic'); 7273 } 7274 } else { 7275 // browser does not support speech synthesis 7276 // load the new description into the container div for screen readers to read 7277 this.$descDiv.html(descText); 7278 } 7279 // Only pause video if not using a described video. 7280 if (this.prefDescPause && this.descMethod === 'text') { 7281 this.pauseMedia(); 7282 this.pausedForDescription = true; 7283 } 7284 this.currentDescription = thisDescription; 7285 } 7286 } else { 7287 this.$descDiv.html(''); 7288 this.currentDescription = -1; 7289 // restore aria-live to $status 7290 this.$status.attr('aria-live','polite'); 7291 } 7292 }; 7293 7294 AblePlayer.prototype.syncSpeechToPlaybackRate = function(rate) { 7295 7296 // called when user changed playback rate 7297 // adjust rate of audio description to match 7298 var speechRate; 7299 7300 if (rate === 0.5) { 7301 speechRate = 0.7; // option 1 in prefs menu 7302 } else if (rate === 0.75) { 7303 speechRate = 0.8; // option 2 in prefs menu 7304 } else if (rate === 1.0) { 7305 speechRate = 1; // option 4 in prefs menu (normal speech, default) 7306 } else if (rate === 1.25) { 7307 speechRate = 1.1; // option 5 in prefs menu 7308 } else if (rate === 1.5) { 7309 speechRate = 1.2; // option 6 in prefs menu 7310 } else if (rate === 1.75) { 7311 speechRate = 1.5; // option 7 in prefs menu 7312 } else if (rate === 2.0) { 7313 speechRate = 2; // option 8 in prefs menu (fast) 7314 } else if (rate === 2.25) { 7315 speechRate = 2.5; // option 9 in prefs menu (very fast) 7316 } else if (rate >= 2.5) { 7317 speechRate = 3; // option 10 in prefs menu (super fast) 7318 } 7319 this.prefDescRate = speechRate; 7320 }; 7321 7322 AblePlayer.prototype.announceText = function(context, text, rate) { 7323 7324 // this function announces text using speech synthesis 7325 // it's only called if already determined that browser supports speech synthesis 7326 // context is either: 7327 // 'description' - actual description text extracted from WebVTT file 7328 // 'sample' - called when user changes a setting in Description Prefs dialog 7329 // 'caption' - called when announcing a caption. 7330 7331 var thisObj, voiceName, i, voice, pitch, volume, utterance, 7332 timeElapsed, secondsElapsed; 7333 7334 thisObj = this; 7335 7336 if (!this.speechEnabled) { 7337 // voices array failed to load the first time. Try again 7338 this.initSpeech('desc'); 7339 } 7340 7341 if (context === 'sample') { 7342 // get settings from form 7343 voiceName = $('#' + this.mediaId + '_prefDescVoice').val(); 7344 pitch = $('#' + this.mediaId + '_prefDescPitch').val(); 7345 rate = $('#' + this.mediaId + '_prefDescRate').val(); 7346 volume = $('#' + this.mediaId + '_prefDescVolume').val(); 7347 } else if ( context === 'captionSample' ) { 7348 // get settings from form 7349 voiceName = $('#' + this.mediaId + '_prefCaptionsVoice').val(); 7350 pitch = $('#' + this.mediaId + '_prefCaptionsPitch').val(); 7351 rate = $('#' + this.mediaId + '_prefCaptionsRate').val(); 7352 volume = $('#' + this.mediaId + '_prefCaptionsVolume').val(); 7353 } else if ( context === 'description' ) { 7354 // get settings from global prefs 7355 voiceName = this.prefDescVoice; 7356 pitch = this.prefDescPitch; 7357 rate = this.prefDescRate; 7358 volume = this.prefDescVolume; 7359 } else { 7360 // get settings from global prefs 7361 voiceName = this.prefCaptionsVoice; 7362 pitch = this.prefCaptionsPitch; 7363 rate = ( rate < this.prefCaptionsRate ) ? this.prefCaptionsRate : rate; 7364 volume = this.prefCaptionsVolume; 7365 } 7366 7367 // get the voice associated with the user's chosen voice name 7368 if (this.descVoices) { 7369 if (this.descVoices.length > 0) { 7370 if (voiceName) { 7371 // get the voice that matches user's preferred voiceName 7372 for (i = 0; i < this.descVoices.length; i++) { 7373 if (this.descVoices[i].name == voiceName) { 7374 voice = this.descVoices[i]; 7375 break; 7376 } 7377 } 7378 } 7379 if (typeof voice === 'undefined') { 7380 // no matching voice was found 7381 // use the first voice in the array 7382 voice = this.descVoices[0]; 7383 } 7384 } 7385 } else { 7386 voice = null; 7387 } 7388 utterance = new SpeechSynthesisUtterance(); 7389 if (voice) { 7390 utterance.voice = voice; 7391 } 7392 utterance.voiceURI = 'native'; 7393 utterance.volume = volume; 7394 utterance.rate = rate; 7395 utterance.pitch = pitch; 7396 utterance.text = text; 7397 // TODO: Consider the best language for the utterance: 7398 // language of the web page? (this.lang) 7399 // language of the WebVTT description track? 7400 // language of the user's chosen voice? 7401 // If there's a mismatch between any of these, the description will likely be unintelligible 7402 utterance.lang = this.lang; 7403 utterance.onstart = function(e) { 7404 // utterance has started 7405 }; 7406 utterance.onpause = function(e) { 7407 // utterance has paused 7408 }; 7409 utterance.onend = function(e) { 7410 // utterance has ended 7411 if ( 'description' === context ) { 7412 this.speakingDescription = false; 7413 } else if ( 'caption' === context ) { 7414 this.speakingCaption = false; 7415 } 7416 timeElapsed = e.elapsedTime; 7417 // As of Firefox 95, e.elapsedTime is expressed in seconds 7418 // Other browsers (tested in Chrome & Edge) express this in milliseconds 7419 // Assume no utterance will require over 100 seconds to express... 7420 // If a large value, time is likely expressed in milliseconds. 7421 secondsElapsed = (timeElapsed > 100) ? (e.elapsedTime/1000).toFixed(2) : (e.elapsedTime).toFixed(2); 7422 7423 if (this.debug) { 7424 console.log('Finished speaking. That took ' + secondsElapsed + ' seconds.'); 7425 } 7426 if (context === 'description') { 7427 if (thisObj.prefDescPause) { 7428 if (thisObj.pausedForDescription) { 7429 thisObj.playMedia(); 7430 this.pausedForDescription = false; 7431 } 7432 } 7433 } 7434 }; 7435 utterance.onerror = function(e) { 7436 // handle error 7437 console.log('Web Speech API error',e); 7438 }; 7439 if (this.synth.paused) { 7440 this.synth.resume(); 7441 } 7442 this.synth.speak(utterance); 7443 if ( 'description' === context ) { 7444 this.speakingDescription = true; 7445 } else if ( 'caption' === context ) { 7446 this.speakingCaption = true; 7447 } 7448 }; 7449 7450 } 7451 7452 // Outdented for a simpler diff 7453 var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; 7454 7455 // Based on the incredible accessible modal dialog. 7456 function AccessibleDialog( modalDiv, $returnElement, title, closeButtonLabel) { 7457 7458 this.title = title; 7459 this.closeButtonLabel = closeButtonLabel; 7460 this.focusedElementBeforeModal = $returnElement; 7461 this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString(); 7462 var thisObj = this; 7463 var modal = modalDiv; 7464 this.modal = modal; 7465 7466 modal.addClass('able-modal-dialog'); 7467 7468 var closeButton = $('<button>',{ 7469 'class': 'modalCloseButton', 7470 'title': thisObj.closeButtonLabel, 7471 'aria-label': thisObj.closeButtonLabel 7472 }).text('Ã'); 7473 closeButton.on( 'keydown', function (e) { 7474 if (e.key === ' ') { 7475 thisObj.hide(); 7476 } 7477 }).on( 'click', function () { 7478 thisObj.hide(); 7479 }); 7480 7481 var titleH1 = $('<h1></h1>'); 7482 titleH1.attr('id', 'modalTitle-' + this.baseId); 7483 titleH1.text(title); 7484 this.titleH1 = titleH1; 7485 7486 modal.attr({ 7487 'aria-labelledby': 'modalTitle-' + this.baseId, 7488 }); 7489 var modalHeader = $( '<div>', { 7490 'class': 'able-modal-header' 7491 }); 7492 modalHeader.prepend(titleH1); 7493 modalHeader.prepend(closeButton); 7494 modal.prepend(modalHeader); 7495 7496 modal.attr({ 7497 'aria-hidden': 'true', 7498 'role': 'dialog', 7499 'aria-modal': 'true' 7500 }); 7501 7502 modal.on( 'keydown', function (e) { 7503 if (e.key === 'Escape') { 7504 thisObj.hide(); 7505 e.preventDefault(); 7506 } else if (e.key === 'Tab') { 7507 // Manually loop tab navigation inside the modal. 7508 var parts = modal.find('*'); 7509 var focusable = parts.filter(focusableElementsSelector).filter(':visible'); 7510 7511 if (focusable.length === 0) { 7512 return; 7513 } 7514 7515 var focused = $(':focus'); 7516 var currentIndex = focusable.index(focused); 7517 if (e.shiftKey) { 7518 // If backwards from first element, go to last. 7519 if (currentIndex === 0) { 7520 focusable.get(focusable.length - 1).trigger('focus'); 7521 e.preventDefault(); 7522 } 7523 } else { 7524 if (currentIndex === focusable.length - 1) { 7525 focusable.get(0).trigger('focus'); 7526 e.preventDefault(); 7527 } 7528 } 7529 } 7530 e.stopPropagation(); 7531 }); 7532 7533 if ( $( 'body' ).hasClass( 'able-modal-active' ) ) { 7534 $( 'body > *') .not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert'); 7535 $( 'body' ).removeClass( 'able-modal-active' ); 7536 } 7537 } 7538 AccessibleDialog.prototype.show = function () { 7539 if (!this.overlay) { 7540 // Generate overlay. 7541 var overlay = $('<div></div>').attr({ 7542 'class': 'able-modal-overlay', 7543 'tabindex': '-1' 7544 }); 7545 this.overlay = overlay; 7546 $('body').append(overlay); 7547 7548 // Keep from moving focus out of dialog when clicking outside of it. 7549 overlay.on('mousedown.accessibleModal', function (e) { 7550 e.preventDefault(); 7551 thisObj.hide(); 7552 }); 7553 } 7554 7555 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('inert', true); 7556 $( 'body' ).addClass( 'able-modal-active' ); 7557 7558 this.overlay.css('display', 'block'); 7559 this.modal.css('display', 'block'); 7560 this.modal.attr({ 7561 'aria-hidden': 'false', 7562 'tabindex': '-1' 7563 }); 7564 7565 var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible'); 7566 if (focusable.length === 0) { 7567 this.focusedElementBeforeModal.blur(); 7568 } 7569 var thisObj = this; 7570 setTimeout(function () { 7571 // set focus on the first focusable element 7572 thisObj.modal.find('button.modalCloseButton').first().trigger('focus'); 7573 }, 300); 7574 }; 7575 7576 AccessibleDialog.prototype.hide = function () { 7577 if (this.overlay) { 7578 this.overlay.css('display', 'none'); 7579 } 7580 this.modal.css('display', 'none'); 7581 this.modal.attr('aria-hidden', 'true'); 7582 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert'); 7583 $( 'body' ).removeClass( 'able-modal-active' ); 7584 7585 this.focusedElementBeforeModal.trigger('focus'); 7586 }; 7587 7588 AccessibleDialog.prototype.getInputs = function () { 7589 7590 // return an array of input elements within this dialog 7591 if (this.modal) { 7592 var inputs = this.modal.find('input'); 7593 return inputs; 7594 } 7595 return false; 7596 }; 7597 7598 function addDragdropFunctions(AblePlayer) { 7599 AblePlayer.prototype.initDragDrop = function ( which ) { 7600 7601 // supported values of which: 'sign', 'transcript' 7602 7603 // NOTE: "Drag and Drop" for Able Player is a metaphor only!!! 7604 // HTML5 Drag & Drop API enables moving elements to new locations in the DOM 7605 // Thats not our purpose; we're simply changing the visible position on-screen 7606 // Therefore, the drag & drop interface was overhauled in v2.3.41 to simply 7607 // use mouse (and keyboard) events to change CSS positioning properties 7608 7609 // There are nevertheless lessons to be learned from Drag & Drop about accessibility: 7610 // http://dev.opera.com/articles/accessible-drag-and-drop/ 7611 7612 var thisObj, $window, $toolbar, windowName, $dragHandle, $resizeHandle, $resizeSvg, 7613 i, x1, y1, x2, y2, $resizeLine, resizeZIndex; 7614 7615 thisObj = this; 7616 7617 if (which === 'transcript') { 7618 $window = this.$transcriptArea; 7619 windowName = 'transcript-window'; 7620 $toolbar = this.$transcriptToolbar; 7621 $toolbar.attr( 'aria-label', this.translate( 'transcriptControls', 'Transcript Window Controls' ) ); 7622 } else if (which === 'sign') { 7623 $window = this.$signWindow; 7624 windowName = 'sign-window'; 7625 $toolbar = this.$signToolbar; 7626 $toolbar.attr( 'aria-label', this.translate( 'signControls', 'Sign Language Window Controls' ) ); 7627 } 7628 7629 // add class to trigger change in cursor on hover 7630 $toolbar.addClass('able-draggable'); 7631 $toolbar.attr( 'role', 'application' ); 7632 7633 $dragHandle = $('<div>',{ 7634 'class': 'able-drag-handle' 7635 }); 7636 7637 $dragHandle.html('<svg version="1.1" viewBox="262.48 487.5 675.03 225" xmlns="http://www.w3.org/2000/svg"><path d="m900 562.5h-600c-13.398 0-25.777-7.1484-32.477-18.75-6.6992-11.602-6.6992-25.898 0-37.5 6.6992-11.602 19.078-18.75 32.477-18.75h600c13.398 0 25.777 7.1484 32.477 18.75 6.6992 11.602 6.6992 25.898 0 37.5-6.6992 11.602-19.078 18.75-32.477 18.75z" fill="#fff"></path> <path d="m900 712.5h-600c-13.398 0-25.777-7.1484-32.477-18.75-6.6992-11.602-6.6992-25.898 0-37.5 6.6992-11.602 19.078-18.75 32.477-18.75h600c13.398 0 25.777 7.1484 32.477 18.75 6.6992 11.602 6.6992 25.898 0 37.5-6.6992 11.602-19.078 18.75-32.477 18.75z" fill="#fff"></path></svg>'); 7638 // add resize handle selector to bottom right corner 7639 $resizeHandle = $('<div>',{ 7640 'class': 'able-resizable' 7641 }); 7642 7643 // fill it with three parallel diagonal lines 7644 $resizeSvg = $('<svg>').attr({ 7645 'width': '100%', 7646 'height': '100%', 7647 'viewBox': '0 0 100 100', 7648 'preserveAspectRatio': 'none' 7649 }); 7650 for (i=1; i<=3; i++) { 7651 if (i === 1) { 7652 x1 = '100'; 7653 y1 = '0'; 7654 x2 = '0'; 7655 y2 = '100'; 7656 } else if (i === 2) { 7657 x1 = '33'; 7658 y1 = '100'; 7659 x2 = '100'; 7660 y2 = '33'; 7661 } else if (i === 3) { 7662 x1 = '67'; 7663 y1 = '100'; 7664 x2 = '100'; 7665 y2 = '67'; 7666 } 7667 $resizeLine = $('<line>').attr({ 7668 'x1': x1, 7669 'y1': y1, 7670 'x2': x2, 7671 'y2': y2, 7672 'vector-effect': 'non-scaling-stroke' 7673 }); 7674 $resizeSvg.append($resizeLine); 7675 } 7676 $resizeHandle.html($resizeSvg); 7677 7678 // assign z-index that's slightly higher than parent window 7679 resizeZIndex = parseInt($window.css('z-index')) + 100; 7680 $resizeHandle.css('z-index',resizeZIndex); 7681 $window.append($resizeHandle); 7682 $toolbar.append($dragHandle); 7683 7684 // Final step: Need to refresh the DOM in order for browser to process & display the SVG 7685 $resizeHandle.html($resizeHandle.html()); 7686 7687 // add event listener to toolbar to start and end drag 7688 // other event listeners will be added when drag starts 7689 $dragHandle.on('mousedown mouseup touchstart touchend', function(e) { 7690 e.stopPropagation(); 7691 if (e.type === 'mousedown' || e.type === 'touchstart' ) { 7692 if (!thisObj.windowMenuClickRegistered) { 7693 thisObj.windowMenuClickRegistered = true; 7694 thisObj.startMouseX = e.pageX; 7695 thisObj.startMouseY = e.pageY; 7696 thisObj.dragDevice = 'mouse'; // ok to use this even if device is a touchpad 7697 thisObj.startDrag(which, $window); 7698 } 7699 } else if (e.type === 'mouseup' || e.type === 'touchend') { 7700 if (thisObj.dragging && thisObj.dragDevice === 'mouse') { 7701 thisObj.endDrag(which); 7702 } 7703 } 7704 return false; 7705 }); 7706 7707 // add event listeners for resizing 7708 $resizeHandle.on('mousedown mouseup touchstart touchend', function(e) { 7709 e.stopPropagation(); 7710 if (e.type === 'mousedown' || e.type === 'touchstart') { 7711 if (!thisObj.windowMenuClickRegistered) { 7712 thisObj.windowMenuClickRegistered = true; 7713 thisObj.startMouseX = e.pageX; 7714 thisObj.startMouseY = e.pageY; 7715 thisObj.startResize(which, $window); 7716 } 7717 } else if (e.type === 'mouseup' || e.type === 'touchend') { 7718 if (thisObj.resizing) { 7719 thisObj.endResize(which); 7720 } 7721 } 7722 return false; 7723 }); 7724 7725 // whenever a window is clicked, bring it to the foreground 7726 $window.on('click', function() { 7727 7728 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 7729 thisObj.updateZIndex(which); 7730 } 7731 thisObj.finishingDrag = false; 7732 }); 7733 this.addWindowMenu(which,$window,windowName); 7734 }; 7735 7736 AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) { 7737 7738 var thisObj, menuId, $newButton, tooltipId, $tooltip, $popup; 7739 7740 thisObj = this; 7741 7742 // Add a Boolean that will be set to true temporarily if window button or a menu item is clicked 7743 // This will prevent the click event from also triggering a mousedown event on the toolbar 7744 // (which would unexpectedly send the window into drag mode) 7745 this.windowMenuClickRegistered = false; 7746 7747 // Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag 7748 // this will prevent the click event from being triggered 7749 this.finishingDrag = false; 7750 7751 // add button to draggable window which triggers a popup menu 7752 menuId = this.mediaId + '-' + windowName + '-menu'; 7753 $newButton = $('<button>',{ 7754 'type': 'button', 7755 'tabindex': '0', 7756 'aria-haspopup': 'true', 7757 'aria-controls': menuId, 7758 'aria-expanded': 'false', 7759 'class': 'able-button-handler-preferences' 7760 }); 7761 this.getIcon( $newButton, 'preferences' ); 7762 this.setText( $newButton, this.translate( 'windowButtonLabel', 'Window options' ) ); 7763 7764 // add a tooltip that displays aria-label on mouseenter or focus 7765 tooltipId = this.mediaId + '-' + windowName + '-tooltip'; 7766 $tooltip = $('<div>',{ 7767 'class' : 'able-tooltip', 7768 'id' : tooltipId 7769 }).hide(); 7770 7771 $newButton.on('mouseenter focus',function(e) { 7772 var label = $(this).attr('aria-label'); 7773 var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label); 7774 // get height of the tooltip 7775 var tooltipHeight = tooltip.height(); 7776 var tooltipY = ( tooltipHeight + 2 ) * -1; 7777 var tooltipX = 0; 7778 var tooltipStyle = { 7779 right: '', 7780 left: tooltipX + 'px', 7781 top: tooltipY + 'px' 7782 }; 7783 tooltip.css(tooltipStyle); 7784 thisObj.showTooltip(tooltip); 7785 $(this).on('mouseleave blur',function() { 7786 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 7787 }); 7788 }); 7789 7790 // setup popup menu 7791 $popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window' 7792 // define vars and assemble all the parts 7793 if (which === 'transcript') { 7794 this.$transcriptPopupButton = $newButton; 7795 this.$transcriptPopup = $popup; 7796 this.$transcriptToolbar.prepend($newButton,$tooltip,$popup); 7797 } else if (which === 'sign') { 7798 this.$signPopupButton = $newButton; 7799 this.$signPopup = $popup; 7800 this.$signToolbar.append($newButton,$tooltip,$popup); 7801 } 7802 7803 // handle button click 7804 $newButton.on('click keydown',function(e) { 7805 7806 if (thisObj.focusNotClick) { 7807 return false; 7808 } 7809 if (thisObj.dragging) { 7810 thisObj.dragKeys(which, e); 7811 return false; 7812 } 7813 e.stopPropagation(); 7814 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 7815 console.log( 'firing' ); 7816 // don't set windowMenuClickRegistered yet; that happens in handler function 7817 thisObj.handleWindowButtonClick(which, e); 7818 } 7819 thisObj.finishingDrag = false; 7820 }); 7821 7822 this.addResizeDialog(which, $window); 7823 }; 7824 7825 AblePlayer.prototype.addResizeDialog = function (which) { 7826 7827 var thisObj, $windowPopup, $windowButton, widthId, heightId, 7828 $resizeForm, $resizeWrapper, $resizeControls, $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel, 7829 $resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel, $saveButton, $cancelButton, 7830 newWidth, newHeight, resizeDialog; 7831 7832 thisObj = this; 7833 7834 if (which === 'transcript') { 7835 $windowPopup = this.$transcriptPopup; 7836 $windowButton = this.$transcriptPopupButton; 7837 } else if (which === 'sign') { 7838 $windowPopup = this.$signPopup; 7839 $windowButton = this.$signPopupButton; 7840 } 7841 7842 widthId = this.mediaId + '-resize-' + which + '-width'; 7843 heightId = this.mediaId + '-resize-' + which + '-height'; 7844 7845 $resizeForm = $('<div></div>',{ 7846 'class' : 'able-resize-form' 7847 }); 7848 7849 // inner container for all content, will be assigned to modal div's aria-describedby 7850 $resizeWrapper = $('<div></div>'); 7851 $resizeControls = $( '<div class="able-prefs-buttons"></div>' ); 7852 7853 // width field 7854 $resizeWidthDiv = $('<div></div>'); 7855 $resizeWidthInput = $('<input>',{ 7856 'type': 'number', 7857 'id': widthId, 7858 'min': 0, 7859 'value': '', 7860 }); 7861 $resizeWidthLabel = $('<label>',{ 7862 'for': widthId 7863 }).text( this.translate( 'width', 'Width' ) ); 7864 7865 // height field 7866 $resizeHeightDiv = $('<div></div>'); 7867 $resizeHeightInput = $('<input>',{ 7868 'type': 'number', 7869 'id': heightId, 7870 'min': 0, 7871 'value': '', 7872 }); 7873 $resizeHeightLabel = $('<label>',{ 7874 'for': heightId 7875 }).text( this.translate( 'height', 'Height' ) ); 7876 7877 // Add save and cancel buttons. 7878 $saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>'); 7879 $cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>'); 7880 $saveButton.on('click',function () { 7881 newWidth = $('#' + widthId).val(); 7882 newHeight = $('#' + heightId).val(); 7883 thisObj.resizeObject(which,newWidth,newHeight); 7884 thisObj.updatePreferences(which); 7885 7886 resizeDialog.hide(); 7887 $windowPopup.hide(); 7888 $windowButton.trigger('focus'); 7889 }); 7890 $cancelButton.on('click',function () { 7891 resizeDialog.hide(); 7892 $windowPopup.hide(); 7893 $windowButton.trigger('focus'); 7894 }); 7895 7896 // Now assemble all the parts 7897 $resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput); 7898 $resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput); 7899 $resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv); 7900 $resizeControls.append($saveButton,$cancelButton); 7901 $resizeForm.append($resizeWrapper,$resizeControls); 7902 7903 // must be appended to the BODY! 7904 // otherwise when aria-hidden="true" is applied to all background content 7905 // that will include an ancestor of the dialog, 7906 // which will render the dialog unreadable by screen readers 7907 $('body').append($resizeForm); 7908 resizeDialog = new AccessibleDialog( 7909 $resizeForm, 7910 $windowButton, 7911 this.translate( 'windowResizeHeading', 'Resize Window' ), 7912 this.translate( 'closeButtonLabel', 'Close' ), 7913 ); 7914 if (which === 'transcript') { 7915 this.transcriptResizeDialog = resizeDialog; 7916 } else if (which === 'sign') { 7917 this.signResizeDialog = resizeDialog; 7918 } 7919 }; 7920 7921 AblePlayer.prototype.handleWindowButtonClick = function (which, e) { 7922 7923 var thisObj, $windowPopup, $windowButton, $toolbar, popupTop; 7924 7925 thisObj = this; 7926 if (this.focusNotClick) { 7927 // transcript or sign window has just opened, 7928 // and focus moved to the window button 7929 // ignore the keystroke that triggered the popup 7930 return false; 7931 } 7932 7933 if (which === 'transcript') { 7934 $windowPopup = this.$transcriptPopup; 7935 $windowButton = this.$transcriptPopupButton; 7936 $toolbar = this.$transcriptToolbar; 7937 } else if (which === 'sign') { 7938 $windowPopup = this.$signPopup; 7939 $windowButton = this.$signPopupButton; 7940 $toolbar = this.$signToolbar; 7941 } 7942 if (e.type === 'keydown') { 7943 // user pressed a key 7944 if (e.key === ' ' || e.key === 'Enter') { 7945 this.windowMenuClickRegistered = true; 7946 } else if (e.key === 'Escape') { 7947 if ($windowPopup.is(':visible')) { 7948 // close the popup menu 7949 $windowPopup.hide(); 7950 // also reset the Boolean 7951 thisObj.windowMenuClickRegistered = false; 7952 // also restore menu items to their original state 7953 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 7954 // also return focus to window options button 7955 $windowButton.trigger('focus'); 7956 } else { 7957 // popup isn't open. Close the window 7958 if (which === 'sign') { 7959 this.handleSignToggle(); 7960 } else if (which === 'transcript') { 7961 this.handleTranscriptToggle(); 7962 } 7963 } 7964 } else { 7965 return false; 7966 } 7967 } else { 7968 // this was a mouse event 7969 this.windowMenuClickRegistered = true; 7970 } 7971 7972 if ( $windowPopup.is(':visible') ) { 7973 $windowPopup.hide(); 7974 thisObj.windowMenuClickRegistered = false; // reset 7975 $windowPopup.find('li').removeClass('able-focus'); 7976 $windowButton.attr('aria-expanded','false').trigger('focus'); 7977 } else { 7978 // first, be sure window is on top 7979 this.updateZIndex(which); 7980 popupTop = $toolbar.outerHeight() - 1; 7981 $windowPopup.css('top', popupTop); 7982 $windowPopup.show(); 7983 $windowButton.attr('aria-expanded','true'); 7984 $(this).find('li').first().trigger('focus').addClass('able-focus'); 7985 thisObj.windowMenuClickRegistered = false; // reset 7986 } 7987 }; 7988 7989 AblePlayer.prototype.handleMenuChoice = function (which, choice, e) { 7990 7991 var thisObj, $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight, 7992 aspectRatio, tempWidth, tempHeight; 7993 7994 thisObj = this; 7995 if (which === 'transcript') { 7996 $window = this.$transcriptArea; 7997 $windowPopup = this.$transcriptPopup; 7998 $windowButton = this.$transcriptPopupButton; 7999 resizeDialog = this.transcriptResizeDialog; 8000 } else if (which === 'sign') { 8001 $window = this.$signWindow; 8002 $windowPopup = this.$signPopup; 8003 $windowButton = this.$signPopupButton; 8004 resizeDialog = this.signResizeDialog; 8005 8006 startingWidth = $window.outerWidth(); 8007 startingHeight = $window.outerHeight(); 8008 aspectRatio = startingWidth / startingHeight; 8009 // make height a read-only field 8010 // and calculate its value based on width to preserve aspect ratio 8011 let widthId = this.mediaId + '-resize-' + which + '-width'; 8012 let heightId = this.mediaId + '-resize-' + which + '-height'; 8013 $( '#' + heightId ).prop('readonly',true); 8014 $( '#' + widthId ).on('input',function() { 8015 tempWidth = $(this).val(); 8016 tempHeight = Math.round(tempWidth/aspectRatio); 8017 $( '#' + heightId ).val(tempHeight); 8018 }); 8019 } 8020 this.$activeWindow = $window; 8021 8022 if (e.type === 'keydown') { 8023 if (e.key === 'Escape') { // escape 8024 // hide the popup menu 8025 $windowPopup.hide(); 8026 // also reset the Boolean 8027 thisObj.windowMenuClickRegistered = false; 8028 // also restore menu items to their original state 8029 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 8030 $windowButton.attr('aria-expanded','false'); 8031 // also return focus to window options button 8032 $windowButton.trigger('focus'); 8033 8034 return false; 8035 } else { 8036 // all other keys will be handled by upstream functions 8037 if (choice !== 'close') { 8038 this.$activeWindow = $window; 8039 } 8040 return false; 8041 } 8042 } 8043 8044 // hide the popup menu 8045 $windowPopup.hide(); 8046 // also reset the boolean 8047 thisObj.windowMenuClickRegistered = false; 8048 // also restore menu items to their original state 8049 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 8050 $windowButton.attr('aria-expanded','false'); 8051 8052 if (choice !== 'close') { 8053 $windowButton.trigger('focus'); 8054 } 8055 if (choice === 'move') { 8056 // temporarily add role="application" to activeWindow 8057 // otherwise, screen readers incercept arrow keys and moving window will not work 8058 this.$activeWindow.attr('role','application'); 8059 8060 if (!this.showedAlert(which)) { 8061 this.showAlert( this.translate( 'windowMoveAlert', 'Drag or use arrow keys to move the window; Enter to stop' ),which); 8062 if (which === 'transcript') { 8063 this.showedTranscriptAlert = true; 8064 } else if (which === 'sign') { 8065 this.showedSignAlert = true; 8066 } 8067 } 8068 this.dragDevice = (e.type === 'keydown') ? 'keyboard' : 'mouse'; 8069 this.startDrag(which, $window); 8070 $windowPopup.hide().parent().attr( 'tabindex', '-1' ).trigger('focus'); 8071 } else if (choice == 'resize') { 8072 // resize through the menu uses a form, not drag 8073 var resizeFields = resizeDialog.getInputs(); 8074 if (resizeFields) { 8075 // reset width and height values in form 8076 resizeFields[0].value = Math.round( $window.outerWidth() ); 8077 resizeFields[1].value = Math.round( $window.outerHeight() ); 8078 } 8079 resizeDialog.show(); 8080 } else if (choice == 'close') { 8081 // close window, place focus on corresponding button on controller bar 8082 if (which === 'transcript') { 8083 this.closingTranscript = true; // stopgap to prevent double-firing of keypress 8084 this.handleTranscriptToggle(); 8085 } else if (which === 'sign') { 8086 this.closingSign = true; // stopgap to prevent double-firing of keypress 8087 this.handleSignToggle(); 8088 } 8089 } 8090 }; 8091 8092 AblePlayer.prototype.startDrag = function(which, $element) { 8093 8094 var thisObj, $windowPopup, startPos, newX, newY; 8095 8096 thisObj = this; 8097 8098 if (!this.$activeWindow) { 8099 this.$activeWindow = $element; 8100 } 8101 this.dragging = true; 8102 8103 if (which === 'transcript') { 8104 $windowPopup = this.$transcriptPopup; 8105 } else if (which === 'sign') { 8106 $windowPopup = this.$signPopup; 8107 } 8108 8109 // if window's popup menu is open, close it 8110 if ($windowPopup.is(':visible')) { 8111 $windowPopup.hide(); 8112 } 8113 8114 // be sure this window is on top 8115 this.updateZIndex(which); 8116 8117 // get starting position of element 8118 startPos = this.$activeWindow.position(); 8119 this.dragStartX = startPos.left; 8120 this.dragStartY = startPos.top; 8121 8122 if (typeof this.startMouseX === 'undefined') { 8123 this.dragDevice = 'keyboard'; 8124 this.dragKeyX = this.dragStartX; 8125 this.dragKeyY = this.dragStartY; 8126 // add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd() 8127 this.startingDrag = true; 8128 } else { 8129 this.dragDevice = 'mouse'; 8130 // get offset between mouse position and top left corner of draggable element 8131 this.dragOffsetX = this.startMouseX - this.dragStartX; 8132 this.dragOffsetY = this.startMouseY - this.dragStartY; 8133 } 8134 8135 // prepare element for dragging 8136 this.$activeWindow.addClass('able-drag').css({ 8137 'position': 'absolute', 8138 'top': this.dragStartY + 'px', 8139 'left': this.dragStartX + 'px' 8140 }).trigger('focus'); 8141 8142 // add device-specific event listeners 8143 if (this.dragDevice === 'mouse') { // might also be a touchpad 8144 $(document).on('mousemove touchmove',function(e) { 8145 if (thisObj.dragging) { 8146 // calculate new top left based on current mouse position - offset 8147 newX = e.pageX - thisObj.dragOffsetX; 8148 newY = e.pageY - thisObj.dragOffsetY; 8149 thisObj.resetDraggedObject( newX, newY ); 8150 } 8151 }); 8152 } else if (this.dragDevice === 'keyboard') { 8153 this.$activeWindow.on('keydown',function(e) { 8154 if (thisObj.dragging) { 8155 thisObj.dragKeys(which, e); 8156 } 8157 }); 8158 } 8159 return false; 8160 }; 8161 8162 /** 8163 * Handle moving the transcript or sign window from the keyboard. 8164 * 8165 * @param {string} which 'transcript' or 'sign' window. 8166 * @param {Event} e Triggered event. 8167 */ 8168 AblePlayer.prototype.dragKeys = function(which, e) { 8169 8170 var key, keySpeed; 8171 8172 // stopgap to prevent firing on initial Enter or space 8173 // that selected "Move" from menu 8174 if (this.startingDrag) { 8175 this.startingDrag = false; 8176 return false; 8177 } 8178 key = e.key; 8179 keySpeed = 10; // pixels per keypress event 8180 8181 switch (key) { 8182 case 'ArrowLeft': // left 8183 this.dragKeyX -= keySpeed; 8184 this.$srAlertBox.text( this.translate( 'windowMoveLeft', 'Window moved left' ) ); 8185 break; 8186 case 'ArrowUp': // up 8187 this.dragKeyY -= keySpeed; 8188 this.$srAlertBox.text( this.translate( 'windowMoveUp', 'Window moved up' ) ); 8189 break; 8190 case 'ArrowRight': // right 8191 this.dragKeyX += keySpeed; 8192 this.$srAlertBox.text( this.translate( 'windowMoveRight', 'Window moved right' ) ); 8193 break; 8194 case 'ArrowDown': // down 8195 this.dragKeyY += keySpeed; 8196 this.$srAlertBox.text( this.translate( 'windowMoveDown', 'Window moved down' ) ); 8197 break; 8198 case 'Enter': // enter 8199 case 'Escape': // escape 8200 this.$srAlertBox.text( this.translate( 'windowMoveStopped', 'Window move stopped' ) ); 8201 this.endDrag(which); 8202 return false; 8203 default: 8204 return false; 8205 } 8206 this.resetDraggedObject(this.dragKeyX,this.dragKeyY); 8207 if (e.preventDefault) { 8208 e.preventDefault(); 8209 } 8210 return false; 8211 }; 8212 8213 AblePlayer.prototype.resetDraggedObject = function ( x, y) { 8214 setTimeout( () => { 8215 this.$srAlertBox.text( '' ); 8216 }, 2000 ); 8217 8218 this.$activeWindow.css({ 8219 'left': x + 'px', 8220 'top': y + 'px' 8221 }); 8222 }, 8223 8224 AblePlayer.prototype.resizeObject = function ( which, width, height ) { 8225 8226 var innerHeight; 8227 8228 // which is either 'transcript' or 'sign' 8229 this.$activeWindow.css({ 8230 'width': width + 'px', 8231 'height': height + 'px' 8232 }); 8233 8234 if (which === 'transcript') { 8235 // $activeWindow is the outer $transcriptArea 8236 // but the inner able-transcript also needs to be resized proportionally 8237 // (it's 50px less than its outer container) 8238 innerHeight = height - 50; 8239 this.$transcriptDiv.css('height', innerHeight + 'px'); 8240 } 8241 }; 8242 8243 AblePlayer.prototype.endDrag = function(which) { 8244 8245 var thisObj, $windowButton; 8246 thisObj = this; 8247 8248 if (which === 'transcript') { 8249 $windowButton = this.$transcriptPopupButton; 8250 } else if (which === 'sign') { 8251 $windowButton = this.$signPopupButton; 8252 } 8253 8254 $(document).off('mousemove mouseup touchmove touchup'); 8255 this.$activeWindow.off('keydown').removeClass('able-drag'); 8256 // restore activeWindow role from 'application' to 'dialog' 8257 this.$activeWindow.attr('role','dialog'); 8258 this.$activeWindow = null; 8259 8260 if (this.dragDevice === 'keyboard') { 8261 $windowButton.trigger('focus'); 8262 } 8263 this.dragging = false; 8264 8265 // save final position of dragged element 8266 this.updatePreferences(which); 8267 8268 // reset starting mouse positions 8269 this.startMouseX = undefined; 8270 this.startMouseY = undefined; 8271 8272 // Boolean to stop stray events from firing 8273 this.windowMenuClickRegistered = false; 8274 this.finishingDrag = true; // will be reset after window click event 8275 // finishingDrag should be reset after window click event, 8276 // which is triggered automatically after mouseup 8277 // However, in case that's not reliable in some browsers 8278 // need to ensure this gets cancelled 8279 setTimeout(function() { 8280 thisObj.finishingDrag = false; 8281 }, 100); 8282 }; 8283 8284 AblePlayer.prototype.startResize = function(which, $element) { 8285 8286 var thisObj, $windowPopup, newWidth, newHeight; 8287 8288 thisObj = this; 8289 this.$activeWindow = $element; 8290 this.resizing = true; 8291 8292 $windowPopup = (which === 'transcript') ? this.$transcriptPopup : this.$signPopup; 8293 8294 // if window's popup menu is open, close it & place focus on button (???) 8295 if ($windowPopup.is(':visible')) { 8296 $windowPopup.hide().parent().trigger('focus'); 8297 } 8298 8299 // get starting width and height 8300 this.dragKeyX = this.dragStartX; 8301 this.dragKeyY = this.dragStartY; 8302 this.dragStartWidth = this.$activeWindow.width(); 8303 this.dragStartHeight = this.$activeWindow.outerHeight(); 8304 8305 // add event listeners 8306 $(document).on('mousemove touchmove',function(e) { 8307 if (thisObj.resizing) { 8308 // calculate new width and height based on changes to mouse position 8309 newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX); 8310 newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY); 8311 thisObj.resizeObject( which, newWidth, newHeight ); 8312 } 8313 }); 8314 8315 return false; 8316 }; 8317 8318 AblePlayer.prototype.endResize = function(which) { 8319 8320 var $windowButton; 8321 8322 if (which === 'transcript') { 8323 $windowButton = this.$transcriptPopupButton; 8324 } else if (which === 'sign') { 8325 $windowButton = this.$signPopupButton; 8326 } 8327 8328 $(document).off('mousemove mouseup touchmove touchup'); 8329 this.$activeWindow.off('keydown'); 8330 $windowButton.show().trigger('focus'); 8331 this.resizing = false; 8332 this.$activeWindow.removeClass('able-resize'); 8333 8334 // save final width and height of dragged element 8335 this.updatePreferences(which); 8336 8337 // Booleans for preventing stray events 8338 this.windowMenuClickRegistered = false; 8339 this.finishingDrag = true; 8340 8341 // finishingDrag should e reset after window click event, 8342 // which is triggered automatically after mouseup 8343 // However, in case that's not reliable in some browsers 8344 // need to ensure this gets cancelled 8345 setTimeout(function() { 8346 this.finishingDrag = false; 8347 }, 100); 8348 }; 8349 } 8350 8351 function addEventFunctions(AblePlayer) { 8352 // Media events 8353 AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) { 8354 8355 // duration and elapsed are passed from callback functions of Vimeo API events 8356 // duration is expressed as sss.xxx 8357 // elapsed is expressed as sss.xxx 8358 var thisObj = this; 8359 this.getMediaTimes(duration,elapsed).then(function(mediaTimes) { 8360 thisObj.duration = mediaTimes['duration']; 8361 thisObj.elapsed = mediaTimes['elapsed']; 8362 if (thisObj.duration > 0) { 8363 // do all the usual time-sync stuff during playback 8364 if (thisObj.prefHighlight === 1) { 8365 thisObj.highlightTranscript(thisObj.elapsed); 8366 } 8367 thisObj.updateCaption(thisObj.elapsed); 8368 thisObj.showDescription(thisObj.elapsed); 8369 thisObj.updateChapter(thisObj.elapsed); 8370 thisObj.updateMeta(thisObj.elapsed); 8371 thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed); 8372 } 8373 }); 8374 }; 8375 8376 AblePlayer.prototype.onMediaPause = function () { 8377 8378 if (this.controlsHidden) { 8379 this.fadeControls('in'); 8380 this.controlsHidden = false; 8381 } 8382 if (this.hideControlsTimeoutStatus === 'active') { 8383 window.clearTimeout(this.hideControlsTimeout); 8384 this.hideControlsTimeoutStatus = 'clear'; 8385 8386 } 8387 this.refreshControls('playpause'); 8388 }; 8389 8390 AblePlayer.prototype.onMediaComplete = function () { 8391 // if there's a playlist, advance to next item and start playing 8392 if (this.hasPlaylist && !this.cueingPlaylistItem) { 8393 if (this.playlistIndex === (this.$playlist.length - 1)) { 8394 // this is the last track in the playlist 8395 if (this.loop) { 8396 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 8397 this.cuePlaylistItem(0); 8398 } else { 8399 this.playing = false; 8400 this.paused = true; 8401 } 8402 } else { 8403 // this is not the last track. Play the next one. 8404 this.playlistIndex++; 8405 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 8406 this.cuePlaylistItem(this.playlistIndex); 8407 } 8408 } 8409 this.refreshControls(); 8410 }; 8411 8412 AblePlayer.prototype.onMediaNewSourceLoad = function () { 8413 8414 var loadIsComplete = false; 8415 8416 if (this.cueingPlaylistItem) { 8417 // this variable was set to address bugs caused by multiple firings of media 'end' event 8418 // safe to reset now 8419 this.cueingPlaylistItem = false; 8420 } 8421 if (this.recreatingPlayer) { 8422 // same as above; different bugs 8423 this.recreatingPlayer = false; 8424 } 8425 if (this.playbackRate) { 8426 // user has set playbackRate on a previous src or track 8427 // use that setting on the new src or track too 8428 this.setPlaybackRate(this.playbackRate); 8429 } 8430 if (this.userClickedPlaylist) { 8431 if (!this.startedPlaying || this.okToPlay) { 8432 // start playing; no further user action is required 8433 this.playMedia(); 8434 loadIsComplete = true; 8435 } 8436 } else if (this.seekTrigger == 'restart' || 8437 this.seekTrigger == 'chapter' || 8438 this.seekTrigger == 'transcript' || 8439 this.seekTrigger == 'search' 8440 ) { 8441 // by clicking on any of these elements, user is likely intending to play 8442 // Not included: elements where user might click multiple times in succession 8443 // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play 8444 this.playMedia(); 8445 loadIsComplete = true; 8446 } else if (this.swappingSrc) { 8447 // new source file has just been loaded 8448 if (this.hasPlaylist) { 8449 // a new source file from the playlist has just been loaded 8450 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 8451 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 8452 this.playMedia(); 8453 loadIsComplete = true; 8454 } 8455 } else if (this.swapTime > 0) { 8456 if (this.seekStatus === 'complete') { 8457 if (this.okToPlay) { 8458 // should be able to resume playback 8459 this.playMedia(); 8460 } 8461 loadIsComplete = true; 8462 } else if (this.seekStatus === 'seeking') ; else { 8463 if (this.swapTime === this.elapsed) { 8464 // seek is finished! 8465 this.seekStatus = 'complete'; 8466 if (this.okToPlay) { 8467 // should be able to resume playback 8468 this.playMedia(); 8469 } 8470 loadIsComplete = true; 8471 } else { 8472 // seeking hasn't started yet 8473 // first, determine whether it's possible 8474 if (this.hasDescTracks) { 8475 // do nothing. Unable to seek ahead if there are descTracks 8476 loadIsComplete = true; 8477 } else if (this.durationsAreCloseEnough(this.duration,this.prevDuration)) { 8478 // durations of two sources are close enough to making seek ahead in new source ok 8479 this.seekStatus = 'seeking'; 8480 this.seekTo(this.swapTime); 8481 } else { 8482 // durations of two sources are too dissimilar to support seeking ahead to swapTime. 8483 loadIsComplete = true; 8484 } 8485 } 8486 } 8487 } else { 8488 // swapTime is 0. No seeking required. 8489 if (this.playing) { 8490 this.playMedia(); 8491 // swap is complete. Reset vars. 8492 loadIsComplete = true; 8493 } 8494 } 8495 } else if (!this.startedPlaying) { 8496 if (this.startTime > 0) { 8497 if (this.seeking) { 8498 // a seek has already been initiated 8499 // since canplaythrough has been triggered, the seek is complete 8500 this.seeking = false; 8501 if (this.okToPlay) { 8502 this.playMedia(); 8503 } 8504 loadIsComplete = true; 8505 } else { 8506 // haven't started seeking yet 8507 this.seekTo(this.startTime); 8508 } 8509 } else if (this.defaultChapter && typeof this.selectedChapters !== 'undefined') { 8510 this.seekToChapter(this.defaultChapter); 8511 } else { 8512 // there is no startTime, therefore no seeking required 8513 if (this.okToPlay) { 8514 this.playMedia(); 8515 } 8516 loadIsComplete = true; 8517 } 8518 } else if (this.hasPlaylist) { 8519 // new source media is part of a playlist, but user didn't click on it 8520 // (and somehow, swappingSrc is false) 8521 // this may happen when the previous track ends and next track loads 8522 // this same code is called above when swappingSrc is true 8523 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 8524 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 8525 this.playMedia(); 8526 loadIsComplete = true; 8527 } 8528 } else { 8529 // None of the above. 8530 // User is likely seeking to a new time, but not loading a new media source 8531 // need to reset vars 8532 loadIsComplete = true; 8533 } 8534 if (loadIsComplete) { 8535 // reset vars 8536 this.swappingSrc = false; 8537 this.seekStatus = null; 8538 this.swapTime = 0; 8539 this.seekTrigger = null; 8540 this.seekingFromTranscript = false; 8541 this.userClickedPlaylist = false; 8542 this.okToPlay = false; 8543 } 8544 this.refreshControls(); 8545 if (this.$focusedElement) { 8546 this.restoreFocus(); 8547 this.$focusedElement = null; 8548 this.activeMedia = null; 8549 } 8550 }; 8551 8552 AblePlayer.prototype.durationsAreCloseEnough = function(d1,d2) { 8553 8554 // Compare the durations of two media sources to determine whether it's ok to seek ahead after swapping src 8555 // The durations may not be exact, but they might be "close enough" 8556 // returns true if "close enough", otherwise false 8557 8558 var tolerance, diff; 8559 8560 tolerance = 1; // number of seconds between rounded durations that is considered "close enough" 8561 diff = Math.abs(Math.round(d1) - Math.round(d2)); 8562 8563 return (diff <= tolerance) ? true : false; 8564 }; 8565 8566 AblePlayer.prototype.restoreFocus = function() { 8567 8568 // function called after player has been rebuilt (during media swap) 8569 // the original focusedElement no longer exists, 8570 // but this function finds a match in the new player 8571 // and places focus there 8572 8573 var classList, $mediaParent; 8574 8575 if ( this.$focusedElement && null !== this.activeMedia ) { 8576 $mediaParent = $( '#' + this.activeMedia ).closest( '.able' ); 8577 if ( (this.$focusedElement).attr('role') === 'button' ) { 8578 classList = this.$focusedElement.attr("class").split(/\s+/); 8579 $.each(classList, function(index, item) { 8580 if (item.substring(0,20) === 'able-button-handler-') { 8581 $mediaParent.find('div.able-controller div.' + item).trigger('focus'); 8582 } 8583 }); 8584 } 8585 } 8586 8587 }; 8588 8589 AblePlayer.prototype.addSeekbarListeners = function () { 8590 8591 var thisObj = this; 8592 8593 // Handle seek bar events. 8594 this.seekBar.seekbarDiv.on('startTracking', function (e) { 8595 thisObj.pausedBeforeTracking = thisObj.paused; 8596 thisObj.pauseMedia(); 8597 }).on('tracking', function (e, position) { 8598 // Scrub transcript, captions, and metadata. 8599 thisObj.highlightTranscript(position); 8600 thisObj.updateCaption(position); 8601 thisObj.showDescription(position); 8602 thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position)); 8603 thisObj.updateMeta(position); 8604 thisObj.refreshControls(); 8605 }).on('stopTracking', function (e, position) { 8606 if (thisObj.useChapterTimes) { 8607 thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position)); 8608 } else { 8609 thisObj.seekTo(position); 8610 } 8611 if (!thisObj.pausedBeforeTracking) { 8612 setTimeout(function () { 8613 thisObj.playMedia(); 8614 }, 200); 8615 } 8616 }); 8617 }; 8618 8619 AblePlayer.prototype.onClickPlayerButton = function (el) { 8620 var whichButton, prefsPopup; 8621 whichButton = this.getButtonNameFromClass($(el).attr('class')); 8622 switch ( whichButton ) { 8623 case 'play': 8624 this.clickedPlay = true; 8625 this.handlePlay(); 8626 break; 8627 case 'restart': 8628 this.seekTrigger = 'restart'; 8629 this.handleRestart(); 8630 break; 8631 case 'previous': 8632 this.userClickedPlaylist = true; 8633 this.okToPlay = true; 8634 this.seekTrigger = 'previous'; 8635 this.buttonWithFocus = 'previous'; 8636 this.handlePrevTrack(); 8637 break; 8638 case 'next': 8639 this.userClickedPlaylist = true; 8640 this.okToPlay = true; 8641 this.seekTrigger = 'next'; 8642 this.buttonWithFocus = 'next'; 8643 this.handleNextTrack(); 8644 break; 8645 case 'rewind': 8646 this.seekTrigger = 'rewind'; 8647 this.handleRewind(); 8648 break; 8649 case 'forward': 8650 this.seekTrigger = 'forward'; 8651 this.handleFastForward(); 8652 break; 8653 case 'mute': 8654 this.handleMute(); 8655 break; 8656 case 'volume': 8657 this.handleVolumeButtonClick(); 8658 break; 8659 case 'faster': 8660 this.handleRateIncrease(); 8661 break; 8662 case 'slower': 8663 this.handleRateDecrease(); 8664 break; 8665 case 'captions': 8666 this.handleCaptionToggle(); 8667 break; 8668 case 'chapters': 8669 this.handleChapters(); 8670 break; 8671 case 'descriptions': 8672 this.handleDescriptionToggle(); 8673 break; 8674 case 'sign': 8675 if ( ! this.closingSign ) { 8676 this.handleSignToggle(); 8677 } 8678 break; 8679 case 'preferences': 8680 if ($(el).attr('data-prefs-popup') === 'menu') { 8681 this.handlePrefsClick(); 8682 } else { 8683 this.showingPrefsDialog = true; // stopgap 8684 this.closePopups(); 8685 prefsPopup = $(el).attr('data-prefs-popup'); 8686 if (prefsPopup === 'keyboard') { 8687 this.keyboardPrefsDialog.show(); 8688 } else if (prefsPopup === 'captions') { 8689 this.captionPrefsDialog.show(); 8690 } else if (prefsPopup === 'descriptions') { 8691 this.descPrefsDialog.show(); 8692 } else if (prefsPopup === 'transcript') { 8693 this.transcriptPrefsDialog.show(); 8694 } 8695 this.showingPrefsDialog = false; 8696 } 8697 break; 8698 case 'transcript': 8699 if ( !this.closingTranscript ) { 8700 this.handleTranscriptToggle(); 8701 } 8702 break; 8703 case 'fullscreen': 8704 this.clickedFullscreenButton = true; 8705 this.handleFullscreenToggle(); 8706 break; 8707 } 8708 }; 8709 8710 AblePlayer.prototype.getButtonNameFromClass = function (classString) { 8711 // player control buttons all have class="able-button-handler-x" where x is the identifier 8712 // buttons might also have other classes assigned though 8713 var classes, i; 8714 8715 classes = classString.split(' '); 8716 for (i = 0; i < classes.length; i++) { 8717 if (classes[i].substring(0,20) === 'able-button-handler-') { 8718 return classes[i].substring(20); 8719 } 8720 } 8721 return classString; 8722 }; 8723 8724 AblePlayer.prototype.okToHandleKeyPress = function () { 8725 let defaultReturn = true; 8726 if ( this.prefNoKeyShortcuts === 1 ) { 8727 defaultReturn = false; 8728 } 8729 // returns true unless user's focus is on a UI element or user has disabled keyboard shortcuts. 8730 // that is likely to need supported keystrokes, including space 8731 var activeElement = AblePlayer.getActiveDOMElement(); 8732 8733 return ($(activeElement).prop('tagName') === 'INPUT') ? false : defaultReturn; 8734 }; 8735 8736 AblePlayer.prototype.onPlayerKeyPress = function (e) { 8737 8738 // handle keystrokes (using DHTML Style Guide recommended key combinations) 8739 // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer 8740 // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences 8741 // - Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down 8742 // The rest are reasonable best choices 8743 // - If there are multiple players on a single page, keystroke handlers 8744 // are only bound to the FIRST player 8745 // - The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions 8746 // including removal of the "media player" design pattern. There's an issue about that: 8747 // https://github.com/w3c/aria-practices/issues/27 8748 8749 var key, $thisElement; 8750 8751 // Convert to lower case. 8752 key = e.key; 8753 $thisElement = $(document.activeElement); 8754 8755 if (key === 'Escape') { 8756 if (this.$transcriptArea && $.contains(this.$transcriptArea[0],$thisElement[0]) && !this.hidingPopup) { 8757 // This element is part of transcript area. 8758 this.handleTranscriptToggle(); 8759 return false; 8760 } 8761 } 8762 if (!this.okToHandleKeyPress()) { 8763 return false; 8764 } 8765 8766 // Only use keypress to control player if focus is NOT on a form field or contenteditable element 8767 // (or a textarea element with player in stenoMode) 8768 if (!( 8769 $(':focus').is('[contenteditable]') || 8770 $(':focus').is('input') || 8771 ($(':focus').is('textarea') && !this.stenoMode) || 8772 $(':focus').is('select') || 8773 e.target.hasAttribute('contenteditable') || 8774 e.target.tagName === 'INPUT' || 8775 (e.target.tagName === 'TEXTAREA' && !this.stenoMode) || 8776 e.target.tagName === 'SELECT' 8777 )){ 8778 if (key === 'Escape') { 8779 this.closePopups(); 8780 this.$tooltipDiv.hide(); 8781 this.seekBar.hideSliderTooltips(); 8782 } else if (key === ' ') { 8783 // disable spacebar support for play/pause toggle as of 4.2.10 8784 // spacebar should not be handled everywhere on the page, since users use that to scroll the page 8785 // when the player has focus, most controls are buttons so spacebar should be used to trigger the buttons 8786 if ($thisElement.attr('role') === 'button') { 8787 // register a click on this element 8788 e.preventDefault(); 8789 $thisElement.trigger( 'click' ); 8790 } 8791 } else if ( key === 'p' ) { 8792 if (this.usingModifierKeys(e)) { 8793 e.preventDefault(); 8794 this.handlePlay(); 8795 } 8796 } else if (key === 's') { 8797 if (this.usingModifierKeys(e)) { 8798 e.preventDefault(); 8799 this.handleRestart(); 8800 } 8801 } else if (key === 'm') { 8802 if (this.usingModifierKeys(e)) { 8803 e.preventDefault(); 8804 this.handleMute(); 8805 } 8806 } else if (key === 'v') { 8807 if (this.usingModifierKeys(e)) { 8808 e.preventDefault(); 8809 this.handleVolumeButtonClick(); 8810 } 8811 } else if (key >= 0 && key <= 9) { 8812 if (this.usingModifierKeys(e)) { 8813 e.preventDefault(); 8814 this.handleVolumeKeystroke(key); 8815 } 8816 } else if (key === 'c') { 8817 if (this.usingModifierKeys(e)) { 8818 e.preventDefault(); 8819 this.handleCaptionToggle(); 8820 } 8821 } else if (key === 'd') { 8822 if (this.usingModifierKeys(e)) { 8823 e.preventDefault(); 8824 this.handleDescriptionToggle(); 8825 } 8826 } else if (key === 'f') { 8827 if (this.usingModifierKeys(e)) { 8828 e.preventDefault(); 8829 this.handleFastForward(); 8830 } 8831 } else if (key === 'r') { 8832 if (this.usingModifierKeys(e)) { 8833 e.preventDefault(); 8834 this.handleRewind(); 8835 } 8836 } else if (key === 'b') { 8837 if (this.usingModifierKeys(e)) { 8838 e.preventDefault(); 8839 this.handlePrevTrack(); 8840 } 8841 } else if (key === 'n') { 8842 if (this.usingModifierKeys(e)) { 8843 e.preventDefault(); 8844 this.handleNextTrack(); 8845 } 8846 } else if (key === 'e') { 8847 if (this.usingModifierKeys(e)) { 8848 e.preventDefault(); 8849 this.handlePrefsClick(); 8850 } 8851 } else if (key === 'Enter') { 8852 if ($thisElement.attr('role') === 'button' || $thisElement.prop('tagName') === 'SPAN') { 8853 // register a click on this element 8854 // if it's a transcript span the transcript span click handler will take over 8855 $thisElement.trigger( 'click' ); 8856 } else if ($thisElement.prop('tagName') === 'LI') { 8857 $thisElement.trigger( 'click' ); 8858 } 8859 } 8860 } 8861 }; 8862 8863 AblePlayer.prototype.addHtml5MediaListeners = function () { 8864 8865 var thisObj = this; 8866 8867 // NOTE: iOS and some browsers do not support autoplay 8868 // and no events are triggered until media begins to play 8869 // Able Player gets around this by automatically loading media in some circumstances 8870 // (see initialize.js > initPlayer() for details) 8871 8872 this.$media 8873 .on('emptied',function() { 8874 // do something 8875 }) 8876 .on('loadedmetadata',function() { 8877 // should be able to get duration now 8878 thisObj.duration = thisObj.media.duration; 8879 }) 8880 .on('canplay',function() { 8881 // previously handled seeking to startTime here 8882 // but it's probably safer to wait for canplaythrough 8883 // so we know player can seek ahead to anything 8884 }) 8885 .on('canplaythrough',function() { 8886 // previously onMediaNewSourceLoad() was called on 'loadedmetadata' 8887 // but that proved to be too soon for some of this functionality. 8888 // TODO: Monitor this. If moving it here causes performance issues, 8889 // consider moving some or all of this functionality to 'canplay' 8890 thisObj.onMediaNewSourceLoad(); 8891 }) 8892 .on('play',function() { 8893 // 'play' indicates that the play method has been called. 8894 // Don't do anything until playback has actually started. 8895 }) 8896 .on('playing',function() { 8897 // 'playing' indicates that the video is playing. 8898 thisObj.playing = true; 8899 thisObj.paused = false; 8900 thisObj.swappingSrc = false; 8901 thisObj.refreshControls('playpause'); 8902 }) 8903 .on('ended',function() { 8904 thisObj.playing = false; 8905 thisObj.paused = true; 8906 thisObj.onMediaComplete(); 8907 }) 8908 .on('progress', function() { 8909 thisObj.refreshControls('timeline'); 8910 }) 8911 .on('waiting',function() { 8912 // could fire a notification about loss of data. 8913 }) 8914 .on('durationchange',function() { 8915 // Display new duration. 8916 thisObj.refreshControls('timeline'); 8917 }) 8918 .on('timeupdate',function() { 8919 thisObj.onMediaUpdateTime(); // includes a call to refreshControls() 8920 }) 8921 .on('pause',function() { 8922 if (!thisObj.clickedPlay) { 8923 // 'pause' was triggered automatically, not initiated by user 8924 // this happens in some browsers when swapping source 8925 // (e.g., between tracks in a playlist or swapping description) 8926 if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else { 8927 thisObj.playing = false; 8928 thisObj.paused = true; 8929 } 8930 } else { 8931 thisObj.playing = false; 8932 thisObj.paused = true; 8933 } 8934 thisObj.clickedPlay = false; // done with this variable 8935 thisObj.onMediaPause(); // includes a call to refreshControls() 8936 }) 8937 .on('ratechange',function() { 8938 // do something 8939 }) 8940 .on('volumechange',function() { 8941 thisObj.volume = thisObj.getVolume(); 8942 }) 8943 .on('error',function() { 8944 if (thisObj.debug) { 8945 switch (thisObj.media.error.code) { 8946 case 1: 8947 console.log('HTML5 Media Error: MEDIA_ERR_ABORTED'); 8948 break; 8949 case 2: 8950 console.log('HTML5 Media Error: MEDIA_ERR_NETWORK '); 8951 break; 8952 case 3: 8953 console.log('HTML5 Media Error: MEDIA_ERR_DECODE '); 8954 break; 8955 case 4: 8956 console.log('HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED '); 8957 break; 8958 } 8959 } 8960 }); 8961 }; 8962 8963 AblePlayer.prototype.addVimeoListeners = function () { 8964 8965 var thisObj = this; 8966 8967 // Vimeo doesn't seem to support chaining of on() functions 8968 // so each event listener must be attached separately 8969 this.vimeoPlayer.on('loaded', function(vimeoId) { 8970 // Triggered when a new video is loaded in the player 8971 thisObj.onMediaNewSourceLoad(); 8972 }); 8973 this.vimeoPlayer.on('play', function(e) { 8974 // Triggered when the video plays 8975 thisObj.playing = true; 8976 thisObj.startedPlaying = true; 8977 thisObj.paused = false; 8978 thisObj.refreshControls('playpause'); 8979 }); 8980 this.vimeoPlayer.on('ended', function(e) { 8981 // Triggered any time the video playback reaches the end. 8982 // Note: when loop is turned on, the ended event will not fire. 8983 thisObj.playing = false; 8984 thisObj.paused = true; 8985 thisObj.onMediaComplete(); 8986 }); 8987 this.vimeoPlayer.on('bufferstart', function() { 8988 // Triggered when buffering starts in the player. 8989 // This is also triggered during preload and while seeking. 8990 // There is no associated data with this event. 8991 }); 8992 this.vimeoPlayer.on('bufferend', function() { 8993 // Triggered when buffering ends in the player. 8994 // This is also triggered at the end of preload and seeking. 8995 // There is no associated data with this event. 8996 }); 8997 this.vimeoPlayer.on('progress', function(e) { 8998 // Triggered as the video is loaded. 8999 // Reports back the amount of the video that has been buffered (NOT the amount played) 9000 // Data has keys duration, percent, and seconds 9001 }); 9002 this.vimeoPlayer.on('seeking', function(e) { 9003 // Triggered when the player starts seeking to a specific time. 9004 // A timeupdate event will also be fired at the same time. 9005 }); 9006 this.vimeoPlayer.on('seeked', function(e) { 9007 // Triggered when the player seeks to a specific time. 9008 // A timeupdate event will also be fired at the same time. 9009 }); 9010 this.vimeoPlayer.on('timeupdate',function(e) { 9011 // Triggered as the currentTime of the video updates. 9012 // It generally fires every 250ms, but it may vary depending on the browser. 9013 thisObj.onMediaUpdateTime(e['duration'], e['seconds']); 9014 }); 9015 this.vimeoPlayer.on('pause',function(e) { 9016 // Triggered when the video pauses 9017 if (!thisObj.clickedPlay) { 9018 // 'pause' was triggered automatically, not initiated by user 9019 // this happens in some browsers (not Chrome, as of 70.x) 9020 // when swapping source (e.g., between tracks in a playlist, or swapping description) 9021 if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else { 9022 thisObj.playing = false; 9023 thisObj.paused = true; 9024 } 9025 } else { 9026 thisObj.playing = false; 9027 thisObj.paused = true; 9028 } 9029 thisObj.clickedPlay = false; // done with this variable 9030 thisObj.onMediaPause(); 9031 thisObj.refreshControls('playpause'); 9032 }); 9033 this.vimeoPlayer.on('playbackratechange',function(e) { 9034 // Triggered when the playback rate of the video in the player changes. 9035 // The ability to change rate can be disabled by the creator 9036 // and the event will not fire for those videos. 9037 // data contains one key: 'playbackRate' 9038 thisObj.vimeoPlaybackRate = e['playbackRate']; 9039 }); 9040 this.vimeoPlayer.on('texttrackchange', function(e) { 9041 // Triggered when the active text track (captions/subtitles) changes. 9042 // The values will be null if text tracks are turned off. 9043 // data contains three keys: kind, label, language 9044 }); 9045 this.vimeoPlayer.on('volumechange',function(e) { 9046 // Triggered when the volume in the player changes. 9047 // Some devices do not support setting the volume of the video 9048 // independently from the system volume, 9049 // so this event will never fire on those devices. 9050 thisObj.volume = e['volume'] * 10; 9051 }); 9052 this.vimeoPlayer.on('error',function(e) { 9053 // do something with the available data 9054 // data contains three keys: message, method, name 9055 // message: A user-friendly error message 9056 // method: The Vimeo API method call that triggered the error 9057 // name: Name of the error (not necesssarily user-friendly) 9058 }); 9059 }; 9060 9061 AblePlayer.prototype.addEventListeners = function () { 9062 9063 var thisObj = this; 9064 9065 // Appropriately resize media player for full screen. 9066 $(window).on('resize',function () { 9067 thisObj.resizePlayer(); 9068 }); 9069 9070 // Refresh player if it changes from hidden to visible 9071 // There is no event triggered by a change in visibility 9072 // but MutationObserver works in most browsers (but NOT in IE 10 or earlier) 9073 // http://caniuse.com/#feat=mutationobserver 9074 if (window.MutationObserver) { 9075 var target = this.$ableDiv[0]; 9076 var observer = new MutationObserver(function(mutations) { 9077 mutations.forEach(function(mutation) { 9078 if (mutation.type === 'attributes' && mutation.attributeName === 'style') { 9079 // the player's style attribute has changed. Check to see if it's visible 9080 if (thisObj.$ableDiv.is(':visible')) { 9081 thisObj.refreshControls(); 9082 } 9083 } 9084 }); 9085 }); 9086 var config = { attributes: true, childList: true, characterData: true }; 9087 observer.observe(target, config); 9088 } 9089 if (typeof this.seekBar !== 'undefined') { 9090 this.addSeekbarListeners(); 9091 } else { 9092 // wait a bit and try again 9093 // TODO: Should set this up to keep trying repeatedly. 9094 // Seekbar listeners are critical. 9095 setTimeout(function() { 9096 if (typeof thisObj.seekBar !== 'undefined') { 9097 thisObj.addSeekbarListeners(); 9098 } 9099 },2000); 9100 } 9101 9102 // handle clicks on player buttons 9103 this.$controllerDiv.find('div[role="button"]').on('click',function(e){ 9104 e.stopPropagation(); 9105 thisObj.onClickPlayerButton(this); 9106 }); 9107 9108 // handle clicks (left only) anywhere on the page. If any popups are open, close them. 9109 $('body').on('click', function(e) { 9110 9111 if (e.button !== 0) { // not a left click 9112 return false; 9113 } 9114 if ($('.able-popup:visible').length || $('.able-volume-slider:visible').length ) { 9115 // at least one popup is visible 9116 thisObj.closePopups(); 9117 } 9118 if (e.target.tagName === 'VIDEO') { 9119 // user clicked the video (not an element that sits on top of the video) 9120 // handle this as a play/pause toggle click 9121 thisObj.clickedPlay = true; 9122 } 9123 }); 9124 9125 // handle mouse movement over player; make controls visible again if hidden 9126 this.$ableDiv.on('mousemove',function() { 9127 if (thisObj.controlsHidden) { 9128 thisObj.fadeControls('in'); 9129 thisObj.controlsHidden = false; 9130 // if there's already an active timeout, clear it and start timer again 9131 if (thisObj.hideControlsTimeoutStatus === 'active') { 9132 window.clearTimeout(thisObj.hideControlsTimeout); 9133 thisObj.hideControlsTimeoutStatus = 'clear'; 9134 } 9135 if (thisObj.hideControls) { 9136 // after showing controls, hide them again after a brief timeout 9137 thisObj.invokeHideControlsTimeout(); 9138 } 9139 } else { 9140 // if there's already an active timeout, clear it and start timer again 9141 if (thisObj.hideControlsTimeoutStatus === 'active') { 9142 window.clearTimeout(thisObj.hideControlsTimeout); 9143 thisObj.hideControlsTimeoutStatus = 'clear'; 9144 if (thisObj.hideControls) { 9145 thisObj.invokeHideControlsTimeout(); 9146 } 9147 } 9148 } 9149 }); 9150 9151 // if user presses a key from anywhere on the page, show player controls 9152 $(document).on( 'keydown', function(e) { 9153 if (thisObj.controlsHidden) { 9154 thisObj.fadeControls('in'); 9155 thisObj.controlsHidden = false; 9156 if (thisObj.hideControlsTimeoutStatus === 'active') { 9157 window.clearTimeout(thisObj.hideControlsTimeout); 9158 thisObj.hideControlsTimeoutStatus = 'clear'; 9159 } 9160 if (thisObj.hideControls) { 9161 // after showing controls, hide them again after a brief timeout 9162 thisObj.invokeHideControlsTimeout(); 9163 } 9164 } else { 9165 // controls are visible 9166 // if there's already an active timeout, clear it and start timer again 9167 if (thisObj.hideControlsTimeoutStatus === 'active') { 9168 window.clearTimeout(thisObj.hideControlsTimeout); 9169 thisObj.hideControlsTimeoutStatus = 'clear'; 9170 9171 if (thisObj.hideControls) { 9172 thisObj.invokeHideControlsTimeout(); 9173 } 9174 } 9175 } 9176 }); 9177 9178 // handle local keydown events if this isn't the only player on the page; 9179 // otherwise these are dispatched by global handler (see ableplayer-base.js) 9180 this.$ableDiv.on( 'keydown', function (e) { 9181 if (!AblePlayer.hasSingleInstance()) { 9182 thisObj.onPlayerKeyPress(e); 9183 } 9184 }); 9185 9186 // If stenoMode is enabled in an iframe, handle keydown events from the iframe 9187 if (this.stenoMode && (typeof this.stenoFrameContents !== 'undefined')) { 9188 this.stenoFrameContents.on('keydown',function(e) { 9189 thisObj.onPlayerKeyPress(e); 9190 }); 9191 } 9192 // transcript is not a child of this.$ableDiv 9193 // therefore, must be added separately 9194 if (this.$transcriptArea) { 9195 this.$transcriptArea.on('keydown',function (e) { 9196 if (!AblePlayer.hasSingleInstance()) { 9197 thisObj.onPlayerKeyPress(e); 9198 } 9199 }); 9200 } 9201 9202 // handle clicks on playlist items 9203 if (this.$playlist) { 9204 this.$playlist.on( 'click', function(e) { 9205 if (!thisObj.userClickedPlaylist) { 9206 // stopgap in case multiple clicks are fired on the same playlist item 9207 thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered 9208 thisObj.playlistIndex = $(this).index(); 9209 thisObj.cuePlaylistItem(thisObj.playlistIndex); 9210 } 9211 }); 9212 } 9213 9214 // Also play/pause when clicking on the media. 9215 this.$media.on( 'click', function () { 9216 thisObj.handlePlay(); 9217 }); 9218 9219 // add listeners for media events 9220 if (this.player === 'html5') { 9221 this.addHtml5MediaListeners(); 9222 } else if (this.player === 'vimeo') { 9223 this.addVimeoListeners(); 9224 } else if (this.player === 'youtube') { 9225 // Youtube doesn't give us time update events, so we just periodically generate them ourselves 9226 setInterval(function () { 9227 thisObj.onMediaUpdateTime(); 9228 }, 300); 9229 } 9230 }; 9231 } 9232 9233 function addInitializeFunctions(AblePlayer) { 9234 // Set default variable values. 9235 AblePlayer.prototype.setDefaults = function () { 9236 9237 this.playerCreated = false; // will set to true after recreatePlayer() is complete the first time 9238 this.playing = false; // will change to true after 'playing' event is triggered 9239 this.paused = true; // will always be the opposite of this.playing (available for convenience) 9240 this.clickedPlay = false; // will change to true temporarily if user clicks 'play' (or pause) 9241 this.fullscreen = false; // will change to true if player is in full screen mode 9242 this.swappingSrc = false; // will change to true temporarily while media source is being swapped 9243 this.initializing = false; // will change to true temporarily while initPlayer() is processing 9244 this.cueingPlaylistItems = false; // will change to true temporarily while cueing next playlist item 9245 this.buttonWithFocus = null; // will change to 'previous' or 'next' if user clicks either of those buttons 9246 this.speechEnabled = null; // will change either to 'true' in initSpeech(), or false if not supported 9247 9248 this.setIconColor(); 9249 }; 9250 9251 AblePlayer.prototype.setIconColor = function() { 9252 9253 // determine the best color choice (white or black) for icons, 9254 // given the background-color of their container elements 9255 // Source for relative luminance formula: 9256 // https://en.wikipedia.org/wiki/Relative_luminance 9257 9258 // We need to know the color *before* creating the element 9259 // so the element doesn't exist yet when this function is called 9260 // therefore, need to create a temporary element then remove it after color is determined 9261 // Temp element must be added to the DOM or WebKit can't retrieve its CSS properties 9262 9263 var $elements, i, $el, bgColor, rgb, red, green, blue, luminance, iconColor; 9264 9265 $elements = ['controller', 'toolbar']; 9266 for (i=0; i<$elements.length; i++) { 9267 if ($elements[i] == 'controller') { 9268 $el = $('<div>', { 9269 'class': 'able-controller' 9270 }).hide(); 9271 } else if ($elements[i] === 'toolbar') { 9272 $el = $('<div>', { 9273 'class': 'able-window-toolbar' 9274 }).hide(); 9275 } 9276 $('body').append($el); 9277 bgColor = $el.css('background-color'); 9278 // bgColor is a string in the form 'rgb(R, G, B)', perhaps with a 4th item for alpha; 9279 // split the 3 or 4 channels into an array 9280 rgb = bgColor.replace(/[^\d,]/g, '').split(','); 9281 red = rgb[0]; 9282 green = rgb[1]; 9283 blue = rgb[2]; 9284 luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); 9285 // range is 1 - 255; therefore 125 is the tipping point 9286 iconColor = (luminance < 125) ? 'white' : 'black'; 9287 9288 if ($elements[i] === 'controller') { 9289 this.iconColor = iconColor; 9290 } else if ($elements[i] === 'toolbar') { 9291 this.toolbarIconColor = iconColor; 9292 } 9293 $el.remove(); 9294 } 9295 }; 9296 9297 AblePlayer.prototype.getIconData = function(button) { 9298 9299 // returns array of values for creating <svg> tag for specified button 9300 // 0 = <svg> viewBox attribute 9301 // 1 = <path> d (description) attribute 9302 // 2 = icon class for font icons 9303 // 3 = img URL for images. 9304 var svg = Array(); 9305 9306 switch (button) { 9307 9308 case 'play': 9309 svg[0] = '0 0 16 20'; 9310 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'; 9311 break; 9312 9313 case 'pause': 9314 svg[0] = '0 0 20 20'; 9315 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'; 9316 break; 9317 9318 case 'restart': 9319 svg[0] = '0 0 20 20'; 9320 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'; 9321 break; 9322 9323 case 'rewind': 9324 svg[0] = '0 0 20 20'; 9325 svg[1] = 'M11.25 3.125v6.25l6.25-6.25v13.75l-6.25-6.25v6.25l-6.875-6.875z'; 9326 break; 9327 9328 case 'forward': 9329 svg[0] = '0 0 20 20'; 9330 svg[1] = 'M10 16.875v-6.25l-6.25 6.25v-13.75l6.25 6.25v-6.25l6.875 6.875z'; 9331 break; 9332 9333 case 'previous': 9334 svg[0] = '0 0 20 20'; 9335 svg[1] = 'M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z'; 9336 break; 9337 9338 case 'next': 9339 svg[0] = '0 0 20 20'; 9340 svg[1] = 'M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z'; 9341 break; 9342 9343 case 'slower': 9344 svg[0] = '0 0 11 20'; 9345 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'; 9346 break; 9347 9348 case 'faster': 9349 svg[0] = '0 0 11 20'; 9350 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'; 9351 break; 9352 9353 case 'turtle': 9354 svg[0] = '0 0 20 20'; 9355 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'; 9356 break; 9357 9358 case 'rabbit': 9359 svg[0] = '0 0 20 20'; 9360 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'; 9361 break; 9362 9363 case 'ellipsis': 9364 svg[0] = '0 0 20 20'; 9365 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'; 9366 break; 9367 9368 case 'pipe': 9369 svg[0] = '0 0 20 20'; 9370 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'; 9371 break; 9372 9373 case 'captions': 9374 svg[0] = '0 0 20 20'; 9375 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'; 9376 break; 9377 9378 case 'descriptions': 9379 svg[0] = '0 0 20 20'; 9380 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'; 9381 break; 9382 9383 case 'sign': 9384 svg[0] = '0 0 20 20'; 9385 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'; 9386 break; 9387 9388 case 'mute': 9389 case 'volume-mute': 9390 svg[0] = '0 0 20 20'; 9391 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'; 9392 break; 9393 9394 case 'volume-soft': 9395 svg[0] = '0 0 20 20'; 9396 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'; 9397 break; 9398 9399 case 'volume-medium': 9400 svg[0] = '0 0 20 20'; 9401 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'; 9402 break; 9403 9404 case 'volume-loud': 9405 svg[0] = '0 0 21 20'; 9406 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'; 9407 break; 9408 9409 case 'chapters': 9410 svg[0] = '0 0 20 20'; 9411 svg[1] = 'M5 2.5v17.5l6.25-6.25 6.25 6.25v-17.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z'; 9412 break; 9413 9414 case 'transcript': 9415 svg[0] = '0 0 20 20'; 9416 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'; 9417 break; 9418 9419 case 'preferences': 9420 svg[0] = '0 0 20 20'; 9421 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'; 9422 break; 9423 9424 case 'close': 9425 svg[0] = '0 0 16 20'; 9426 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'; 9427 break; 9428 9429 case 'fullscreen-expand': 9430 svg[0] = '0 0 20 20'; 9431 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'; 9432 break; 9433 9434 case 'fullscreen-collapse': 9435 svg[0] = '0 0 20 20'; 9436 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'; 9437 break; 9438 } 9439 9440 return svg; 9441 }; 9442 9443 // Initialize player based on data on page. 9444 // This sets some variables, but does not modify anything. Safe to call multiple times. 9445 // Can call again after updating this.media so long as new media element has the same ID. 9446 AblePlayer.prototype.reinitialize = function () { 9447 9448 var deferred, promise; 9449 9450 deferred = new this.defer(); 9451 promise = deferred.promise(); 9452 9453 this.startedPlaying = false; 9454 // TODO: Move this setting to preferences. 9455 this.autoScrollTranscript = true; 9456 //this.autoScrollTranscript = this.getPref(autoScrollTranscript); // (doesn't work) 9457 9458 // Bootstrap from this.media possibly being an ID or other selector. 9459 this.$media = $(this.media).first(); 9460 this.media = this.$media[0]; 9461 9462 // Set media type to 'audio' or 'video'; this determines some of the behavior of player creation. 9463 if (this.$media.is('audio')) { 9464 this.mediaType = 'audio'; 9465 } else if (this.$media.is('video')) { 9466 this.mediaType = 'video'; 9467 } else { 9468 // Able Player was initialized with some element other than <video> or <audio> 9469 this.provideFallback(); 9470 deferred.reject(); 9471 return promise; 9472 } 9473 9474 this.$sources = this.$media.find('source'); 9475 9476 this.player = this.getPlayer(); 9477 if (!this.player) { 9478 // an error was generated in getPlayer() 9479 this.provideFallback(); 9480 } 9481 9482 deferred.resolve(); 9483 return promise; 9484 }; 9485 9486 AblePlayer.prototype.setPlayerSize = function(width, height) { 9487 9488 // Called again after width and height are known 9489 if (this.mediaType !== 'audio' && width > 0 && height > 0) { 9490 this.playerWidth = width; 9491 this.playerHeight = height; 9492 this.aspectRatio = height / width; 9493 } 9494 }; 9495 9496 // Perform one-time setup for this instance of player; called after player is first initialized. 9497 AblePlayer.prototype.setupInstance = function () { 9498 9499 var deferred = new this.defer(); 9500 var promise = deferred.promise(); 9501 9502 if (this.$media.attr('id')) { 9503 this.mediaId = this.$media.attr('id'); 9504 } else { 9505 // Ensure the base media element always has an ID. 9506 this.mediaId = "ableMediaId_" + this.ableIndex; 9507 this.$media.attr('id', this.mediaId); 9508 } 9509 deferred.resolve(); 9510 return promise; 9511 }; 9512 9513 AblePlayer.prototype.setupInstancePlaylist = function() { 9514 9515 // find a matching playlist and set this.hasPlaylist 9516 // if there is one, also set this.$playlist, this.playlistIndex, & this.playlistEmbed 9517 var thisObj = this; 9518 9519 this.hasPlaylist = false; // will change to true if a matching playlist is found 9520 9521 $('.able-playlist').each(function() { 9522 if ($(this).data('player') === thisObj.mediaId) { 9523 // this is the playlist for the current player 9524 thisObj.hasPlaylist = true; 9525 // If using an embedded player, we'll replace $playlist with the clone later. 9526 thisObj.$playlist = $(this).find('li'); 9527 9528 // check to see if list item has YouTube as its source 9529 // if it does, inject a thumbnail from YouTube 9530 var $youTubeVideos = $(this).find('li[data-youtube-id]'); 9531 $youTubeVideos.each(function() { 9532 var youTubeId = purify.sanitize( $(this).attr('data-youtube-id') ); 9533 var youTubePoster = thisObj.getYouTubePosterUrl(youTubeId,'120'); 9534 var $youTubeImg = $('<img>',{ 9535 'src': youTubePoster, 9536 'alt': '' 9537 }); 9538 $(this).find('button').prepend($youTubeImg); 9539 }); 9540 9541 // check to see if list item has Vimeo as its source 9542 // if it does, inject a thumbnail from Vimeo 9543 var $vimeoVideos = $(this).find('li[data-vimeo-id]'); 9544 $vimeoVideos.each(function() { 9545 var vimeoId = $(this).attr('data-vimeo-id'); 9546 var vimeoPoster = thisObj.getVimeoPosterUrl(vimeoId,'120'); 9547 var $vimeoImg = $('<img>',{ 9548 'src': vimeoPoster, 9549 'alt': '' 9550 }); 9551 $(this).find('button').prepend($vimeoImg); 9552 }); 9553 9554 // add accessibility to the list markup 9555 $(this).find('li span').attr('aria-hidden','true'); 9556 thisObj.playlistIndex = 0; 9557 var dataEmbedded = $(this).data('embedded'); 9558 // is playlist embedded within player? 9559 thisObj.playlistEmbed = (typeof dataEmbedded !== 'undefined' && dataEmbedded !== false) ? true : false; 9560 } 9561 }); 9562 9563 if (this.hasPlaylist && this.loop) { 9564 // browser will loop the current track in the playlist, rather than the playlist 9565 // therefore, need to remove loop attribute from media element 9566 // but keep this.loop as true and handle the playlist looping ourselves 9567 this.media.removeAttribute('loop'); 9568 } 9569 if (this.hasPlaylist && this.playlistEmbed) { 9570 // Copy the playlist out of the dom, so we can reinject when we build the player. 9571 var parent = this.$playlist.parent(); 9572 this.$playlistDom = parent.clone(); 9573 parent.remove(); 9574 } 9575 if (this.hasPlaylist && this.$sources.length === 0) { 9576 // no source elements were provided. Construct them from the first playlist item 9577 this.cuePlaylistItem(0); 9578 // redefine this.$sources now that media contains one or more <source> elements 9579 this.$sources = this.$media.find('source'); 9580 } 9581 }; 9582 9583 AblePlayer.prototype.recreatePlayer = function () { 9584 9585 // Creates the appropriate player for the current source. 9586 // This function is called each time a new media instance is loaded 9587 // e.g., 9588 // User clicks on an item in a playlist 9589 // User swaps to/from described version of video 9590 // Blocks of code that only need to be executed once are controlled 9591 // by this.playerCreated 9592 9593 // TODO: Ensure when recreating player that we carry over the mediaId 9594 if (!this.player) { 9595 console.log("Can't create player; no appropriate player type detected."); 9596 return; 9597 } 9598 9599 var deferred, promise, thisObj, prefsGroups, i; 9600 9601 deferred = new this.defer(); 9602 promise = deferred.promise(); 9603 thisObj = this; 9604 9605 this.playerDeleted = false; // reset after deletePlayer() 9606 9607 // set temp stopgap to prevent this function from executing again before finished 9608 this.recreatingPlayer = true; 9609 9610 if (!this.playerCreated) { 9611 // only call these functions once 9612 this.loadCurrentPreferences(); 9613 this.injectPlayerCode(); 9614 this.resizePlayer(this.media.videoWidth,this.media.videoHeight); 9615 } 9616 9617 this.getSampleDescriptionText(); 9618 9619 this.initSignLanguage(); 9620 9621 this.initPlayer().then(function() { 9622 9623 thisObj.getTracks().then(function() { 9624 9625 thisObj.initDescription().then(function() { 9626 9627 thisObj.setupTracks().then(function() { 9628 if (thisObj.hasClosedDesc) { 9629 if (!thisObj.$descDiv || (thisObj.$descDiv && !($.contains(thisObj.$ableDiv[0], thisObj.$descDiv[0])))) { 9630 // descDiv either doesn't exist, or exists in an orphaned state 9631 // Either way, it needs to be rebuilt... 9632 thisObj.injectTextDescriptionArea(); 9633 } 9634 } 9635 thisObj.initSpeech('init'); 9636 9637 thisObj.setupTranscript().then(function() { 9638 9639 thisObj.initStenoFrame().then(function() { 9640 9641 if (thisObj.stenoMode && thisObj.$stenoFrame) { 9642 thisObj.stenoFrameContents = thisObj.$stenoFrame.contents(); 9643 } 9644 thisObj.getMediaTimes().then(function(mediaTimes) { 9645 9646 thisObj.duration = mediaTimes['duration']; 9647 thisObj.elapsed = mediaTimes['elapsed']; 9648 9649 if (typeof thisObj.volume === 'undefined') { 9650 thisObj.volume = thisObj.defaultVolume; 9651 } 9652 if (thisObj.volume) { 9653 thisObj.setVolume(thisObj.volume); 9654 } 9655 if (thisObj.transcriptType) { 9656 thisObj.addTranscriptAreaEvents(); 9657 thisObj.updateTranscript(); 9658 } 9659 if (thisObj.captions.length) { 9660 thisObj.initDefaultCaption(); 9661 } 9662 9663 // setMediaAttributes() sets textTrack.mode to 'disabled' for all tracks 9664 // This tells browsers to ignore the text tracks so Able Player can handle them 9665 // However, timing is critical as browsers - especially Safari - tend to ignore this request 9666 // unless it's sent late in the intialization process. 9667 // If browsers ignore the request, the result is redundant captions 9668 thisObj.setMediaAttributes(); 9669 thisObj.addControls(); 9670 thisObj.addEventListeners(); 9671 9672 // inject each of the hidden forms that will be accessed from the Preferences popup menu 9673 prefsGroups = thisObj.getPreferencesGroups(); 9674 for (i = 0; i < prefsGroups.length; i++) { 9675 thisObj.injectPrefsForm(prefsGroups[i]); 9676 } 9677 thisObj.setupPopups(); 9678 thisObj.updateCaption(); 9679 thisObj.injectVTS(); 9680 thisObj.populateChaptersDiv(); 9681 thisObj.showSearchResults(); 9682 9683 // Go ahead and load media, without user requesting it 9684 // Ideally, we would wait until user clicks play, rather than unnecessarily consume their bandwidth 9685 // However, the media needs to load for us to get the media's duration 9686 if (thisObj.player === 'html5') { 9687 if (!thisObj.loadingMedia) { 9688 thisObj.$media[0].load(); 9689 thisObj.loadingMedia = true; 9690 } 9691 } 9692 // refreshControls is called twice building/initializing the player 9693 // this is the second. Best to pause a bit before executing, to be sure all prior steps are complete 9694 setTimeout(function() { 9695 thisObj.refreshControls(); 9696 deferred.resolve(); 9697 },100); 9698 }); 9699 }); 9700 }); 9701 }); 9702 }); 9703 }); 9704 }, 9705 function() { // initPlayer fail 9706 thisObj.provideFallback(); 9707 }); 9708 return promise; 9709 }; 9710 9711 AblePlayer.prototype.initPlayer = function () { 9712 9713 var thisObj = this; 9714 var playerPromise; 9715 // First run player specific initialization. 9716 if (this.player === 'html5') { 9717 playerPromise = this.initHtml5Player(); 9718 } else if (this.player === 'youtube') { 9719 playerPromise = this.initYouTubePlayer(); 9720 } else if (this.player === 'vimeo') { 9721 playerPromise = this.initVimeoPlayer(); 9722 } 9723 // After player specific initialization is done, run remaining general initialization. 9724 var deferred = new this.defer(); 9725 var promise = deferred.promise(); 9726 playerPromise.then( 9727 function () { // done/resolved 9728 if (thisObj.useFixedSeekInterval) { 9729 // if fixed seekInterval was not already assigned (using value of data-seek-interval) 9730 if (!thisObj.seekInterval) { 9731 thisObj.seekInterval = thisObj.defaultSeekInterval; 9732 } 9733 thisObj.seekIntervalCalculated = true; 9734 } else { 9735 thisObj.setSeekInterval(); 9736 } 9737 deferred.resolve(); 9738 } 9739 ).finally(function () { // failed 9740 deferred.reject(); 9741 } 9742 ); 9743 9744 return promise; 9745 }; 9746 9747 AblePlayer.prototype.initStenoFrame = function() { 9748 9749 var deferred, promise; 9750 deferred = new this.defer(); 9751 promise = deferred.promise(); 9752 9753 if (this.stenoMode && this.$stenoFrame) { 9754 9755 if (this.$stenoFrame[0].contentWindow,document.readyState == 'complete') { 9756 // iframe has already loaded 9757 deferred.resolve(); 9758 } else { 9759 // iframe has not loaded. Wait for it. 9760 this.$stenoFrame.on('load',function() { 9761 deferred.resolve(); 9762 }); 9763 } 9764 } else { 9765 // there is no stenoFrame to initialize 9766 deferred.resolve(); 9767 } 9768 return promise; 9769 }; 9770 9771 AblePlayer.prototype.setSeekInterval = function () { 9772 9773 // this function is only called if this.useFixedSeekInterval is false 9774 // if this.useChapterTimes, this is called as each new chapter is loaded 9775 // otherwise, it's called once, as the player is initialized 9776 var duration; 9777 this.seekInterval = this.defaultSeekInterval; 9778 duration = (this.useChapterTimes) ? this.chapterDuration : this.duration; 9779 9780 if (typeof duration === 'undefined' || duration < 1) { 9781 // no duration; just use default for now but keep trying until duration is available 9782 this.seekIntervalCalculated = false; 9783 return; 9784 } else { 9785 if (duration <= 20) { 9786 this.seekInterval = 5; // 4 steps max 9787 } else if (duration <= 30) { 9788 this.seekInterval = 6; // 5 steps max 9789 } else if (duration <= 40) { 9790 this.seekInterval = 8; // 5 steps max 9791 } else if (duration <= 100) { 9792 this.seekInterval = 10; // 10 steps max 9793 } else { 9794 // never more than 10 steps from start to end 9795 this.seekInterval = Math.round(duration / 10, 0); 9796 } 9797 this.seekIntervalCalculated = true; 9798 } 9799 }; 9800 9801 AblePlayer.prototype.initDefaultCaption = function () { 9802 9803 var captions, i; 9804 9805 captions = this.captions; 9806 if (captions.length > 0) { 9807 for (i=0; i<captions.length; i++) { 9808 if (captions[i].def === true) { 9809 this.captionLang = captions[i].language; 9810 this.selectedCaptions = captions[i]; 9811 } 9812 } 9813 if (typeof this.captionLang === 'undefined') { 9814 // No caption track was flagged as default 9815 // find and use a caption language that matches the player language 9816 for (i=0; i<captions.length; i++) { 9817 if (captions[i].language === this.lang) { 9818 this.captionLang = captions[i].language; 9819 this.selectedCaptions = captions[i]; 9820 } 9821 } 9822 } 9823 if (typeof this.captionLang === 'undefined') { 9824 // Still no matching caption track 9825 // just use the first track 9826 this.captionLang = captions[0].language; 9827 this.selectedCaptions = captions[0]; 9828 } 9829 if (typeof this.captionLang !== 'undefined') { 9830 // reset transcript selected <option> to this.captionLang 9831 if (this.$transcriptLanguageSelect) { 9832 this.$transcriptLanguageSelect.find('option[lang=' + this.captionLang + ']').prop('selected',true); 9833 } 9834 // sync all other tracks to this same languge 9835 this.syncTrackLanguages('init',this.captionLang); 9836 } 9837 if (this.player === 'vimeo') { 9838 if (this.usingVimeoCaptions && this.prefCaptions == 1) { 9839 // initialize Vimeo captions to the default language 9840 this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) { 9841 switch (error.name) { 9842 case 'InvalidTrackLanguageError': 9843 // There is no text track for the specified language 9844 console.log(`No Vimeo text track is available in the specified language (${this.captionLang})`); 9845 break; 9846 case 'InvalidTrackError': 9847 // There is no such text track 9848 console.log('No Vimeo text track is available'); 9849 break; 9850 default: 9851 // some other error occurred 9852 console.log('Error enabling Vimeo text track'); 9853 break; 9854 } 9855 }); 9856 } else { 9857 // disable Vimeo captions. 9858 this.vimeoPlayer.disableTextTrack().then(function() { 9859 // Vimeo captions disabled 9860 }).catch(function(error) { 9861 console.log('Error disabling Vimeo text track: ',error); 9862 }); 9863 } 9864 } 9865 } 9866 }; 9867 9868 AblePlayer.prototype.initHtml5Player = function () { 9869 // Nothing special to do! 9870 var deferred = new this.defer(); 9871 var promise = deferred.promise(); 9872 deferred.resolve(); 9873 return promise; 9874 }; 9875 9876 // Sets media/track/source attributes; is called whenever player is recreated since $media may have changed. 9877 AblePlayer.prototype.setMediaAttributes = function () { 9878 // Firefox puts videos in tab order; remove. 9879 this.$media.attr('tabindex', -1); 9880 9881 // Keep native player from displaying captions/subtitles by setting textTrack.mode='disabled' 9882 // https://dev.w3.org/html5/spec-author-view/video.html#text-track-mode 9883 // This *should* work but historically hasn't been supported in all browsers 9884 // As of July 2025, 96% supported per https://caniuse.com/?search=text-track-mode. 9885 // Workaround for non-supporting browsers is to remove default attribute 9886 // We're doing that too in track.js > setupCaptions() 9887 var textTracks = this.$media.get(0).textTracks; 9888 if (textTracks) { 9889 var i = 0; 9890 while (i < textTracks.length) { 9891 textTracks[i].mode = 'disabled'; 9892 i += 1; 9893 } 9894 } 9895 }; 9896 9897 AblePlayer.prototype.getPlayer = function() { 9898 9899 // Determine which player to use, if any 9900 // return 'html5', 'youtube', 'vimeo', or null 9901 if (this.testFallback) { 9902 return null; 9903 } else if (this.youTubeId) { 9904 // null if attempting to play a YouTube video using an element other than <video> 9905 return (this.mediaType !== 'video') ? null : 'youtube'; 9906 } else if (this.vimeoId) { 9907 // null if attempting to play a Vimeo video using an element other than <video> 9908 return (this.mediaType !== 'video') ? null : 'vimeo'; 9909 } else if (this.media.canPlayType) { 9910 return 'html5'; 9911 } else { 9912 // Browser does not support the available media file 9913 return null; 9914 } 9915 }; 9916 } 9917 9918 function addLangsFunctions(AblePlayer) { 9919 // Look up ISO 639-1 language codes to be used as subtitle labels 9920 // In some instances "name" has been trunctation for readability 9921 // Sources: 9922 // http://stackoverflow.com/questions/3217492/list-of-language-codes-in-yaml-or-json/4900304#4900304 9923 // https://www.venea.net/web/culture_code 9924 9925 var isoLangs = { 9926 "ab":{ 9927 "name":"Abkhaz", 9928 "nativeName":"аҧÑÑа" 9929 }, 9930 "aa":{ 9931 "name":"Afar", 9932 "nativeName":"Afaraf" 9933 }, 9934 "af":{ 9935 "name":"Afrikaans", 9936 "nativeName":"Afrikaans" 9937 }, 9938 "ak":{ 9939 "name":"Akan", 9940 "nativeName":"Akan" 9941 }, 9942 "sq":{ 9943 "name":"Albanian", 9944 "nativeName":"Shqip" 9945 }, 9946 "am":{ 9947 "name":"Amharic", 9948 "nativeName":"á ááá" 9949 }, 9950 "ar":{ 9951 "name":"Arabic", 9952 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ©" 9953 }, 9954 "an":{ 9955 "name":"Aragonese", 9956 "nativeName":"Aragonés" 9957 }, 9958 "hy":{ 9959 "name":"Armenian", 9960 "nativeName":"ÕÕ¡ÕµÕ¥ÖÕ¥Õ¶" 9961 }, 9962 "as":{ 9963 "name":"Assamese", 9964 "nativeName":"ঠসমà§à¦¯à¦¼à¦¾" 9965 }, 9966 "av":{ 9967 "name":"Avaric", 9968 "nativeName":"Ð°Ð²Ð°Ñ Ð¼Ð°ÑÓ, магÓаÑÑл маÑÓ" 9969 }, 9970 "ae":{ 9971 "name":"Avestan", 9972 "nativeName":"avesta" 9973 }, 9974 "ay":{ 9975 "name":"Aymara", 9976 "nativeName":"aymar aru" 9977 }, 9978 "az":{ 9979 "name":"Azerbaijani", 9980 "nativeName":"azÉrbaycan dili" 9981 }, 9982 "bm":{ 9983 "name":"Bambara", 9984 "nativeName":"bamanankan" 9985 }, 9986 "ba":{ 9987 "name":"Bashkir", 9988 "nativeName":"баÑҡоÑÑ Ñеле" 9989 }, 9990 "eu":{ 9991 "name":"Basque", 9992 "nativeName":"euskara, euskera" 9993 }, 9994 "be":{ 9995 "name":"Belarusian", 9996 "nativeName":"ÐелаÑÑÑкаÑ" 9997 }, 9998 "bn":{ 9999 "name":"Bengali", 10000 "nativeName":"বাà¦à¦²à¦¾" 10001 }, 10002 "bh":{ 10003 "name":"Bihari", 10004 "nativeName":"à¤à¥à¤à¤ªà¥à¤°à¥" 10005 }, 10006 "bi":{ 10007 "name":"Bislama", 10008 "nativeName":"Bislama" 10009 }, 10010 "bs":{ 10011 "name":"Bosnian", 10012 "nativeName":"bosanski jezik" 10013 }, 10014 "br":{ 10015 "name":"Breton", 10016 "nativeName":"brezhoneg" 10017 }, 10018 "bg":{ 10019 "name":"Bulgarian", 10020 "nativeName":"бÑлгаÑÑки език" 10021 }, 10022 "my":{ 10023 "name":"Burmese", 10024 "nativeName":"ááá¬á á¬" 10025 }, 10026 "ca":{ 10027 "name":"Catalan", 10028 "nativeName":"Català " 10029 }, 10030 "ch":{ 10031 "name":"Chamorro", 10032 "nativeName":"Chamoru" 10033 }, 10034 "ce":{ 10035 "name":"Chechen", 10036 "nativeName":"Ð½Ð¾Ñ Ñийн моÑÑ" 10037 }, 10038 "ny":{ 10039 "name":"Chichewa", 10040 "nativeName":"chiCheŵa, chinyanja" 10041 }, 10042 "zh":{ 10043 "name":"Chinese", 10044 "nativeName":"䏿 (ZhÅngwén), æ±è¯, æ¼¢èª" 10045 }, 10046 "cv":{ 10047 "name":"Chuvash", 10048 "nativeName":"ÑÓÐ²Ð°Ñ ÑÓÐ»Ñ Ð¸" 10049 }, 10050 "kw":{ 10051 "name":"Cornish", 10052 "nativeName":"Kernewek" 10053 }, 10054 "co":{ 10055 "name":"Corsican", 10056 "nativeName":"corsu, lingua corsa" 10057 }, 10058 "cr":{ 10059 "name":"Cree", 10060 "nativeName":"áá¦ááááá£" 10061 }, 10062 "hr":{ 10063 "name":"Croatian", 10064 "nativeName":"hrvatski" 10065 }, 10066 "cs":{ 10067 "name":"Czech", 10068 "nativeName":"Äesky, ÄeÅ¡tina" 10069 }, 10070 "da":{ 10071 "name":"Danish", 10072 "nativeName":"dansk" 10073 }, 10074 "dv":{ 10075 "name":"Divehi", 10076 "nativeName":"ÞÞ¨ÞÞ¬ÞÞ¨" 10077 }, 10078 "nl":{ 10079 "name":"Dutch", 10080 "nativeName":"Nederlands, Vlaams" 10081 }, 10082 "en":{ 10083 "name":"English", 10084 "nativeName":"English" 10085 }, 10086 "eo":{ 10087 "name":"Esperanto", 10088 "nativeName":"Esperanto" 10089 }, 10090 "et":{ 10091 "name":"Estonian", 10092 "nativeName":"eesti, eesti keel" 10093 }, 10094 "ee":{ 10095 "name":"Ewe", 10096 "nativeName":"EÊegbe" 10097 }, 10098 "fo":{ 10099 "name":"Faroese", 10100 "nativeName":"føroyskt" 10101 }, 10102 "fj":{ 10103 "name":"Fijian", 10104 "nativeName":"vosa Vakaviti" 10105 }, 10106 "fi":{ 10107 "name":"Finnish", 10108 "nativeName":"suomi, suomen kieli" 10109 }, 10110 "fr":{ 10111 "name":"French", 10112 "nativeName":"français, langue française" 10113 }, 10114 "ff":{ 10115 "name":"Fula", 10116 "nativeName":"Fulfulde, Pulaar, Pular" 10117 }, 10118 "gl":{ 10119 "name":"Galician", 10120 "nativeName":"Galego" 10121 }, 10122 "ka":{ 10123 "name":"Georgian", 10124 "nativeName":"á¥áá áá£áá" 10125 }, 10126 "de":{ 10127 "name":"German", 10128 "nativeName":"Deutsch" 10129 }, 10130 "el":{ 10131 "name":"Greek", 10132 "nativeName":"Îλληνικά" 10133 }, 10134 "gn":{ 10135 "name":"GuaranÃ", 10136 "nativeName":"Avañeẽ" 10137 }, 10138 "gu":{ 10139 "name":"Gujarati", 10140 "nativeName":"àªà«àªàª°àª¾àª¤à«" 10141 }, 10142 "ht":{ 10143 "name":"Haitian", 10144 "nativeName":"Kreyòl ayisyen" 10145 }, 10146 "ha":{ 10147 "name":"Hausa", 10148 "nativeName":"Hausa, ÙÙÙÙØ³Ù" 10149 }, 10150 "he":{ 10151 "name":"Hebrew", 10152 "nativeName":"×¢×ר×ת" 10153 }, 10154 "hz":{ 10155 "name":"Herero", 10156 "nativeName":"Otjiherero" 10157 }, 10158 "hi":{ 10159 "name":"Hindi", 10160 "nativeName":"हिनà¥à¤¦à¥, हिà¤à¤¦à¥" 10161 }, 10162 "ho":{ 10163 "name":"Hiri Motu", 10164 "nativeName":"Hiri Motu" 10165 }, 10166 "hu":{ 10167 "name":"Hungarian", 10168 "nativeName":"Magyar" 10169 }, 10170 "ia":{ 10171 "name":"Interlingua", 10172 "nativeName":"Interlingua" 10173 }, 10174 "id":{ 10175 "name":"Indonesian", 10176 "nativeName":"Bahasa Indonesia" 10177 }, 10178 "ie":{ 10179 "name":"Interlingue", 10180 "nativeName":"Originally called Occidental; then Interlingue after WWII" 10181 }, 10182 "ga":{ 10183 "name":"Irish", 10184 "nativeName":"Gaeilge" 10185 }, 10186 "ig":{ 10187 "name":"Igbo", 10188 "nativeName":"Asụsụ Igbo" 10189 }, 10190 "ik":{ 10191 "name":"Inupiaq", 10192 "nativeName":"Iñupiaq, Iñupiatun" 10193 }, 10194 "io":{ 10195 "name":"Ido", 10196 "nativeName":"Ido" 10197 }, 10198 "is":{ 10199 "name":"Icelandic", 10200 "nativeName":"Ãslenska" 10201 }, 10202 "it":{ 10203 "name":"Italian", 10204 "nativeName":"Italiano" 10205 }, 10206 "iu":{ 10207 "name":"Inuktitut", 10208 "nativeName":"áááááá¦" 10209 }, 10210 "ja":{ 10211 "name":"Japanese", 10212 "nativeName":"æ¥æ¬èª (ã«ã»ããï¼ã«ã£ã½ãã)" 10213 }, 10214 "jv":{ 10215 "name":"Javanese", 10216 "nativeName":"basa Jawa" 10217 }, 10218 "kl":{ 10219 "name":"Kalaallisut", 10220 "nativeName":"kalaallisut, kalaallit oqaasii" 10221 }, 10222 "kn":{ 10223 "name":"Kannada", 10224 "nativeName":"à²à²¨à³à²¨à²¡" 10225 }, 10226 "kr":{ 10227 "name":"Kanuri", 10228 "nativeName":"Kanuri" 10229 }, 10230 "ks":{ 10231 "name":"Kashmiri", 10232 "nativeName":"à¤à¤¶à¥à¤®à¥à¤°à¥, ÙØ´Ù ÙØ±Ùâ" 10233 }, 10234 "kk":{ 10235 "name":"Kazakh", 10236 "nativeName":"ÒÐ°Ð·Ð°Ò ÑÑлÑ" 10237 }, 10238 "km":{ 10239 "name":"Khmer", 10240 "nativeName":"áá¶áá¶ááááá" 10241 }, 10242 "ki":{ 10243 "name":"Kikuyu", 10244 "nativeName":"GÄ©kÅ©yÅ©" 10245 }, 10246 "rw":{ 10247 "name":"Kinyarwanda", 10248 "nativeName":"Ikinyarwanda" 10249 }, 10250 "ky":{ 10251 "name":"Kyrgyz", 10252 "nativeName":"кÑÑгÑз Ñили" 10253 }, 10254 "kv":{ 10255 "name":"Komi", 10256 "nativeName":"коми кÑв" 10257 }, 10258 "kg":{ 10259 "name":"Kongo", 10260 "nativeName":"KiKongo" 10261 }, 10262 "ko":{ 10263 "name":"Korean", 10264 "nativeName":"íêµì´ (éåèª), ì¡°ì ë§ (æé®®èª)" 10265 }, 10266 "ku":{ 10267 "name":"Kurdish", 10268 "nativeName":"Kurdî, ÙÙØ±Ø¯Ûâ" 10269 }, 10270 "kj":{ 10271 "name":"Kuanyama", 10272 "nativeName":"Kuanyama" 10273 }, 10274 "la":{ 10275 "name":"Latin", 10276 "nativeName":"latine, lingua latina" 10277 }, 10278 "lb":{ 10279 "name":"Luxembourgish", 10280 "nativeName":"Lëtzebuergesch" 10281 }, 10282 "lg":{ 10283 "name":"Luganda", 10284 "nativeName":"Luganda" 10285 }, 10286 "li":{ 10287 "name":"Limburgish", 10288 "nativeName":"Limburgs" 10289 }, 10290 "ln":{ 10291 "name":"Lingala", 10292 "nativeName":"Lingála" 10293 }, 10294 "lo":{ 10295 "name":"Lao", 10296 "nativeName":"àºàº²àºªàº²àº¥àº²àº§" 10297 }, 10298 "lt":{ 10299 "name":"Lithuanian", 10300 "nativeName":"lietuvių kalba" 10301 }, 10302 "lu":{ 10303 "name":"Luba-Katanga", 10304 "nativeName":"" 10305 }, 10306 "lv":{ 10307 "name":"Latvian", 10308 "nativeName":"latvieÅ¡u valoda" 10309 }, 10310 "gv":{ 10311 "name":"Manx", 10312 "nativeName":"Gaelg, Gailck" 10313 }, 10314 "mk":{ 10315 "name":"Macedonian", 10316 "nativeName":"македонÑки Ñазик" 10317 }, 10318 "mg":{ 10319 "name":"Malagasy", 10320 "nativeName":"Malagasy fiteny" 10321 }, 10322 "ms":{ 10323 "name":"Malay", 10324 "nativeName":"bahasa Melayu, Ø¨ÙØ§Ø³ Ù ÙØ§ÙÙâ" 10325 }, 10326 "ml":{ 10327 "name":"Malayalam", 10328 "nativeName":"മലയാളà´" 10329 }, 10330 "mt":{ 10331 "name":"Maltese", 10332 "nativeName":"Malti" 10333 }, 10334 "mi":{ 10335 "name":"MÄori", 10336 "nativeName":"te reo MÄori" 10337 }, 10338 "mr":{ 10339 "name":"Marathi", 10340 "nativeName":"मराठà¥" 10341 }, 10342 "mh":{ 10343 "name":"Marshallese", 10344 "nativeName":"Kajin M̧ajeļ" 10345 }, 10346 "mn":{ 10347 "name":"Mongolian", 10348 "nativeName":"монгол" 10349 }, 10350 "na":{ 10351 "name":"Nauru", 10352 "nativeName":"EkakairÅ© Naoero" 10353 }, 10354 "nv":{ 10355 "name":"Navajo", 10356 "nativeName":"Diné bizaad, DinékʼehǰÃ" 10357 }, 10358 "nb":{ 10359 "name":"Norwegian BokmÃ¥l", 10360 "nativeName":"Norsk bokmÃ¥l" 10361 }, 10362 "nd":{ 10363 "name":"North Ndebele", 10364 "nativeName":"isiNdebele" 10365 }, 10366 "ne":{ 10367 "name":"Nepali", 10368 "nativeName":"नà¥à¤ªà¤¾à¤²à¥" 10369 }, 10370 "ng":{ 10371 "name":"Ndonga", 10372 "nativeName":"Owambo" 10373 }, 10374 "nn":{ 10375 "name":"Norwegian Nynorsk", 10376 "nativeName":"Norsk nynorsk" 10377 }, 10378 "no":{ 10379 "name":"Norwegian", 10380 "nativeName":"Norsk" 10381 }, 10382 "ii":{ 10383 "name":"Nuosu", 10384 "nativeName":"êê ê¿ Nuosuhxop" 10385 }, 10386 "nr":{ 10387 "name":"South Ndebele", 10388 "nativeName":"isiNdebele" 10389 }, 10390 "oc":{ 10391 "name":"Occitan", 10392 "nativeName":"Occitan" 10393 }, 10394 "oj":{ 10395 "name":"Ojibwe", 10396 "nativeName":"ááááá¯á§áá" 10397 }, 10398 "cu":{ 10399 "name":"Church Slavonic", 10400 "nativeName":"ѩзÑÐºÑ ÑловѣнÑÑкÑ" 10401 }, 10402 "om":{ 10403 "name":"Oromo", 10404 "nativeName":"Afaan Oromoo" 10405 }, 10406 "or":{ 10407 "name":"Oriya", 10408 "nativeName":"à¬à¬¡à¬¼à¬¿à¬" 10409 }, 10410 "os":{ 10411 "name":"Ossetian", 10412 "nativeName":"иÑон æвзаг" 10413 }, 10414 "pa":{ 10415 "name":"Punjabi", 10416 "nativeName":"ਪੰà¨à¨¾à¨¬à©, Ù¾ÙØ¬Ø§Ø¨Ûâ" 10417 }, 10418 "pi":{ 10419 "name":"PÄli", 10420 "nativeName":"पाऴि" 10421 }, 10422 "fa":{ 10423 "name":"Persian", 10424 "nativeName":"ÙØ§Ø±Ø³Û" 10425 }, 10426 "pl":{ 10427 "name":"Polish", 10428 "nativeName":"polski" 10429 }, 10430 "ps":{ 10431 "name":"Pashto", 10432 "nativeName":"Ù¾ÚØªÙ" 10433 }, 10434 "pt":{ 10435 "name":"Portuguese", 10436 "nativeName":"Português" 10437 }, 10438 "qu":{ 10439 "name":"Quechua", 10440 "nativeName":"Runa Simi, Kichwa" 10441 }, 10442 "rm":{ 10443 "name":"Romansh", 10444 "nativeName":"rumantsch grischun" 10445 }, 10446 "rn":{ 10447 "name":"Kirundi", 10448 "nativeName":"kiRundi" 10449 }, 10450 "ro":{ 10451 "name":"Romanian", 10452 "nativeName":"românÄ" 10453 }, 10454 "ru":{ 10455 "name":"Russian", 10456 "nativeName":"ÑÑÑÑкий ÑзÑк" 10457 }, 10458 "sa":{ 10459 "name":"Sanskrit", 10460 "nativeName":"सà¤à¤¸à¥à¤à¥à¤¤à¤®à¥" 10461 }, 10462 "sc":{ 10463 "name":"Sardinian", 10464 "nativeName":"sardu" 10465 }, 10466 "sd":{ 10467 "name":"Sindhi", 10468 "nativeName":"सिनà¥à¤§à¥, سÙÚÙØ Ø³ÙØ¯Ú¾Ûâ" 10469 }, 10470 "se":{ 10471 "name":"Northern Sami", 10472 "nativeName":"Davvisámegiella" 10473 }, 10474 "sm":{ 10475 "name":"Samoan", 10476 "nativeName":"gagana faa Samoa" 10477 }, 10478 "sg":{ 10479 "name":"Sango", 10480 "nativeName":"yângâ tî sängö" 10481 }, 10482 "sr":{ 10483 "name":"Serbian", 10484 "nativeName":"ÑÑпÑки Ñезик" 10485 }, 10486 "gd":{ 10487 "name":"Gaelic", 10488 "nativeName":"Gà idhlig" 10489 }, 10490 "sn":{ 10491 "name":"Shona", 10492 "nativeName":"chiShona" 10493 }, 10494 "si":{ 10495 "name":"Sinhalese", 10496 "nativeName":"à·à·à¶à·à¶½" 10497 }, 10498 "sk":{ 10499 "name":"Slovak", 10500 "nativeName":"slovenÄina" 10501 }, 10502 "sl":{ 10503 "name":"Slovene", 10504 "nativeName":"slovenÅ¡Äina" 10505 }, 10506 "so":{ 10507 "name":"Somali", 10508 "nativeName":"Soomaaliga, af Soomaali" 10509 }, 10510 "st":{ 10511 "name":"Southern Sotho", 10512 "nativeName":"Sesotho" 10513 }, 10514 "es":{ 10515 "name":"Spanish", 10516 "nativeName":"español, castellano" 10517 }, 10518 "su":{ 10519 "name":"Sundanese", 10520 "nativeName":"Basa Sunda" 10521 }, 10522 "sw":{ 10523 "name":"Swahili", 10524 "nativeName":"Kiswahili" 10525 }, 10526 "ss":{ 10527 "name":"Swati", 10528 "nativeName":"SiSwati" 10529 }, 10530 "sv":{ 10531 "name":"Swedish", 10532 "nativeName":"svenska" 10533 }, 10534 "ta":{ 10535 "name":"Tamil", 10536 "nativeName":"தமிழà¯" 10537 }, 10538 "te":{ 10539 "name":"Telugu", 10540 "nativeName":"à°¤à±à°²à±à°à±" 10541 }, 10542 "tg":{ 10543 "name":"Tajik", 10544 "nativeName":"Ñоҷикӣ, toÄikÄ«, تاجÛÚ©Ûâ" 10545 }, 10546 "th":{ 10547 "name":"Thai", 10548 "nativeName":"à¹à¸à¸¢" 10549 }, 10550 "ti":{ 10551 "name":"Tigrinya", 10552 "nativeName":"áµááá" 10553 }, 10554 "bo":{ 10555 "name":"Tibetan", 10556 "nativeName":"à½à½¼à½à¼à½¡à½²à½" 10557 }, 10558 "tk":{ 10559 "name":"Turkmen", 10560 "nativeName":"Türkmen, ТүÑкмен" 10561 }, 10562 "tl":{ 10563 "name":"Tagalog", 10564 "nativeName":"Wikang Tagalog, áááá á áááááá" 10565 }, 10566 "tn":{ 10567 "name":"Tswana", 10568 "nativeName":"Setswana" 10569 }, 10570 "to":{ 10571 "name":"Tonga", 10572 "nativeName":"faka Tonga" 10573 }, 10574 "tr":{ 10575 "name":"Turkish", 10576 "nativeName":"Türkçe" 10577 }, 10578 "ts":{ 10579 "name":"Tsonga", 10580 "nativeName":"Xitsonga" 10581 }, 10582 "tt":{ 10583 "name":"Tatar", 10584 "nativeName":"ÑаÑаÑÑа, tatarça, ØªØ§ØªØ§Ø±ÚØ§â" 10585 }, 10586 "tw":{ 10587 "name":"Twi", 10588 "nativeName":"Twi" 10589 }, 10590 "ty":{ 10591 "name":"Tahitian", 10592 "nativeName":"Reo Tahiti" 10593 }, 10594 "ug":{ 10595 "name":"Uyghur", 10596 "nativeName":"UyÆ£urqÉ, ئÛÙØºÛرÚÛâ" 10597 }, 10598 "uk":{ 10599 "name":"Ukrainian", 10600 "nativeName":"ÑкÑаÑнÑÑка" 10601 }, 10602 "ur":{ 10603 "name":"Urdu", 10604 "nativeName":"اردÙ" 10605 }, 10606 "uz":{ 10607 "name":"Uzbek", 10608 "nativeName":"zbek, Ðзбек, Ø£ÛØ²Ø¨ÛÙâ" 10609 }, 10610 "ve":{ 10611 "name":"Venda", 10612 "nativeName":"Tshivená¸a" 10613 }, 10614 "vi":{ 10615 "name":"Vietnamese", 10616 "nativeName":"Tiếng Viá»t" 10617 }, 10618 "vo":{ 10619 "name":"Volapük", 10620 "nativeName":"Volapük" 10621 }, 10622 "wa":{ 10623 "name":"Walloon", 10624 "nativeName":"Walon" 10625 }, 10626 "cy":{ 10627 "name":"Welsh", 10628 "nativeName":"Cymraeg" 10629 }, 10630 "wo":{ 10631 "name":"Wolof", 10632 "nativeName":"Wollof" 10633 }, 10634 "fy":{ 10635 "name":"Western Frisian", 10636 "nativeName":"Frysk" 10637 }, 10638 "xh":{ 10639 "name":"Xhosa", 10640 "nativeName":"isiXhosa" 10641 }, 10642 "yi":{ 10643 "name":"Yiddish", 10644 "nativeName":"××Ö´××ש" 10645 }, 10646 "yo":{ 10647 "name":"Yoruba", 10648 "nativeName":"Yorùbá" 10649 }, 10650 "za":{ 10651 "name":"Zhuang", 10652 "nativeName":"Saɯ cueÅÆ , Saw cuengh" 10653 }, 10654 "ar-dz":{ 10655 "name":"Arabic (Algeria)", 10656 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¬Ø²Ø§Ø¦Ø±)" 10657 }, 10658 "ar-bh":{ 10659 "name":"Arabic (Bahrain)", 10660 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¨ØØ±ÙÙ)" 10661 }, 10662 "ar-eg":{ 10663 "name":"Arabic (Egypt)", 10664 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (٠صر)" 10665 }, 10666 "ar-iq":{ 10667 "name":"Arabic (Iraq)", 10668 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¹Ø±Ø§Ù)" 10669 }, 10670 "ar-jo":{ 10671 "name":"Arabic (Jordan)", 10672 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ£Ø±Ø¯Ù)" 10673 }, 10674 "ar-kw":{ 10675 "name":"Arabic (Kuwait)", 10676 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙÙØª)" 10677 }, 10678 "ar-lb":{ 10679 "name":"Arabic (Lebanon)", 10680 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ¨ÙاÙ)" 10681 }, 10682 "ar-ly":{ 10683 "name":"Arabic (Libya)", 10684 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙÙØ¨Ùا)" 10685 }, 10686 "ar-ma":{ 10687 "name":"Arabic (Morocco)", 10688 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© اÙÙ ØºØ±Ø¨ÙØ©)" 10689 }, 10690 "ar-om":{ 10691 "name":"Arabic (Oman)", 10692 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ع٠اÙ)" 10693 }, 10694 "ar-qa":{ 10695 "name":"Arabic (Qatar)", 10696 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ·Ø±)" 10697 }, 10698 "ar-sa":{ 10699 "name":"Arabic (Saudi Arabia)", 10700 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© Ø§ÙØ¹Ø±Ø¨ÙØ© Ø§ÙØ³Ø¹ÙØ¯ÙØ©)" 10701 }, 10702 "ar-sy":{ 10703 "name":"Arabic (Syria)", 10704 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø³ÙØ±Ùا)" 10705 }, 10706 "ar-tn":{ 10707 "name":"Arabic (Tunisia)", 10708 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (تÙÙØ³)" 10709 }, 10710 "ar-ae":{ 10711 "name":"Arabic (U.A.E.)", 10712 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¥Ù ارات Ø§ÙØ¹Ø±Ø¨ÙØ© اÙÙ ØªØØ¯Ø©)" 10713 }, 10714 "ar-ye":{ 10715 "name":"Arabic (Yemen)", 10716 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙ Ù)" 10717 }, 10718 "de-at":{ 10719 "name":"German (Austria)", 10720 "nativeName":"Deutsch (Ãsterreich)" 10721 }, 10722 "de-li":{ 10723 "name":"German (Liechtenstein)", 10724 "nativeName":"Deutsch (Liechtenstein)" 10725 }, 10726 "de-lu":{ 10727 "name":"German (Luxembourg)", 10728 "nativeName":"Deutsch (Luxemburg)" 10729 }, 10730 "de-ch":{ 10731 "name":"German (Switzerland)", 10732 "nativeName":"Deutsch (Schweiz)" 10733 }, 10734 "en-au":{ 10735 "name":"English (Australia)", 10736 "nativeName":"English (Australia)" 10737 }, 10738 "en-bz":{ 10739 "name":"English (Belize)", 10740 "nativeName":"English (Belize)" 10741 }, 10742 "en-ca":{ 10743 "name":"English (Canada)", 10744 "nativeName":"English (Canada)" 10745 }, 10746 "en-ie":{ 10747 "name":"English (Ireland)", 10748 "nativeName":"English (Ireland)" 10749 }, 10750 "en-jm":{ 10751 "name":"English (Jamaica)", 10752 "nativeName":"English (Jamaica)" 10753 }, 10754 "en-nz":{ 10755 "name":"English (New Zealand)", 10756 "nativeName":"" 10757 }, 10758 "en-za":{ 10759 "name":"English (South Africa)", 10760 "nativeName":"English (South Africa)" 10761 }, 10762 "en-tt":{ 10763 "name":"English (Trinidad)", 10764 "nativeName":"English (Trinidad y Tobago)" 10765 }, 10766 "en-gb":{ 10767 "name":"English (United Kingdom)", 10768 "nativeName":"English (United Kingdom)" 10769 }, 10770 "en-us":{ 10771 "name":"English (United States)", 10772 "nativeName":"English (United States)" 10773 }, 10774 "es-ar":{ 10775 "name":"Spanish (Argentina)", 10776 "nativeName":"Español (Argentina)" 10777 }, 10778 "es-bo":{ 10779 "name":"Spanish (Bolivia)", 10780 "nativeName":"Español (Bolivia)" 10781 }, 10782 "es-cl":{ 10783 "name":"Spanish (Chile)", 10784 "nativeName":"Español (Chile)" 10785 }, 10786 "es-co":{ 10787 "name":"Spanish (Colombia)", 10788 "nativeName":"Español (Colombia)" 10789 }, 10790 "es-cr":{ 10791 "name":"Spanish (Costa Rica)", 10792 "nativeName":"Español (Costa Rica)" 10793 }, 10794 "es-do":{ 10795 "name":"Spanish (Dominican Republic)", 10796 "nativeName":"Español (República Dominicana)" 10797 }, 10798 "es-ec":{ 10799 "name":"Spanish (Ecuador)", 10800 "nativeName":"Español (Ecuador)" 10801 }, 10802 "es-sv":{ 10803 "name":"Spanish (El Salvador)", 10804 "nativeName":"Español (El Salvador)" 10805 }, 10806 "es-gt":{ 10807 "name":"Spanish (Guatemala)", 10808 "nativeName":"Español (Guatemala)" 10809 }, 10810 "es-hn":{ 10811 "name":"Spanish (Honduras)", 10812 "nativeName":"Español (Honduras)" 10813 }, 10814 "es-mx":{ 10815 "name":"Spanish (Mexico)", 10816 "nativeName":"Español (México)" 10817 }, 10818 "es-ni":{ 10819 "name":"Spanish (Nicaragua)", 10820 "nativeName":"Español (Nicaragua)" 10821 }, 10822 "es-pa":{ 10823 "name":"Spanish (Panama)", 10824 "nativeName":"Español (Panamá)" 10825 }, 10826 "es-py":{ 10827 "name":"Spanish (Paraguay)", 10828 "nativeName":"Español (Paraguay)" 10829 }, 10830 "es-pe":{ 10831 "name":"Spanish (Peru)", 10832 "nativeName":"Español (Perú)" 10833 }, 10834 "es-pr":{ 10835 "name":"Spanish (Puerto Rico)", 10836 "nativeName":"Español (Puerto Rico)" 10837 }, 10838 "es-uy":{ 10839 "name":"Spanish (Uruguay)", 10840 "nativeName":"Español (Uruguay)" 10841 }, 10842 "es-ve":{ 10843 "name":"Spanish (Venezuela)", 10844 "nativeName":"Español (Venezuela)" 10845 }, 10846 "fr-be":{ 10847 "name":"French (Belgium)", 10848 "nativeName":"français (Belgique)" 10849 }, 10850 "fr-ca":{ 10851 "name":"French (Canada)", 10852 "nativeName":"français (Canada)" 10853 }, 10854 "fr-lu":{ 10855 "name":"French (Luxembourg)", 10856 "nativeName":"français (Luxembourg)" 10857 }, 10858 "fr-ch":{ 10859 "name":"French (Switzerland)", 10860 "nativeName":"français (Suisse)" 10861 }, 10862 "it-ch":{ 10863 "name":"Italian (Switzerland)", 10864 "nativeName":"italiano (Svizzera)" 10865 }, 10866 "nl-be":{ 10867 "name":"Dutch (Belgium)", 10868 "nativeName":"Nederlands (België)" 10869 }, 10870 "pt-br":{ 10871 "name":"Portuguese (Brazil)", 10872 "nativeName":"Português (Brasil)" 10873 }, 10874 "sv-fi":{ 10875 "name":"Swedish (Finland)", 10876 "nativeName":"svenska (Finland)" 10877 }, 10878 "zh-hk":{ 10879 "name":"Chinese (Hong Kong)", 10880 "nativeName":"䏿(馿¸¯ç¹å«è¡æ¿å)" 10881 }, 10882 "zh-cn":{ 10883 "name":"Chinese (PRC)", 10884 "nativeName":"䏿(ä¸å人æ°å ±åå½)" 10885 }, 10886 "zh-sg":{ 10887 "name":"Chinese (Singapore)", 10888 "nativeName":"䏿(æ°å å¡)" 10889 }, 10890 "zh-tw":{ 10891 "name":"Chinese Traditional (Taiwan)", 10892 "nativeName":"䏿ï¼å°ç£ï¼" 10893 } 10894 }; 10895 10896 AblePlayer.prototype.getLanguageName = function (key,whichName) { 10897 10898 // return language name associated with lang code "key" 10899 // whichName is either "English" or "local" (i.e., native name) 10900 10901 var lang, code, subTag; 10902 lang = isoLangs[key.toLowerCase()]; 10903 if (lang) { 10904 return (whichName === 'local') ? lang.nativeName : lang.name; 10905 } else if (key.includes('-')) { 10906 code = key.substring(0,2); 10907 subTag = key.substring(3); 10908 lang = isoLangs[code.toLowerCase()]; 10909 if (lang) { 10910 return (whichName === 'local') ? lang.nativeName + ' (' + subTag + ')' : lang.name + ' (' + subTag + ')'; 10911 } 10912 } 10913 // if all else has failed, use the key as the label 10914 return key; 10915 }; 10916 10917 } 10918 10919 function addMetadataFunctions(AblePlayer) { 10920 AblePlayer.prototype.updateMeta = function (time) { 10921 if (this.hasMeta) { 10922 if (this.metaType === "text") { 10923 this.$metaDiv.show(); 10924 this.showMeta(time || this.elapsed); 10925 } else { 10926 this.showMeta(time || this.elapsed); 10927 } 10928 } 10929 }; 10930 10931 AblePlayer.prototype.showMeta = function (now) { 10932 var tempSelectors, 10933 m, 10934 thisMeta, 10935 cues, 10936 cueText, 10937 cueLines, 10938 i, 10939 line, 10940 showDuration, 10941 focusTarget; 10942 10943 tempSelectors = []; 10944 if (this.meta.length >= 1) { 10945 cues = this.meta; 10946 } else { 10947 cues = []; 10948 } 10949 for (m = 0; m < cues.length; m++) { 10950 if (cues[m].start <= now && cues[m].end > now) { 10951 thisMeta = m; 10952 break; 10953 } 10954 } 10955 if (typeof thisMeta !== "undefined") { 10956 if (this.currentMeta !== thisMeta) { 10957 if (this.metaType === "text") { 10958 // it's time to load the new metadata cue into the container div 10959 this.$metaDiv.html( 10960 this.flattenCueForMeta(cues[thisMeta]).replace(/\n/g, "<br>") 10961 ); 10962 } else if (this.metaType === "selector") { 10963 // it's time to show content referenced by the designated selector(s) 10964 cueText = this.flattenCueForMeta(cues[thisMeta]); 10965 cueLines = cueText.split("\n"); 10966 for (i = 0; i < cueLines.length; i++) { 10967 line = cueLines[i].trim(); 10968 if (line.toLowerCase().trim() === "pause") { 10969 // don't show big play button when pausing via metadata 10970 this.hideBigPlayButton = true; 10971 this.pauseMedia(); 10972 } else if (line.toLowerCase().substring(0, 6) == "focus:") { 10973 focusTarget = line.substring(6).trim(); 10974 if ($(focusTarget).length) { 10975 $(focusTarget).trigger('focus'); 10976 } 10977 } else { 10978 if ($(line).length) { 10979 // selector exists 10980 this.currentMeta = thisMeta; 10981 showDuration = parseInt($(line).attr("data-duration")); 10982 if ( 10983 typeof showDuration !== "undefined" && 10984 !isNaN(showDuration) 10985 ) { 10986 $(line).show(); 10987 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 10988 delay(showDuration).then(() => { 10989 $(line).hide(); 10990 }); 10991 } else { 10992 // no duration specified. Just show the element until end time specified in VTT file 10993 $(line).show(); 10994 } 10995 // add to array of visible selectors so it can be hidden at end time 10996 this.visibleSelectors.push(line); 10997 tempSelectors.push(line); 10998 } 10999 } 11000 } 11001 // now step through this.visibleSelectors and remove anything that's stale 11002 if (this.visibleSelectors && this.visibleSelectors.length) { 11003 if (this.visibleSelectors.length !== tempSelectors.length) { 11004 for (i = this.visibleSelectors.length - 1; i >= 0; i--) { 11005 if ($.inArray(this.visibleSelectors[i], tempSelectors) == -1) { 11006 $(this.visibleSelectors[i]).hide(); 11007 this.visibleSelectors.splice(i, 1); 11008 } 11009 } 11010 } 11011 } 11012 } 11013 } 11014 } else { 11015 // there is currently no metadata. Empty stale content 11016 if (typeof this.$metaDiv !== "undefined") { 11017 this.$metaDiv.html(""); 11018 } 11019 if (this.visibleSelectors && this.visibleSelectors.length) { 11020 for (i = 0; i < this.visibleSelectors.length; i++) { 11021 $(this.visibleSelectors[i]).hide(); 11022 } 11023 // reset array 11024 this.visibleSelectors = []; 11025 } 11026 this.currentMeta = -1; 11027 } 11028 }; 11029 11030 // Takes a cue and returns the metadata text to display for it. 11031 AblePlayer.prototype.flattenCueForMeta = function (cue) { 11032 var result = []; 11033 11034 var flattenComponent = function (component) { 11035 var result = [], 11036 ii; 11037 if (component.type === "string") { 11038 result.push(component.value); 11039 } else if (component.type === "v") { 11040 result.push("[" + component.value + "]"); 11041 for (ii = 0; ii < component.children.length; ii++) { 11042 result.push(flattenComponent(component.children[ii])); 11043 } 11044 } else { 11045 for (ii = 0; ii < component.children.length; ii++) { 11046 result.push(flattenComponent(component.children[ii])); 11047 } 11048 } 11049 return result.join(""); 11050 }; 11051 11052 for (var ii = 0; ii < cue.components.children.length; ii++) { 11053 result.push(flattenComponent(cue.components.children[ii])); 11054 } 11055 11056 return result.join(""); 11057 }; 11058 } 11059 11060 function addMiscFunctions(AblePlayer) { 11061 AblePlayer.prototype.getNextHeadingLevel = function ($element) { 11062 // Finds the nearest heading in the ancestor tree 11063 // Loops over each parent of the current element until a heading is found 11064 // If multiple headings are found beneath a given parent, get the closest 11065 // Returns an integer (1-6) representing the next available heading level 11066 11067 var $parents, $foundHeadings, numHeadings, headingType, headingNumber; 11068 11069 $parents = $element.parents(); 11070 $parents.each(function () { 11071 $foundHeadings = $(this).children(":header"); 11072 numHeadings = $foundHeadings.length; 11073 if (numHeadings) { 11074 headingType = $foundHeadings.eq(numHeadings - 1).prop("tagName"); 11075 return false; 11076 } 11077 }); 11078 if (typeof headingType === "undefined") { 11079 // page has no headings 11080 headingNumber = 1; 11081 } else { 11082 // Increment closest heading by one if less than 6. 11083 headingNumber = parseInt(headingType[1]); 11084 headingNumber += 1; 11085 if (headingNumber > 6) { 11086 headingNumber = 6; 11087 } 11088 } 11089 return headingNumber; 11090 }; 11091 11092 AblePlayer.prototype.countProperties = function (obj) { 11093 // returns the number of properties in an object 11094 var count, prop; 11095 count = 0; 11096 for (prop in obj) { 11097 if (Object.hasOwn(obj, prop)) { 11098 ++count; 11099 } 11100 } 11101 return count; 11102 }; 11103 11104 AblePlayer.prototype.formatSecondsAsColonTime = function ( 11105 seconds, 11106 showFullTime 11107 ) { 11108 // Takes seconds and converts to string of form hh:mm:ss 11109 // If showFullTime is true, shows 00 for hours if time is less than an hour 11110 // and show milliseconds (e.g., 00:00:04.123 as in Video Track Sorter) 11111 // Otherwise, omits empty hours and milliseconds (e.g., 00:04 as in timer on controller) 11112 11113 var times,format,parts,milliSeconds,numShort,i; 11114 11115 if (showFullTime) { 11116 // preserve milliseconds, if included in seconds 11117 parts = seconds.toString().split("."); 11118 if (parts.length === 2) { 11119 milliSeconds = parts[1]; 11120 if (milliSeconds.length < 3) { 11121 numShort = 3 - milliSeconds.length; 11122 for (i = 1; i <= numShort; i++) { 11123 milliSeconds += "0"; 11124 } 11125 } 11126 } else { 11127 milliSeconds = "000"; 11128 } 11129 } 11130 times = this.secondsToTime( seconds ); 11131 format = times['value']; 11132 11133 return (showFullTime) ? format + '.' + milliSeconds : format; 11134 }; 11135 11136 AblePlayer.prototype.getSecondsFromColonTime = function (timeStr) { 11137 // Converts string of form hh:mm:ss to seconds 11138 var timeParts, hours, minutes, seconds; 11139 11140 timeParts = timeStr.split(":"); 11141 if (timeParts.length === 3) { 11142 hours = parseInt(timeParts[0]); 11143 minutes = parseInt(timeParts[1]); 11144 seconds = parseFloat(timeParts[2]); 11145 return hours * 3600 + minutes * 60 + seconds; 11146 } else if (timeParts.length === 2) { 11147 minutes = parseInt(timeParts[0]); 11148 seconds = parseFloat(timeParts[1]); 11149 return minutes * 60 + seconds; 11150 } else if (timeParts.length === 1) { 11151 seconds = parseFloat(timeParts[0]); 11152 return seconds; 11153 } 11154 }; 11155 11156 AblePlayer.prototype.capitalizeFirstLetter = function (string) { 11157 return string.charAt(0).toUpperCase() + string.slice(1); 11158 }; 11159 11160 AblePlayer.prototype.roundDown = function (value, decimals) { 11161 // round value down to the nearest X decimal points 11162 // where X is the value of the decimals parameter 11163 return Number(Math.floor(value + "e" + decimals) + "e-" + decimals); 11164 }; 11165 11166 AblePlayer.prototype.defer = function() { 11167 const self = this; 11168 const promise = new Promise((resolve, reject) => { 11169 self.resolve = resolve; 11170 self.reject = reject; 11171 self.promise = () => promise; 11172 }); 11173 }; 11174 11175 AblePlayer.prototype.getScript = function( source, callback ) { 11176 var script = document.createElement('script'); 11177 var prior = document.getElementsByTagName('script')[0]; 11178 script.async = 1; 11179 11180 script.onload = script.onreadystatechange = function( _, isAbort ) { 11181 if ( isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) { 11182 script.onload = script.onreadystatechange = null; 11183 script = undefined; 11184 11185 if ( !isAbort && callback ) { 11186 setTimeout(callback, 0); 11187 } 11188 } 11189 }; 11190 11191 script.src = source; 11192 prior.parentNode.insertBefore(script, prior); 11193 }; 11194 11195 AblePlayer.prototype.hasAttr = function (object, attribute) { 11196 // surprisingly, there is no hasAttr() function in Jquery as of 3.2.1 11197 // return true if object has attribute; otherwise false 11198 // selector is a Jquery object 11199 // attribute is a string 11200 11201 var attr = object.attr(attribute); 11202 11203 // For some browsers, `attr` is undefined; for others, 11204 // `attr` is false. Check for both. 11205 if (typeof attr !== typeof undefined && attr !== false) { 11206 return true; 11207 } else { 11208 return false; 11209 } 11210 }; 11211 11212 } 11213 11214 /* global Cookies */ 11215 11216 function addPreferenceFunctions(AblePlayer) { 11217 AblePlayer.prototype.setPrefs = function(preferences) { 11218 if ( typeof Cookies !== 'undefined' ) { 11219 Cookies.set('Able-Player', JSON.stringify(preferences), { 11220 expires: 90, 11221 sameSite: 'strict' 11222 }); 11223 } else { 11224 localStorage.setItem( 'Able-Player', JSON.stringify( preferences ) ); 11225 } 11226 }; 11227 11228 AblePlayer.prototype.getPref = function() { 11229 11230 var defaultPrefs = { 11231 preferences: {}, 11232 sign: {}, 11233 transcript: {}, 11234 voices: [] 11235 }; 11236 11237 var preferences; 11238 try { 11239 if ( typeof Cookies !== 'undefined' ) { 11240 preferences = JSON.parse( Cookies.get('Able-Player') ); 11241 } else { 11242 preferences = JSON.parse( localStorage.getItem('Able-Player') ); 11243 } 11244 } 11245 catch (err) { 11246 // Original preferences can't be parsed; update to default 11247 this.setPrefs( defaultPrefs ); 11248 preferences = defaultPrefs; 11249 } 11250 return (preferences) ? preferences : defaultPrefs; 11251 }; 11252 11253 AblePlayer.prototype.updatePreferences = function( setting ) { 11254 // useful for settings updated independently of Preferences dialog 11255 // e.g., prefAutoScrollTranscript, which is updated in control.js > handleTranscriptLockToggle() 11256 // setting is any supported preference name (e.g., "prefCaptions") 11257 // OR 'transcript' or 'sign' (not user-defined preferences, used to save position of draggable windows) 11258 var preferences, $window, windowPos, available, i, prefName, voiceLangFound, newVoice; 11259 preferences = this.getPref(); 11260 if (setting === 'transcript' || setting === 'sign') { 11261 if (setting === 'transcript') { 11262 $window = this.$transcriptArea; 11263 windowPos = $window.position(); 11264 if (typeof preferences.transcript === 'undefined') { 11265 preferences.transcript = {}; 11266 } 11267 preferences.transcript['position'] = $window.css('position'); // either 'relative' or 'absolute' 11268 preferences.transcript['zindex'] = $window.css('z-index'); 11269 preferences.transcript['top'] = windowPos.top; 11270 preferences.transcript['left'] = windowPos.left; 11271 preferences.transcript['width'] = $window.width(); 11272 preferences.transcript['height'] = $window.height(); 11273 } else if (setting === 'sign') { 11274 $window = this.$signWindow; 11275 windowPos = $window.position(); 11276 if (typeof preferences.sign === 'undefined') { 11277 preferences.sign = {}; 11278 } 11279 preferences.sign['position'] = $window.css('position'); // either 'relative' or 'absolute' 11280 preferences.sign['zindex'] = $window.css('z-index'); 11281 preferences.sign['top'] = windowPos.top; 11282 preferences.sign['left'] = windowPos.left; 11283 preferences.sign['width'] = $window.width(); 11284 preferences.sign['height'] = $window.height(); 11285 } 11286 } else if (setting === 'voice') { 11287 if (typeof preferences.voices === 'undefined') { 11288 preferences.voices = []; 11289 } 11290 // replace preferred voice for this lang in preferences.voices array, if one exists 11291 // otherwise, add it to the array 11292 voiceLangFound = false; 11293 for (var v=0; v < preferences.voices.length; v++) { 11294 if (preferences.voices[v].lang === this.prefDescVoiceLang) { 11295 voiceLangFound = true; 11296 preferences.voices[v].name = this.prefDescVoice; 11297 } 11298 } 11299 if (!voiceLangFound) { 11300 // no voice has been saved yet for this language. Add it to array. 11301 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 11302 preferences.voices.push(newVoice); 11303 } 11304 } else { 11305 available = this.getAvailablePreferences(); 11306 // Rebuild preferences with current preferences values, 11307 // replacing the one value that's been changed 11308 for (i = 0; i < available.length; i++) { 11309 prefName = available[i]['name']; 11310 if (prefName == setting) { 11311 // this is the one that requires an update 11312 preferences.preferences[prefName] = this[prefName]; 11313 } 11314 } 11315 } 11316 // Save updated preferences 11317 this.setPrefs(preferences); 11318 }; 11319 11320 AblePlayer.prototype.getPreferencesGroups = function() { 11321 11322 // return array of groups in the order in which they will appear 11323 // in the Preferences popup menu 11324 // Human-readable label for each group is defined in translation table 11325 if (this.usingYouTubeCaptions) { 11326 // no transcript is possible 11327 return ['captions','descriptions','keyboard']; 11328 } else if (this.usingVimeoCaptions) { 11329 // users cannot control caption appearance 11330 // and no transcript is possible 11331 return ['descriptions','keyboard']; 11332 } else { 11333 return ['captions','descriptions','keyboard','transcript']; 11334 } 11335 }; 11336 11337 AblePlayer.prototype.getAvailablePreferences = function() { 11338 11339 // Return the list of currently available preferences. 11340 // Preferences with no 'label' are set within player, not shown in Prefs dialog 11341 var prefs = []; 11342 11343 // Modifier keys preferences 11344 prefs.push({ 11345 'name': 'prefAltKey', // use alt key with shortcuts 11346 'label': this.translate( 'prefAltKey', 'Alt' ), 11347 'group': 'keyboard', 11348 'default': 1 11349 }); 11350 prefs.push({ 11351 'name': 'prefCtrlKey', // use ctrl key with shortcuts 11352 'label': this.translate( 'prefCtrlKey', 'Control' ), 11353 'group': 'keyboard', 11354 'default': 1 11355 }); 11356 prefs.push({ 11357 'name': 'prefShiftKey', 11358 'label': this.translate( 'prefShiftKey', 'Shift' ), 11359 'group': 'keyboard', 11360 'default': 0 11361 }); 11362 prefs.push({ 11363 'name': 'prefNoKeyShortcuts', 11364 'label': this.translate( 'prefNoKeyShortcuts', 'Disable Keyboard Shortcuts' ), 11365 'group': 'keyboard', 11366 'default': 0 11367 }); 11368 11369 // Transcript preferences 11370 prefs.push({ 11371 'name': 'prefTranscript', // transcript default state 11372 'label': null, 11373 'group': 'transcript', 11374 'default': 0 // off because turning it on has a certain WOW factor 11375 }); 11376 prefs.push({ 11377 'name': 'prefHighlight', // highlight transcript as media plays 11378 'label': this.translate( 'prefHighlight', 'Highlight transcript as media plays' ), 11379 'group': 'transcript', 11380 'default': 1 // on because many users can benefit 11381 }); 11382 prefs.push({ 11383 'name': 'prefAutoScrollTranscript', 11384 'label': null, 11385 'group': 'transcript', 11386 'default': 1 11387 }); 11388 prefs.push({ 11389 'name': 'prefTabbable', // tab-enable transcript 11390 'label': this.translate( 'prefTabbable', 'Keyboard-enable transcript' ), 11391 'group': 'transcript', 11392 'default': 0 // off because if users don't need it, it impedes tabbing elsewhere on the page 11393 }); 11394 11395 // Caption preferences 11396 prefs.push({ 11397 'name': 'prefCaptions', // closed captions default state 11398 'label': null, 11399 'group': 'captions', 11400 'default': this.defaultStateCaptions 11401 }); 11402 11403 if (!this.usingYouTubeCaptions) { 11404 11405 /* // not supported yet 11406 prefs.push({ 11407 'name': 'prefCaptionsStyle', 11408 'label': this.translate( 'prefCaptionsStyle', 'Style' ), 11409 'group': 'captions', 11410 'default': this.translate( 'captionsStylePopOn', 'Pop-on' ) 11411 }); 11412 */ 11413 // captions are always positioned above the player for audio 11414 if (this.mediaType === 'video') { 11415 prefs.push({ 11416 'name': 'prefCaptionsPosition', 11417 'label': this.translate( 'prefCaptionsPosition', 'Position' ), 11418 'group': 'captions', 11419 'default': this.defaultCaptionsPosition 11420 }); 11421 } 11422 prefs.push({ 11423 'name': 'prefCaptionsFont', 11424 'label': this.translate( 'prefCaptionsFont', 'Font' ), 11425 'group': 'captions', 11426 'default': 'sans-serif' 11427 }); 11428 } 11429 // This is the one option that is supported by YouTube IFrame API 11430 prefs.push({ 11431 'name': 'prefCaptionsSize', 11432 'label': this.translate( 'prefCaptionsSize', 'Font size' ), 11433 'group': 'captions', 11434 'default': '100%' 11435 }); 11436 11437 if (!this.usingYouTubeCaptions) { 11438 11439 prefs.push({ 11440 'name': 'prefCaptionsColor', 11441 'label': this.translate( 'prefCaptionsColor', 'Text Color' ), 11442 'group': 'captions', 11443 'default': 'white' 11444 }); 11445 prefs.push({ 11446 'name': 'prefCaptionsBGColor', 11447 'label': this.translate( 'prefCaptionsBGColor', 'Background' ), 11448 'group': 'captions', 11449 'default': 'black' 11450 }); 11451 prefs.push({ 11452 'name': 'prefCaptionsOpacity', 11453 'label': this.translate( 'prefCaptionsOpacity', 'Opacity' ), 11454 'group': 'captions', 11455 'default': '100%' 11456 }); 11457 prefs.push({ 11458 'name': 'prefCaptionsSpeak', 11459 'label': this.translate( 'prefVoicedCaptions', 'Spoken Captions' ), 11460 'group': 'captions', 11461 'default': 0 11462 }); 11463 prefs.push({ 11464 'name': 'prefCaptionsVoice', 11465 'label': this.translate( 'prefDescVoice', 'Voice' ), 11466 'group': 'captions', 11467 'default': null // will be set later, in injectPrefsForm() 11468 }); 11469 prefs.push({ 11470 'name': 'prefCaptionsPitch', 11471 'label': this.translate( 'prefDescPitch', 'Pitch' ), 11472 'group': 'captions', 11473 'default': 1 // 0 to 2 11474 }); 11475 prefs.push({ 11476 'name': 'prefCaptionsRate', 11477 'label': this.translate( 'prefCaptionRate', 'Spoken Caption Rate' ), 11478 'group': 'captions', 11479 'default': 1.2 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast) 11480 }); 11481 prefs.push({ 11482 'name': 'prefCaptionsVolume', 11483 'label': this.translate( 'volume', 'Volume' ), 11484 'group': 'captions', 11485 'default': 1 // 0 to 1 11486 }); 11487 } 11488 11489 if (this.mediaType === 'video') { 11490 // Description preferences 11491 prefs.push({ 11492 'name': 'prefDesc', // audio description default state 11493 'label': null, 11494 'group': 'descriptions', 11495 'default': this.defaultStateDescriptions 11496 }); 11497 prefs.push({ 11498 'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available) 11499 'label': null, 11500 'group': 'descriptions', 11501 'default': 'video' // video (an alternative described version) always wins 11502 }); 11503 prefs.push({ 11504 'name': 'prefDescVoice', 11505 'label': this.translate( 'prefDescVoice', 'Voice' ), 11506 'group': 'descriptions', 11507 'default': null // will be set later, in injectPrefsForm() 11508 }); 11509 prefs.push({ 11510 'name': 'prefDescPitch', 11511 'label': this.translate( 'prefDescPitch', 'Pitch' ), 11512 'group': 'descriptions', 11513 'default': 1 // 0 to 2 11514 }); 11515 prefs.push({ 11516 'name': 'prefDescRate', 11517 'label': this.translate( 'prefDescRate', 'Spoken Description Rate' ), 11518 'group': 'descriptions', 11519 'default': 1 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast) 11520 }); 11521 prefs.push({ 11522 'name': 'prefDescVolume', 11523 'label': this.translate( 'volume', 'Volume' ), 11524 'group': 'descriptions', 11525 'default': 1 // 0 to 1 11526 }); 11527 // Don't enable pause option if video described files in use. 11528 if ( this.descMethod !== 'video' ) { 11529 prefs.push({ 11530 'name': 'prefDescPause', // automatically pause when closed description starts 11531 'label': this.translate( 'prefDescPause', 'Automatically pause video when description starts' ), 11532 'group': 'descriptions', 11533 'default': this.defaultDescPause 11534 }); 11535 } 11536 prefs.push({ 11537 'name': 'prefDescVisible', // visibly show closed description (if avilable and used) 11538 'label': this.translate( 'prefDescVisible', 'Make description visible' ), 11539 'group': 'descriptions', 11540 'default': 0 // off as of 4.3.16, to avoid overloading the player with visible features 11541 }); 11542 } 11543 // Preferences without a category (not shown in Preferences dialogs) 11544 prefs.push({ 11545 'name': 'prefSign', // open sign language window by default if avilable 11546 'label': null, 11547 'group': null, 11548 'default': 0 // off because clicking an icon to see the sign window has a powerful impact 11549 }); 11550 11551 return prefs; 11552 }; 11553 11554 AblePlayer.prototype.loadCurrentPreferences = function () { 11555 11556 // Load current/default preferences into the AblePlayer object. 11557 11558 var available = this.getAvailablePreferences(); 11559 var preferences = this.getPref(); 11560 // Copy current preferences values into this object, and fill in any default values. 11561 for (var ii = 0; ii < available.length; ii++) { 11562 var prefName = available[ii]['name']; 11563 var defaultValue = available[ii]['default']; 11564 if (preferences.preferences[prefName] !== undefined) { 11565 this[prefName] = preferences.preferences[prefName]; 11566 } else { 11567 preferences.preferences[prefName] = defaultValue; 11568 this[prefName] = defaultValue; 11569 } 11570 } 11571 11572 // Also load array of preferred voices from preferences 11573 if (typeof preferences.voices !== 'undefined') { 11574 this.prefVoices = preferences.voices; 11575 } 11576 11577 this.setPrefs(preferences); 11578 }; 11579 11580 AblePlayer.prototype.injectPrefsForm = function (form) { 11581 11582 // Creates a preferences form and injects it. 11583 // form is one of the supported forms (groups) defined in getPreferencesGroups() 11584 11585 var thisObj, available, 11586 $prefsDiv, formTitle, introText, $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j, 11587 $fieldset, fieldsetClass, fieldsetId, $legend, legendId, thisPref, $thisDiv, thisClass, 11588 thisId, $thisLabel, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText, 11589 changedPref,changedSpan,changedText, currentDescState, prefDescVoice, prefCaptionVoice, $kbHeading,$kbList, 11590 kbLabels,keys,kbListText,$kbListItem, dialog,$saveButton,$cancelButton,$buttonContainer; 11591 11592 thisObj = this; 11593 available = this.getAvailablePreferences(); 11594 11595 // outer container, will be assigned role="dialog" 11596 $prefsDiv = $('<div>',{ 11597 'class': 'able-prefs-form ' 11598 }); 11599 var customClass = 'able-prefs-form-' + form; 11600 $prefsDiv.addClass(customClass); 11601 11602 // add titles and intros 11603 if (form == 'captions') { 11604 formTitle = this.translate( 'prefTitleCaptions', 'Captions Preferences' ); 11605 } else if (form == 'descriptions') { 11606 formTitle = this.translate( 'prefTitleDescriptions', 'Audio Description Preferences' ); 11607 $prefsIntro = $('<p>',{ 11608 text: this.translate( 'prefIntroDescription1', 'This media player supports audio description in two ways: ' ) 11609 }); 11610 var $prefsIntroUL = $('<ul>'); 11611 var $prefsIntroLI1 = $('<li>',{ 11612 text: this.translate( 'prefDescFormatOption1', 'alternative described version of video' ) 11613 }); 11614 var $prefsIntroLI2 = $('<li>',{ 11615 text: this.translate( 'prefDescFormatOption2', 'text-based description, announced by screen reader' ) 11616 }); 11617 11618 $prefsIntroUL.append($prefsIntroLI1,$prefsIntroLI2); 11619 let prefDescription1 = ''; 11620 let prefDescription2 = ''; 11621 let prefDescription3 = ''; 11622 let prefDescriptionNone = ''; 11623 if (this.hasOpenDesc && this.hasClosedDesc) { 11624 prefDescription1 = this.translate( 'prefDescription1', 'The current video has an alternative described version and text-based description, announced by screen reader'); 11625 } else if (this.hasOpenDesc) { 11626 prefDescription2 = this.translate( 'prefDescription2', 'The current video has an alternative described version.' ); 11627 } else if (this.hasClosedDesc) { 11628 prefDescription3 = this.translate( 'prefDescription3', 'The current video has text-based description, announced by screen reader.'); 11629 } else { 11630 prefDescriptionNone = this.translate( 'prefDescriptionNone', 'The current video has no audio description in either format.' ); 11631 } 11632 currentDescState = prefDescription1 + prefDescription2 + prefDescription3 + prefDescriptionNone; 11633 $prefsIntroP2 = $('<p>',{ 11634 html: currentDescState 11635 }); 11636 11637 p3Text = this.translate( 'prefIntroDescription3', 'Use the following form to set your preferences related to text-based audio description.' ); 11638 if (this.hasOpenDesc || this.hasClosedDesc) { 11639 p3Text += ' ' + this.translate( 'prefIntroDescription4', 'After you save your settings, audio description can be toggled on/off using the Description button.' ); 11640 } 11641 $prefsIntroP3 = $('<p>',{ 11642 text: p3Text 11643 }); 11644 11645 $prefsDiv.append( $prefsIntro, $prefsIntroUL, $prefsIntroP2, $prefsIntroP3 ); 11646 } else if (form == 'keyboard') { 11647 formTitle = this.translate( 'prefTitleKeyboard', 'Keyboard Preferences' ); 11648 introText = this.translate( 'prefIntroKeyboard1', 'The media player on this web page can be operated from anywhere on the page using keyboard shortcuts (see below for a list).' ); 11649 introText += ' ' + this.translate( 'prefIntroKeyboard2', 'Modifier keys (Shift, Alt, and Control) can be assigned below.' ); 11650 introText += ' ' + this.translate( 'prefIntroKeyboard3', 'NOTE: Some key combinations might conflict with keys used by your browser and/or other software applications. Try various combinations of modifier keys to find one that works for you.' ); 11651 $prefsIntro = $('<p>',{ 11652 text: introText 11653 }); 11654 $prefsDiv.append($prefsIntro); 11655 } else if (form == 'transcript') { 11656 formTitle = this.translate( 'prefTitleTranscript', 'Transcript Preferences' ); 11657 } 11658 11659 $fieldset = $('<div>').attr('role','group'); 11660 fieldsetClass = 'able-prefs-' + form; 11661 fieldsetId = this.mediaId + '-prefs-' + form; 11662 legendId = fieldsetId + '-legend'; 11663 $fieldset.addClass(fieldsetClass).attr('id',fieldsetId); 11664 if (form === 'keyboard') { 11665 $legend = $('<h2>' + this.translate( 'prefHeadingKeyboard1', 'Modifier keys used for shortcuts' ) + '</h2>'); 11666 $legend.attr('id',legendId); 11667 $fieldset.attr('aria-labelledby',legendId); 11668 $fieldset.append($legend); 11669 } else if (form === 'descriptions') { 11670 $legend = $('<h2>' + this.translate( 'prefHeadingTextDescription', 'Text-based audio description' ) + '</h2>'); 11671 $legend.attr('id',legendId); 11672 $fieldset.attr('aria-labelledby',legendId); 11673 $fieldset.append($legend); 11674 } 11675 for (i=0; i<available.length; i++) { 11676 11677 // only include prefs on the current form if they have a label 11678 if ((available[i]['group'] == form) && available[i]['label']) { 11679 11680 thisPref = available[i]['name']; 11681 thisClass = 'able-' + thisPref; 11682 thisId = this.mediaId + '_' + thisPref; 11683 $thisDiv = $('<div>').addClass(thisClass + ' able-player-setting'); 11684 if (form === 'captions' ) { 11685 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11686 $thisField = $('<select>',{ 11687 name: thisPref, 11688 id: thisId, 11689 }); 11690 // add a change handler that updates the style of the sample caption text 11691 let viewingOptions = ['prefCaptionsPosition','prefCaptionsFont','prefCaptionsSize','prefCaptionsColor','prefCaptionsBGColor','prefCaptionsOpacity']; 11692 if ( viewingOptions.indexOf(thisPref) !== -1 ) { 11693 $thisField.on( 'change', function() { 11694 changedPref = $(this).attr('name'); 11695 thisObj.stylizeCaptions(thisObj.$sampleCapsDiv,changedPref); 11696 }); 11697 } 11698 captionsOptions = this.getCaptionsOptions(thisPref); 11699 if ( ! ( thisPref === 'prefCaptionsVoice' && ! this.descVoices.length ) ) { 11700 $thisDiv.append($thisLabel,$thisField); 11701 } 11702 for (j=0; j < captionsOptions.length; j++) { 11703 if (thisPref === 'prefCaptionsPosition') { 11704 optionValue = captionsOptions[j]; 11705 if (optionValue === 'overlay') { 11706 optionText = this.translate( 'captionsPositionOverlay', 'Overlay' ); 11707 } else if (optionValue === 'below') { 11708 optionValue = captionsOptions[j]; 11709 optionText = this.translate( 'captionsPositionBelow', 'Below video' ); 11710 } 11711 } else if (thisPref === 'prefCaptionsFont' || thisPref === 'prefCaptionsColor' || thisPref === 'prefCaptionsBGColor' || thisPref === 'prefCaptionsSpeak' ) { 11712 optionValue = captionsOptions[j][0]; 11713 optionText = captionsOptions[j][1]; 11714 } else if (thisPref === 'prefCaptionsOpacity') { 11715 optionValue = captionsOptions[j]; 11716 optionText = captionsOptions[j]; 11717 optionText += (optionValue === '0%') ? ' (' + this.translate( 'transparent', 'transparent' ) + ')' : ' (' + this.translate( 'solid', 'solid' ) + ')'; 11718 } else if (thisPref === 'prefCaptionsSize') { 11719 optionValue = captionsOptions[j]; 11720 optionText = captionsOptions[j]; 11721 } 11722 let voicingOptions = ['prefCaptionsPitch','prefCaptionsRate','prefCaptionsVolume']; 11723 if ( optionValue && voicingOptions.indexOf(thisPref) === -1 ) { 11724 $thisOption = $('<option>',{ 11725 value: optionValue, 11726 text: optionText 11727 }); 11728 if (this[thisPref] === optionValue) { 11729 $thisOption.prop('selected',true); 11730 } 11731 $thisField.append($thisOption); 11732 } 11733 // If synth is possible, show voicing options. 11734 if ( this.synth ) { 11735 if ( thisPref === 'prefCaptionsVoice' && this.descVoices.length ) { 11736 prefCaptionVoice = this.getPrefVoice(); 11737 for (j=0; j < this.descVoices.length; j++) { 11738 optionValue = this.descVoices[j].name; 11739 optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase(); 11740 optionText = optionValue + ' (' + this.descVoices[j].lang + ')'; 11741 $thisOption = $('<option>',{ 11742 'value': optionValue, 11743 'data-lang': optionLang, 11744 text: optionText 11745 }); 11746 if (prefCaptionVoice === optionValue) { 11747 $thisOption.prop('selected',true); 11748 } 11749 $thisField.append($thisOption); 11750 } 11751 this.$voiceSelectField = $thisField; 11752 } else { 11753 if ( thisPref == 'prefCaptionsPitch' || thisPref == 'prefCaptionsRate' || thisPref == 'prefCaptionsVolume' ) { 11754 options = false; 11755 // Options values described in audio description preferences. 11756 options = (thisPref == 'prefCaptionsPitch') ? [0,0.5,1,1.5,2] : options; 11757 options = (thisPref == 'prefCaptionsRate') ? [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3] : options; 11758 options = (thisPref == 'prefCaptionsVolume') ? [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] : options; 11759 if ( options ) { 11760 for (j=0; j < options.length; j++) { 11761 optionValue = options[j]; 11762 optionText = this.makePrefsValueReadable(thisPref,optionValue); 11763 $thisOption = $('<option>',{ 11764 value: optionValue, 11765 text: optionText 11766 }); 11767 if (this[thisPref] == optionValue) { 11768 $thisOption.prop('selected',true); 11769 } 11770 $thisField.append($thisOption); 11771 } 11772 // add a change handler that announces the sample text 11773 $thisField.on('change',function() { 11774 let captionSample = thisObj.translate( 'sampleCaptionText', 'Sample caption text' ); 11775 thisObj.announceText('captionSample',captionSample); 11776 }); 11777 $thisDiv.append($thisLabel,$thisField); 11778 } 11779 } 11780 } 11781 } 11782 } 11783 } else if (form === 'descriptions') { 11784 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11785 if (thisPref === 'prefDescPause' || thisPref === 'prefDescVisible') { 11786 // these preferences are checkboxes 11787 $thisDiv.addClass('able-prefs-checkbox'); 11788 $thisField = $('<input>',{ 11789 type: 'checkbox', 11790 name: thisPref, 11791 id: thisId, 11792 value: 'true' 11793 }); 11794 // check current active value for this preference 11795 if (this[thisPref] === 1) { 11796 $thisField.prop('checked',true); 11797 } 11798 $thisDiv.append($thisField,$thisLabel); 11799 } else if (this.synth) { 11800 // Only show these options if browser supports speech synthesis 11801 $thisDiv.addClass('able-prefs-select'); 11802 $thisField = $('<select>',{ 11803 name: thisPref, 11804 id: thisId, 11805 }); 11806 if ( thisPref === 'prefDescVoice' && this.descVoices.length) { 11807 prefDescVoice = this.getPrefVoice(); 11808 for (j=0; j < this.descVoices.length; j++) { 11809 optionValue = this.descVoices[j].name; 11810 optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase(); 11811 optionText = optionValue + ' (' + this.descVoices[j].lang + ')'; 11812 $thisOption = $('<option>',{ 11813 'value': optionValue, 11814 'data-lang': optionLang, 11815 text: optionText 11816 }); 11817 if (prefDescVoice === optionValue) { 11818 $thisOption.prop('selected',true); 11819 } 11820 $thisField.append($thisOption); 11821 } 11822 this.$voiceSelectField = $thisField; 11823 } else { 11824 if (thisPref == 'prefDescPitch') { // 0 to 2 11825 options = [0,0.5,1,1.5,2]; 11826 } else if (thisPref == 'prefDescRate') { // 0.1 to 10 11827 // Tests with a variety of voices on MacOS and Windows 11828 // yielded the following choices that seem reasonable for audio description: 11829 // 0.5 - too slow (exclude this) 11830 // 0.7 - casual 11831 // 0.8 - add this 11832 // 0.9 - add this 11833 // 1 - normal 11834 // 1.1 - add this 11835 // 1.2 - add this 11836 // 1.5 - quick 11837 // 2 - speedy 11838 // 2.5 - fleet 11839 // 3 - fast! (some voices don't get any faster than this 11840 11841 // Note: if these values are modified, must also modfiy them 11842 // in makePrefsValueReadable() 11843 options = [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3]; 11844 } else if (thisPref == 'prefDescVolume') { // 0 (mute) to 1 11845 options = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]; 11846 } 11847 if (typeof options !== 'undefined') { 11848 for (j=0; j < options.length; j++) { 11849 optionValue = options[j]; 11850 optionText = this.makePrefsValueReadable(thisPref,optionValue); 11851 $thisOption = $('<option>',{ 11852 value: optionValue, 11853 text: optionText 11854 }); 11855 if (this[thisPref] == optionValue) { 11856 $thisOption.prop('selected',true); 11857 } 11858 $thisField.append($thisOption); 11859 $thisDiv.append($thisLabel,$thisField); 11860 } 11861 } 11862 } 11863 // add a change handler that announces the sample description text 11864 $thisField.on('change',function() { 11865 thisObj.announceText('sample',thisObj.currentSampleText); 11866 }); 11867 $thisDiv.append($thisLabel,$thisField); 11868 } 11869 } else { // all other fields are checkboxes 11870 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11871 $thisField = $('<input>',{ 11872 type: 'checkbox', 11873 name: thisPref, 11874 id: thisId, 11875 value: 'true' 11876 }); 11877 // check current active value for this preference 11878 if (this[thisPref] === 1) { 11879 $thisField.prop('checked',true); 11880 } 11881 if (form === 'keyboard') { 11882 // add a change handler that updates the list of current keyboard shortcuts 11883 $thisField.on('change',function() { 11884 changedPref = $(this).attr('name'); 11885 if (changedPref === 'prefAltKey') { 11886 changedSpan = '.able-modkey-alt'; 11887 changedText = thisObj.translate( 'prefAltKey', 'Alt' ) + ' + '; 11888 } else if (changedPref === 'prefCtrlKey') { 11889 changedSpan = '.able-modkey-ctrl'; 11890 changedText = thisObj.translate( 'prefCtrlKey', 'Control' ) + ' + '; 11891 } else if (changedPref === 'prefShiftKey') { 11892 changedSpan = '.able-modkey-shift'; 11893 changedText = thisObj.translate( 'prefShiftKey', 'Shift' ) + ' + '; 11894 } 11895 if ( changedPref !== 'prefNoKeyShortcuts' ) { 11896 if ($(this).is(':checked')) { 11897 $(changedSpan).text(changedText); 11898 } else { 11899 $(changedSpan).text(''); 11900 } 11901 } else { 11902 if ($(this).is(':checked')) { 11903 $('.able-modkey-item').addClass('hidden'); 11904 } else { 11905 $('.able-modkey-item').removeClass('hidden'); 11906 } 11907 } 11908 }); 11909 } 11910 $thisDiv.append($thisField,$thisLabel); 11911 } 11912 if (thisPref === 'prefDescVoice' && !this.descVoices.length) ; else { 11913 $fieldset.append($thisDiv); 11914 } 11915 } 11916 } 11917 $prefsDiv.append($fieldset); 11918 11919 if (form === 'captions') { 11920 // add a sample closed caption div to prefs dialog 11921 // do not show this for YouTube captions, since it's not an accurate reflection 11922 if (!this.usingYouTubeCaptions) { 11923 this.$sampleCapsDiv = $('<div>',{ 11924 'class': 'able-captions-sample' 11925 }).text( this.translate( 'sampleCaptionText', 'Sample caption text' ) ); 11926 $prefsDiv.append(this.$sampleCapsDiv); 11927 this.stylizeCaptions(this.$sampleCapsDiv); 11928 } 11929 } else if (form === 'descriptions') { 11930 if (this.synth) { 11931 // add a div with sample audio description text 11932 this.$sampleDescDiv = $('<div>',{ 11933 'class': 'able-desc-sample' 11934 }).text( this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) ); 11935 $prefsDiv.append(this.$sampleDescDiv); 11936 this.currentSampleText = this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ); 11937 } 11938 } else if (form === 'keyboard') { 11939 let shortcutClass = (this.prefNoKeyShortcuts === 1 ) ? 'able-modkey-item hidden' : 'able-modkey-item'; 11940 11941 // add a current list of keyboard shortcuts 11942 $kbHeading = $('<h2>',{ 11943 text: this.translate( 'prefHeadingKeyboard2', 'Current keyboard shortcuts' ) 11944 }); 11945 $kbList = $('<ul>'); 11946 // create arrays of kbLabels and keys 11947 kbLabels = []; 11948 keys = []; 11949 for (i=0; i<this.controls.length; i++) { 11950 if (this.controls[i] === 'play') { 11951 kbLabels.push( this.translate( 'play', 'Play' ) + '/' + this.translate( 'pause', 'Pause' ) ); 11952 keys.push('p</span>, <span class="able-help-modifiers"> ' + this.translate( 'spacebar', 'spacebar' )); 11953 } else if (this.controls[i] === 'restart') { 11954 kbLabels.push(this.translate( 'restart', 'Restart' )); 11955 keys.push('s'); 11956 } else if (this.controls[i] === 'previous') { 11957 kbLabels.push( this.translate( 'prevTrack', 'Previous track' ) ); 11958 keys.push('b'); // b = back 11959 } else if (this.controls[i] === 'next') { 11960 kbLabels.push( this.translate( 'nextTrack', 'Next track' ) ); 11961 keys.push('n'); 11962 } else if (this.controls[i] === 'rewind') { 11963 kbLabels.push(this.translate( 'rewind', 'Rewind' )); 11964 keys.push('r'); 11965 } else if (this.controls[i] === 'forward') { 11966 kbLabels.push(this.translate( 'forward', 'Forward' )); 11967 keys.push('f'); 11968 } else if (this.controls[i] === 'volume') { 11969 kbLabels.push(this.translate( 'volume', 'Volume' )); 11970 keys.push('v</span>,' + ' <span class="able-modkey">1-9'); 11971 // mute toggle 11972 kbLabels.push(this.translate( 'mute', 'Mute' ) + '/' + this.translate( 'unmute', 'Unmute' )); 11973 keys.push('m'); 11974 } else if (this.controls[i] === 'captions') { 11975 if (this.captions.length > 1) { 11976 // caption button launches a Captions popup menu 11977 kbLabels.push(this.translate( 'captions', 'Captions' )); 11978 } else { 11979 // there is only one caption track 11980 // therefore caption button is a toggle 11981 if (this.captionsOn) { 11982 kbLabels.push(this.translate( 'hideCaptions', 'Hide captions' )); 11983 } else { 11984 kbLabels.push(this.translate( 'showCaptions', 'Show captions' )); 11985 } 11986 } 11987 keys.push('c'); 11988 } else if (this.controls[i] === 'descriptions') { 11989 if (this.descOn) { 11990 kbLabels.push(this.translate( 'turnOffDescriptions', 'Turn off descriptions' )); 11991 } else { 11992 kbLabels.push(this.translate( 'turnOnDescriptions', 'Turn on descriptions' )); 11993 } 11994 keys.push('d'); 11995 } else if (this.controls[i] === 'prefs') { 11996 kbLabels.push(this.translate( 'preferences', 'Preferences' )); 11997 keys.push('e'); 11998 } 11999 } 12000 for (i=0; i<keys.length; i++) { 12001 // alt 12002 kbListText = '<span class="able-modkey-alt">'; 12003 if (this.prefAltKey === 1) { 12004 kbListText += this.translate( 'prefAltKey', 'Alt' ) + ' + '; 12005 } 12006 kbListText += '</span>'; 12007 // ctrl 12008 kbListText += '<span class="able-modkey-ctrl">'; 12009 if (this.prefCtrlKey === 1) { 12010 kbListText += this.translate( 'prefCtrlKey', 'Control' ) + ' + '; 12011 } 12012 kbListText += '</span>'; 12013 // shift 12014 kbListText += '<span class="able-modkey-shift">'; 12015 if (this.prefShiftKey === 1) { 12016 kbListText += this.translate( 'prefShiftKey', 'Shift' ) + ' + '; 12017 } 12018 kbListText += '</span>'; 12019 kbListText += '<span class="able-modkey">' + keys[i] + '</span>'; 12020 kbListText += ' = ' + kbLabels[i]; 12021 $kbListItem = $('<li>',{ 12022 'class': shortcutClass, 12023 html: kbListText, 12024 }); 12025 $kbList.append($kbListItem); 12026 } 12027 // add Escape key 12028 kbListText = '<span class="able-modkey">' + this.translate( 'escapeKey', 'Escape' ) + '</span>'; 12029 kbListText += ' = ' + this.translate( 'escapeKeyFunction', 'Close current dialog or popup menu' ); 12030 $kbListItem = $('<li>',{ 12031 html: kbListText 12032 }); 12033 $kbList.append($kbListItem); 12034 // put it all together 12035 $prefsDiv.append($kbHeading,$kbList); 12036 } 12037 12038 // $prefsDiv (dialog) must be appended to the BODY! 12039 $('body').append($prefsDiv); 12040 dialog = new AccessibleDialog( 12041 $prefsDiv, 12042 this.$prefsButton, 12043 formTitle, 12044 thisObj.translate( 'closeButtonLabel', 'Close' ) 12045 ); 12046 12047 // Add save and cancel buttons. 12048 $buttonContainer = $( '<div class="able-prefs-buttons"></div>' ); 12049 $saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>'); 12050 $cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>'); 12051 $saveButton.on( 'click', function () { 12052 dialog.hide(); 12053 thisObj.savePrefsFromForm(); 12054 }); 12055 $cancelButton.on( 'click', function () { 12056 dialog.hide(); 12057 thisObj.resetPrefsForm(); 12058 }); 12059 $buttonContainer.append( $saveButton,$cancelButton ); 12060 $prefsDiv.append($buttonContainer); 12061 // Associate the dialog's H1 as aria-labelledby for groups of fields 12062 // (alternative to fieldset and legend) 12063 if (form === 'captions' || form === 'transcript') { 12064 $fieldset.attr('aria-labelledby',dialog.titleH1.attr('id')); 12065 } 12066 12067 // add global reference for future control 12068 if (form === 'captions') { 12069 this.captionPrefsDialog = dialog; 12070 } else if (form === 'descriptions') { 12071 this.descPrefsDialog = dialog; 12072 } else if (form === 'keyboard') { 12073 this.keyboardPrefsDialog = dialog; 12074 } else if (form === 'transcript') { 12075 this.transcriptPrefsDialog = dialog; 12076 } 12077 12078 // Add click handler for dialog close button 12079 // (button is added in dialog.js) 12080 $('div.able-prefs-form button.modalCloseButton').on( 'click', function() { 12081 thisObj.resetPrefsForm(); 12082 }); 12083 // Add handler for escape key 12084 $('div.able-prefs-form').on( 'keydown', function(e) { 12085 if (e.key === 'Escape') { 12086 thisObj.resetPrefsForm(); 12087 } 12088 }); 12089 }; 12090 12091 AblePlayer.prototype.getPrefVoice = function () { 12092 12093 // return user's preferred voice for the current language from preferences.voices 12094 var lang, preferences, i; 12095 12096 if (this.selectedDescriptions) { 12097 lang = this.selectedDescriptions.language; 12098 } else if (this.captionLang) { 12099 lang = this.captionLang; 12100 } else { 12101 lang = this.lang; 12102 } 12103 preferences = this.getPref(); 12104 if (preferences.voices) { 12105 for (i=0; i < preferences.voices.length; i++) { 12106 if (preferences.voices[i].lang === lang) { 12107 return preferences.voices[i].name; 12108 } 12109 } 12110 } 12111 return null; // user has no saved preference 12112 }; 12113 12114 AblePlayer.prototype.rebuildVoicePrefsForm = function ( field ) { 12115 12116 // Called if this.descVoices changes, which may happen if: 12117 // getBrowserVoices() succeeds after an earlier failure 12118 // user changes language of captions/subtitles and descVoices changes to match the new language 12119 12120 var i, optionValue, optionText, $thisOption; 12121 12122 this.$voiceSelectField = $('#' + this.mediaId + field); 12123 this.$voiceSelectField.empty(); 12124 for (i=0; i < this.descVoices.length; i++) { 12125 optionValue = this.descVoices[i].name; 12126 optionText = optionValue + ' (' + this.descVoices[i].lang + ')'; 12127 $thisOption = $('<option>',{ 12128 'value': optionValue, 12129 'data-lang': this.descVoices[i].lang.substring(0,2).toLowerCase(), 12130 text: optionText 12131 }); 12132 if (this.prefDescVoice == optionValue) { 12133 $thisOption.prop('selected',true); 12134 } 12135 this.$voiceSelectField.append($thisOption); 12136 } 12137 }; 12138 12139 AblePlayer.prototype.makePrefsValueReadable = function(pref,value) { 12140 12141 // The values for pitch, rate, and volume (web speech API) 12142 // are strange and inconsistent between variables 12143 // this function returns text that is more readable than the values themselves 12144 12145 if (pref === 'prefDescPitch' || pref === 'prefCaptionsPitch' ) { 12146 if (value === 0) { 12147 return this.translate( 'prefDescPitch1', 'Very low' ); 12148 } else if (value === 0.5) { 12149 return this.translate( 'prefDescPitch2', 'Low' ); 12150 } else if (value === 1) { 12151 return this.translate( 'prefDescPitch3', 'Default' ); 12152 } else if (value === 1.5) { 12153 return this.translate( 'prefDescPitch4', 'High' ); 12154 } else if (value === 2) { 12155 return this.translate( 'prefDescPitch5', 'Very high' ); 12156 } 12157 } else if (pref === 'prefDescRate' || pref === 'prefCaptionsRate' ) { 12158 // default in the API is 0.1 to 10, where 1 is normal speaking voice 12159 // our custom range offers several rates close to 1 12160 // plus a couple of crazy fast ones for sport 12161 // Our more readable options (1-10) or mapped here to API values 12162 if (value === 0.7) { 12163 return 1; 12164 } else if (value === 0.8) { 12165 return 2; 12166 } else if (value === 0.9) { 12167 return 3; 12168 } else if (value === 1) { 12169 return 4; 12170 } else if (value === 1.1) { 12171 return 5; 12172 } else if (value === 1.2) { 12173 return 6; 12174 } else if (value === 1.5) { 12175 return 7; 12176 } else if (value === 2) { 12177 return 8; 12178 } else if (value === 2.5) { 12179 return 9; 12180 } else if (value === 3) { 12181 return 10; 12182 } 12183 } else if (pref === 'prefDescVolume' || pref === 'prefCaptionsVolume' ) { 12184 // values range from 0.1 to 1.0 12185 return value * 100 + '%'; 12186 } 12187 return value; 12188 }; 12189 12190 AblePlayer.prototype.resetPrefsForm = function () { 12191 12192 // Reset preferences form with default values from preferences 12193 // Called when: 12194 // User clicks cancel or close button in Prefs Dialog 12195 // User presses Escape to close Prefs dialog 12196 // User clicks Save in Prefs dialog, & there's more than one player on page 12197 12198 var preferences, available, i, prefName; 12199 12200 preferences = this.getPref(); 12201 available = this.getAvailablePreferences(); 12202 for (i=0; i<available.length; i++) { 12203 prefName = available[i]['name']; 12204 if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 12205 // this is a caption-related select box 12206 $('select[name="' + prefName + '"]').val(preferences.preferences[prefName]); 12207 } else { // all others are checkboxes 12208 if (this[prefName] === 1) { 12209 $('input[name="' + prefName + '"]').prop('checked',true); 12210 } else { 12211 $('input[name="' + prefName + '"]').prop('checked',false); 12212 } 12213 } 12214 } 12215 // also restore style of sample caption div 12216 this.stylizeCaptions(this.$sampleCapsDiv); 12217 }; 12218 12219 AblePlayer.prototype.savePrefsFromForm = function () { 12220 12221 // Return a prefs object constructed from the form. 12222 // called when user saves the Preferences form 12223 // update preferences with new value 12224 var preferences, available, prefName, prefId, 12225 voiceSelectId, newVoice, numChanges, voiceLangFound, 12226 numCapChanges, capSizeChanged, capSizeValue, newValue; 12227 12228 numChanges = 0; 12229 numCapChanges = 0; // changes to caption-style-related preferences 12230 capSizeChanged = false; 12231 preferences = this.getPref(); 12232 available = this.getAvailablePreferences(); 12233 for (var i=0; i < available.length; i++) { 12234 // only prefs with labels are used in the Prefs form 12235 if (available[i]['label']) { 12236 prefName = available[i]['name']; 12237 prefId = this.mediaId + '_' + prefName; 12238 if (prefName === 'prefDescVoice') { 12239 if (typeof preferences.voices === 'undefined') { 12240 preferences.voices = []; 12241 } 12242 voiceSelectId = this.mediaId + '_prefDescVoice'; 12243 this.prefDescVoice = $('select#' + voiceSelectId).find(':selected').val(); 12244 this.prefDescVoiceLang = $('select#' + voiceSelectId).find(':selected').attr('data-lang'); 12245 // replace preferred voice for this lang in preferences.voices array, if one exists 12246 // otherwise, add it to the array 12247 voiceLangFound = false; 12248 for (var v=0; v < preferences.voices.length; v++) { 12249 if (preferences.voices[v].lang === this.prefDescVoiceLang) { 12250 voiceLangFound = true; 12251 preferences.voices[v].name = this.prefDescVoice; 12252 } 12253 } 12254 if (!voiceLangFound) { 12255 // no voice has been saved yet for this language. Add it to array. 12256 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 12257 preferences.voices.push(newVoice); 12258 } 12259 numChanges++; 12260 } else if (prefName == 'prefDescMethod') { 12261 // As of v4.0.10, prefDescMethod is no longer a choice 12262 this.prefDescMethod = 'video'; 12263 if (this.prefDescMethod !== preferences.preferences['prefDescMethod']) { // user's preference has changed 12264 preferences.preferences['prefDescMethod'] = this.prefDescMethod; 12265 numChanges++; 12266 } 12267 } else if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 12268 // this is one of the caption-related select fields 12269 newValue = $('select[id="' + prefId + '"]').val(); 12270 if (preferences.preferences[prefName] !== newValue) { // user changed setting 12271 preferences.preferences[prefName] = newValue; 12272 // also update global var for this pref (for caption fields, not done elsewhere) 12273 this[prefName] = newValue; 12274 numChanges++; 12275 numCapChanges++; 12276 } 12277 if (prefName === 'prefCaptionsSize') { 12278 capSizeChanged = true; 12279 capSizeValue = newValue; 12280 } 12281 } else if ((prefName.indexOf('Desc') !== -1) && (prefName !== 'prefDescPause') && prefName !== 'prefDescVisible') { 12282 // this is one of the description-related select fields 12283 newValue = $('select[id="' + prefId + '"]').val(); 12284 if (preferences.preferences[prefName] !== newValue) { // user changed setting 12285 preferences.preferences[prefName] = newValue; 12286 // also update global var for this pref 12287 this[prefName] = newValue; 12288 numChanges++; 12289 } 12290 } else { // all other fields are checkboxes 12291 if ($('input[id="' + prefId + '"]').is(':checked')) { 12292 preferences.preferences[prefName] = 1; 12293 if (this[prefName] === 1) ; else { 12294 // user has just turned this pref on 12295 this[prefName] = 1; 12296 numChanges++; 12297 } 12298 } else { // thisPref is not checked 12299 preferences.preferences[prefName] = 0; 12300 if (this[prefName] === 1) { 12301 // user has just turned this pref off 12302 this[prefName] = 0; 12303 numChanges++; 12304 } 12305 } 12306 } 12307 } 12308 } 12309 if (numChanges > 0) { 12310 this.setPrefs(preferences); 12311 this.showAlert( this.translate( 'prefSuccess', 'Your changes have been saved.' ) ); 12312 } else { 12313 this.showAlert( this.translate( 'prefNoChange', "You didn't make any changes" ) ); 12314 } 12315 if (this.player === 'youtube' && 12316 (typeof this.usingYouTubeCaptions !== 'undefined' && this.usingYouTubeCaptions) && 12317 capSizeChanged) { 12318 // update font size of YouTube captions 12319 this.youTubePlayer.setOption('captions','fontSize',this.translatePrefs('size',capSizeValue,'youtube')); 12320 } 12321 if (!AblePlayer.hasSingleInstance()) { 12322 // there are multiple players on this page. 12323 // update prefs for ALL of them 12324 for (const instance of AblePlayer.ablePlayerInstances) { 12325 instance.updatePlayerPrefs(); 12326 instance.loadCurrentPreferences(); 12327 instance.resetPrefsForm(); 12328 if (numCapChanges > 0) { 12329 instance.stylizeCaptions(instance.$captionsDiv); 12330 // also apply same changes to descriptions, if present 12331 if (typeof instance.$descDiv !== 'undefined') { 12332 instance.stylizeCaptions(instance.$descDiv); 12333 } 12334 } 12335 } 12336 } else { 12337 // there is only one player 12338 this.updatePlayerPrefs(); 12339 if (numCapChanges > 0) { 12340 this.stylizeCaptions(this.$captionsDiv); 12341 // also apply same changes to descriptions, if present 12342 if (typeof this.$descDiv !== 'undefined') { 12343 this.stylizeCaptions(this.$descDiv); 12344 } 12345 } 12346 } 12347 }; 12348 12349 AblePlayer.prototype.updatePlayerPrefs = function () { 12350 12351 // Update player based on current prefs. Safe to call multiple times. 12352 if (this.$transcriptDiv) { 12353 // tabbable transcript 12354 if (this.prefTabbable === 1) { 12355 this.$transcriptDiv.find('span.able-transcript-seekpoint').attr('tabindex','0'); 12356 } else { 12357 this.$transcriptDiv.find('span.able-transcript-seekpoint').removeAttr('tabindex'); 12358 } 12359 12360 // transcript highlights 12361 if (this.prefHighlight === 0) { 12362 // user doesn't want highlights; remove any existing highlights 12363 this.$transcriptDiv.find('span').removeClass('able-highlight'); 12364 } 12365 } 12366 12367 // Re-initialize caption and description in case relevant settings have changed 12368 this.updateCaption(); 12369 this.initDescription(); 12370 }; 12371 12372 AblePlayer.prototype.usingModifierKeys = function(e) { 12373 12374 // return true if user is holding down required modifier keys 12375 if ((this.prefAltKey === 1) && !e.altKey) { 12376 return false; 12377 } 12378 if ((this.prefCtrlKey === 1) && !e.ctrlKey) { 12379 return false; 12380 } 12381 if ((this.prefShiftKey === 1) && !e.shiftKey) { 12382 return false; 12383 } 12384 return true; 12385 }; 12386 } 12387 12388 function addSearchFunctions(AblePlayer) { 12389 AblePlayer.prototype.showSearchResults = function () { 12390 // search VTT file for all instances of searchTerms 12391 // Currently just supports search terms separated with one or more spaces 12392 12393 // TODO: Add support for more robust search syntax: 12394 // Search terms wrapped in quotation marks ("") must occur exactly as they appear in the quotes 12395 // Search terms with an attached minus sign (e.g., -term) are to be excluded from results 12396 // Boolean AND/OR operators 12397 // ALSO: Add localization support 12398 12399 var thisObj = this; 12400 if (this.searchDiv && this.searchString) { 12401 // sanitize search string 12402 var cleanSearchString = purify.sanitize(this.searchString); 12403 if ($("#" + this.SearchDiv)) { 12404 var searchStringHtml = "<p>" + this.translate( 'resultsSummary1', 'You searched for:') + ' '; 12405 searchStringHtml += 12406 '<span id="able-search-term-echo">' + cleanSearchString + "</span>"; 12407 searchStringHtml += "</p>"; 12408 var resultsArray = this.searchFor( 12409 cleanSearchString, 12410 this.searchIgnoreCaps 12411 ); 12412 if (resultsArray.length > 0) { 12413 var $resultsSummary = $("<p>", { 12414 class: "able-search-results-summary", 12415 }); 12416 var resultsSummaryText = this.translate( 'resultsSummary2', 'Found %1 matching items.', [ '<strong>' + resultsArray.length + '</strong>' ] ); 12417 resultsSummaryText += ' ' + this.translate( 'resultsSummary3', 'Click the time associated with any item to play the video from that point.' ); 12418 $resultsSummary.html( resultsSummaryText ); 12419 var $resultsList = $("<ul>"); 12420 for (var i = 0; i < resultsArray.length; i++) { 12421 var resultId = "aria-search-result-" + i; 12422 var $resultsItem = $("<li>", {}); 12423 var itemStartTime = this.secondsToTime(resultsArray[i]["start"]); 12424 var itemLabel = 12425 this.translate( 'searchButtonLabel', 'Play at %1', [ itemStartTime["title"] ] ); 12426 var itemStartSpan = $("<button>", { 12427 class: "able-search-results-time", 12428 "data-start": resultsArray[i]["start"], 12429 "aria-label": itemLabel, 12430 "aria-describedby": resultId, 12431 }); 12432 itemStartSpan.text(itemStartTime["value"]); 12433 // add a listener for clisk on itemStart 12434 itemStartSpan.on("click", function (e) { 12435 thisObj.seekTrigger = "search"; 12436 var spanStart = parseFloat($(this).attr("data-start")); 12437 // Add a tiny amount so that we're inside the span. 12438 spanStart += 0.01; 12439 thisObj.seeking = true; 12440 thisObj.seekTo(spanStart); 12441 }); 12442 12443 var itemText = $("<span>", { 12444 class: "able-search-result-text", 12445 id: resultId, 12446 }); 12447 itemText.html('...' + resultsArray[i]["caption"] + '...'); 12448 $resultsItem.append(itemStartSpan, itemText); 12449 $resultsList.append($resultsItem); 12450 } 12451 $('#' + this.searchDiv) 12452 .html(searchStringHtml) 12453 .append($resultsSummary, $resultsList); 12454 } else { 12455 var noResults = $('<p>').text( this.translate( 'noResultsFound', 'No results found.' ) ); 12456 $('#' + this.searchDiv) 12457 .html(searchStringHtml) 12458 .append(noResults); 12459 } 12460 } 12461 } 12462 }; 12463 12464 AblePlayer.prototype.searchFor = function (searchString, ignoreCaps) { 12465 // return chronological array of caption cues that match searchTerms 12466 var captionLang, captions, results, caption, c, i, j; 12467 results = []; 12468 // split searchTerms into an array 12469 var searchTerms = searchString.split(" "); 12470 if (this.captions.length > 0) { 12471 // Get caption track that matches this.searchLang 12472 for (i = 0; i < this.captions.length; i++) { 12473 if (this.captions[i].language === this.searchLang) { 12474 captionLang = this.searchLang; 12475 captions = this.captions[i].cues; 12476 } 12477 } 12478 if (captions.length > 0) { 12479 c = 0; 12480 for (i = 0; i < captions.length; i++) { 12481 if ( 12482 $.inArray(captions[i].components.children[0]["type"], [ 12483 "string", 12484 "i", 12485 "b", 12486 "u", 12487 "v", 12488 "c", 12489 ]) !== -1 12490 ) { 12491 caption = this.flattenCueForCaption(captions[i]); 12492 var captionNormalized = ignoreCaps 12493 ? caption.toLowerCase() 12494 : caption; 12495 for (j = 0; j < searchTerms.length; j++) { 12496 var searchTermNormalized = ignoreCaps 12497 ? searchTerms[j].toLowerCase() 12498 : searchTerms[j]; 12499 if (captionNormalized.indexOf(searchTermNormalized) !== -1) { 12500 results[c] = []; 12501 results[c]["start"] = captions[i].start; 12502 results[c]["lang"] = captionLang; 12503 results[c]["caption"] = this.highlightSearchTerm( 12504 searchTerms, 12505 caption 12506 ); 12507 c++; 12508 break; 12509 } 12510 } 12511 } 12512 } 12513 } 12514 } 12515 return results; 12516 }; 12517 12518 AblePlayer.prototype.highlightSearchTerm = function ( 12519 searchTerms, 12520 resultString 12521 ) { 12522 // highlight ALL found searchTerms in the current resultString 12523 // Need to step through the remaining terms to see if they're present as well 12524 searchTerms.forEach(function (searchTerm) { 12525 var reg = new RegExp(searchTerm, "gi"); 12526 resultString = resultString.replace( 12527 reg, 12528 '<span class="able-search-term">$&</span>' 12529 ); 12530 }); 12531 return resultString; 12532 }; 12533 12534 /** 12535 * Convert a number of seconds into readable time information. 12536 * 12537 * @param {int} totalSecondsFloat 12538 * 12539 * @returns {string[]} array 'value' HH:MM:SS and 'title' speakable time. 12540 */ 12541 AblePlayer.prototype.secondsToTime = function (totalSecondsFloat) { 12542 // return an array of totalSeconds converted into two formats 12543 // time['value'] = HH:MM:SS with hours dropped if there are none 12544 // time['title'] = a speakable rendering, so speech rec users can easily speak the link 12545 var totalSeconds = Math.floor(totalSecondsFloat); 12546 12547 var h = parseInt(totalSeconds / 3600, 10) % 24; 12548 var m = parseInt(totalSeconds / 60, 10) % 60; 12549 var s = totalSeconds % 60; 12550 var value = ''; 12551 var title = ''; 12552 if (h > 0) { 12553 value += String(h).padStart(2, '0') + ':'; 12554 title += h > 0 ? h + ' ' + (h === 1 ? this.translate( 'hour', 'hour' ) : this.translate( 'hours', 'hours' ) ) : ''; 12555 } 12556 12557 value += String(m).padStart(2, '0') + ':'; 12558 title += m > 0 ? m + ' ' + (m === 1 ? this.translate( 'minute', 'minute' ) : this.translate( 'minutes', 'minutes' ) ) : ''; 12559 12560 value += String(s).padStart(2, '0'); 12561 title += s > 0 ? s + ' ' + (s === 1 ? this.translate( 'second', 'second' ) : this.translate( 'seconds', 'seconds' ) ) : ''; 12562 12563 var time = []; 12564 time["value"] = value; 12565 time["title"] = title; 12566 12567 return time; 12568 }; 12569 } 12570 12571 /* global YT */ 12572 12573 function addSignFunctions(AblePlayer) { 12574 AblePlayer.prototype.initSignLanguage = function() { 12575 this.hasSignLanguage = false; 12576 // Sign language is only currently supported in HTML5 player and YouTube. 12577 var hasLocalSrc = ( this.$sources.first().attr('data-sign-src') !== undefined && this.$sources.first().attr('data-sign-src') !== "" ); 12578 // YouTube src can either be on a `source` element or on the `video` element. 12579 var hasRemoteSrc = ( this.$media.data('youtube-sign-src') !== undefined && this.$media.data('youtube-sign-src') !== "" ); 12580 var hasRemoteSource = ( this.$sources.first().attr('data-youtube-sign-src') !== undefined && this.$sources.first().attr('data-youtube-sign-src') !== '' ); 12581 if ( ! this.isIOS() && ( hasLocalSrc || hasRemoteSrc || hasRemoteSource ) && ( this.player === 'html5' || this.player === 'youtube' ) ) { 12582 // check to see if there's a sign language video accompanying this video 12583 // check only the first source 12584 // If sign language is provided, it must be provided for all sources 12585 let ytSignSrc = this.youTubeSignId ?? purify.sanitize( this.$sources.first().attr('data-youtube-sign-src') ); 12586 let signSrc = purify.sanitize( this.$sources.first().attr('data-sign-src') ); 12587 let signVideo = purify.sanitize( this.$media.data('youtube-sign-src') ); 12588 this.signFile = (hasLocalSrc ) ? signSrc : false; 12589 if ( hasRemoteSrc ) { 12590 this.signYoutubeId = signVideo; 12591 } else if ( hasRemoteSource ) { 12592 this.signYoutubeId = ytSignSrc; 12593 } 12594 if ( this.signFile || this.signYoutubeId ) { 12595 if (this.isIOS()) { 12596 // iOS does not allow multiple videos to play simultaneously 12597 // Therefore, sign language as rendered by Able Player unfortunately won't work 12598 if (this.debug) { 12599 console.log('Sign language has been disabled due to iOS restrictions'); 12600 } 12601 } else { 12602 if (this.debug) { 12603 console.log('This video has an accompanying sign language video: ' + this.signFile); 12604 } 12605 this.hasSignLanguage = true; 12606 this.injectSignPlayerCode(); 12607 } 12608 } 12609 } 12610 }; 12611 12612 AblePlayer.prototype.injectSignPlayerCode = function() { 12613 12614 // create and inject surrounding HTML structure 12615 var signVideoId, i, signSrc, srcType, $signSource; 12616 12617 signVideoId = this.mediaId + '-sign'; 12618 12619 if ( this.signFile || this.signYoutubeId ) { 12620 if ( null !== this.$signDivLocation ) { 12621 this.$signDivLocation.addClass( 'able-sign-window able-fixed' ); 12622 this.$signWindow = this.$signDivLocation; 12623 } else { 12624 this.$signWindow = $('<div>',{ 12625 'class' : 'able-sign-window', 12626 'role': 'dialog', 12627 'aria-label': this.translate( 'sign', 'Sign language' ) 12628 }); 12629 this.$signToolbar = $('<div>',{ 12630 'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls' 12631 }); 12632 this.$signWindow.append(this.$signToolbar); 12633 } 12634 12635 this.$ableWrapper.append(this.$signWindow); 12636 } 12637 12638 if ( this.signFile ) { 12639 this.$signVideo = $('<video>',{ 12640 'id' : signVideoId, 12641 'tabindex' : '-1', 12642 'muted' : true, 12643 }); 12644 this.signVideo = this.$signVideo[0]; 12645 12646 if ( this.signFile ) { 12647 $signSource = $('<source>',{ 12648 'src' : this.signFile, 12649 'type' : 'video/' + this.signFile.substr(-3) 12650 }); 12651 this.$signVideo.append($signSource); 12652 } else { 12653 // for each original <source>, add a <source> to the sign <video> 12654 for (i=0; i < this.$sources.length; i++) { 12655 signSrc = purify.sanitize( this.$sources[i].getAttribute('data-sign-src') ); 12656 srcType = this.$sources[i].getAttribute('type'); 12657 if (signSrc) { 12658 $signSource = $('<source>',{ 12659 'src' : signSrc, 12660 'type' : srcType 12661 }); 12662 this.$signVideo.append($signSource); 12663 } else { 12664 // source is missing a sign language version 12665 // can't include sign language 12666 this.hasSignLanguage = false; 12667 return; 12668 } 12669 } 12670 } 12671 this.$signWindow.append( this.$signVideo ); 12672 } else if ( this.signYoutubeId ) { 12673 this.signYoutube = this.initYouTubeSignPlayer(); 12674 } 12675 12676 // make it draggable 12677 if ( null === this.$signDivLocation ) { 12678 this.initDragDrop('sign'); 12679 } 12680 12681 if (this.prefSign === 1) { 12682 // sign window is on. Go ahead and position it and show it 12683 if ( null === this.$signDivLocation ) { 12684 this.positionDraggableWindow('sign',this.getDefaultWidth('sign')); 12685 } 12686 } else { 12687 this.$signWindow.hide(); 12688 } 12689 }; 12690 12691 12692 AblePlayer.prototype.initYouTubeSignPlayer = function () { 12693 12694 var thisObj, deferred, promise; 12695 thisObj = this; 12696 deferred = new this.defer(); 12697 promise = deferred.promise(); 12698 12699 this.youTubeSignPlayerReady = false; 12700 12701 if (AblePlayer.youTubeIframeAPIReady) { 12702 // Script already loaded and ready. 12703 thisObj.finalizeYoutubeSignInit().then(function() { 12704 deferred.resolve(); 12705 }); 12706 } else { 12707 // Has another player already started loading the script? If so, abort... 12708 if ( ! AblePlayer.loadingYouTubeIframeAPI ) { 12709 thisObj.getScript('https://www.youtube.com/iframe_api', function () { 12710 console.log( 'YouTube API loaded' ); 12711 }); 12712 } 12713 12714 // Otherwise, keeping waiting for script load event... 12715 $('body').on('youTubeIframeAPIReady', function () { 12716 thisObj.finalizeYoutubeSignInit().then(function() { 12717 deferred.resolve(); 12718 }); 12719 }); 12720 } 12721 return promise; 12722 }; 12723 12724 AblePlayer.prototype.finalizeYoutubeSignInit = function () { 12725 12726 // This is called once we're sure the Youtube iFrame API is loaded -- see above 12727 var deferred, promise, thisObj, containerId, autoplay; 12728 12729 deferred = new this.defer(); 12730 promise = deferred.promise(); 12731 thisObj = this; 12732 containerId = this.mediaId + '_youtube_sign'; 12733 12734 this.$signWindow.append($('<div>').attr('id', containerId)); 12735 autoplay = (this.okToPlay) ? 1 : 0; 12736 12737 // Documentation https://developers.google.com/youtube/player_parameters 12738 this.youTubeSignPlayer = new YT.Player(containerId, { 12739 videoId: this.getYouTubeId(this.signYoutubeId), 12740 host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com', 12741 playerVars: { 12742 autoplay: autoplay, 12743 cc_lang_pref: this.captionLang, // set the caption language 12744 cc_load_policy: 0, 12745 controls: 0, // no controls, using our own 12746 disableKb: 1, // disable keyboard shortcuts, using our own 12747 enablejsapi: 1, 12748 hl: this.lang, // set the UI language to match Able Player 12749 iv_load_policy: 3, // do not show video annotations 12750 origin: window.location.origin, 12751 playsinline: this.playsInline, 12752 rel: 0, // when video ends, show only related videos from same channel (1 shows any) 12753 start: this.startTime 12754 }, 12755 events: { 12756 onReady: function (player) { 12757 player.target.mute(); 12758 player.target.unloadModule( 'captions' ); 12759 thisObj.youTubeSignPlayerReady = true; 12760 12761 deferred.resolve(); 12762 }, 12763 onError: function (e) { 12764 deferred.reject(); 12765 }, 12766 onStateChange: function (e) { 12767 thisObj.getPlayerState().then(function() { 12768 // no actions 12769 }); 12770 }, 12771 onApiChange: function() { 12772 // No actions 12773 }, 12774 onPlaybackQualityChange: function () { 12775 // no actions 12776 }, 12777 } 12778 }); 12779 12780 return promise; 12781 }; 12782 12783 } 12784 12785 function addTrackFunctions(AblePlayer) { 12786 // Loads files referenced in track elements, and performs appropriate setup. 12787 // For example, captions and text descriptions. 12788 // This will be called whenever the player is recreated. 12789 12790 AblePlayer.prototype.setupTracks = function () { 12791 var thisObj, deferred, promise, loadingPromises, loadingPromise, i, tracks, track, kind; 12792 12793 thisObj = this; 12794 12795 deferred = new this.defer(); 12796 promise = deferred.promise(); 12797 12798 loadingPromises = []; 12799 12800 if ($("#able-vts").length) { 12801 // Page includes a container for a VTS instance 12802 this.vtsTracks = []; 12803 this.hasVts = true; 12804 } else { 12805 this.hasVts = false; 12806 } 12807 12808 // Source array for populating the above arrays 12809 // varies, depending on whether there are dedicated description tracks 12810 if (this.hasDescTracks && this.descOn) { 12811 tracks = this.altTracks; 12812 } else { 12813 tracks = this.tracks; 12814 } 12815 for (i = 0; i < tracks.length; i++) { 12816 track = tracks[i]; 12817 kind = ( track.kind ) ? track.kind : 'subtitles'; 12818 12819 if (!track.src) { 12820 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 12821 // skip all the hullabaloo and go straight to setupCaptions 12822 thisObj.setupCaptions(track); 12823 } 12824 continue; 12825 } 12826 var trackSrc = track.src; 12827 loadingPromise = this.loadTextObject(trackSrc); 12828 loadingPromises.push( 12829 loadingPromise.catch(function (src) { 12830 console.warn("Failed to load captions track from " + src); 12831 }) 12832 ); 12833 loadingPromise.then( 12834 (function (track, kind) { 12835 trackSrc = track.src; 12836 var trackLang = track.language; 12837 var trackLabel = track.label; 12838 var trackDesc = track.desc; 12839 12840 return function (data) { 12841 var cues = thisObj.parseWebVTT(data).cues; 12842 if (thisObj.hasVts) { 12843 thisObj.setupVtsTracks( 12844 kind, 12845 trackLang, 12846 trackDesc, 12847 trackLabel, 12848 trackSrc, 12849 data.text 12850 ); 12851 } 12852 if (kind === 'captions' || kind === 'subtitles') { 12853 thisObj.setupCaptions(track, cues); 12854 } else if (kind === 'descriptions') { 12855 thisObj.setupDescriptions(track, cues); 12856 } else if (kind === 'chapters') { 12857 thisObj.setupChapters(track, cues); 12858 } else if (kind === 'metadata') { 12859 thisObj.setupMetadata(cues); 12860 } 12861 }; 12862 })(track, kind) 12863 ); 12864 } 12865 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 12866 deferred.resolve(); 12867 } else { 12868 $.when.apply($, loadingPromises).then(function () { 12869 deferred.resolve(); 12870 }); 12871 } 12872 return promise; 12873 }; 12874 12875 AblePlayer.prototype.getTracks = function () { 12876 // define an array tracks with the following structure: 12877 // kind - string, e.g. "captions", "descriptions" 12878 // src - string, URL of WebVTT source file 12879 // language - string, lang code 12880 // label - string to display, e.g., in CC menu 12881 // def - Boolean, true if this is the default track 12882 // cues - array with startTime, endTime, and payload 12883 // desc - Boolean, true if track includes a data-desc attribute 12884 12885 var thisObj, deferred, promise, trackLang, trackLabel, isDefault, forDesc, 12886 hasDefault, hasTrackInDefLang, trackFound, i; 12887 12888 thisObj = this; 12889 hasDefault = false; 12890 12891 deferred = new this.defer(); 12892 promise = deferred.promise(); 12893 12894 this.$tracks = this.$media.find('track'); 12895 this.tracks = []; // only includes tracks that do NOT have data-desc 12896 this.altTracks = []; // only includes tracks that DO have data-desc 12897 12898 // Arrays for each kind, to be populated later 12899 this.captions = []; 12900 this.descriptions = []; 12901 this.chapters = []; 12902 this.meta = []; 12903 12904 this.hasCaptionsTrack = false; // will change to true if one or more tracks has kind="captions" 12905 this.hasDescTracks = false; // will change to true if one or more tracks has data-desc 12906 12907 if (this.$tracks.length) { 12908 this.usingYouTubeCaptions = false; 12909 // create object from HTML5 tracks 12910 this.$tracks.each(function (index, element) { 12911 if ($(this).attr('kind') === 'captions') { 12912 thisObj.hasCaptionsTrack = true; 12913 } else if ($(this).attr('kind') === 'descriptions') { 12914 thisObj.hasClosedDesc = true; 12915 } 12916 12917 // srcLang should always be included with <track>, but HTML5 spec doesn't require it 12918 // if not provided, assume track is the same language as the default player language 12919 if ($(this).attr('srclang')) { 12920 trackLang = $(this).attr('srclang'); 12921 } else { 12922 trackLang = thisObj.lang; 12923 } 12924 if ($(this).attr('label')) { 12925 trackLabel = $(this).attr('label'); 12926 } else { 12927 trackLabel = thisObj.getLanguageName(trackLang); 12928 } 12929 12930 if (typeof $(this).attr('default') !== 'undefined' && !hasDefault) { 12931 isDefault = true; 12932 hasDefault = true; 12933 } else if (trackLang === thisObj.lang) { 12934 // this track is in the default lang of the player 12935 // if there is no other default track specified 12936 // this will be the default 12937 hasTrackInDefLang = true; 12938 isDefault = false; // for now; this could change if there's no default attribute 12939 } else { 12940 isDefault = false; 12941 } 12942 if (isDefault) { 12943 // this.captionLang will also be the default language for non-caption tracks 12944 thisObj.captionLang = trackLang; 12945 } 12946 12947 if ($(this).data("desc") !== undefined) { 12948 forDesc = true; 12949 thisObj.hasDescTracks = true; 12950 } else { 12951 forDesc = false; 12952 } 12953 if (forDesc) { 12954 thisObj.altTracks.push({ 12955 kind: $(this).attr('kind'), 12956 src: $(this).attr('src'), 12957 language: trackLang, 12958 label: trackLabel, 12959 def: isDefault, 12960 desc: forDesc, 12961 }); 12962 } else { 12963 thisObj.tracks.push({ 12964 kind: $(this).attr('kind'), 12965 src: $(this).attr('src'), 12966 language: trackLang, 12967 label: trackLabel, 12968 def: isDefault, 12969 desc: forDesc, 12970 }); 12971 } 12972 12973 if (index == thisObj.$tracks.length - 1) { 12974 // This is the last track. 12975 if (!hasDefault) { 12976 if (hasTrackInDefLang) { 12977 thisObj.captionLang = thisObj.lang; 12978 trackFound = false; 12979 i = 0; 12980 while (i < thisObj.tracks.length && !trackFound) { 12981 if (thisObj.tracks[i]['language'] === thisObj.lang) { 12982 thisObj.tracks[i]['def'] = true; 12983 trackFound = true; 12984 } 12985 i++; 12986 } 12987 } else { 12988 // use the first track 12989 thisObj.tracks[0]['def'] = true; 12990 thisObj.captionLang = thisObj.tracks[0]['language']; 12991 } 12992 } 12993 // Remove 'default' attribute from all <track> elements 12994 // This data has already been saved to this.tracks 12995 // and some browsers will display the default captions, 12996 // despite all standard efforts to suppress them 12997 thisObj.$media.find("track").removeAttr("default"); 12998 } 12999 }); 13000 } 13001 if (!this.$tracks.length || !this.hasCaptionsTrack) { 13002 // this media has no track elements 13003 // if this is a youtube or vimeo player, check there for captions/subtitles 13004 if (this.player === 'youtube') { 13005 this.getYouTubeCaptionTracks().then(function () { 13006 if (thisObj.hasCaptions) { 13007 thisObj.usingYouTubeCaptions = true; 13008 if (thisObj.$captionsWrapper) { 13009 thisObj.$captionsWrapper.remove(); 13010 } 13011 } 13012 deferred.resolve(); 13013 }); 13014 } else if (this.player === 'vimeo') { 13015 this.getVimeoCaptionTracks().then(function () { 13016 if (thisObj.hasCaptions) { 13017 thisObj.usingVimeoCaptions = true; 13018 if (thisObj.$captionsWrapper) { 13019 thisObj.$captionsWrapper.remove(); 13020 } 13021 } 13022 deferred.resolve(); 13023 }); 13024 } else { 13025 // this is neither YouTube nor Vimeo 13026 // there just ain't no tracks (captions or otherwise) 13027 this.hasCaptions = false; 13028 if (thisObj.$captionsWrapper) { 13029 thisObj.$captionsWrapper.remove(); 13030 } 13031 deferred.resolve(); 13032 } 13033 } else { 13034 // there is at least one track with kind="captions" 13035 deferred.resolve(); 13036 } 13037 return promise; 13038 }; 13039 13040 AblePlayer.prototype.setupCaptions = function (track, cues) { 13041 // Setup player for display of captions (one track at a time) 13042 var inserted, i, capLabel; 13043 13044 // Insert track into captions array 13045 // in its proper alphabetical sequence by label 13046 if (typeof cues === "undefined") { 13047 cues = null; 13048 } 13049 13050 if (this.usingYouTubeCaptions || this.usingVimeoCaptions) ; else { 13051 if (this.captions.length === 0) { 13052 // this is the first 13053 this.captions.push({ 13054 language: track.language, 13055 label: track.label, 13056 def: track.def, 13057 cues: cues, 13058 }); 13059 } else { 13060 // there are already captions in the array 13061 inserted = false; 13062 for (i = 0; i < this.captions.length; i++) { 13063 capLabel = track.label; 13064 if (capLabel.toLowerCase() < this.captions[i].label.toLowerCase()) { 13065 // insert before track i 13066 this.captions.splice(i, 0, { 13067 language: track.language, 13068 label: track.label, 13069 def: track.def, 13070 cues: cues, 13071 }); 13072 inserted = true; 13073 break; 13074 } 13075 } 13076 if (!inserted) { 13077 // just add track to the end 13078 this.captions.push({ 13079 language: track.language, 13080 label: track.label, 13081 def: track.def, 13082 cues: cues, 13083 }); 13084 } 13085 } 13086 } 13087 13088 // there are captions available 13089 this.hasCaptions = true; 13090 this.currentCaption = -1; 13091 if (this.prefCaptions === 1) { 13092 this.captionsOn = true; 13093 } else if (this.prefCaptions === 0) { 13094 this.captionsOn = false; 13095 } else { 13096 // user has no prefs. Use default state. 13097 if (this.defaultStateCaptions === 1) { 13098 this.captionsOn = true; 13099 } else { 13100 this.captionsOn = false; 13101 } 13102 } 13103 if (this.mediaType === 'audio' && this.captionsOn) { 13104 this.$captionsContainer.removeClass('captions-off'); 13105 } 13106 13107 if ( 13108 !this.$captionsWrapper || 13109 (this.$captionsWrapper && 13110 !$.contains(this.$ableDiv[0], this.$captionsWrapper[0])) 13111 ) { 13112 // captionsWrapper either doesn't exist, or exists in an orphaned state 13113 // Either way, it needs to be rebuilt... 13114 this.$captionsDiv = $('<div>', { 13115 class: "able-captions", 13116 }); 13117 this.$captionsWrapper = $('<div>', { 13118 class: 'able-captions-wrapper', 13119 'aria-hidden': 'true', 13120 }).hide(); 13121 if (this.prefCaptionsPosition === 'below') { 13122 this.$captionsWrapper.addClass('able-captions-below'); 13123 } else { 13124 this.$captionsWrapper.addClass('able-captions-overlay'); 13125 } 13126 this.$captionsWrapper.append(this.$captionsDiv); 13127 this.$captionsContainer.append(this.$captionsWrapper); 13128 } 13129 }; 13130 13131 AblePlayer.prototype.setupDescriptions = function (track, cues) { 13132 // called via setupTracks() only if there is track with kind="descriptions" 13133 // prepares for delivery of text description , in case it's needed 13134 // whether and how it's delivered is controlled within description.js > initDescription() 13135 13136 this.hasClosedDesc = true; 13137 this.currentDescription = -1; 13138 this.descriptions.push({ 13139 cues: cues, 13140 language: track.language, 13141 }); 13142 }; 13143 13144 AblePlayer.prototype.setupChapters = function (track, cues) { 13145 // NOTE: WebVTT supports nested timestamps (to form an outline) 13146 // This is not currently supported. 13147 13148 this.hasChapters = true; 13149 this.chapters.push({ 13150 cues: cues, 13151 language: track.language, 13152 }); 13153 }; 13154 13155 AblePlayer.prototype.setupMetadata = function (cues) { 13156 if (this.metaType === 'text') { 13157 // Metadata is only supported if data-meta-div is provided 13158 // The player does not display metadata internally 13159 if (this.metaDiv) { 13160 if ($('#' + this.metaDiv)) { 13161 // container exists 13162 this.$metaDiv = $('#' + this.metaDiv); 13163 this.hasMeta = true; 13164 this.meta = cues; 13165 } 13166 } 13167 } else if (this.metaType === 'selector') { 13168 this.hasMeta = true; 13169 this.visibleSelectors = []; 13170 this.meta = cues; 13171 } 13172 }; 13173 13174 AblePlayer.prototype.loadTextObject = function (src) { 13175 var deferred, promise, thisObj, $tempDiv; 13176 13177 deferred = new this.defer(); 13178 promise = deferred.promise(); 13179 thisObj = this; 13180 13181 // create a temp div for holding data 13182 $tempDiv = $('<div>', { 13183 style: 'display:none', 13184 }); 13185 13186 // Fetch the content manually so it can be sanitized 13187 fetch(src) 13188 .then( response => { 13189 13190 return response.text(); 13191 }) 13192 .then( vtt => { 13193 // Split the input on double line breaks to handle each cue individually. 13194 var preParsed = vtt.split(/\r?\n\s*\r?\n/); 13195 var lines = '', line; 13196 13197 preParsed.forEach((l) => { 13198 // Sanitize each line. 13199 line = validate.sanitizeVttContent(l); 13200 lines += line + "\n\n"; 13201 }); 13202 // Load the sanitized content into the $tempDiv 13203 $tempDiv.html(lines); 13204 // Resolve the promise with the sanitized content 13205 let data = { 'src': src, 'text': lines }; 13206 deferred.resolve(data); 13207 $tempDiv.remove(); 13208 }) 13209 .catch( error => { 13210 if (thisObj.debug) { 13211 console.log( "error reading file " + src + ": " + error ); 13212 } 13213 deferred.reject(src); 13214 $tempDiv.remove(); 13215 }); 13216 13217 return promise; 13218 }; 13219 } 13220 13221 function addTranscriptFunctions(AblePlayer) { 13222 AblePlayer.prototype.setupTranscript = function () { 13223 var deferred = new this.defer(); 13224 var promise = deferred.promise(); 13225 13226 if (this.usingYouTubeCaptions || this.usingVimeoCaptions || this.hideTranscriptButton ) { 13227 // a transcript is not possible or is disabled. 13228 this.transcriptType = null; 13229 deferred.resolve(); 13230 } else { 13231 if (!this.transcriptType) { 13232 // previously set transcriptType to null since there are no <track> elements 13233 // check again to see if captions have been collected from other sources (e.g., YouTube) 13234 13235 if (this.captions.length) { 13236 // captions are possible! Use the default type (popup) 13237 // if other types ('external' and 'manual') were desired, transcriptType would not be null here 13238 this.transcriptType = "popup"; 13239 } 13240 } 13241 if (this.transcriptType) { 13242 if ( this.transcriptType === "popup" || this.transcriptType === "external" ) { 13243 this.injectTranscriptArea(); 13244 deferred.resolve(); 13245 } else if (this.transcriptType === "manual") { 13246 this.setupManualTranscript(); 13247 deferred.resolve(); 13248 } 13249 } else { 13250 // there is no transcript 13251 deferred.resolve(); 13252 } 13253 } 13254 return promise; 13255 }; 13256 13257 AblePlayer.prototype.injectTranscriptArea = function () { 13258 var $autoScrollLabel, 13259 $autoScrollContainer, 13260 $languageSelectWrapper, 13261 $languageSelectLabel, 13262 i, 13263 $option; 13264 13265 this.$transcriptArea = $("<div>", { 13266 class: "able-transcript-area", 13267 role: "dialog", 13268 "aria-label": this.translate( 'transcriptTitle', 'Transcript' ), 13269 }); 13270 13271 this.$transcriptToolbar = $("<div>", { 13272 class: "able-window-toolbar able-" + this.toolbarIconColor + "-controls", 13273 }); 13274 13275 this.$transcriptDiv = $("<div>", { 13276 class: "able-transcript", 13277 }); 13278 13279 // Transcript toolbar content 13280 13281 // Add auto Scroll checkbox 13282 this.$autoScrollTranscriptCheckbox = $("<input>", { 13283 id: "autoscroll-transcript-checkbox-" + this.mediaId, 13284 type: "checkbox", 13285 }); 13286 $autoScrollLabel = $("<label>", { 13287 for: "autoscroll-transcript-checkbox-" + this.mediaId, 13288 }).text( this.translate( 'autoScroll', 'Auto scroll' ) ); 13289 $autoScrollContainer = $( '<div>', { 13290 'class': 'autoscroll-transcript' 13291 }); 13292 $autoScrollContainer.append( 13293 $autoScrollLabel, 13294 this.$autoScrollTranscriptCheckbox 13295 ); 13296 this.$transcriptToolbar.append( $autoScrollContainer ); 13297 13298 // Add field for selecting a transcript language 13299 // Only necessary if there is more than one language 13300 if (this.captions.length > 1) { 13301 $languageSelectWrapper = $("<div>", { 13302 class: "transcript-language-select-wrapper", 13303 }); 13304 $languageSelectLabel = $("<label>", { 13305 for: "transcript-language-select-" + this.mediaId, 13306 }).text( this.translate( 'language', 'Language' ) ); 13307 this.$transcriptLanguageSelect = $("<select>", { 13308 id: "transcript-language-select-" + this.mediaId, 13309 }); 13310 for (i = 0; i < this.captions.length; i++) { 13311 $option = $("<option></option>", { 13312 value: this.captions[i]["language"], 13313 lang: this.captions[i]["language"], 13314 }).text(this.captions[i]["label"]); 13315 if (this.captions[i]["def"]) { 13316 $option.prop("selected", true); 13317 } 13318 this.$transcriptLanguageSelect.append($option); 13319 } 13320 } 13321 if ($languageSelectWrapper) { 13322 $languageSelectWrapper.append( 13323 $languageSelectLabel, 13324 this.$transcriptLanguageSelect 13325 ); 13326 this.$transcriptToolbar.append($languageSelectWrapper); 13327 } 13328 this.$transcriptArea.append(this.$transcriptToolbar, this.$transcriptDiv); 13329 13330 // If client has provided separate transcript location, put it there. 13331 // Otherwise append it to the body 13332 if (this.transcriptDivLocation) { 13333 this.$transcriptArea.removeAttr( 'role' ); 13334 this.$transcriptArea.removeAttr( 'aria-label' ); 13335 $("#" + this.transcriptDivLocation).append(this.$transcriptArea); 13336 } else { 13337 this.$ableWrapper.append(this.$transcriptArea); 13338 } 13339 13340 // make it draggable (popup only; NOT external transcript) 13341 if (!this.transcriptDivLocation) { 13342 this.initDragDrop("transcript"); 13343 if (this.prefTranscript === 1) { 13344 // transcript is on. Go ahead and position it 13345 this.positionDraggableWindow( 13346 "transcript", 13347 this.getDefaultWidth("transcript") 13348 ); 13349 } 13350 } 13351 13352 // If client has provided separate transcript location, override user's preference for hiding transcript 13353 if (!this.prefTranscript && !this.transcriptDivLocation) { 13354 this.$transcriptArea.hide(); 13355 } 13356 }; 13357 13358 AblePlayer.prototype.addTranscriptAreaEvents = function () { 13359 var thisObj = this; 13360 13361 this.$autoScrollTranscriptCheckbox.on( 'click', function () { 13362 thisObj.handleTranscriptLockToggle( 13363 thisObj.$autoScrollTranscriptCheckbox.prop("checked") 13364 ); 13365 }); 13366 13367 this.$transcriptDiv.on( 13368 "mousewheel DOMMouseScroll click scroll", 13369 function (e) { 13370 // Propagation is stopped in transcript click handler, so clicks are on the scrollbar 13371 // or outside of a clickable span. 13372 if (!thisObj.scrollingTranscript) { 13373 thisObj.autoScrollTranscript = false; 13374 thisObj.refreshControls("transcript"); 13375 } 13376 thisObj.scrollingTranscript = false; 13377 } 13378 ); 13379 13380 if (typeof this.$transcriptLanguageSelect !== "undefined") { 13381 this.$transcriptLanguageSelect.on('click', function (e) { 13382 // execute default behavior 13383 // prevent propagation of mouse event to toolbar or window 13384 e.stopPropagation(); 13385 }); 13386 13387 this.$transcriptLanguageSelect.on("change", function () { 13388 var language = thisObj.$transcriptLanguageSelect.val(); 13389 13390 thisObj.syncTrackLanguages("transcript", language); 13391 }); 13392 } 13393 }; 13394 13395 AblePlayer.prototype.transcriptSrcHasRequiredParts = function () { 13396 // check the external transcript to be sure it has all required components 13397 // return true or false 13398 // in the process, define all the needed variables and properties 13399 13400 if ($("#" + this.transcriptSrc).length) { 13401 this.$transcriptArea = $("#" + this.transcriptSrc); 13402 if (this.$transcriptArea.find(".able-window-toolbar").length) { 13403 this.$transcriptToolbar = this.$transcriptArea 13404 .find(".able-window-toolbar") 13405 .eq(0); 13406 if (this.$transcriptArea.find(".able-transcript").length) { 13407 this.$transcriptDiv = this.$transcriptArea 13408 .find(".able-transcript") 13409 .eq(0); 13410 if (this.$transcriptArea.find(".able-transcript-seekpoint").length) { 13411 this.$transcriptSeekpoints = this.$transcriptArea.find( 13412 ".able-transcript-seekpoint" 13413 ); 13414 return true; 13415 } 13416 } 13417 } 13418 } 13419 return false; 13420 }; 13421 13422 AblePlayer.prototype.setupManualTranscript = function () { 13423 var $autoScrollInput, $autoScrollLabel; 13424 13425 $autoScrollInput = $("<input>", { 13426 id: "autoscroll-transcript-checkbox-" + this.mediaId, 13427 type: "checkbox", 13428 }); 13429 $autoScrollLabel = $("<label>", { 13430 for: "autoscroll-transcript-checkbox-" + this.mediaId, 13431 }).text( this.translate( 'autoScroll', 'Auto scroll' ) ); 13432 13433 // Add an auto-scroll checkbox to the toolbar. 13434 this.$autoScrollTranscriptCheckbox = $autoScrollInput; 13435 this.$transcriptToolbar.append( 13436 $autoScrollLabel, 13437 this.$autoScrollTranscriptCheckbox 13438 ); 13439 }; 13440 13441 AblePlayer.prototype.updateTranscript = function () { 13442 if (!this.transcriptType) { 13443 return; 13444 } 13445 if (this.playerCreated && !this.$transcriptArea) { 13446 return; 13447 } 13448 if (this.transcriptType === "external" || this.transcriptType === "popup") { 13449 var chapters, captions, descriptions; 13450 13451 // Language of transcript might be different than language of captions 13452 // But both are in sync by default 13453 if (this.transcriptLang) { 13454 captions = this.transcriptCaptions.cues; 13455 } else { 13456 if (this.transcriptCaptions) { 13457 this.transcriptLang = this.transcriptCaptions.language; 13458 captions = this.transcriptCaptions.cues; 13459 } else if (this.selectedCaptions) { 13460 this.transcriptLang = this.captionLang; 13461 captions = this.selectedCaptions.cues; 13462 } 13463 } 13464 13465 // setup chapters 13466 if (this.transcriptChapters) { 13467 chapters = this.transcriptChapters.cues; 13468 } else if (this.chapters.length > 0) { 13469 // Try and match the caption language. 13470 if (this.transcriptLang) { 13471 for (var i = 0; i < this.chapters.length; i++) { 13472 if (this.chapters[i].language === this.transcriptLang) { 13473 chapters = this.chapters[i].cues; 13474 } 13475 } 13476 } 13477 if (typeof chapters === "undefined") { 13478 chapters = this.chapters[0].cues || []; 13479 } 13480 } 13481 13482 // setup descriptions 13483 if (this.transcriptDescriptions) { 13484 descriptions = this.transcriptDescriptions.cues; 13485 } else if (this.descriptions.length > 0) { 13486 // Try and match the caption language. 13487 if (this.transcriptLang) { 13488 for (i = 0; i < this.descriptions.length; i++) { 13489 if (this.descriptions[i].language === this.transcriptLang) { 13490 descriptions = this.descriptions[i].cues; 13491 } 13492 } 13493 } 13494 if (!descriptions) { 13495 descriptions = this.descriptions[0].cues || []; 13496 } 13497 } 13498 13499 var div = this.generateTranscript( 13500 chapters || [], 13501 captions || [], 13502 descriptions || [] 13503 ); 13504 this.$transcriptDiv.html(div); 13505 // reset transcript selected <option> to this.transcriptLang 13506 if (this.$transcriptLanguageSelect) { 13507 this.$transcriptLanguageSelect 13508 .find("option:selected") 13509 .prop("selected", false); 13510 this.$transcriptLanguageSelect 13511 .find("option[lang=" + this.transcriptLang + "]") 13512 .prop("selected", true); 13513 } 13514 } 13515 13516 var thisObj = this; 13517 13518 // Make transcript tabbable if preference is turned on. 13519 if (this.prefTabbable === 1) { 13520 this.$transcriptDiv 13521 .find("span.able-transcript-seekpoint") 13522 .attr("tabindex", "0"); 13523 } 13524 13525 // handle clicks on text within transcript 13526 // Note: This event listeners handles clicks only, not keydown events 13527 // Pressing Enter on an element that is not natively clickable does NOT trigger click() 13528 // Keydown events are handled elsehwere, both globally (ableplayer-base.js) and locally (event.js) 13529 if (this.$transcriptArea.length > 0) { 13530 this.$transcriptArea 13531 .find("span.able-transcript-seekpoint") 13532 .on( 'click', function (e) { 13533 thisObj.seekTrigger = "transcript"; 13534 var spanStart = parseFloat($(this).attr("data-start")); 13535 // Add a tiny amount so that we're inside the span. 13536 spanStart += 0.01; 13537 // Each click within the transcript triggers two click events (not sure why) 13538 // this.seekingFromTranscript is a stopgab to prevent two calls to SeekTo() 13539 if (!thisObj.seekingFromTranscript) { 13540 thisObj.seekingFromTranscript = true; 13541 thisObj.seekTo(spanStart); 13542 } else { 13543 // don't seek a second time, but do reset var 13544 thisObj.seekingFromTranscript = false; 13545 } 13546 }); 13547 } 13548 }; 13549 13550 AblePlayer.prototype.highlightTranscript = function (currentTime) { 13551 // Show highlight in transcript marking current caption. 13552 13553 if (!this.transcriptType) { 13554 return; 13555 } 13556 13557 var start, end, isChapterHeading; 13558 var thisObj = this; 13559 13560 currentTime = parseFloat(currentTime); 13561 13562 // Highlight the current transcript item. 13563 this.$transcriptArea 13564 .find("span.able-transcript-seekpoint") 13565 .each(function () { 13566 start = parseFloat($(this).attr("data-start")); 13567 end = parseFloat($(this).attr("data-end")); 13568 // be sure this isn't a chapter (don't highlight chapter headings) 13569 if ($(this).parent().hasClass("able-transcript-chapter-heading")) { 13570 isChapterHeading = true; 13571 } else { 13572 isChapterHeading = false; 13573 } 13574 13575 if (currentTime >= start && currentTime <= end && !isChapterHeading) { 13576 // If this item isn't already highlighted, it should be 13577 if (!$(this).hasClass("able-highlight")) { 13578 // remove all previous highlights before adding one to current span 13579 thisObj.$transcriptArea 13580 .find(".able-highlight") 13581 .removeClass("able-highlight"); 13582 $(this).addClass("able-highlight"); 13583 thisObj.movingHighlight = true; 13584 } 13585 return false; 13586 } 13587 }); 13588 thisObj.currentHighlight = thisObj.$transcriptArea.find(".able-highlight"); 13589 if (thisObj.currentHighlight.length === 0) { 13590 // Nothing highlighted. 13591 thisObj.currentHighlight = null; 13592 } 13593 }; 13594 13595 AblePlayer.prototype.generateTranscript = function ( 13596 chapters, 13597 captions, 13598 descriptions 13599 ) { 13600 var thisObj = this; 13601 13602 var $main = $('<div class="able-transcript-container"></div>'); 13603 var transcriptTitle, firstStart; 13604 13605 // set language for transcript container 13606 $main.attr("lang", this.transcriptLang); 13607 13608 if (typeof this.transcriptTitle !== "undefined") { 13609 transcriptTitle = this.transcriptTitle; 13610 } else if (this.lyricsMode) { 13611 transcriptTitle = this.translate( 'lyricsTitle', 'Lyrics' ); 13612 } else { 13613 transcriptTitle = this.translate( 'transcriptTitle', 'Transcript' ); 13614 } 13615 13616 if (!this.transcriptDivLocation) { 13617 // only add an HTML heading to internal transcript 13618 // external transcript is expected to have its own heading 13619 var headingNumber = this.playerHeadingLevel; 13620 headingNumber += 1; 13621 var chapterHeadingNumber = headingNumber + 1; 13622 13623 let transcriptHeading; 13624 if (headingNumber <= 6) { 13625 transcriptHeading = "h" + headingNumber.toString(); 13626 } else { 13627 transcriptHeading = "div"; 13628 } 13629 var $transcriptHeadingTag = $("<" + transcriptHeading + ">"); 13630 $transcriptHeadingTag.addClass("able-transcript-heading"); 13631 if (headingNumber > 6) { 13632 $transcriptHeadingTag.attr({ 13633 role: "heading", 13634 "aria-level": headingNumber, 13635 }); 13636 } 13637 $transcriptHeadingTag.text(transcriptTitle); 13638 13639 // set language of transcript heading to language of player 13640 // this is independent of language of transcript 13641 $transcriptHeadingTag.attr("lang", this.lang); 13642 13643 $main.append($transcriptHeadingTag); 13644 } 13645 13646 var nextChapter = 0; 13647 var nextCap = 0; 13648 var nextDesc = 0; 13649 13650 var addChapter = function (div, chap) { 13651 let chapterHeading; 13652 if (chapterHeadingNumber <= 6) { 13653 chapterHeading = "h" + chapterHeadingNumber.toString(); 13654 } else { 13655 chapterHeading = "div"; 13656 } 13657 13658 var $chapterHeadingTag = $("<" + chapterHeading + ">", { 13659 class: "able-transcript-chapter-heading", 13660 }); 13661 if (chapterHeadingNumber > 6) { 13662 $chapterHeadingTag.attr({ 13663 role: "heading", 13664 "aria-level": chapterHeadingNumber, 13665 }); 13666 } 13667 13668 var flattenComponentForChapter = function (comp) { 13669 var result = []; 13670 if (comp.type === "string") { 13671 result.push(comp.value); 13672 } else { 13673 for (var i = 0; i < comp.children.length; i++) { 13674 result = result.concat( 13675 flattenComponentForChapter(comp.children[i]) 13676 ); 13677 } 13678 } 13679 return result; 13680 }; 13681 13682 var $chapSpan = $("<span>", { 13683 class: "able-transcript-seekpoint", 13684 }); 13685 for (var i = 0; i < chap.components.children.length; i++) { 13686 var results = flattenComponentForChapter(chap.components.children[i]); 13687 for (var jj = 0; jj < results.length; jj++) { 13688 $chapSpan.append(results[jj]); 13689 } 13690 } 13691 $chapSpan.attr("data-start", chap.start.toString()); 13692 $chapSpan.attr("data-end", chap.end.toString()); 13693 $chapterHeadingTag.append($chapSpan); 13694 13695 div.append($chapterHeadingTag); 13696 }; 13697 13698 var addDescription = function (div, desc) { 13699 var $descDiv = $("<div>", { 13700 class: "able-transcript-desc", 13701 }); 13702 var $descHiddenSpan = $("<span>", { 13703 class: "able-hidden", 13704 }); 13705 $descHiddenSpan.attr("lang", thisObj.lang); 13706 $descHiddenSpan.text( thisObj.translate( 'prefHeadingDescription', 'Audio description' ) + ": "); 13707 $descDiv.append($descHiddenSpan); 13708 13709 var flattenComponentForDescription = function (comp) { 13710 var result = []; 13711 if (comp.type === "string") { 13712 result.push(comp.value); 13713 } else { 13714 for (var i = 0; i < comp.children.length; i++) { 13715 result = result.concat( 13716 flattenComponentForDescription(comp.children[i]) 13717 ); 13718 } 13719 } 13720 return result; 13721 }; 13722 13723 var $descSpan = $("<span>", { 13724 class: "able-transcript-seekpoint", 13725 }); 13726 for (var i = 0; i < desc.components.children.length; i++) { 13727 var results = flattenComponentForDescription( 13728 desc.components.children[i] 13729 ); 13730 for (var jj = 0; jj < results.length; jj++) { 13731 $descSpan.append(results[jj]); 13732 } 13733 } 13734 $descSpan.attr("data-start", desc.start.toString()); 13735 $descSpan.attr("data-end", desc.end.toString()); 13736 $descDiv.append($descSpan); 13737 13738 div.append($descDiv); 13739 }; 13740 13741 var addCaption = function (div, cap) { 13742 var $capSpan = $("<span>", { 13743 class: "able-transcript-seekpoint able-transcript-caption", 13744 }); 13745 13746 var flattenComponentForCaption = function (comp) { 13747 var result = []; 13748 13749 var parts = 0; 13750 13751 var flattenString = function (str) { 13752 parts++; 13753 13754 var flatStr; 13755 var result = []; 13756 if (str === "") { 13757 return result; 13758 } 13759 13760 var openBracket = str.indexOf("["); 13761 var closeBracket = str.indexOf("]"); 13762 var openParen = str.indexOf("("); 13763 var closeParen = str.indexOf(")"); 13764 13765 var hasBrackets = openBracket !== -1 && closeBracket !== -1; 13766 var hasParens = openParen !== -1 && closeParen !== -1; 13767 13768 if (hasParens || hasBrackets) { 13769 let silentSpanBreak; 13770 if (parts > 1) { 13771 // force a line break between sections that contain parens or brackets 13772 silentSpanBreak = "<br/>"; 13773 } else { 13774 silentSpanBreak = ""; 13775 } 13776 var silentSpanOpen = 13777 silentSpanBreak + '<span class="able-unspoken">'; 13778 var silentSpanClose = "</span>"; 13779 if (hasParens && hasBrackets) { 13780 // string has both! 13781 if (openBracket < openParen) { 13782 // brackets come first. Parse parens separately 13783 hasParens = false; 13784 } else { 13785 // parens come first. Parse brackets separately 13786 hasBrackets = false; 13787 } 13788 } 13789 } 13790 if (hasParens) { 13791 flatStr = str.substring(0, openParen); 13792 flatStr += silentSpanOpen; 13793 flatStr += str.substring(openParen, closeParen + 1); 13794 flatStr += silentSpanClose; 13795 flatStr += flattenString(str.substring(closeParen + 1)); 13796 result.push(flatStr); 13797 } else if (hasBrackets) { 13798 flatStr = str.substring(0, openBracket); 13799 flatStr += silentSpanOpen; 13800 flatStr += str.substring(openBracket, closeBracket + 1); 13801 flatStr += silentSpanClose; 13802 flatStr += flattenString(str.substring(closeBracket + 1)); 13803 result.push(flatStr); 13804 } else { 13805 result.push(str); 13806 } 13807 return result; 13808 }; 13809 13810 if (comp.type === "string") { 13811 result = result.concat(flattenString(comp.value)); 13812 } else if (comp.type === "v") { 13813 var $vSpan = $("<span>", { 13814 class: "able-unspoken", 13815 }); 13816 // don't display "title=" when rendering the voice tag title in the transcript 13817 comp.value = comp.value.replace(/^title="|"$/g, ""); 13818 $vSpan.text("(" + comp.value + ")"); 13819 result.push($vSpan); 13820 for (var i = 0; i < comp.children.length; i++) { 13821 let subResults = flattenComponentForCaption(comp.children[i]); 13822 for (let jj = 0; jj < subResults.length; jj++) { 13823 result.push(subResults[jj]); 13824 } 13825 } 13826 } else if (comp.type === "b" || comp.type === "i") { 13827 let $tag; 13828 if (comp.type === "b") { 13829 $tag = $("<strong>"); 13830 } else if (comp.type === "i") { 13831 $tag = $("<em>"); 13832 } 13833 for (i = 0; i < comp.children.length; i++) { 13834 let subResults = flattenComponentForCaption(comp.children[i]); 13835 for (let jj = 0; jj < subResults.length; jj++) { 13836 $tag.append(subResults[jj]); 13837 } 13838 } 13839 if (comp.type === "b" || comp.type == "i") { 13840 result.push($tag); 13841 } 13842 } else { 13843 for (i = 0; i < comp.children.length; i++) { 13844 result = result.concat( 13845 flattenComponentForCaption(comp.children[i]) 13846 ); 13847 } 13848 } 13849 return result; 13850 }; 13851 13852 for (var i = 0; i < cap.components.children.length; i++) { 13853 var next_child_tagname; 13854 if ( i < cap.components.children.length - 1 ) { 13855 next_child_tagname = cap.components.children[i + 1].tagName; 13856 } 13857 var results = flattenComponentForCaption(cap.components.children[i]); 13858 for (var jj = 0; jj < results.length; jj++) { 13859 var result = results[jj]; 13860 if (typeof result === "string") { 13861 if (thisObj.lyricsMode) { 13862 // add <br> WITHIN each caption (if payload includes "\n") 13863 result = result.replace(/\n/g,'<br>'); 13864 13865 // add <br> BETWEEN each caption, but do not consider sibling style tags within this caption as the next caption! 13866 if ( !next_child_tagname || ( next_child_tagname !== 'i' && next_child_tagname !== 'b' ) ) { 13867 result += '<br>'; 13868 } 13869 } else { 13870 // just add a space between captions 13871 result += " "; 13872 } 13873 } 13874 $capSpan.append(result); 13875 } 13876 } 13877 $capSpan.attr("data-start", cap.start.toString()); 13878 $capSpan.attr("data-end", cap.end.toString()); 13879 div.append($capSpan); 13880 div.append(" \n"); 13881 }; 13882 13883 // keep looping as long as any one of the three arrays has content 13884 while ( 13885 nextChapter < chapters.length || 13886 nextDesc < descriptions.length || 13887 nextCap < captions.length 13888 ) { 13889 if ( 13890 nextChapter < chapters.length && 13891 nextDesc < descriptions.length && 13892 nextCap < captions.length 13893 ) { 13894 // they all three have content 13895 firstStart = Math.min( 13896 chapters[nextChapter].start, 13897 descriptions[nextDesc].start, 13898 captions[nextCap].start 13899 ); 13900 } else if ( 13901 nextChapter < chapters.length && 13902 nextDesc < descriptions.length 13903 ) { 13904 // chapters & descriptions have content 13905 firstStart = Math.min( 13906 chapters[nextChapter].start, 13907 descriptions[nextDesc].start 13908 ); 13909 } else if (nextChapter < chapters.length && nextCap < captions.length) { 13910 // chapters & captions have content 13911 firstStart = Math.min( 13912 chapters[nextChapter].start, 13913 captions[nextCap].start 13914 ); 13915 } else if (nextDesc < descriptions.length && nextCap < captions.length) { 13916 // descriptions & captions have content 13917 firstStart = Math.min( 13918 descriptions[nextDesc].start, 13919 captions[nextCap].start 13920 ); 13921 } else { 13922 firstStart = null; 13923 } 13924 if (firstStart !== null) { 13925 if ( 13926 typeof chapters[nextChapter] !== "undefined" && 13927 chapters[nextChapter].start === firstStart 13928 ) { 13929 addChapter($main, chapters[nextChapter]); 13930 nextChapter += 1; 13931 } else if ( 13932 typeof descriptions[nextDesc] !== "undefined" && 13933 descriptions[nextDesc].start === firstStart 13934 ) { 13935 addDescription($main, descriptions[nextDesc]); 13936 nextDesc += 1; 13937 } else { 13938 addCaption($main, captions[nextCap]); 13939 nextCap += 1; 13940 } 13941 } else { 13942 if (nextChapter < chapters.length) { 13943 addChapter($main, chapters[nextChapter]); 13944 nextChapter += 1; 13945 } else if (nextDesc < descriptions.length) { 13946 addDescription($main, descriptions[nextDesc]); 13947 nextDesc += 1; 13948 } else if (nextCap < captions.length) { 13949 addCaption($main, captions[nextCap]); 13950 nextCap += 1; 13951 } 13952 } 13953 } 13954 // organize transcript into blocks using [] and () as starting points 13955 var $components = $main.children(); 13956 var spanCount = 0; 13957 $components.each(function () { 13958 if ($(this).hasClass("able-transcript-caption")) { 13959 if ( 13960 $(this).text().indexOf("[") !== -1 || 13961 $(this).text().indexOf("(") !== -1 13962 ) { 13963 // this caption includes a bracket or parenth. Start a new block 13964 // close the previous block first 13965 if (spanCount > 0) { 13966 $main = wrapTranscriptBlocks( $main ); 13967 spanCount = 0; 13968 } 13969 } 13970 $(this).addClass("able-block-temp"); 13971 spanCount++; 13972 } else { 13973 // this is not a caption. Close the caption block 13974 if (spanCount > 0) { 13975 $main = wrapTranscriptBlocks( $main ); 13976 spanCount = 0; 13977 } 13978 } 13979 }); 13980 // Close out remaining temp blocks. 13981 $main = wrapTranscriptBlocks( $main ); 13982 13983 return $main; 13984 }; 13985 13986 var wrapTranscriptBlocks = function( $main ) { 13987 $main.find(".able-block-temp") 13988 .removeClass("able-block-temp") 13989 .wrapAll('<div class="able-transcript-block"></div>'); 13990 13991 return $main; 13992 }; 13993 } 13994 13995 var playerHeading$k = "Reproductor"; 13996 var audioPlayer$k = "Reproductor d'à udio"; 13997 var videoPlayer$k = "Reproductor de vÃdeo"; 13998 var faster$k = "Rà pid"; 13999 var slower$k = "Lent"; 14000 var play$k = "Reprodueix"; 14001 var pause$k = "Pausa"; 14002 var restart$k = "Reinicia"; 14003 var prevTrack$k = "Pista anterior"; 14004 var nextTrack$k = "Pista següent"; 14005 var rewind$k = "Endarrere"; 14006 var forward$k = "Endavant"; 14007 var captions$k = "SubtÃtols"; 14008 var showCaptions$k = "Mostra els subtÃtols"; 14009 var hideCaptions$k = "Oculta els subtÃtols"; 14010 var captionsOff$k = "Desactiva els subtÃtols"; 14011 var showTranscript$k = "Mostra la transcripció"; 14012 var hideTranscript$k = "Oculta la transcripció"; 14013 var turnOnDescriptions$k = "Activa l'audiodescripció"; 14014 var turnOffDescriptions$k = "Desactiva l'audiodescripció"; 14015 var chapters$k = "CapÃtols"; 14016 var language$k = "Idioma"; 14017 var sign$k = "Llengua de signes"; 14018 var showSign$k = "Mostra la llengua de signes"; 14019 var hideSign$k = "Oculta la llengua de signes"; 14020 var seekbarLabel$k = "LÃnia de temps"; 14021 var mute$k = "Silencia"; 14022 var unmute$k = "Activa el so"; 14023 var volume$k = "Volum"; 14024 var volumeUpDown$k = "Apuja i abaixa el volum"; 14025 var preferences$k = "Preferències"; 14026 var enterFullScreen$k = "Entra en el mode de pantalla completa"; 14027 var exitFullScreen$k = "Surt del mode de pantalla completa"; 14028 var speed$k = "Velocitat"; 14029 var spacebar$k = "Barra espaiadora"; 14030 var transcriptTitle$k = "Transcripció"; 14031 var lyricsTitle$k = "Lletra"; 14032 var autoScroll$k = "Desplaçament automà tic"; 14033 var statusPlaying$k = "S'està reproduint"; 14034 var statusPaused$k = "En pausa"; 14035 var statusStopped$k = "Aturat"; 14036 var statusBuffering$k = "Emmagatzemant"; 14037 var statusEnd$k = "Fi de pista"; 14038 var selectedTrack$j = "Pista seleccionada"; 14039 var alertDescribedVersion$j = "S'està utilitzant la versió amb audiodescripció del vÃdeo"; 14040 var alertNonDescribedVersion$j = "S'està utilitzant la versió sense audiodescripció del vÃdeo"; 14041 var prefMenuCaptions$k = "SubtÃtols"; 14042 var prefVoicedCaptions$k = "Spoken Captions"; 14043 var prefMenuDescriptions$k = "Descripcions"; 14044 var prefMenuKeyboard$k = "Teclat"; 14045 var prefMenuTranscript$k = "Transcripció"; 14046 var prefTitleCaptions$k = "Preferències dels subtÃtols"; 14047 var prefTitleDescriptions$k = "Preferències de l'audiodescripció"; 14048 var prefTitleKeyboard$k = "Preferències del teclat"; 14049 var prefTitleTranscript$j = "Preferències de la transcripció"; 14050 var prefIntroDescription1$j = "Aquest reproductor suporta l'audiodescripció de dues maneres: "; 14051 var prefDescription1$k = "L'actual vÃdeo té una versió alternativa amb audiodescripció, descripció textual anunciada pel lector de pantalla."; 14052 var prefDescription2$k = "L'actual vÃdeo té una versió alternativa amb audiodescripció."; 14053 var prefDescription3$k = "L'actual vÃdeo té descripció textual."; 14054 var prefIntroDescriptionNone$k = "L'actual vÃdeo no disposa d'audiodescripció en cap format."; 14055 var prefIntroDescription3$k = "Utilitzeu el formulari següent per definir les preferències relacionades amb l'audiodescripció textual."; 14056 var prefIntroDescription4$k = "Desprès de desar la configuració, podeu commutar l'ús de l'audiodescripció amb el mateix botó."; 14057 var prefIntroKeyboard1$k = "Aquest reproductor pot ser utilitzat des de qualsevol lloc de la pà gina utilitzant les dreceres de teclat (vegeu la llista a continuació)."; 14058 var prefIntroKeyboard2$k = "A continuació, podeu asignar les tecles modificadores (Majúscules, Alt, i la tecla d'inserció)."; 14059 var prefIntroKeyboard3$k = "NOTA: algunes combinacions de tecles poden entrar en conflicte amb les utilitzades pel navegador o altres aplicacions. Proveu diferents combinacions o tecles modificadores fins a trobar les adequades en cada cas."; 14060 var prefHeadingKeyboard1$k = "Tecles modificadores emprades com a dreceres de teclat"; 14061 var prefHeadingKeyboard2$k = "Dreceres de teclat actuals"; 14062 var prefHeadingDescription$k = "Audiodescripció"; 14063 var prefHeadingTextDescription$k = "Audiodescripció textual"; 14064 var prefAltKey$k = "Alt"; 14065 var prefCtrlKey$k = "Control"; 14066 var prefShiftKey$k = "Majúscula"; 14067 var prefNoKeyShortcuts$k = "Desactiva les dreceres de teclat"; 14068 var escapeKey$k = "Escapada"; 14069 var escapeKeyFunction$k = "Tanca el dià leg o finestre emergent actual"; 14070 var prefDescPause$k = "Pausa automà ticament el vÃdeo en el moment que comenci una audiodescripció"; 14071 var prefDescVisible$k = "Fes visible la audiodescripció textual si es troba activada"; 14072 var prefDescVoice$k = "Veu"; 14073 var prefDescRate$k = "Spoken Description Rate"; 14074 var prefCaptionRate$k = "Spoken Caption Rate"; 14075 var prefDescPitch$k = "Tonalitat"; 14076 var prefDescPitch1$k = "Molt baixa"; 14077 var prefDescPitch2$k = "Baixa"; 14078 var prefDescPitch3$k = "Per defecte"; 14079 var prefDescPitch4$k = "Alta"; 14080 var prefDescPitch5$k = "Molt alta"; 14081 var sampleDescriptionText$k = "Ajusta la configuració per escoltar aquest text de mostra."; 14082 var prefHighlight$k = "Ressalta la transcripció a mesura que avança el contingut"; 14083 var prefTabbable$k = "Transcripció operable per teclat"; 14084 var prefCaptionsFont$k = "Tipus de lletra"; 14085 var prefCaptionsColor$k = "Color del text"; 14086 var prefCaptionsBGColor$k = "Fons"; 14087 var prefCaptionsSize$k = "Mida del text"; 14088 var prefCaptionsOpacity$k = "Opacitat"; 14089 var prefCaptionsStyle$k = "Estil"; 14090 var serif$k = "Serifa"; 14091 var sans$k = "Sensa serifa"; 14092 var cursive$k = "Cursiva"; 14093 var fantasy$k = "fantasia"; 14094 var monospace$k = "Monoespaiada"; 14095 var white$k = "Blanc"; 14096 var yellow$k = "Groc"; 14097 var green$k = "Verd"; 14098 var cyan$j = "Cian"; 14099 var blue$k = "Blau"; 14100 var magenta$k = "Magenta"; 14101 var red$k = "Vermell"; 14102 var black$k = "Negre"; 14103 var transparent$k = "transparent"; 14104 var solid$k = "Sòlid"; 14105 var captionsStylePopOn$k = "Aparició instantà nia"; 14106 var captionsStyleRollUp$k = "Desplaçament cap amunt"; 14107 var prefCaptionsPosition$k = "Posició"; 14108 var captionsPositionOverlay$k = "Superposició"; 14109 var captionsPositionBelow$k = "A continuació del vÃdeo"; 14110 var sampleCaptionText$k = "Text de mostra dels subtÃtols"; 14111 var prefSuccess$k = "S'han desat els canvis."; 14112 var prefNoChange$k = "No s'ha fet cap canvi."; 14113 var save$k = "Desa"; 14114 var cancel$k = "Cancel·la"; 14115 var dismissButton$k = "Ignora"; 14116 var windowButtonLabel$k = "Opcions de la finestra"; 14117 var windowMove$k = "Moure"; 14118 var windowMoveLeft$k = "Finestra desplaçada cap a l'esquerra"; 14119 var windowMoveRight$k = "Finestra desplaçada cap a la dreta"; 14120 var windowMoveUp$k = "Finestra desplaçada cap amunt"; 14121 var windowMoveDown$k = "Finestra desplaçada cap avall"; 14122 var windowMoveStopped$k = "S'ha aturat el desplaçament de la finestra"; 14123 var transcriptControls$k = "Controls de la finestra de transcripció"; 14124 var signControls$k = "Controls de la finestra de llengua de signes"; 14125 var windowMoveAlert$k = "Arrossegueu o feu servir les tecles de direcció per moure la finestra, polseu retorn per aturar."; 14126 var windowResize$k = "Redimensiona"; 14127 var windowResizeHeading$k = "Redimensiona la finestra amb l'intèrpret"; 14128 var closeButtonLabel$k = "Tanca"; 14129 var width$k = "Amplada"; 14130 var height$k = "Alçada"; 14131 var resultsSummary1$k = "Heu cercat:"; 14132 var resultsSummary2$k = "S'han trobat %1 elements coincidents."; 14133 var resultsSummary3$k = "Feu clic al moment associat a qualsevol element per reproduir el vÃdeo des d'aquell punt."; 14134 var noResultsFound$k = "No s'han trobat resultats."; 14135 var searchButtonLabel$k = "Reprodueix a %1"; 14136 var hour$k = "hora"; 14137 var minute$j = "minut"; 14138 var second$k = "segon"; 14139 var hours$k = "hores"; 14140 var minutes$k = "minuts"; 14141 var seconds$k = "segons"; 14142 var vtsHeading$k = "Gestor de transcripcions de vÃdeo"; 14143 var vtsInstructions1$k = "Utilitzeu el gestor de transcripcions de vÃdeo per modificar les pistes de text:"; 14144 var vtsInstructions2$k = "Reordeneu capÃtols, descripcions, subtÃtols o subtÃtols per a persones sordes perquè apareguin en la seqüència correcta a la transcripció generada automà ticament per Able Player."; 14145 var vtsInstructions3$k = "Modifiqueu el contingut o els temps d'inici i final (tots són editables directament a la taula)."; 14146 var vtsInstructions4$k = "Afegiu contingut nou, com ara capÃtols o descripcions."; 14147 var vtsInstructions5$k = "Després d'editar, feu clic al botó \"Desa els canvis\" per generar contingut nou per a tots els fitxers de text temporitzat rellevants. El text nou es pot copiar i enganxar en fitxers WebVTT nous."; 14148 var vtsSelectLanguage$k = "Seleccioneu una llengua"; 14149 var vtsSave$k = "Genera contingut .vtt nou"; 14150 var vtsReturn$k = "Torna a l'editor"; 14151 var vtsCancel$k = "S'ha cancel·lat el desament. Totes les edicions que heu fet s'han restaurat a la taula del GTV."; 14152 var vtsRow$k = "Fila"; 14153 var vtsKind$k = "Tipus"; 14154 var vtsStart$k = "Inici"; 14155 var vtsEnd$k = "Final"; 14156 var vtsContent$k = "Contingut"; 14157 var vtsActions$k = "Accions"; 14158 var vtsNewRow$k = "S'ha inserit una fila nova %1."; 14159 var vtsDeletedRow$k = "S'ha suprimit la fila %1."; 14160 var vtsMovedRow$k = "La fila %1 s'ha mogut %2 i ara és la fila %3."; 14161 var ca = { 14162 playerHeading: playerHeading$k, 14163 audioPlayer: audioPlayer$k, 14164 videoPlayer: videoPlayer$k, 14165 faster: faster$k, 14166 slower: slower$k, 14167 play: play$k, 14168 pause: pause$k, 14169 restart: restart$k, 14170 prevTrack: prevTrack$k, 14171 nextTrack: nextTrack$k, 14172 rewind: rewind$k, 14173 forward: forward$k, 14174 captions: captions$k, 14175 showCaptions: showCaptions$k, 14176 hideCaptions: hideCaptions$k, 14177 captionsOff: captionsOff$k, 14178 showTranscript: showTranscript$k, 14179 hideTranscript: hideTranscript$k, 14180 turnOnDescriptions: turnOnDescriptions$k, 14181 turnOffDescriptions: turnOffDescriptions$k, 14182 chapters: chapters$k, 14183 language: language$k, 14184 sign: sign$k, 14185 showSign: showSign$k, 14186 hideSign: hideSign$k, 14187 seekbarLabel: seekbarLabel$k, 14188 mute: mute$k, 14189 unmute: unmute$k, 14190 volume: volume$k, 14191 volumeUpDown: volumeUpDown$k, 14192 preferences: preferences$k, 14193 enterFullScreen: enterFullScreen$k, 14194 exitFullScreen: exitFullScreen$k, 14195 speed: speed$k, 14196 spacebar: spacebar$k, 14197 transcriptTitle: transcriptTitle$k, 14198 lyricsTitle: lyricsTitle$k, 14199 autoScroll: autoScroll$k, 14200 statusPlaying: statusPlaying$k, 14201 statusPaused: statusPaused$k, 14202 statusStopped: statusStopped$k, 14203 statusBuffering: statusBuffering$k, 14204 statusEnd: statusEnd$k, 14205 selectedTrack: selectedTrack$j, 14206 alertDescribedVersion: alertDescribedVersion$j, 14207 alertNonDescribedVersion: alertNonDescribedVersion$j, 14208 prefMenuCaptions: prefMenuCaptions$k, 14209 prefVoicedCaptions: prefVoicedCaptions$k, 14210 prefMenuDescriptions: prefMenuDescriptions$k, 14211 prefMenuKeyboard: prefMenuKeyboard$k, 14212 prefMenuTranscript: prefMenuTranscript$k, 14213 prefTitleCaptions: prefTitleCaptions$k, 14214 prefTitleDescriptions: prefTitleDescriptions$k, 14215 prefTitleKeyboard: prefTitleKeyboard$k, 14216 prefTitleTranscript: prefTitleTranscript$j, 14217 prefIntroDescription1: prefIntroDescription1$j, 14218 prefDescription1: prefDescription1$k, 14219 prefDescription2: prefDescription2$k, 14220 prefDescription3: prefDescription3$k, 14221 prefIntroDescriptionNone: prefIntroDescriptionNone$k, 14222 prefIntroDescription3: prefIntroDescription3$k, 14223 prefIntroDescription4: prefIntroDescription4$k, 14224 prefIntroKeyboard1: prefIntroKeyboard1$k, 14225 prefIntroKeyboard2: prefIntroKeyboard2$k, 14226 prefIntroKeyboard3: prefIntroKeyboard3$k, 14227 prefHeadingKeyboard1: prefHeadingKeyboard1$k, 14228 prefHeadingKeyboard2: prefHeadingKeyboard2$k, 14229 prefHeadingDescription: prefHeadingDescription$k, 14230 prefHeadingTextDescription: prefHeadingTextDescription$k, 14231 prefAltKey: prefAltKey$k, 14232 prefCtrlKey: prefCtrlKey$k, 14233 prefShiftKey: prefShiftKey$k, 14234 prefNoKeyShortcuts: prefNoKeyShortcuts$k, 14235 escapeKey: escapeKey$k, 14236 escapeKeyFunction: escapeKeyFunction$k, 14237 prefDescPause: prefDescPause$k, 14238 prefDescVisible: prefDescVisible$k, 14239 prefDescVoice: prefDescVoice$k, 14240 prefDescRate: prefDescRate$k, 14241 prefCaptionRate: prefCaptionRate$k, 14242 prefDescPitch: prefDescPitch$k, 14243 prefDescPitch1: prefDescPitch1$k, 14244 prefDescPitch2: prefDescPitch2$k, 14245 prefDescPitch3: prefDescPitch3$k, 14246 prefDescPitch4: prefDescPitch4$k, 14247 prefDescPitch5: prefDescPitch5$k, 14248 sampleDescriptionText: sampleDescriptionText$k, 14249 prefHighlight: prefHighlight$k, 14250 prefTabbable: prefTabbable$k, 14251 prefCaptionsFont: prefCaptionsFont$k, 14252 prefCaptionsColor: prefCaptionsColor$k, 14253 prefCaptionsBGColor: prefCaptionsBGColor$k, 14254 prefCaptionsSize: prefCaptionsSize$k, 14255 prefCaptionsOpacity: prefCaptionsOpacity$k, 14256 prefCaptionsStyle: prefCaptionsStyle$k, 14257 serif: serif$k, 14258 sans: sans$k, 14259 cursive: cursive$k, 14260 fantasy: fantasy$k, 14261 monospace: monospace$k, 14262 white: white$k, 14263 yellow: yellow$k, 14264 green: green$k, 14265 cyan: cyan$j, 14266 blue: blue$k, 14267 magenta: magenta$k, 14268 red: red$k, 14269 black: black$k, 14270 transparent: transparent$k, 14271 solid: solid$k, 14272 captionsStylePopOn: captionsStylePopOn$k, 14273 captionsStyleRollUp: captionsStyleRollUp$k, 14274 prefCaptionsPosition: prefCaptionsPosition$k, 14275 captionsPositionOverlay: captionsPositionOverlay$k, 14276 captionsPositionBelow: captionsPositionBelow$k, 14277 sampleCaptionText: sampleCaptionText$k, 14278 prefSuccess: prefSuccess$k, 14279 prefNoChange: prefNoChange$k, 14280 save: save$k, 14281 cancel: cancel$k, 14282 dismissButton: dismissButton$k, 14283 windowButtonLabel: windowButtonLabel$k, 14284 windowMove: windowMove$k, 14285 windowMoveLeft: windowMoveLeft$k, 14286 windowMoveRight: windowMoveRight$k, 14287 windowMoveUp: windowMoveUp$k, 14288 windowMoveDown: windowMoveDown$k, 14289 windowMoveStopped: windowMoveStopped$k, 14290 transcriptControls: transcriptControls$k, 14291 signControls: signControls$k, 14292 windowMoveAlert: windowMoveAlert$k, 14293 windowResize: windowResize$k, 14294 windowResizeHeading: windowResizeHeading$k, 14295 closeButtonLabel: closeButtonLabel$k, 14296 width: width$k, 14297 height: height$k, 14298 resultsSummary1: resultsSummary1$k, 14299 resultsSummary2: resultsSummary2$k, 14300 resultsSummary3: resultsSummary3$k, 14301 noResultsFound: noResultsFound$k, 14302 searchButtonLabel: searchButtonLabel$k, 14303 hour: hour$k, 14304 minute: minute$j, 14305 second: second$k, 14306 hours: hours$k, 14307 minutes: minutes$k, 14308 seconds: seconds$k, 14309 vtsHeading: vtsHeading$k, 14310 vtsInstructions1: vtsInstructions1$k, 14311 vtsInstructions2: vtsInstructions2$k, 14312 vtsInstructions3: vtsInstructions3$k, 14313 vtsInstructions4: vtsInstructions4$k, 14314 vtsInstructions5: vtsInstructions5$k, 14315 vtsSelectLanguage: vtsSelectLanguage$k, 14316 vtsSave: vtsSave$k, 14317 vtsReturn: vtsReturn$k, 14318 vtsCancel: vtsCancel$k, 14319 vtsRow: vtsRow$k, 14320 vtsKind: vtsKind$k, 14321 vtsStart: vtsStart$k, 14322 vtsEnd: vtsEnd$k, 14323 vtsContent: vtsContent$k, 14324 vtsActions: vtsActions$k, 14325 vtsNewRow: vtsNewRow$k, 14326 vtsDeletedRow: vtsDeletedRow$k, 14327 vtsMovedRow: vtsMovedRow$k 14328 }; 14329 14330 var playerHeading$j = "PÅehrávaÄ médiÃ"; 14331 var audioPlayer$j = "Audio player"; 14332 var videoPlayer$j = "Video player"; 14333 var faster$j = "Rychleji"; 14334 var slower$j = "Pomaleji"; 14335 var play$j = "Spustit"; 14336 var pause$j = "Pauza"; 14337 var restart$j = "PÅehrát od zaÄátku"; 14338 var prevTrack$j = "PÅedchozà stopa"; 14339 var nextTrack$j = "Dalšà stopa"; 14340 var rewind$j = "PÅetoÄit vzad"; 14341 var forward$j = "PÅetoÄit vpÅed"; 14342 var captions$j = "Titulky"; 14343 var showCaptions$j = "Zobrazit titulky"; 14344 var hideCaptions$j = "Skrýt titulky"; 14345 var captionsOff$j = "Titulky vypnuty"; 14346 var showTranscript$j = "Zobrazit pÅepis"; 14347 var hideTranscript$j = "Skrýt pÅepis"; 14348 var turnOnDescriptions$j = "Zapnout popisy"; 14349 var turnOffDescriptions$j = "vypnout popisy"; 14350 var chapters$j = "Kapitoly"; 14351 var language$j = "Jazyk"; 14352 var sign$j = "Znaková ÅeÄ"; 14353 var showSign$j = "Zobrazit znakovou ÅeÄ"; 14354 var hideSign$j = "Skrýt znakovou ÅeÄ"; 14355 var seekbarLabel$j = "Äasová osa"; 14356 var mute$j = "Vypnout zvuk"; 14357 var unmute$j = "Zapnout zvuk"; 14358 var volume$j = "Hlasitost"; 14359 var volumeUpDown$j = "Hlasitost zvýšit snÞit"; 14360 var preferences$j = "PÅedvolby"; 14361 var enterFullScreen$j = "Zobrazit na celou obrazovku"; 14362 var exitFullScreen$j = "UkonÄit celou obrazovku"; 14363 var speed$j = "Rychlost"; 14364 var spacebar$j = "mezernÃk"; 14365 var transcriptTitle$j = "PÅepis"; 14366 var lyricsTitle$j = "Text"; 14367 var autoScroll$j = "Automatické posouvánÃ"; 14368 var statusPlaying$j = "PÅehrávánÃ"; 14369 var statusPaused$j = "Pozastaveno"; 14370 var statusStopped$j = "Zastaveno"; 14371 var statusBuffering$j = "Vyrovnávacà pamÄÅ¥"; 14372 var statusEnd$j = "Konec stopy"; 14373 var selectedTrack$i = "Vybraná stopa"; 14374 var alertDescribVersion = "PoužÃvánà zvukovÄ popsané verze tohoto videa"; 14375 var alertNonDescribVersion = "Použità nepopsané verze tohoto videa"; 14376 var prefMenuCaptions$j = "Titulky"; 14377 var prefVoicedCaptions$j = "Spoken Captions"; 14378 var prefMenuDescriptions$j = "Popisy"; 14379 var prefMenuKeyboard$j = "Klávesnice"; 14380 var prefMenuTranscript$j = "PÅepis"; 14381 var prefTitleCaptions$j = "PÅedvolby titulků"; 14382 var prefTitleDescriptions$j = "PÅedvolby zvukového popisu"; 14383 var prefTitleKeyboard$j = "PÅedvolby klávesnice"; 14384 var prefTitleTranscript$i = "PÅedvolby pÅepisu"; 14385 var prefIntroDescription1$i = "Tento pÅehrávaÄ médià podporuje zvukový popis dvÄma způsoby:"; 14386 var prefDescription1$j = "Aktuálnà video má alternativnÄ popsaná verze, textový popis."; 14387 var prefDescription2$j = "Aktuálnà video má alternativnà popsaná verze videa."; 14388 var prefDescription3$j = "Aktuálnà video má textový popis, oznámený ÄteÄkou obrazovky."; 14389 var prefIntroDescriptionNone$j = "Aktuálnà video nemá žádný zvukový popis v žádném formátu."; 14390 var prefIntroDescription3$j = "Pomocà následujÃcÃho formuláÅe můžete nastavit pÅedvolby týkajÃcà se textového zvukového popisu."; 14391 var prefIntroDescription4$j = "Po uloženà nastavenà lze zvukový popis zapnout / vypnout pomocà tlaÄÃtka Popis."; 14392 var prefIntroKeyboard1$j = "PÅehrávaÄ médià na této webové stránce lze ovládat odkudkoli na stránce pomocà klávesových zkratek (seznam nÞe)."; 14393 var prefIntroKeyboard2$j = "NÞe lze pÅiÅadit modifikaÄnà klávesy (Shift, Alt a Control)."; 14394 var prefIntroKeyboard3$j = "POZNÃMKA: NÄkteré kombinace kláves mohou být v konfliktu s klávesami použÃvanými vaÅ¡Ãm prohlÞeÄem nebo jinými softwarovými aplikacemi. Zkuste najÃt různé kombinace modifikaÄnÃch kláves, které vám vyhovujÃ."; 14395 var prefHeadingKeyboard1$j = "ModifikaÄnà klávesy použÃvané pro zástupce"; 14396 var prefHeadingKeyboard2$j = "Aktuálnà klávesové zkratky"; 14397 var prefHeadingDescription$j = "Zvukový popis"; 14398 var prefHeadingTextDescription$j = "Textový zvukový popis"; 14399 var prefAltKey$j = "Alt"; 14400 var prefCtrlKey$j = "Control"; 14401 var prefShiftKey$j = "Shift"; 14402 var prefNoKeyShortcuts$j = "Disable keyboard shortcuts"; 14403 var escapeKey$j = "Escape"; 14404 var escapeKeyFunction$j = "ZavÅÃt aktuálnà dialogové okno nebo vyskakovacà nabÃdku"; 14405 var prefDescPause$j = "Automaticky pozastavit video pÅi spuÅ¡tÄnà popisu"; 14406 var prefDescVisible$j = "Zviditelnit popis"; 14407 var prefDescVoice$j = "Voice"; 14408 var prefDescRate$j = "Spoken Description Rate"; 14409 var prefCaptionRate$j = "Spoken Caption Rate"; 14410 var prefDescPitch$j = "Pitch"; 14411 var prefDescPitch1$j = "Very low"; 14412 var prefDescPitch2$j = "Low"; 14413 var prefDescPitch3$j = "Default"; 14414 var prefDescPitch4$j = "High"; 14415 var prefDescPitch5$j = "Very high"; 14416 var sampleDescriptionText$j = "Adjust settings to hear this sample text."; 14417 var prefHighlight$j = "Zvýraznit pÅepis pÅi pÅehrávánà médiÃ"; 14418 var prefTabbable$j = "PÅepis umožÅujÃcà klávesnici"; 14419 var prefCaptionsFont$j = "PÃsmo"; 14420 var prefCaptionsColor$j = "Barva textu"; 14421 var prefCaptionsBGColor$j = "PozadÃ"; 14422 var prefCaptionsSize$j = "Velikost pÃsma"; 14423 var prefCaptionsOpacity$j = "Neprůhlednost"; 14424 var prefCaptionsStyle$j = "Styl"; 14425 var serif$j = "patkové"; 14426 var sans$j = "bezpatkové"; 14427 var cursive$j = "kurzÃva"; 14428 var fantasy$j = "fantasy"; 14429 var monospace$j = "jednoprostorový"; 14430 var white$j = "bÃlá"; 14431 var yellow$j = "žlutá"; 14432 var green$j = "zelená"; 14433 var cyan$i = "azurová"; 14434 var blue$j = "modrá"; 14435 var magenta$j = "purpurová"; 14436 var red$j = "Äervená"; 14437 var black$j = "Äerná"; 14438 var transparent$j = "transparentnÃ"; 14439 var solid$j = "jednolitý"; 14440 var captionsStylePopOn$j = "Vyskakovat"; 14441 var captionsStyleRollUp$j = "Srolovat"; 14442 var prefCaptionsPosition$j = "Pozice"; 14443 var captionsPositionOverlay$j = "PÅekrytÃ"; 14444 var captionsPositionBelow$j = "NÞe video"; 14445 var sampleCaptionText$j = "Ukázkový text titulku"; 14446 var prefSuccess$j = "VaÅ¡e zmÄny byly uloženy."; 14447 var prefNoChange$j = "Neprovedli jste žádné zmÄny."; 14448 var save$j = "Uložit"; 14449 var cancel$j = "ZruÅ¡it"; 14450 var dismissButton$j = "Dismiss"; 14451 var windowButtonLabel$j = "Možnosti okna"; 14452 var windowMove$j = "PÅesunout"; 14453 var windowMoveLeft$j = "Window moved left"; 14454 var windowMoveRight$j = "Window moved right"; 14455 var windowMoveUp$j = "Window moved up"; 14456 var windowMoveDown$j = "Window moved down"; 14457 var windowMoveStopped$j = "Window move stopped"; 14458 var transcriptControls$j = "Transcript Window Controls"; 14459 var signControls$j = "Sign Language Window Controls"; 14460 var windowMoveAlert$j = "PÅetaženÃm nebo použitÃm kláves se Å¡ipkami pÅesuÅte okno; klávesou Enter zastavÃte"; 14461 var windowResize$j = "ZmÄnit velikost"; 14462 var windowResizeHeading$j = "ZmÄnit velikost okna"; 14463 var closeButtonLabel$j = "ZavÅÃt"; 14464 var width$j = "Å ÃÅka"; 14465 var height$j = "Výška"; 14466 var resultsSummary1$j = "Hledali jste:"; 14467 var resultsSummary2$j = "Nalezeno %1 odpovÃdajÃcà položky."; 14468 var resultsSummary3$j = "KlepnutÃm na Äas spojený s libovolnou položkou pÅehrajete video od tohoto bodu."; 14469 var noResultsFound$j = "Nebyly nalezeny žádné výsledky."; 14470 var searchButtonLabel$j = "PÅehrát v %1"; 14471 var hour$j = "hodina"; 14472 var minuta = "minuta"; 14473 var second$j = "sekunda"; 14474 var hours$j = "hodiny"; 14475 var minutes$j = "minuty"; 14476 var seconds$j = "sekundy"; 14477 var vtsHeading$j = "Video Transcript Sorter"; 14478 var vtsInstructions1$j = "Use the Video Transcript Sorter to modify text tracks:"; 14479 var vtsInstructions2$j = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 14480 var vtsInstructions3$j = "Modify content or start/end times (all are directly editable within the table)."; 14481 var vtsInstructions4$j = "Add new content, such as chapters or descriptions."; 14482 var vtsInstructions5$j = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 14483 var vtsSelectLanguage$j = "Select a language"; 14484 var vtsSave$j = "Generate new .vtt content"; 14485 var vtsReturn$j = "Return to Editor"; 14486 var vtsCancel$j = "Cancelling saving. Any edits you made have been restored in the VTS table."; 14487 var vtsRow$j = "Row"; 14488 var vtsKind$j = "Kind"; 14489 var vtsStart$j = "Start"; 14490 var vtsEnd$j = "End"; 14491 var vtsContent$j = "Content"; 14492 var vtsActions$j = "Actions"; 14493 var vtsNewRow$j = "A new row %1 has been inserted."; 14494 var vtsDeletedRow$j = "Row %1 has been deleted."; 14495 var vtsMovedRow$j = "Row %1 has been moved %2 and is now Row %3."; 14496 var cs = { 14497 playerHeading: playerHeading$j, 14498 audioPlayer: audioPlayer$j, 14499 videoPlayer: videoPlayer$j, 14500 faster: faster$j, 14501 slower: slower$j, 14502 play: play$j, 14503 pause: pause$j, 14504 restart: restart$j, 14505 prevTrack: prevTrack$j, 14506 nextTrack: nextTrack$j, 14507 rewind: rewind$j, 14508 forward: forward$j, 14509 captions: captions$j, 14510 showCaptions: showCaptions$j, 14511 hideCaptions: hideCaptions$j, 14512 captionsOff: captionsOff$j, 14513 showTranscript: showTranscript$j, 14514 hideTranscript: hideTranscript$j, 14515 turnOnDescriptions: turnOnDescriptions$j, 14516 turnOffDescriptions: turnOffDescriptions$j, 14517 chapters: chapters$j, 14518 language: language$j, 14519 sign: sign$j, 14520 showSign: showSign$j, 14521 hideSign: hideSign$j, 14522 seekbarLabel: seekbarLabel$j, 14523 mute: mute$j, 14524 unmute: unmute$j, 14525 volume: volume$j, 14526 volumeUpDown: volumeUpDown$j, 14527 preferences: preferences$j, 14528 enterFullScreen: enterFullScreen$j, 14529 exitFullScreen: exitFullScreen$j, 14530 speed: speed$j, 14531 spacebar: spacebar$j, 14532 transcriptTitle: transcriptTitle$j, 14533 lyricsTitle: lyricsTitle$j, 14534 autoScroll: autoScroll$j, 14535 statusPlaying: statusPlaying$j, 14536 statusPaused: statusPaused$j, 14537 statusStopped: statusStopped$j, 14538 statusBuffering: statusBuffering$j, 14539 statusEnd: statusEnd$j, 14540 selectedTrack: selectedTrack$i, 14541 alertDescribVersion: alertDescribVersion, 14542 alertNonDescribVersion: alertNonDescribVersion, 14543 prefMenuCaptions: prefMenuCaptions$j, 14544 prefVoicedCaptions: prefVoicedCaptions$j, 14545 prefMenuDescriptions: prefMenuDescriptions$j, 14546 prefMenuKeyboard: prefMenuKeyboard$j, 14547 prefMenuTranscript: prefMenuTranscript$j, 14548 prefTitleCaptions: prefTitleCaptions$j, 14549 prefTitleDescriptions: prefTitleDescriptions$j, 14550 prefTitleKeyboard: prefTitleKeyboard$j, 14551 prefTitleTranscript: prefTitleTranscript$i, 14552 prefIntroDescription1: prefIntroDescription1$i, 14553 prefDescription1: prefDescription1$j, 14554 prefDescription2: prefDescription2$j, 14555 prefDescription3: prefDescription3$j, 14556 prefIntroDescriptionNone: prefIntroDescriptionNone$j, 14557 prefIntroDescription3: prefIntroDescription3$j, 14558 prefIntroDescription4: prefIntroDescription4$j, 14559 prefIntroKeyboard1: prefIntroKeyboard1$j, 14560 prefIntroKeyboard2: prefIntroKeyboard2$j, 14561 prefIntroKeyboard3: prefIntroKeyboard3$j, 14562 prefHeadingKeyboard1: prefHeadingKeyboard1$j, 14563 prefHeadingKeyboard2: prefHeadingKeyboard2$j, 14564 prefHeadingDescription: prefHeadingDescription$j, 14565 prefHeadingTextDescription: prefHeadingTextDescription$j, 14566 prefAltKey: prefAltKey$j, 14567 prefCtrlKey: prefCtrlKey$j, 14568 prefShiftKey: prefShiftKey$j, 14569 prefNoKeyShortcuts: prefNoKeyShortcuts$j, 14570 escapeKey: escapeKey$j, 14571 escapeKeyFunction: escapeKeyFunction$j, 14572 prefDescPause: prefDescPause$j, 14573 prefDescVisible: prefDescVisible$j, 14574 prefDescVoice: prefDescVoice$j, 14575 prefDescRate: prefDescRate$j, 14576 prefCaptionRate: prefCaptionRate$j, 14577 prefDescPitch: prefDescPitch$j, 14578 prefDescPitch1: prefDescPitch1$j, 14579 prefDescPitch2: prefDescPitch2$j, 14580 prefDescPitch3: prefDescPitch3$j, 14581 prefDescPitch4: prefDescPitch4$j, 14582 prefDescPitch5: prefDescPitch5$j, 14583 sampleDescriptionText: sampleDescriptionText$j, 14584 prefHighlight: prefHighlight$j, 14585 prefTabbable: prefTabbable$j, 14586 prefCaptionsFont: prefCaptionsFont$j, 14587 prefCaptionsColor: prefCaptionsColor$j, 14588 prefCaptionsBGColor: prefCaptionsBGColor$j, 14589 prefCaptionsSize: prefCaptionsSize$j, 14590 prefCaptionsOpacity: prefCaptionsOpacity$j, 14591 prefCaptionsStyle: prefCaptionsStyle$j, 14592 serif: serif$j, 14593 sans: sans$j, 14594 cursive: cursive$j, 14595 fantasy: fantasy$j, 14596 monospace: monospace$j, 14597 white: white$j, 14598 yellow: yellow$j, 14599 green: green$j, 14600 cyan: cyan$i, 14601 blue: blue$j, 14602 magenta: magenta$j, 14603 red: red$j, 14604 black: black$j, 14605 transparent: transparent$j, 14606 solid: solid$j, 14607 captionsStylePopOn: captionsStylePopOn$j, 14608 captionsStyleRollUp: captionsStyleRollUp$j, 14609 prefCaptionsPosition: prefCaptionsPosition$j, 14610 captionsPositionOverlay: captionsPositionOverlay$j, 14611 captionsPositionBelow: captionsPositionBelow$j, 14612 sampleCaptionText: sampleCaptionText$j, 14613 prefSuccess: prefSuccess$j, 14614 prefNoChange: prefNoChange$j, 14615 save: save$j, 14616 cancel: cancel$j, 14617 dismissButton: dismissButton$j, 14618 windowButtonLabel: windowButtonLabel$j, 14619 windowMove: windowMove$j, 14620 windowMoveLeft: windowMoveLeft$j, 14621 windowMoveRight: windowMoveRight$j, 14622 windowMoveUp: windowMoveUp$j, 14623 windowMoveDown: windowMoveDown$j, 14624 windowMoveStopped: windowMoveStopped$j, 14625 transcriptControls: transcriptControls$j, 14626 signControls: signControls$j, 14627 windowMoveAlert: windowMoveAlert$j, 14628 windowResize: windowResize$j, 14629 windowResizeHeading: windowResizeHeading$j, 14630 closeButtonLabel: closeButtonLabel$j, 14631 width: width$j, 14632 height: height$j, 14633 resultsSummary1: resultsSummary1$j, 14634 resultsSummary2: resultsSummary2$j, 14635 resultsSummary3: resultsSummary3$j, 14636 noResultsFound: noResultsFound$j, 14637 searchButtonLabel: searchButtonLabel$j, 14638 hour: hour$j, 14639 minuta: minuta, 14640 second: second$j, 14641 hours: hours$j, 14642 minutes: minutes$j, 14643 seconds: seconds$j, 14644 vtsHeading: vtsHeading$j, 14645 vtsInstructions1: vtsInstructions1$j, 14646 vtsInstructions2: vtsInstructions2$j, 14647 vtsInstructions3: vtsInstructions3$j, 14648 vtsInstructions4: vtsInstructions4$j, 14649 vtsInstructions5: vtsInstructions5$j, 14650 vtsSelectLanguage: vtsSelectLanguage$j, 14651 vtsSave: vtsSave$j, 14652 vtsReturn: vtsReturn$j, 14653 vtsCancel: vtsCancel$j, 14654 vtsRow: vtsRow$j, 14655 vtsKind: vtsKind$j, 14656 vtsStart: vtsStart$j, 14657 vtsEnd: vtsEnd$j, 14658 vtsContent: vtsContent$j, 14659 vtsActions: vtsActions$j, 14660 vtsNewRow: vtsNewRow$j, 14661 vtsDeletedRow: vtsDeletedRow$j, 14662 vtsMovedRow: vtsMovedRow$j 14663 }; 14664 14665 var playerHeading$i = "Medieafspiller"; 14666 var audioPlayer$i = "Audio player"; 14667 var videoPlayer$i = "Video player"; 14668 var faster$i = "Hurtigere"; 14669 var slower$i = "Langsommere"; 14670 var play$i = "Afspil"; 14671 var pause$i = "Pause"; 14672 var restart$i = "Genstart"; 14673 var prevTrack$i = "Forrige spor"; 14674 var nextTrack$i = "Næste spor"; 14675 var rewind$i = "Spol tilbage"; 14676 var forward$i = "Spol frem"; 14677 var captions$i = "Undertekster"; 14678 var showCaptions$i = "Vis undertekster"; 14679 var hideCaptions$i = "Gem undertekster"; 14680 var captionsOff$i = "SlÃ¥ undertekster fra"; 14681 var showTranscript$i = "Vis transskription"; 14682 var hideTranscript$i = "Gem transskription"; 14683 var turnOnDescriptions$i = "Start synstolkning"; 14684 var turnOffDescriptions$i = "Stop synstolkning"; 14685 var chapters$i = "Kapitler"; 14686 var language$i = "Sprog"; 14687 var sign$i = "Tegnsprog"; 14688 var showSign$i = "Vis tegnsprog"; 14689 var hideSign$i = "Gen tegnsprog"; 14690 var seekbarLabel$i = "tidslinie"; 14691 var mute$i = "Stop lyd"; 14692 var unmute$i = "Start lyd"; 14693 var volume$i = "Lydstyrke"; 14694 var volumeUpDown$i = "Lydstyrke op"; 14695 var preferences$i = "Indstillinger"; 14696 var enterFullScreen$i = "Vis i fuldskærm"; 14697 var exitFullScreen$i = "Afslut fuldskærmsvisning"; 14698 var speed$i = "Hastighed"; 14699 var spacebar$i = "mellemrumstast"; 14700 var transcriptTitle$i = "Transskription"; 14701 var lyricsTitle$i = "Lyrik"; 14702 var autoScroll$i = "Auto scroll"; 14703 var statusPlaying$i = "Afspiller"; 14704 var statusPaused$i = "PÃ¥ pause"; 14705 var statusStopped$i = "Stoppet"; 14706 var statusBuffering$i = "Henter data"; 14707 var statusEnd$i = "Slut pÃ¥ spor"; 14708 var selectedTrack$h = "Valgt spor"; 14709 var alertDescribedVersion$i = "Anvender synstolket version af denne video"; 14710 var alertNonDescribedVersion$i = "Anvende ikke synstolket version af denne video"; 14711 var prefMenuCaptions$i = "Undertekster"; 14712 var prefVoicedCaptions$i = "Spoken Captions"; 14713 var prefMenuDescriptions$i = "Synstolkning"; 14714 var prefMenuKeyboard$i = "Tastatur"; 14715 var prefMenuTranscript$i = "Transkript"; 14716 var prefTitleCaptions$i = "Indstillinger for undertekster"; 14717 var prefTitleDescriptions$i = "Indstillinger for synstolkning"; 14718 var prefTitleKeyboard$i = "Indstillinger for tastatur"; 14719 var prefTitleTranscript$h = "Indstillinger for transskription"; 14720 var prefIntroDescription1$h = "Denne medieafspiller understøtter audio pÃ¥ to mÃ¥der: "; 14721 var prefDescription1$i = "Nuværende videoer har en alternativ synstolket version, textbaseret synstolkning."; 14722 var prefDescription2$i = "Nuværende videoer har alternativ synstolket version af videoen."; 14723 var prefDescription3$i = "Nuværende videoer har textbaseret synstolkning."; 14724 var prefIntroDescriptionNone$i = "Nuværende video har ingen synstolkning i noget format."; 14725 var prefIntroDescription3$i = "Anvende følgende formular for at indstille gældende textbasererede synstolkning."; 14726 var prefIntroDescription4$i = "Efter du gemmer dine indstillinger, kan synstolkning slÃ¥es til og fra med synstolkningsknappen."; 14727 var prefIntroKeyboard1$i = "Medieafspilleren pÃ¥ denne webside kan betjenest med tastuturgenveje (se neden for en liste)."; 14728 var prefIntroKeyboard2$i = "Meta-taster (Shift, Alt, og Ctrl) kan tildeles nedenfor."; 14729 var prefIntroKeyboard3$i = "BEMÃRK: Visse tastekombinationer kan være i konflikt med din webbrowser eller andre programmers indstillinger. Benyt de tastekombinationer der virker for dig."; 14730 var prefHeadingKeyboard1$i = "Metataster brugt til genveje"; 14731 var prefHeadingKeyboard2$i = "Nuværende tastatur genveje"; 14732 var prefHeadingDescription$i = "Synstolkning"; 14733 var prefHeadingTextDescription$i = "Tekstbaseret synstolkning"; 14734 var prefAltKey$i = "Alt"; 14735 var prefCtrlKey$i = "Ctrl"; 14736 var prefShiftKey$i = "Shift"; 14737 var prefNoKeyShortcuts$i = "Disable keyboard shortcuts"; 14738 var escapeKey$i = "Escape"; 14739 var escapeKeyFunction$i = "Luk nuværinde dialog eller popup-menu"; 14740 var prefDescPause$i = "Pause automatisk video nÃ¥r synstolkning starter"; 14741 var prefDescVisible$i = "Vis synstolkning"; 14742 var prefDescVoice$i = "Stemme"; 14743 var prefDescRate$i = "Spoken Description Rate"; 14744 var prefCaptionRate$i = "Spoken Caption Rate"; 14745 var prefDescPitch$i = "Tonehøjde"; 14746 var prefDescPitch1$i = "Meget lavt"; 14747 var prefDescPitch2$i = "Lavt"; 14748 var prefDescPitch3$i = "Normal"; 14749 var prefDescPitch4$i = "Højt"; 14750 var prefDescPitch5$i = "Meget højt"; 14751 var sampleDescriptionText$i = "Juster indstillinger for at høre denne eksempel tekst."; 14752 var prefHighlight$i = "Marker tekst som læses op i transskriptet"; 14753 var prefTabbable$i = "Tastaturaktiveret transskription"; 14754 var prefCaptionsFont$i = "Skrifttype"; 14755 var prefCaptionsColor$i = "Tekstfarve"; 14756 var prefCaptionsBGColor$i = "Baggrundsfarve"; 14757 var prefCaptionsSize$i = "Tekststørrelse"; 14758 var prefCaptionsOpacity$i = "Gennemsigtighed"; 14759 var prefCaptionsStyle$i = "Teksttype"; 14760 var serif$i = "serif"; 14761 var sans$i = "sans-serif"; 14762 var cursive$i = "kursiv"; 14763 var fantasy$i = "fantasi"; 14764 var monospace$i = "monospace"; 14765 var white$i = "hvid"; 14766 var yellow$i = "gul"; 14767 var green$i = "grøn"; 14768 var cyan$h = "cyan"; 14769 var blue$i = "blÃ¥"; 14770 var magenta$i = "lilla"; 14771 var red$i = "rød"; 14772 var black$i = "sort"; 14773 var transparent$i = "gennemsigtig"; 14774 var solid$i = "massiv"; 14775 var captionsStylePopOn$i = "Pop op"; 14776 var captionsStyleRollUp$i = "Pop ned"; 14777 var prefCaptionsPosition$i = "Position"; 14778 var captionsPositionOverlay$i = "Vis over video"; 14779 var captionsPositionBelow$i = "Vis under video"; 14780 var sampleCaptionText$i = "Eksempel pÃ¥ undertekst"; 14781 var prefSuccess$i = "Dine ændringer er gemt."; 14782 var prefNoChange$i = "Du har ikke lavet nogen ændringer."; 14783 var save$i = "Gem"; 14784 var cancel$i = "Afbryd"; 14785 var dismissButton$i = "Dismiss"; 14786 var windowButtonLabel$i = "Vindueindstillinger"; 14787 var windowMove$i = "Flyt"; 14788 var windowMoveLeft$i = "Window moved left"; 14789 var windowMoveRight$i = "Window moved right"; 14790 var windowMoveUp$i = "Window moved up"; 14791 var windowMoveDown$i = "Window moved down"; 14792 var windowMoveStopped$i = "Window move stopped"; 14793 var transcriptControls$i = "Transcript Window Controls"; 14794 var signControls$i = "Sign Language Window Controls"; 14795 var windowMoveAlert$i = "Flyt med mus eller anvende piletasterne for at flytte vinduer; Enter for at slutte"; 14796 var windowResize$i = "Ãndre størrelse"; 14797 var windowResizeHeading$i = "Ãndre vinduestørrelse"; 14798 var closeButtonLabel$i = "Luk"; 14799 var width$i = "Bredde"; 14800 var height$i = "Højde"; 14801 var resultsSummary1$i = "Du søgte efter:"; 14802 var resultsSummary2$i = "Fundne %1 matchende indhold."; 14803 var resultsSummary3$i = "Klik pÃ¥ tidslinien for at afspille derfra."; 14804 var noResultsFound$i = "Ingen resultater."; 14805 var searchButtonLabel$i = "Afspil fra %1"; 14806 var hour$i = "time"; 14807 var minute$i = "minut"; 14808 var second$i = "sekund"; 14809 var hours$i = "timer"; 14810 var minutes$i = "minutter"; 14811 var seconds$i = "sekunder"; 14812 var vtsHeading$i = "Video Transcript Sorter"; 14813 var vtsInstructions1$i = "Use the Video Transcript Sorter to modify text tracks:"; 14814 var vtsInstructions2$i = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 14815 var vtsInstructions3$i = "Modify content or start/end times (all are directly editable within the table)."; 14816 var vtsInstructions4$i = "Add new content, such as chapters or descriptions."; 14817 var vtsInstructions5$i = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 14818 var vtsSelectLanguage$i = "Select a language"; 14819 var vtsSave$i = "Generate new .vtt content"; 14820 var vtsReturn$i = "Return to Editor"; 14821 var vtsCancel$i = "Cancelling saving. Any edits you made have been restored in the VTS table."; 14822 var vtsRow$i = "Row"; 14823 var vtsKind$i = "Kind"; 14824 var vtsStart$i = "Start"; 14825 var vtsEnd$i = "End"; 14826 var vtsContent$i = "Content"; 14827 var vtsActions$i = "Actions"; 14828 var vtsNewRow$i = "A new row %1 has been inserted."; 14829 var vtsDeletedRow$i = "Row %1 has been deleted."; 14830 var vtsMovedRow$i = "Row %1 has been moved %2 and is now Row %3."; 14831 var da = { 14832 playerHeading: playerHeading$i, 14833 audioPlayer: audioPlayer$i, 14834 videoPlayer: videoPlayer$i, 14835 faster: faster$i, 14836 slower: slower$i, 14837 play: play$i, 14838 pause: pause$i, 14839 restart: restart$i, 14840 prevTrack: prevTrack$i, 14841 nextTrack: nextTrack$i, 14842 rewind: rewind$i, 14843 forward: forward$i, 14844 captions: captions$i, 14845 showCaptions: showCaptions$i, 14846 hideCaptions: hideCaptions$i, 14847 captionsOff: captionsOff$i, 14848 showTranscript: showTranscript$i, 14849 hideTranscript: hideTranscript$i, 14850 turnOnDescriptions: turnOnDescriptions$i, 14851 turnOffDescriptions: turnOffDescriptions$i, 14852 chapters: chapters$i, 14853 language: language$i, 14854 sign: sign$i, 14855 showSign: showSign$i, 14856 hideSign: hideSign$i, 14857 seekbarLabel: seekbarLabel$i, 14858 mute: mute$i, 14859 unmute: unmute$i, 14860 volume: volume$i, 14861 volumeUpDown: volumeUpDown$i, 14862 preferences: preferences$i, 14863 enterFullScreen: enterFullScreen$i, 14864 exitFullScreen: exitFullScreen$i, 14865 speed: speed$i, 14866 spacebar: spacebar$i, 14867 transcriptTitle: transcriptTitle$i, 14868 lyricsTitle: lyricsTitle$i, 14869 autoScroll: autoScroll$i, 14870 statusPlaying: statusPlaying$i, 14871 statusPaused: statusPaused$i, 14872 statusStopped: statusStopped$i, 14873 statusBuffering: statusBuffering$i, 14874 statusEnd: statusEnd$i, 14875 selectedTrack: selectedTrack$h, 14876 alertDescribedVersion: alertDescribedVersion$i, 14877 alertNonDescribedVersion: alertNonDescribedVersion$i, 14878 prefMenuCaptions: prefMenuCaptions$i, 14879 prefVoicedCaptions: prefVoicedCaptions$i, 14880 prefMenuDescriptions: prefMenuDescriptions$i, 14881 prefMenuKeyboard: prefMenuKeyboard$i, 14882 prefMenuTranscript: prefMenuTranscript$i, 14883 prefTitleCaptions: prefTitleCaptions$i, 14884 prefTitleDescriptions: prefTitleDescriptions$i, 14885 prefTitleKeyboard: prefTitleKeyboard$i, 14886 prefTitleTranscript: prefTitleTranscript$h, 14887 prefIntroDescription1: prefIntroDescription1$h, 14888 prefDescription1: prefDescription1$i, 14889 prefDescription2: prefDescription2$i, 14890 prefDescription3: prefDescription3$i, 14891 prefIntroDescriptionNone: prefIntroDescriptionNone$i, 14892 prefIntroDescription3: prefIntroDescription3$i, 14893 prefIntroDescription4: prefIntroDescription4$i, 14894 prefIntroKeyboard1: prefIntroKeyboard1$i, 14895 prefIntroKeyboard2: prefIntroKeyboard2$i, 14896 prefIntroKeyboard3: prefIntroKeyboard3$i, 14897 prefHeadingKeyboard1: prefHeadingKeyboard1$i, 14898 prefHeadingKeyboard2: prefHeadingKeyboard2$i, 14899 prefHeadingDescription: prefHeadingDescription$i, 14900 prefHeadingTextDescription: prefHeadingTextDescription$i, 14901 prefAltKey: prefAltKey$i, 14902 prefCtrlKey: prefCtrlKey$i, 14903 prefShiftKey: prefShiftKey$i, 14904 prefNoKeyShortcuts: prefNoKeyShortcuts$i, 14905 escapeKey: escapeKey$i, 14906 escapeKeyFunction: escapeKeyFunction$i, 14907 prefDescPause: prefDescPause$i, 14908 prefDescVisible: prefDescVisible$i, 14909 prefDescVoice: prefDescVoice$i, 14910 prefDescRate: prefDescRate$i, 14911 prefCaptionRate: prefCaptionRate$i, 14912 prefDescPitch: prefDescPitch$i, 14913 prefDescPitch1: prefDescPitch1$i, 14914 prefDescPitch2: prefDescPitch2$i, 14915 prefDescPitch3: prefDescPitch3$i, 14916 prefDescPitch4: prefDescPitch4$i, 14917 prefDescPitch5: prefDescPitch5$i, 14918 sampleDescriptionText: sampleDescriptionText$i, 14919 prefHighlight: prefHighlight$i, 14920 prefTabbable: prefTabbable$i, 14921 prefCaptionsFont: prefCaptionsFont$i, 14922 prefCaptionsColor: prefCaptionsColor$i, 14923 prefCaptionsBGColor: prefCaptionsBGColor$i, 14924 prefCaptionsSize: prefCaptionsSize$i, 14925 prefCaptionsOpacity: prefCaptionsOpacity$i, 14926 prefCaptionsStyle: prefCaptionsStyle$i, 14927 serif: serif$i, 14928 sans: sans$i, 14929 cursive: cursive$i, 14930 fantasy: fantasy$i, 14931 monospace: monospace$i, 14932 white: white$i, 14933 yellow: yellow$i, 14934 green: green$i, 14935 cyan: cyan$h, 14936 blue: blue$i, 14937 magenta: magenta$i, 14938 red: red$i, 14939 black: black$i, 14940 transparent: transparent$i, 14941 solid: solid$i, 14942 captionsStylePopOn: captionsStylePopOn$i, 14943 captionsStyleRollUp: captionsStyleRollUp$i, 14944 prefCaptionsPosition: prefCaptionsPosition$i, 14945 captionsPositionOverlay: captionsPositionOverlay$i, 14946 captionsPositionBelow: captionsPositionBelow$i, 14947 sampleCaptionText: sampleCaptionText$i, 14948 prefSuccess: prefSuccess$i, 14949 prefNoChange: prefNoChange$i, 14950 save: save$i, 14951 cancel: cancel$i, 14952 dismissButton: dismissButton$i, 14953 windowButtonLabel: windowButtonLabel$i, 14954 windowMove: windowMove$i, 14955 windowMoveLeft: windowMoveLeft$i, 14956 windowMoveRight: windowMoveRight$i, 14957 windowMoveUp: windowMoveUp$i, 14958 windowMoveDown: windowMoveDown$i, 14959 windowMoveStopped: windowMoveStopped$i, 14960 transcriptControls: transcriptControls$i, 14961 signControls: signControls$i, 14962 windowMoveAlert: windowMoveAlert$i, 14963 windowResize: windowResize$i, 14964 windowResizeHeading: windowResizeHeading$i, 14965 closeButtonLabel: closeButtonLabel$i, 14966 width: width$i, 14967 height: height$i, 14968 resultsSummary1: resultsSummary1$i, 14969 resultsSummary2: resultsSummary2$i, 14970 resultsSummary3: resultsSummary3$i, 14971 noResultsFound: noResultsFound$i, 14972 searchButtonLabel: searchButtonLabel$i, 14973 hour: hour$i, 14974 minute: minute$i, 14975 second: second$i, 14976 hours: hours$i, 14977 minutes: minutes$i, 14978 seconds: seconds$i, 14979 vtsHeading: vtsHeading$i, 14980 vtsInstructions1: vtsInstructions1$i, 14981 vtsInstructions2: vtsInstructions2$i, 14982 vtsInstructions3: vtsInstructions3$i, 14983 vtsInstructions4: vtsInstructions4$i, 14984 vtsInstructions5: vtsInstructions5$i, 14985 vtsSelectLanguage: vtsSelectLanguage$i, 14986 vtsSave: vtsSave$i, 14987 vtsReturn: vtsReturn$i, 14988 vtsCancel: vtsCancel$i, 14989 vtsRow: vtsRow$i, 14990 vtsKind: vtsKind$i, 14991 vtsStart: vtsStart$i, 14992 vtsEnd: vtsEnd$i, 14993 vtsContent: vtsContent$i, 14994 vtsActions: vtsActions$i, 14995 vtsNewRow: vtsNewRow$i, 14996 vtsDeletedRow: vtsDeletedRow$i, 14997 vtsMovedRow: vtsMovedRow$i 14998 }; 14999 15000 var playerHeading$h = "Medienplayer"; 15001 var audioPlayer$h = "Audioplayer"; 15002 var videoPlayer$h = "Videoplayer"; 15003 var faster$h = "Schneller"; 15004 var slower$h = "Langsamer"; 15005 var chapters$h = "Kapitel"; 15006 var play$h = "Abspielen"; 15007 var pause$h = "Pause"; 15008 var restart$h = "Neustart"; 15009 var prevTrack$h = "Vorheriger Titel"; 15010 var nextTrack$h = "Nächster Titel"; 15011 var rewind$h = "Zurück"; 15012 var forward$h = "Vorwärts"; 15013 var captions$h = "Untertitel"; 15014 var showCaptions$h = "Untertitel anzeigen"; 15015 var hideCaptions$h = "Untertitel ausblenden"; 15016 var captionsOff$h = "Untertitel ausschalten"; 15017 var showTranscript$h = "Transkription anzeigen"; 15018 var hideTranscript$h = "Transkription entfernen"; 15019 var turnOnDescriptions$h = "Audiobeschreibung einschalten"; 15020 var turnOffDescriptions$h = "Audiobeschreibung ausschalten"; 15021 var language$h = "Sprache"; 15022 var sign$h = "Gebärdensprache"; 15023 var showSign$h = "Gebärdensprache anzeigen"; 15024 var hideSign$h = "Gebärdensprache ausblenden"; 15025 var seekbarLabel$h = "Suchleiste"; 15026 var mute$h = "Ton aus"; 15027 var unmute$h = "Ton an"; 15028 var volume$h = "Lautstärke"; 15029 var volumeUpDown$h = "Lautstärkeregler"; 15030 var preferences$h = "Einstellungen"; 15031 var enterFullScreen$h = "Vollbildmodus einschalten"; 15032 var exitFullScreen$h = "Vollbildmodus verlassen"; 15033 var speed$h = "Geschwindigkeit"; 15034 var spacebar$h = "Leertaste"; 15035 var transcriptTitle$h = "Transkription"; 15036 var lyricsTitle$h = "Text"; 15037 var autoScroll$h = "Automatisch scrollen"; 15038 var statusPlaying$h = "Gestartet"; 15039 var statusPaused$h = "Pausiert"; 15040 var statusStopped$h = "Angehalten"; 15041 var statusBuffering$h = "Daten werden empfangen..."; 15042 var statusEnd$h = "Ende des Titels"; 15043 var selectedTrack$g = "Ausgewählter Titel"; 15044 var alertDescribedVersion$h = "Das Video wird mit Audiobeschreibung abgespielt"; 15045 var alertNonDescribedVersion$h = "Das Video wird ohne Audiobeschreibung abgespielt"; 15046 var prefMenuCaptions$h = "Untertitel"; 15047 var prefVoicedCaptions$h = "Spoken Captions"; 15048 var prefMenuDescriptions$h = "Audiobeschreibungen"; 15049 var prefMenuKeyboard$h = "Tastatur"; 15050 var prefMenuTranscript$h = "Transkription"; 15051 var prefTitleCaptions$h = "Untertitel Einstellungen"; 15052 var prefTitleDescriptions$h = "Audiobeschreibung Einstellungen"; 15053 var prefTitleKeyboard$h = "Tastatur Einstellungen"; 15054 var prefTitleTranscript$g = "Transkription Einstellungen"; 15055 var prefIntroDescription1$g = "Dieser Media Player unterstützt zwei Arten von Untertiteln: "; 15056 var prefDescription1$h = "Das aktuelle Video hat eine alternative Version der Audiobeschreibung, eine textbasierte Audiobeschreibung."; 15057 var prefDescription2$h = "Das aktuelle Video hat Version des Videos, die eine Audiobeschreibung enthält."; 15058 var prefDescription3$h = "Das aktuelle Video hat Textbasierte Audiobeschreibung, die vom Screen-Reader vorgelesen wird."; 15059 var prefIntroDescriptionNone$h = "Das aktuelle Video hat keine Audiobeschreibung."; 15060 var prefIntroDescription3$h = "Mit der folgenden Auswahl steuern Sie das Abspielen der textbasierten Audiobeschreibung."; 15061 var prefIntroDescription4$h = "Wenn die Audiobeschreibung aktiviert ist, kann sie per Schaltfläche ein- und ausgeschaltet werden."; 15062 var prefIntroKeyboard1$h = "Dieser Media Player lässt sich innerhalb der gesamten Seite per Tastenkürzel bedienen (siehe unten)."; 15063 var prefIntroKeyboard2$h = "Die Modifikatortasten (Umschalt, Alt, und Strg) können hier zugeordnet werden."; 15064 var prefIntroKeyboard3$h = "Achtung: Einige Tastenkombinationen sind je nach Browser und Betriebssystem nicht möglich. Versuchen Sie gegebenenfalls andere Kombinationen."; 15065 var prefHeadingKeyboard1$h = "Modifikatortasten für die Tastenkürzel"; 15066 var prefHeadingKeyboard2$h = "Aktuell eingestellte Tastenkürzel"; 15067 var prefHeadingDescription$h = "Audiobeschreibung"; 15068 var prefHeadingTextDescription$h = "Textbasierte Audiobeschreibung"; 15069 var prefAltKey$h = "Alt"; 15070 var prefCtrlKey$h = "Strg"; 15071 var prefShiftKey$h = "Umschalttaste"; 15072 var prefNoKeyShortcuts$h = "Tastenkombinationen deaktivieren"; 15073 var escapeKey$h = "ESC Taste"; 15074 var escapeKeyFunction$h = "Dialogfenster schlieÃen"; 15075 var prefDescPause$h = "Video automatisch anhalten, wenn Szenenbeschreibungen eingeblendet werden"; 15076 var prefDescVisible$h = "Textbasierte Szenenbeschreibungen einblenden, wenn diese aktiviert sind"; 15077 var prefDescVoice$h = "Stimme"; 15078 var prefDescRate$h = "Spoken Description Rate"; 15079 var prefCaptionRate$h = "Spoken Caption Rate"; 15080 var prefDescPitch$h = "Tonlage"; 15081 var prefDescPitch1$h = "Sehr tief"; 15082 var prefDescPitch2$h = "Tief"; 15083 var prefDescPitch3$h = "Mittel"; 15084 var prefDescPitch4$h = "Hoch"; 15085 var prefDescPitch5$h = "Sehr hoch"; 15086 var sampleDescriptionText$h = "Einstellungen bearbeiten um diesen Text vorzulesen."; 15087 var prefHighlight$h = "Transkription hervorheben, während das Medium abgespielt wird"; 15088 var prefTabbable$h = "Transkription per Tastatur ein-/ausschaltbar machen"; 15089 var prefCaptionsFont$h = "Schriftart"; 15090 var prefCaptionsColor$h = "Schriftfarbe"; 15091 var prefCaptionsBGColor$h = "Hintergrund"; 15092 var prefCaptionsSize$h = "SchriftgöÃe"; 15093 var prefCaptionsOpacity$h = "Deckkraft"; 15094 var prefCaptionsStyle$h = "Stil"; 15095 var serif$h = "Serifenschrift"; 15096 var sans$h = "Serifenlose Schrift"; 15097 var cursive$h = "kursiv"; 15098 var fantasy$h = "Fantasieschrift"; 15099 var monospace$h = "nichtproportionale Schrift"; 15100 var white$h = "weiÃ"; 15101 var yellow$h = "gelb"; 15102 var green$h = "grün"; 15103 var cyan$g = "cyan"; 15104 var blue$h = "blau"; 15105 var magenta$h = "magenta"; 15106 var red$h = "rot"; 15107 var black$h = "schwarz"; 15108 var transparent$h = "transparent"; 15109 var solid$h = "undurchsichtig"; 15110 var captionsStylePopOn$h = "Pop-on"; 15111 var captionsStyleRollUp$h = "Roll-up"; 15112 var prefCaptionsPosition$h = "Position"; 15113 var captionsPositionOverlay$h = "Ãberlagert"; 15114 var captionsPositionBelow$h = "Unterhalb"; 15115 var sampleCaptionText$h = "Textbeispiel"; 15116 var prefSuccess$h = "Ihre Ãnderungen wurden gespeichert."; 15117 var prefNoChange$h = "Es gab keine Ãnderungen zu speichern."; 15118 var save$h = "Speichern"; 15119 var cancel$h = "Abbrechen"; 15120 var dismissButton$h = "Dismiss"; 15121 var windowButtonLabel$h = "Fenstereinstellungen"; 15122 var windowMove$h = "Verschieben"; 15123 var windowMoveLeft$h = "Das Fenster verschob sich nach links"; 15124 var windowMoveRight$h = "Das Fenster verschob sich nach rechts"; 15125 var windowMoveUp$h = "Das Fenster verschob sich nach oben"; 15126 var windowMoveDown$h = "Das Fenster verschob sich nach unten"; 15127 var windowMoveStopped$h = "Das Fenster hörte auf sich zu verschieben"; 15128 var transcriptControls$h = "Bedienelemente des Transkriptionsfensters"; 15129 var signControls$h = "Bedienelemente des Gebärdensprache-Fensters"; 15130 var windowMoveAlert$h = "Fenster mit Pfeiltasten oder Maus verschieben; beenden mit Eingabetaste"; 15131 var windowResize$h = "GröÃe verändern"; 15132 var windowResizeHeading$h = "GröÃe des Gebärdensprache-Fenster"; 15133 var closeButtonLabel$h = "SchlieÃen"; 15134 var width$h = "Breite"; 15135 var height$h = "Höhe"; 15136 var resultsSummary1$h = "Suche nach:"; 15137 var resultsSummary2$h = "Gefunden %1 Treffer."; 15138 var resultsSummary3$h = "Auf den Zeitindex klicken, um das Video vom Zeitpunkt des jeweiligen Suchergebnisses abzuspielen."; 15139 var noResultsFound$h = "Keine Treffer."; 15140 var searchButtonLabel$h = "Abspielen von %1"; 15141 var hour$h = "Stunde"; 15142 var minute$h = "Minute"; 15143 var second$h = "Sekunde"; 15144 var hours$h = "Stunden"; 15145 var minutes$h = "Minuten"; 15146 var seconds$h = "Sekunden"; 15147 var vtsHeading$h = "Transkriptions-Sortierer für Videos"; 15148 var vtsInstructions1$h = "Verwenden Sie den Transkription-Sortierer für Videos, um Textspuren zu ändern:"; 15149 var vtsInstructions2$h = "Ordnen Sie Kapitel, Beschreibungen, Bildunterschriften, und/oder Untertitel so, dass diese in der richtigen Reihenfolge in der selbsterzeugten Transkription des Able Players erscheinen."; 15150 var vtsInstructions3$h = "Verändern Sie Inhalt oder Anfangs- bzw. Endzeiten (alle sind direkt innerhalb der Tabelle editierbar)."; 15151 var vtsInstructions4$h = "Fügen Sie neuen Inhalt, wie Kapitel oder Beschreibungen hinzu."; 15152 var vtsInstructions5$h = "Nach dem Editieren klicken Sie auf die Schaltfläche \"Ãnderungen speichern\" um neuen Inhalt für alle wichtigen zeitgesteuerten Textdateien zu erzeugen. Der neue Text kann in neue WebVTT-Dateien kopiert und eingefügt werden."; 15153 var vtsSelectLanguage$h = "Wählen Sie eine Sprache aus"; 15154 var vtsSave$h = "Erzeugen Sie neuen .vtt-Inhalt"; 15155 var vtsReturn$h = "Gehen Sie zurück zum Editor"; 15156 var vtsCancel$h = "Abbruch des Speicherns. Jegliche Bearbeitungen, die Sie vorgenommen haben, wurden in der VTS-Tabelle gesichert."; 15157 var vtsRow$h = "Reihe"; 15158 var vtsKind$h = "Art"; 15159 var vtsStart$h = "Anfang"; 15160 var vtsEnd$h = "Ende"; 15161 var vtsContent$h = "Inhalt"; 15162 var vtsActions$h = "Aktionen"; 15163 var vtsNewRow$h = "Eine neue Reihe %1 wurde eingefügt."; 15164 var vtsDeletedRow$h = "Reihe %1 wurde gelöscht."; 15165 var vtsMovedRow$h = "Reihe %1 wurde verschoben %2 ist nun Reihe %3."; 15166 var de = { 15167 playerHeading: playerHeading$h, 15168 audioPlayer: audioPlayer$h, 15169 videoPlayer: videoPlayer$h, 15170 faster: faster$h, 15171 slower: slower$h, 15172 chapters: chapters$h, 15173 play: play$h, 15174 pause: pause$h, 15175 restart: restart$h, 15176 prevTrack: prevTrack$h, 15177 nextTrack: nextTrack$h, 15178 rewind: rewind$h, 15179 forward: forward$h, 15180 captions: captions$h, 15181 showCaptions: showCaptions$h, 15182 hideCaptions: hideCaptions$h, 15183 captionsOff: captionsOff$h, 15184 showTranscript: showTranscript$h, 15185 hideTranscript: hideTranscript$h, 15186 turnOnDescriptions: turnOnDescriptions$h, 15187 turnOffDescriptions: turnOffDescriptions$h, 15188 language: language$h, 15189 sign: sign$h, 15190 showSign: showSign$h, 15191 hideSign: hideSign$h, 15192 seekbarLabel: seekbarLabel$h, 15193 mute: mute$h, 15194 unmute: unmute$h, 15195 volume: volume$h, 15196 volumeUpDown: volumeUpDown$h, 15197 preferences: preferences$h, 15198 enterFullScreen: enterFullScreen$h, 15199 exitFullScreen: exitFullScreen$h, 15200 speed: speed$h, 15201 spacebar: spacebar$h, 15202 transcriptTitle: transcriptTitle$h, 15203 lyricsTitle: lyricsTitle$h, 15204 autoScroll: autoScroll$h, 15205 statusPlaying: statusPlaying$h, 15206 statusPaused: statusPaused$h, 15207 statusStopped: statusStopped$h, 15208 statusBuffering: statusBuffering$h, 15209 statusEnd: statusEnd$h, 15210 selectedTrack: selectedTrack$g, 15211 alertDescribedVersion: alertDescribedVersion$h, 15212 alertNonDescribedVersion: alertNonDescribedVersion$h, 15213 prefMenuCaptions: prefMenuCaptions$h, 15214 prefVoicedCaptions: prefVoicedCaptions$h, 15215 prefMenuDescriptions: prefMenuDescriptions$h, 15216 prefMenuKeyboard: prefMenuKeyboard$h, 15217 prefMenuTranscript: prefMenuTranscript$h, 15218 prefTitleCaptions: prefTitleCaptions$h, 15219 prefTitleDescriptions: prefTitleDescriptions$h, 15220 prefTitleKeyboard: prefTitleKeyboard$h, 15221 prefTitleTranscript: prefTitleTranscript$g, 15222 prefIntroDescription1: prefIntroDescription1$g, 15223 prefDescription1: prefDescription1$h, 15224 prefDescription2: prefDescription2$h, 15225 prefDescription3: prefDescription3$h, 15226 prefIntroDescriptionNone: prefIntroDescriptionNone$h, 15227 prefIntroDescription3: prefIntroDescription3$h, 15228 prefIntroDescription4: prefIntroDescription4$h, 15229 prefIntroKeyboard1: prefIntroKeyboard1$h, 15230 prefIntroKeyboard2: prefIntroKeyboard2$h, 15231 prefIntroKeyboard3: prefIntroKeyboard3$h, 15232 prefHeadingKeyboard1: prefHeadingKeyboard1$h, 15233 prefHeadingKeyboard2: prefHeadingKeyboard2$h, 15234 prefHeadingDescription: prefHeadingDescription$h, 15235 prefHeadingTextDescription: prefHeadingTextDescription$h, 15236 prefAltKey: prefAltKey$h, 15237 prefCtrlKey: prefCtrlKey$h, 15238 prefShiftKey: prefShiftKey$h, 15239 prefNoKeyShortcuts: prefNoKeyShortcuts$h, 15240 escapeKey: escapeKey$h, 15241 escapeKeyFunction: escapeKeyFunction$h, 15242 prefDescPause: prefDescPause$h, 15243 prefDescVisible: prefDescVisible$h, 15244 prefDescVoice: prefDescVoice$h, 15245 prefDescRate: prefDescRate$h, 15246 prefCaptionRate: prefCaptionRate$h, 15247 prefDescPitch: prefDescPitch$h, 15248 prefDescPitch1: prefDescPitch1$h, 15249 prefDescPitch2: prefDescPitch2$h, 15250 prefDescPitch3: prefDescPitch3$h, 15251 prefDescPitch4: prefDescPitch4$h, 15252 prefDescPitch5: prefDescPitch5$h, 15253 sampleDescriptionText: sampleDescriptionText$h, 15254 prefHighlight: prefHighlight$h, 15255 prefTabbable: prefTabbable$h, 15256 prefCaptionsFont: prefCaptionsFont$h, 15257 prefCaptionsColor: prefCaptionsColor$h, 15258 prefCaptionsBGColor: prefCaptionsBGColor$h, 15259 prefCaptionsSize: prefCaptionsSize$h, 15260 prefCaptionsOpacity: prefCaptionsOpacity$h, 15261 prefCaptionsStyle: prefCaptionsStyle$h, 15262 serif: serif$h, 15263 sans: sans$h, 15264 cursive: cursive$h, 15265 fantasy: fantasy$h, 15266 monospace: monospace$h, 15267 white: white$h, 15268 yellow: yellow$h, 15269 green: green$h, 15270 cyan: cyan$g, 15271 blue: blue$h, 15272 magenta: magenta$h, 15273 red: red$h, 15274 black: black$h, 15275 transparent: transparent$h, 15276 solid: solid$h, 15277 captionsStylePopOn: captionsStylePopOn$h, 15278 captionsStyleRollUp: captionsStyleRollUp$h, 15279 prefCaptionsPosition: prefCaptionsPosition$h, 15280 captionsPositionOverlay: captionsPositionOverlay$h, 15281 captionsPositionBelow: captionsPositionBelow$h, 15282 sampleCaptionText: sampleCaptionText$h, 15283 prefSuccess: prefSuccess$h, 15284 prefNoChange: prefNoChange$h, 15285 save: save$h, 15286 cancel: cancel$h, 15287 dismissButton: dismissButton$h, 15288 windowButtonLabel: windowButtonLabel$h, 15289 windowMove: windowMove$h, 15290 windowMoveLeft: windowMoveLeft$h, 15291 windowMoveRight: windowMoveRight$h, 15292 windowMoveUp: windowMoveUp$h, 15293 windowMoveDown: windowMoveDown$h, 15294 windowMoveStopped: windowMoveStopped$h, 15295 transcriptControls: transcriptControls$h, 15296 signControls: signControls$h, 15297 windowMoveAlert: windowMoveAlert$h, 15298 windowResize: windowResize$h, 15299 windowResizeHeading: windowResizeHeading$h, 15300 closeButtonLabel: closeButtonLabel$h, 15301 width: width$h, 15302 height: height$h, 15303 resultsSummary1: resultsSummary1$h, 15304 resultsSummary2: resultsSummary2$h, 15305 resultsSummary3: resultsSummary3$h, 15306 noResultsFound: noResultsFound$h, 15307 searchButtonLabel: searchButtonLabel$h, 15308 hour: hour$h, 15309 minute: minute$h, 15310 second: second$h, 15311 hours: hours$h, 15312 minutes: minutes$h, 15313 seconds: seconds$h, 15314 vtsHeading: vtsHeading$h, 15315 vtsInstructions1: vtsInstructions1$h, 15316 vtsInstructions2: vtsInstructions2$h, 15317 vtsInstructions3: vtsInstructions3$h, 15318 vtsInstructions4: vtsInstructions4$h, 15319 vtsInstructions5: vtsInstructions5$h, 15320 vtsSelectLanguage: vtsSelectLanguage$h, 15321 vtsSave: vtsSave$h, 15322 vtsReturn: vtsReturn$h, 15323 vtsCancel: vtsCancel$h, 15324 vtsRow: vtsRow$h, 15325 vtsKind: vtsKind$h, 15326 vtsStart: vtsStart$h, 15327 vtsEnd: vtsEnd$h, 15328 vtsContent: vtsContent$h, 15329 vtsActions: vtsActions$h, 15330 vtsNewRow: vtsNewRow$h, 15331 vtsDeletedRow: vtsDeletedRow$h, 15332 vtsMovedRow: vtsMovedRow$h 15333 }; 15334 15335 var playerHeading$g = "Media player"; 15336 var audioPlayer$g = "Audio player"; 15337 var videoPlayer$g = "Video player"; 15338 var faster$g = "Faster"; 15339 var slower$g = "Slower"; 15340 var play$g = "Play"; 15341 var pause$g = "Pause"; 15342 var restart$g = "Restart"; 15343 var prevTrack$g = "Previous track"; 15344 var nextTrack$g = "Next track"; 15345 var rewind$g = "Rewind"; 15346 var forward$g = "Forward"; 15347 var captions$g = "Captions"; 15348 var showCaptions$g = "Show captions"; 15349 var hideCaptions$g = "Hide captions"; 15350 var captionsOff$g = "Captions off"; 15351 var showTranscript$g = "Show transcript"; 15352 var hideTranscript$g = "Hide transcript"; 15353 var turnOnDescriptions$g = "Turn on descriptions"; 15354 var turnOffDescriptions$g = "Turn off descriptions"; 15355 var chapters$g = "Chapters"; 15356 var language$g = "Language"; 15357 var sign$g = "Sign language"; 15358 var showSign$g = "Show sign language"; 15359 var hideSign$g = "Hide sign language"; 15360 var seekbarLabel$g = "timeline"; 15361 var mute$g = "Mute"; 15362 var unmute$g = "Unmute"; 15363 var volume$g = "Volume"; 15364 var volumeUpDown$g = "Volume up down"; 15365 var preferences$g = "Preferences"; 15366 var enterFullScreen$g = "Enter full screen"; 15367 var exitFullScreen$g = "Exit full screen"; 15368 var speed$g = "Speed"; 15369 var spacebar$g = "spacebar"; 15370 var transcriptTitle$g = "Transcript"; 15371 var lyricsTitle$g = "Lyrics"; 15372 var autoScroll$g = "Auto scroll"; 15373 var statusPlaying$g = "Playing"; 15374 var statusPaused$g = "Paused"; 15375 var statusStopped$g = "Stopped"; 15376 var statusBuffering$g = "Buffering"; 15377 var statusEnd$g = "End of track"; 15378 var selectedTrack$f = "Selected Track"; 15379 var alertDescribedVersion$g = "Using the audio described version of this video"; 15380 var alertNonDescribedVersion$g = "Using the non-described version of this video"; 15381 var prefMenuCaptions$g = "Captions"; 15382 var prefVoicedCaptions$g = "Spoken Captions"; 15383 var prefMenuDescriptions$g = "Descriptions"; 15384 var prefMenuKeyboard$g = "Keyboard"; 15385 var prefMenuTranscript$g = "Transcript"; 15386 var prefTitleCaptions$g = "Captions Preferences"; 15387 var prefTitleDescriptions$g = "Audio Description Preferences"; 15388 var prefTitleKeyboard$g = "Keyboard Preferences"; 15389 var prefTitleTranscript$f = "Transcript Preferences"; 15390 var prefIntroDescription1$f = "This media player supports audio description in two ways: "; 15391 var prefDescription1$g = "The current video has an alternative described version and text-based description, announced by screen reader."; 15392 var prefDescription2$g = "The current video has text-based description."; 15393 var prefDescription3$g = "The current video has an alternative described version."; 15394 var prefIntroDescriptionNone$g = "The current video has no audio description in either format."; 15395 var prefIntroDescription3$g = "Use the following form to set your preferences related to text-based audio description."; 15396 var prefIntroDescription4$g = "After you save your settings, audio description can be toggled on/off using the Description button."; 15397 var prefIntroKeyboard1$g = "The media player on this web page can be operated from anywhere on the page using keyboard shortcuts (see below for a list)."; 15398 var prefIntroKeyboard2$g = "Modifier keys (Shift, Alt, and Control) can be assigned below."; 15399 var prefIntroKeyboard3$g = "NOTE: Some key combinations might conflict with keys used by your browser and/or other software applications. Try various combinations of modifier keys to find one that works for you."; 15400 var prefHeadingKeyboard1$g = "Modifier keys used for shortcuts"; 15401 var prefHeadingKeyboard2$g = "Current keyboard shortcuts"; 15402 var prefHeadingDescription$g = "Audio description"; 15403 var prefHeadingTextDescription$g = "Text-based audio description"; 15404 var prefAltKey$g = "Alt"; 15405 var prefCtrlKey$g = "Control"; 15406 var prefShiftKey$g = "Shift"; 15407 var prefNoKeyShortcuts$g = "Disable keyboard shortcuts"; 15408 var escapeKey$g = "Escape"; 15409 var escapeKeyFunction$g = "Close current dialog or popup menu"; 15410 var prefDescPause$g = "Automatically pause video when description starts"; 15411 var prefDescVisible$g = "Make description visible"; 15412 var prefDescVoice$g = "Voice"; 15413 var prefDescRate$g = "Spoken Description Rate"; 15414 var prefCaptionRate$g = "Spoken Caption Rate"; 15415 var prefDescPitch$g = "Pitch"; 15416 var prefDescPitch1$g = "Very low"; 15417 var prefDescPitch2$g = "Low"; 15418 var prefDescPitch3$g = "Default"; 15419 var prefDescPitch4$g = "High"; 15420 var prefDescPitch5$g = "Very high"; 15421 var sampleDescriptionText$g = "Adjust settings to hear this sample text."; 15422 var prefHighlight$g = "Highlight transcript as media plays"; 15423 var prefTabbable$g = "Keyboard-enable transcript"; 15424 var prefCaptionsFont$g = "Font"; 15425 var prefCaptionsColor$g = "Text Color"; 15426 var prefCaptionsBGColor$g = "Background"; 15427 var prefCaptionsSize$g = "Font Size"; 15428 var prefCaptionsOpacity$g = "Opacity"; 15429 var prefCaptionsStyle$g = "Style"; 15430 var serif$g = "serif"; 15431 var sans$g = "sans-serif"; 15432 var cursive$g = "cursive"; 15433 var fantasy$g = "fantasy"; 15434 var monospace$g = "monospace"; 15435 var white$g = "white"; 15436 var yellow$g = "yellow"; 15437 var green$g = "green"; 15438 var cyan$f = "cyan"; 15439 var blue$g = "blue"; 15440 var magenta$g = "magenta"; 15441 var red$g = "red"; 15442 var black$g = "black"; 15443 var transparent$g = "transparent"; 15444 var solid$g = "solid"; 15445 var captionsStylePopOn$g = "Pop-on"; 15446 var captionsStyleRollUp$g = "Roll-up"; 15447 var prefCaptionsPosition$g = "Position"; 15448 var captionsPositionOverlay$g = "Overlay"; 15449 var captionsPositionBelow$g = "Below video"; 15450 var sampleCaptionText$g = "Sample caption text"; 15451 var prefSuccess$g = "Your changes have been saved."; 15452 var prefNoChange$g = "You didn't make any changes."; 15453 var save$g = "Save"; 15454 var cancel$g = "Cancel"; 15455 var dismissButton$g = "Dismiss"; 15456 var windowButtonLabel$g = "Window options"; 15457 var windowMove$g = "Move"; 15458 var windowMoveLeft$g = "Window moved left"; 15459 var windowMoveRight$g = "Window moved right"; 15460 var windowMoveUp$g = "Window moved up"; 15461 var windowMoveDown$g = "Window moved down"; 15462 var windowMoveStopped$g = "Window move stopped"; 15463 var transcriptControls$g = "Transcript Window Controls"; 15464 var signControls$g = "Sign Language Window Controls"; 15465 var windowMoveAlert$g = "Drag or use arrow keys to move the window; Enter to stop"; 15466 var windowResize$g = "Resize"; 15467 var windowResizeHeading$g = "Resize Window"; 15468 var closeButtonLabel$g = "Close"; 15469 var width$g = "Width"; 15470 var height$g = "Height"; 15471 var resultsSummary1$g = "You searched for:"; 15472 var resultsSummary2$g = "Found %1 matching items."; 15473 var resultsSummary3$g = "Click the time associated with any item to play the video from that point."; 15474 var noResultsFound$g = "No results found."; 15475 var searchButtonLabel$g = "Play at %1"; 15476 var hour$g = "hour"; 15477 var minute$g = "minute"; 15478 var second$g = "second"; 15479 var hours$g = "hours"; 15480 var minutes$g = "minutes"; 15481 var seconds$g = "seconds"; 15482 var vtsHeading$g = "Video Transcript Sorter"; 15483 var vtsInstructions1$g = "Use the Video Transcript Sorter to modify text tracks:"; 15484 var vtsInstructions2$g = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 15485 var vtsInstructions3$g = "Modify content or start/end times (all are directly editable within the table)."; 15486 var vtsInstructions4$g = "Add new content, such as chapters or descriptions."; 15487 var vtsInstructions5$g = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 15488 var vtsSelectLanguage$g = "Select a language"; 15489 var vtsSave$g = "Generate new .vtt content"; 15490 var vtsReturn$g = "Return to Editor"; 15491 var vtsCancel$g = "Cancelling saving. Any edits you made have been restored in the VTS table."; 15492 var vtsRow$g = "Row"; 15493 var vtsKind$g = "Kind"; 15494 var vtsStart$g = "Start"; 15495 var vtsEnd$g = "End"; 15496 var vtsContent$g = "Content"; 15497 var vtsActions$g = "Actions"; 15498 var vtsNewRow$g = "A new row %1 has been inserted."; 15499 var vtsDeletedRow$g = "Row %1 has been deleted."; 15500 var vtsMovedRow$g = "Row %1 has been moved %2 and is now Row %3."; 15501 var en = { 15502 playerHeading: playerHeading$g, 15503 audioPlayer: audioPlayer$g, 15504 videoPlayer: videoPlayer$g, 15505 faster: faster$g, 15506 slower: slower$g, 15507 play: play$g, 15508 pause: pause$g, 15509 restart: restart$g, 15510 prevTrack: prevTrack$g, 15511 nextTrack: nextTrack$g, 15512 rewind: rewind$g, 15513 forward: forward$g, 15514 captions: captions$g, 15515 showCaptions: showCaptions$g, 15516 hideCaptions: hideCaptions$g, 15517 captionsOff: captionsOff$g, 15518 showTranscript: showTranscript$g, 15519 hideTranscript: hideTranscript$g, 15520 turnOnDescriptions: turnOnDescriptions$g, 15521 turnOffDescriptions: turnOffDescriptions$g, 15522 chapters: chapters$g, 15523 language: language$g, 15524 sign: sign$g, 15525 showSign: showSign$g, 15526 hideSign: hideSign$g, 15527 seekbarLabel: seekbarLabel$g, 15528 mute: mute$g, 15529 unmute: unmute$g, 15530 volume: volume$g, 15531 volumeUpDown: volumeUpDown$g, 15532 preferences: preferences$g, 15533 enterFullScreen: enterFullScreen$g, 15534 exitFullScreen: exitFullScreen$g, 15535 speed: speed$g, 15536 spacebar: spacebar$g, 15537 transcriptTitle: transcriptTitle$g, 15538 lyricsTitle: lyricsTitle$g, 15539 autoScroll: autoScroll$g, 15540 statusPlaying: statusPlaying$g, 15541 statusPaused: statusPaused$g, 15542 statusStopped: statusStopped$g, 15543 statusBuffering: statusBuffering$g, 15544 statusEnd: statusEnd$g, 15545 selectedTrack: selectedTrack$f, 15546 alertDescribedVersion: alertDescribedVersion$g, 15547 alertNonDescribedVersion: alertNonDescribedVersion$g, 15548 prefMenuCaptions: prefMenuCaptions$g, 15549 prefVoicedCaptions: prefVoicedCaptions$g, 15550 prefMenuDescriptions: prefMenuDescriptions$g, 15551 prefMenuKeyboard: prefMenuKeyboard$g, 15552 prefMenuTranscript: prefMenuTranscript$g, 15553 prefTitleCaptions: prefTitleCaptions$g, 15554 prefTitleDescriptions: prefTitleDescriptions$g, 15555 prefTitleKeyboard: prefTitleKeyboard$g, 15556 prefTitleTranscript: prefTitleTranscript$f, 15557 prefIntroDescription1: prefIntroDescription1$f, 15558 prefDescription1: prefDescription1$g, 15559 prefDescription2: prefDescription2$g, 15560 prefDescription3: prefDescription3$g, 15561 prefIntroDescriptionNone: prefIntroDescriptionNone$g, 15562 prefIntroDescription3: prefIntroDescription3$g, 15563 prefIntroDescription4: prefIntroDescription4$g, 15564 prefIntroKeyboard1: prefIntroKeyboard1$g, 15565 prefIntroKeyboard2: prefIntroKeyboard2$g, 15566 prefIntroKeyboard3: prefIntroKeyboard3$g, 15567 prefHeadingKeyboard1: prefHeadingKeyboard1$g, 15568 prefHeadingKeyboard2: prefHeadingKeyboard2$g, 15569 prefHeadingDescription: prefHeadingDescription$g, 15570 prefHeadingTextDescription: prefHeadingTextDescription$g, 15571 prefAltKey: prefAltKey$g, 15572 prefCtrlKey: prefCtrlKey$g, 15573 prefShiftKey: prefShiftKey$g, 15574 prefNoKeyShortcuts: prefNoKeyShortcuts$g, 15575 escapeKey: escapeKey$g, 15576 escapeKeyFunction: escapeKeyFunction$g, 15577 prefDescPause: prefDescPause$g, 15578 prefDescVisible: prefDescVisible$g, 15579 prefDescVoice: prefDescVoice$g, 15580 prefDescRate: prefDescRate$g, 15581 prefCaptionRate: prefCaptionRate$g, 15582 prefDescPitch: prefDescPitch$g, 15583 prefDescPitch1: prefDescPitch1$g, 15584 prefDescPitch2: prefDescPitch2$g, 15585 prefDescPitch3: prefDescPitch3$g, 15586 prefDescPitch4: prefDescPitch4$g, 15587 prefDescPitch5: prefDescPitch5$g, 15588 sampleDescriptionText: sampleDescriptionText$g, 15589 prefHighlight: prefHighlight$g, 15590 prefTabbable: prefTabbable$g, 15591 prefCaptionsFont: prefCaptionsFont$g, 15592 prefCaptionsColor: prefCaptionsColor$g, 15593 prefCaptionsBGColor: prefCaptionsBGColor$g, 15594 prefCaptionsSize: prefCaptionsSize$g, 15595 prefCaptionsOpacity: prefCaptionsOpacity$g, 15596 prefCaptionsStyle: prefCaptionsStyle$g, 15597 serif: serif$g, 15598 sans: sans$g, 15599 cursive: cursive$g, 15600 fantasy: fantasy$g, 15601 monospace: monospace$g, 15602 white: white$g, 15603 yellow: yellow$g, 15604 green: green$g, 15605 cyan: cyan$f, 15606 blue: blue$g, 15607 magenta: magenta$g, 15608 red: red$g, 15609 black: black$g, 15610 transparent: transparent$g, 15611 solid: solid$g, 15612 captionsStylePopOn: captionsStylePopOn$g, 15613 captionsStyleRollUp: captionsStyleRollUp$g, 15614 prefCaptionsPosition: prefCaptionsPosition$g, 15615 captionsPositionOverlay: captionsPositionOverlay$g, 15616 captionsPositionBelow: captionsPositionBelow$g, 15617 sampleCaptionText: sampleCaptionText$g, 15618 prefSuccess: prefSuccess$g, 15619 prefNoChange: prefNoChange$g, 15620 save: save$g, 15621 cancel: cancel$g, 15622 dismissButton: dismissButton$g, 15623 windowButtonLabel: windowButtonLabel$g, 15624 windowMove: windowMove$g, 15625 windowMoveLeft: windowMoveLeft$g, 15626 windowMoveRight: windowMoveRight$g, 15627 windowMoveUp: windowMoveUp$g, 15628 windowMoveDown: windowMoveDown$g, 15629 windowMoveStopped: windowMoveStopped$g, 15630 transcriptControls: transcriptControls$g, 15631 signControls: signControls$g, 15632 windowMoveAlert: windowMoveAlert$g, 15633 windowResize: windowResize$g, 15634 windowResizeHeading: windowResizeHeading$g, 15635 closeButtonLabel: closeButtonLabel$g, 15636 width: width$g, 15637 height: height$g, 15638 resultsSummary1: resultsSummary1$g, 15639 resultsSummary2: resultsSummary2$g, 15640 resultsSummary3: resultsSummary3$g, 15641 noResultsFound: noResultsFound$g, 15642 searchButtonLabel: searchButtonLabel$g, 15643 hour: hour$g, 15644 minute: minute$g, 15645 second: second$g, 15646 hours: hours$g, 15647 minutes: minutes$g, 15648 seconds: seconds$g, 15649 vtsHeading: vtsHeading$g, 15650 vtsInstructions1: vtsInstructions1$g, 15651 vtsInstructions2: vtsInstructions2$g, 15652 vtsInstructions3: vtsInstructions3$g, 15653 vtsInstructions4: vtsInstructions4$g, 15654 vtsInstructions5: vtsInstructions5$g, 15655 vtsSelectLanguage: vtsSelectLanguage$g, 15656 vtsSave: vtsSave$g, 15657 vtsReturn: vtsReturn$g, 15658 vtsCancel: vtsCancel$g, 15659 vtsRow: vtsRow$g, 15660 vtsKind: vtsKind$g, 15661 vtsStart: vtsStart$g, 15662 vtsEnd: vtsEnd$g, 15663 vtsContent: vtsContent$g, 15664 vtsActions: vtsActions$g, 15665 vtsNewRow: vtsNewRow$g, 15666 vtsDeletedRow: vtsDeletedRow$g, 15667 vtsMovedRow: vtsMovedRow$g 15668 }; 15669 15670 var playerHeading$f = "Media player"; 15671 var audioPlayer$f = "Audio player"; 15672 var videoPlayer$f = "Video player"; 15673 var faster$f = "Rápido"; 15674 var slower$f = "Lento"; 15675 var play$f = "Play"; 15676 var pause$f = "Pausa"; 15677 var restart$f = "Reiniciar"; 15678 var prevTrack$f = "Pista Anterior"; 15679 var nextTrack$f = "Siguiente Pista"; 15680 var rewind$f = "Rebobinar"; 15681 var forward$f = "Adelantar"; 15682 var captions$f = "SubtÃtulos"; 15683 var showCaptions$f = "Mostrar subtÃtulos"; 15684 var hideCaptions$f = "Ocultar subtÃtulos"; 15685 var captionsOff$f = "Sin subtÃtulos"; 15686 var showTranscript$f = "Mostrar transcripción"; 15687 var hideTranscript$f = "Ocultar transcripción"; 15688 var turnOnDescriptions$f = "Habilitar descripciones"; 15689 var turnOffDescriptions$f = "Deshabilitar descripciones"; 15690 var chapters$f = "CapÃtulos"; 15691 var language$f = "Idioma"; 15692 var sign$f = "Lengua de señas"; 15693 var showSign$f = "Mostrar lengua de señas"; 15694 var hideSign$f = "Ocultar lengua de señas"; 15695 var seekbarLabel$f = "timeline"; 15696 var mute$f = "Silenciar"; 15697 var unmute$f = "Habilitar sonido"; 15698 var volume$f = "Volumen"; 15699 var volumeUpDown$f = "Bajar sonido"; 15700 var preferences$f = "Preferencias"; 15701 var enterFullScreen$f = "Ver a pantalla completa"; 15702 var exitFullScreen$f = "Salir de pantalla completa"; 15703 var speed$f = "Velocidad"; 15704 var spacebar$f = "Barra espaciadora"; 15705 var transcriptTitle$f = "Transcript"; 15706 var lyricsTitle$f = "Letra"; 15707 var autoScroll$f = "Desplazamiento automático"; 15708 var statusPlaying$f = "Reproduciendo"; 15709 var statusPaused$f = "Pausado"; 15710 var statusStopped$f = "Detenido"; 15711 var statusBuffering$f = "Almacenando"; 15712 var statusEnd$f = "Fin de pista"; 15713 var selectedTrack$e = "Pista seleccionada"; 15714 var alertDescribedVersion$f = "Utilizando la versión audiodescrita del vÃdeo"; 15715 var alertNonDescribedVersion$f = "Utilizando la versión no descrita de este vÃdeo"; 15716 var prefMenuCaptions$f = "SubtÃtulos"; 15717 var prefVoicedCaptions$f = "Spoken Captions"; 15718 var prefMenuDescriptions$f = "Descripciones"; 15719 var prefMenuKeyboard$f = "Teclado"; 15720 var prefMenuTranscript$f = "Transcripción"; 15721 var prefTitleCaptions$f = "Preferencias de subtÃtulos"; 15722 var prefTitleDescriptions$f = "Preferencias de audiodescripción"; 15723 var prefTitleKeyboard$f = "Preferencias de teclado"; 15724 var prefTitleTranscript$e = "Preferencias de transcripción"; 15725 var prefIntroDescription1$e = "Este reproductor soporta la audiodescripción de dos maneras: "; 15726 var prefDescription1$f = "El vÃdeo actual tiene una versión alternativa con descripción, descripción en texto."; 15727 var prefDescription2$f = "El vÃdeo actual tiene versión alternativa del vÃdeo, descrito."; 15728 var prefDescription3$f = "El vÃdeo actual tiene descripción en texto, leÃda por el lector de pantalla."; 15729 var prefIntroDescriptionNone$f = "El vÃdeo actual no tiene audiodescripción de ninguna manera."; 15730 var prefIntroDescription3$f = "Utilice el siguiente formulario para establecer sus preferencias en cuanto a la audiodescripción en texto."; 15731 var prefIntroDescription4$f = "Una vez guardadas sus preferencias, la audiodescripción puede habilitarse o deshabilitarse mediante el botón Descripción."; 15732 var prefIntroKeyboard1$f = "El reproductor en esta página puede manejarse desde cualquier parte de la página utilizando los atajos de teclado (vea la lista más abajo)."; 15733 var prefIntroKeyboard2$f = "Las teclas modificadoras (Mayúsculas, Alt, Control) pueden definirse más abajo."; 15734 var prefIntroKeyboard3$f = "NOTA: Algunas combinaciones de teclas pueden entrar en conflicto con teclas utilizadas por su navegador y/o otras aplicaciones. Intente varias combinaciones de teclas modificadoras para encontrar la que funciona bien en su caso."; 15735 var prefHeadingKeyboard1$f = "Teclas modificadoras utilizadas para atajos de teclado"; 15736 var prefHeadingKeyboard2$f = "Atajos de teclado definidos actualmente"; 15737 var prefHeadingDescription$f = "Audiodescrita"; 15738 var prefHeadingTextDescription$f = "Audiodescrita en texto"; 15739 var prefAltKey$f = "Alt"; 15740 var prefCtrlKey$f = "Control"; 15741 var prefShiftKey$f = "Mayúsculas"; 15742 var prefNoKeyShortcuts$f = "Disable keyboard shortcuts"; 15743 var escapeKey$f = "Escape"; 15744 var escapeKeyFunction$f = "Cerrar el cuadro de diálogo o menú contextual"; 15745 var prefDescPause$f = "Pausar automáticamente el video cuando arranque una descripción"; 15746 var prefDescVisible$f = "Hacer visibles las descripciones en texto si se están usando"; 15747 var prefDescVoice$f = "Voz"; 15748 var prefDescRate$f = "Spoken Description Rate"; 15749 var prefCaptionRate$f = "Spoken Caption Rate"; 15750 var prefDescPitch$f = "Tono"; 15751 var prefDescPitch1$f = "Muy grave"; 15752 var prefDescPitch2$f = "Grave"; 15753 var prefDescPitch3$f = "Normal"; 15754 var prefDescPitch4$f = "Aguda"; 15755 var prefDescPitch5$f = "Muy aguda"; 15756 var sampleDescriptionText$f = "Ajuste la configuración para escuchar este texto de muestra"; 15757 var prefHighlight$f = "Resalte la transcripción a medida que avance el contenido"; 15758 var prefTabbable$f = "Transcripción manejable por teclado"; 15759 var prefCaptionsFont$f = "Fuente"; 15760 var prefCaptionsColor$f = "Color del texto"; 15761 var prefCaptionsBGColor$f = "Fondo"; 15762 var prefCaptionsSize$f = "Tamaño de Fuente"; 15763 var prefCaptionsOpacity$f = "Opacidad"; 15764 var prefCaptionsStyle$f = "Estilo"; 15765 var serif$f = "serif"; 15766 var sans$f = "sans-serif"; 15767 var cursive$f = "cursiva"; 15768 var fantasy$f = "fantasÃa"; 15769 var monospace$f = "mono espaciada"; 15770 var white$f = "blanco"; 15771 var yellow$f = "amarillo"; 15772 var green$f = "verde"; 15773 var cyan$e = "cyan"; 15774 var blue$f = "azul"; 15775 var magenta$f = "magenta"; 15776 var red$f = "rojo"; 15777 var black$f = "negro"; 15778 var transparent$f = "transparente"; 15779 var solid$f = "sólido"; 15780 var captionsStylePopOn$f = "Pop-on"; 15781 var captionsStyleRollUp$f = "Roll-up"; 15782 var prefCaptionsPosition$f = "Posición"; 15783 var captionsPositionOverlay$f = "Cubrir"; 15784 var captionsPositionBelow$f = "Debajo del vÃdeo"; 15785 var sampleCaptionText$f = "Texto de ejemplo de subtÃtulo"; 15786 var prefSuccess$f = "Los cambios han sido guardados."; 15787 var prefNoChange$f = "No se ha hecho ningún cambio."; 15788 var save$f = "Guardar"; 15789 var cancel$f = "Cancelar"; 15790 var dismissButton$f = "Dismiss"; 15791 var windowButtonLabel$f = "Opciones en Windows"; 15792 var windowMove$f = "Mover"; 15793 var windowMoveLeft$f = "Window moved left"; 15794 var windowMoveRight$f = "Window moved right"; 15795 var windowMoveUp$f = "Window moved up"; 15796 var windowMoveDown$f = "Window moved down"; 15797 var windowMoveStopped$f = "Window move stopped"; 15798 var transcriptControls$f = "Transcript Window Controls"; 15799 var signControls$f = "Sign Language Window Controls"; 15800 var windowMoveAlert$f = "Arrastre o use las teclas de flecha para mover la ventana, pulse Enter para parar."; 15801 var windowResize$f = "Redimensionar"; 15802 var windowResizeHeading$f = "Redimensionar la ventana con el intérprete"; 15803 var closeButtonLabel$f = "Cerrar"; 15804 var width$f = "Ancho"; 15805 var height$f = "Alto"; 15806 var resultsSummary1$f = "You searched for:"; 15807 var resultsSummary2$f = "Found %1 matching items."; 15808 var resultsSummary3$f = "Click the time associated with any item to play the video from that point."; 15809 var noResultsFound$f = "No results found."; 15810 var searchButtonLabel$f = "Play at %1"; 15811 var hour$f = "hour"; 15812 var minute$f = "minute"; 15813 var second$f = "second"; 15814 var hours$f = "hours"; 15815 var minutes$f = "minutes"; 15816 var seconds$f = "seconds"; 15817 var vtsHeading$f = "Video Transcript Sorter"; 15818 var vtsInstructions1$f = "Use the Video Transcript Sorter to modify text tracks:"; 15819 var vtsInstructions2$f = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 15820 var vtsInstructions3$f = "Modify content or start/end times (all are directly editable within the table)."; 15821 var vtsInstructions4$f = "Add new content, such as chapters or descriptions."; 15822 var vtsInstructions5$f = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 15823 var vtsSelectLanguage$f = "Select a language"; 15824 var vtsSave$f = "Generate new .vtt content"; 15825 var vtsReturn$f = "Return to Editor"; 15826 var vtsCancel$f = "Cancelling saving. Any edits you made have been restored in the VTS table."; 15827 var vtsRow$f = "Row"; 15828 var vtsKind$f = "Kind"; 15829 var vtsStart$f = "Start"; 15830 var vtsEnd$f = "End"; 15831 var vtsContent$f = "Content"; 15832 var vtsActions$f = "Actions"; 15833 var vtsNewRow$f = "A new row %1 has been inserted."; 15834 var vtsDeletedRow$f = "Row %1 has been deleted."; 15835 var vtsMovedRow$f = "Row %1 has been moved %2 and is now Row %3."; 15836 var es = { 15837 playerHeading: playerHeading$f, 15838 audioPlayer: audioPlayer$f, 15839 videoPlayer: videoPlayer$f, 15840 faster: faster$f, 15841 slower: slower$f, 15842 play: play$f, 15843 pause: pause$f, 15844 restart: restart$f, 15845 prevTrack: prevTrack$f, 15846 nextTrack: nextTrack$f, 15847 rewind: rewind$f, 15848 forward: forward$f, 15849 captions: captions$f, 15850 showCaptions: showCaptions$f, 15851 hideCaptions: hideCaptions$f, 15852 captionsOff: captionsOff$f, 15853 showTranscript: showTranscript$f, 15854 hideTranscript: hideTranscript$f, 15855 turnOnDescriptions: turnOnDescriptions$f, 15856 turnOffDescriptions: turnOffDescriptions$f, 15857 chapters: chapters$f, 15858 language: language$f, 15859 sign: sign$f, 15860 showSign: showSign$f, 15861 hideSign: hideSign$f, 15862 seekbarLabel: seekbarLabel$f, 15863 mute: mute$f, 15864 unmute: unmute$f, 15865 volume: volume$f, 15866 volumeUpDown: volumeUpDown$f, 15867 preferences: preferences$f, 15868 enterFullScreen: enterFullScreen$f, 15869 exitFullScreen: exitFullScreen$f, 15870 speed: speed$f, 15871 spacebar: spacebar$f, 15872 transcriptTitle: transcriptTitle$f, 15873 lyricsTitle: lyricsTitle$f, 15874 autoScroll: autoScroll$f, 15875 statusPlaying: statusPlaying$f, 15876 statusPaused: statusPaused$f, 15877 statusStopped: statusStopped$f, 15878 statusBuffering: statusBuffering$f, 15879 statusEnd: statusEnd$f, 15880 selectedTrack: selectedTrack$e, 15881 alertDescribedVersion: alertDescribedVersion$f, 15882 alertNonDescribedVersion: alertNonDescribedVersion$f, 15883 prefMenuCaptions: prefMenuCaptions$f, 15884 prefVoicedCaptions: prefVoicedCaptions$f, 15885 prefMenuDescriptions: prefMenuDescriptions$f, 15886 prefMenuKeyboard: prefMenuKeyboard$f, 15887 prefMenuTranscript: prefMenuTranscript$f, 15888 prefTitleCaptions: prefTitleCaptions$f, 15889 prefTitleDescriptions: prefTitleDescriptions$f, 15890 prefTitleKeyboard: prefTitleKeyboard$f, 15891 prefTitleTranscript: prefTitleTranscript$e, 15892 prefIntroDescription1: prefIntroDescription1$e, 15893 prefDescription1: prefDescription1$f, 15894 prefDescription2: prefDescription2$f, 15895 prefDescription3: prefDescription3$f, 15896 prefIntroDescriptionNone: prefIntroDescriptionNone$f, 15897 prefIntroDescription3: prefIntroDescription3$f, 15898 prefIntroDescription4: prefIntroDescription4$f, 15899 prefIntroKeyboard1: prefIntroKeyboard1$f, 15900 prefIntroKeyboard2: prefIntroKeyboard2$f, 15901 prefIntroKeyboard3: prefIntroKeyboard3$f, 15902 prefHeadingKeyboard1: prefHeadingKeyboard1$f, 15903 prefHeadingKeyboard2: prefHeadingKeyboard2$f, 15904 prefHeadingDescription: prefHeadingDescription$f, 15905 prefHeadingTextDescription: prefHeadingTextDescription$f, 15906 prefAltKey: prefAltKey$f, 15907 prefCtrlKey: prefCtrlKey$f, 15908 prefShiftKey: prefShiftKey$f, 15909 prefNoKeyShortcuts: prefNoKeyShortcuts$f, 15910 escapeKey: escapeKey$f, 15911 escapeKeyFunction: escapeKeyFunction$f, 15912 prefDescPause: prefDescPause$f, 15913 prefDescVisible: prefDescVisible$f, 15914 prefDescVoice: prefDescVoice$f, 15915 prefDescRate: prefDescRate$f, 15916 prefCaptionRate: prefCaptionRate$f, 15917 prefDescPitch: prefDescPitch$f, 15918 prefDescPitch1: prefDescPitch1$f, 15919 prefDescPitch2: prefDescPitch2$f, 15920 prefDescPitch3: prefDescPitch3$f, 15921 prefDescPitch4: prefDescPitch4$f, 15922 prefDescPitch5: prefDescPitch5$f, 15923 sampleDescriptionText: sampleDescriptionText$f, 15924 prefHighlight: prefHighlight$f, 15925 prefTabbable: prefTabbable$f, 15926 prefCaptionsFont: prefCaptionsFont$f, 15927 prefCaptionsColor: prefCaptionsColor$f, 15928 prefCaptionsBGColor: prefCaptionsBGColor$f, 15929 prefCaptionsSize: prefCaptionsSize$f, 15930 prefCaptionsOpacity: prefCaptionsOpacity$f, 15931 prefCaptionsStyle: prefCaptionsStyle$f, 15932 serif: serif$f, 15933 sans: sans$f, 15934 cursive: cursive$f, 15935 fantasy: fantasy$f, 15936 monospace: monospace$f, 15937 white: white$f, 15938 yellow: yellow$f, 15939 green: green$f, 15940 cyan: cyan$e, 15941 blue: blue$f, 15942 magenta: magenta$f, 15943 red: red$f, 15944 black: black$f, 15945 transparent: transparent$f, 15946 solid: solid$f, 15947 captionsStylePopOn: captionsStylePopOn$f, 15948 captionsStyleRollUp: captionsStyleRollUp$f, 15949 prefCaptionsPosition: prefCaptionsPosition$f, 15950 captionsPositionOverlay: captionsPositionOverlay$f, 15951 captionsPositionBelow: captionsPositionBelow$f, 15952 sampleCaptionText: sampleCaptionText$f, 15953 prefSuccess: prefSuccess$f, 15954 prefNoChange: prefNoChange$f, 15955 save: save$f, 15956 cancel: cancel$f, 15957 dismissButton: dismissButton$f, 15958 windowButtonLabel: windowButtonLabel$f, 15959 windowMove: windowMove$f, 15960 windowMoveLeft: windowMoveLeft$f, 15961 windowMoveRight: windowMoveRight$f, 15962 windowMoveUp: windowMoveUp$f, 15963 windowMoveDown: windowMoveDown$f, 15964 windowMoveStopped: windowMoveStopped$f, 15965 transcriptControls: transcriptControls$f, 15966 signControls: signControls$f, 15967 windowMoveAlert: windowMoveAlert$f, 15968 windowResize: windowResize$f, 15969 windowResizeHeading: windowResizeHeading$f, 15970 closeButtonLabel: closeButtonLabel$f, 15971 width: width$f, 15972 height: height$f, 15973 resultsSummary1: resultsSummary1$f, 15974 resultsSummary2: resultsSummary2$f, 15975 resultsSummary3: resultsSummary3$f, 15976 noResultsFound: noResultsFound$f, 15977 searchButtonLabel: searchButtonLabel$f, 15978 hour: hour$f, 15979 minute: minute$f, 15980 second: second$f, 15981 hours: hours$f, 15982 minutes: minutes$f, 15983 seconds: seconds$f, 15984 vtsHeading: vtsHeading$f, 15985 vtsInstructions1: vtsInstructions1$f, 15986 vtsInstructions2: vtsInstructions2$f, 15987 vtsInstructions3: vtsInstructions3$f, 15988 vtsInstructions4: vtsInstructions4$f, 15989 vtsInstructions5: vtsInstructions5$f, 15990 vtsSelectLanguage: vtsSelectLanguage$f, 15991 vtsSave: vtsSave$f, 15992 vtsReturn: vtsReturn$f, 15993 vtsCancel: vtsCancel$f, 15994 vtsRow: vtsRow$f, 15995 vtsKind: vtsKind$f, 15996 vtsStart: vtsStart$f, 15997 vtsEnd: vtsEnd$f, 15998 vtsContent: vtsContent$f, 15999 vtsActions: vtsActions$f, 16000 vtsNewRow: vtsNewRow$f, 16001 vtsDeletedRow: vtsDeletedRow$f, 16002 vtsMovedRow: vtsMovedRow$f 16003 }; 16004 16005 var playerHeading$e = "Lecteur multimédia"; 16006 var audioPlayer$e = "Audio player"; 16007 var videoPlayer$e = "Video player"; 16008 var faster$e = "Plus rapidement"; 16009 var slower$e = "Plus lentement"; 16010 var play$e = "Lecture"; 16011 var pause$e = "Pause"; 16012 var restart$e = "Redémarrer"; 16013 var prevTrack$e = "Piste Précédente"; 16014 var nextTrack$e = "Piste Suivante"; 16015 var rewind$e = "Reculer"; 16016 var forward$e = "Avancer"; 16017 var captions$e = "Sous-titres"; 16018 var showCaptions$e = "Afficher les sous-titres"; 16019 var hideCaptions$e = "Masquer les sous-titres"; 16020 var captionsOff$e = "Sous-titres désactivés"; 16021 var showTranscript$e = "Afficher la transcription"; 16022 var hideTranscript$e = "Masquer la transcription"; 16023 var turnOnDescriptions$e = "Activer les descriptions"; 16024 var turnOffDescriptions$e = "Désactiver les descriptions"; 16025 var chapters$e = "Chapitres"; 16026 var language$e = "Langue"; 16027 var sign$e = "Langage gestuel"; 16028 var showSign$e = "Afficher le langage gestuel"; 16029 var hideSign$e = "Masque le langage gestuel"; 16030 var seekbarLabel$e = "timeline"; 16031 var mute$e = "Son désactivé"; 16032 var unmute$e = "Son activé"; 16033 var volume$e = "Volume"; 16034 var volumeUpDown$e = "Monter baisser le volume"; 16035 var preferences$e = "Préférences"; 16036 var enterFullScreen$e = "Activer le mode plein écran"; 16037 var exitFullScreen$e = "Quitter le mode plein écran"; 16038 var speed$e = "Vitesse"; 16039 var spacebar$e = "barre dâespacement"; 16040 var transcriptTitle$e = "Transcription"; 16041 var lyricsTitle$e = "Paroles"; 16042 var autoScroll$e = "Défilement automatique"; 16043 var statusPlaying$e = "Lecture en cours"; 16044 var statusPaused$e = "Lecture sur pause"; 16045 var statusStopped$e = "Lecture interrompue"; 16046 var statusBuffering$e = "Tamponnage"; 16047 var statusEnd$e = "Fin de la piste"; 16048 var selectedTrack$d = "Piste choisie"; 16049 var alertDescribedVersion$e = "Utilisation de la version avec description sonore de cette vidéo"; 16050 var alertNonDescribedVersion$e = "Utilisation de la version non décrite de cette vidéo"; 16051 var prefMenuCaptions$e = "Sous-titres"; 16052 var prefVoicedCaptions$e = "Spoken Captions"; 16053 var prefMenuDescriptions$e = "Descriptions"; 16054 var prefMenuKeyboard$e = "Clavier"; 16055 var prefMenuTranscript$e = "Transcription"; 16056 var prefTitleCaptions$e = "Préférences liées au sous-titrage"; 16057 var prefTitleDescriptions$e = "Préférences liées aux descriptions sonores"; 16058 var prefTitleKeyboard$e = "Préférences liées au clavier"; 16059 var prefTitleTranscript$d = "Préférence liées à la transcription"; 16060 var prefIntroDescription1$d = "Ce lecteur multimédia permet dâentendre les descriptions sonores de deux façons:"; 16061 var prefDescription1$e = "Il y a une version autre version avec description, description textuelle."; 16062 var prefDescription2$e = "Il y a une version autre version de la vidéo avec description."; 16063 var prefDescription3$e = "Il y a une version description textuelle, lue à lâaide dâun lecteur dâécran."; 16064 var prefIntroDescriptionNone$e = "Il nây a pas de version avec description sonore (dans ni lâun ni lâautre des formats) de la présente vidéo."; 16065 var prefIntroDescription3$e = "Utilisez le formulaire suivant pour établir vos préférences liées aux descriptions sonores textuelle."; 16066 var prefIntroDescription4$e = "Après avoir enregistré vos préférences, vous pouvez activer ou désactiver la description sonore avec le bouton Description."; 16067 var prefIntroKeyboard1$e = "Le lecteur multimédia de cette page Web peut être utilisé à partir de nâimporte quel endroit sur la page avec des raccourcis du clavier (voir la liste ci-dessous)."; 16068 var prefIntroKeyboard2$e = "Des rôles peuvent être assignés aux touches de modification (Shift, Alt, Ctrl) ci-dessous."; 16069 var prefIntroKeyboard3$e = "Certaines combinaisons de touches pourraient entrer en conflit avec les touches utilisées par votre navigateur ou autres applications logicielles. Essayez diverses combinaisons de touches de modification pour en trouver qui fonctionnent pour vous."; 16070 var prefHeadingKeyboard1$e = "Touches de modification utilisées pour des raccourcis"; 16071 var prefHeadingKeyboard2$e = "Raccourcis du clavier assignés actuellement"; 16072 var prefHeadingDescription$e = "Description sonore"; 16073 var prefHeadingTextDescription$e = "Description sonore textuelle"; 16074 var prefAltKey$e = "Alt"; 16075 var prefCtrlKey$e = "Ctrl"; 16076 var prefShiftKey$e = "Shift"; 16077 var prefNoKeyShortcuts$e = "Disable keyboard shortcuts"; 16078 var escapeKey$e = "Esc"; 16079 var escapeKeyFunction$e = "Fermer la fenêtre de dialogue ou le menu contextuel"; 16080 var prefDescPause$e = "Mettre la vidéo en pause automatiquement quand la description commence"; 16081 var prefDescVisible$e = "Affichez la description"; 16082 var prefDescVoice$e = "Voix"; 16083 var prefDescRate$e = "Spoken Description Rate"; 16084 var prefCaptionRate$e = "Spoken Caption Rate"; 16085 var prefDescPitch$e = "Volume"; 16086 var prefDescPitch1$e = "Très faible"; 16087 var prefDescPitch2$e = "Faible"; 16088 var prefDescPitch3$e = "Par défaut"; 16089 var prefDescPitch4$e = "Fort"; 16090 var prefDescPitch5$e = "Très fort"; 16091 var sampleDescriptionText$e = "Régler les paramètres afin d'entendre cet extrait de texte"; 16092 var prefHighlight$e = "Surligner la transcription pendant la lecture"; 16093 var prefTabbable$e = "Transcription activée par clavier"; 16094 var prefCaptionsFont$e = "Police de caractères"; 16095 var prefCaptionsColor$e = "Couleur du texte"; 16096 var prefCaptionsBGColor$e = "Arrière-plan"; 16097 var prefCaptionsSize$e = "Taille de la police"; 16098 var prefCaptionsOpacity$e = "Opacité"; 16099 var prefCaptionsStyle$e = "Style"; 16100 var serif$e = "avec empattement"; 16101 var sans$e = "sans empattement"; 16102 var cursive$e = "écriture cursive"; 16103 var fantasy$e = "écriture de fantaisie"; 16104 var monospace$e = "à taille fixe"; 16105 var white$e = "blanc"; 16106 var yellow$e = "jaune"; 16107 var green$e = "vert"; 16108 var cyan$d = "cyan"; 16109 var blue$e = "bleu"; 16110 var magenta$e = "magenta"; 16111 var red$e = "rouge"; 16112 var black$e = "noir"; 16113 var transparent$e = "transparent"; 16114 var solid$e = "solide"; 16115 var captionsStylePopOn$e = "Pop-on"; 16116 var captionsStyleRollUp$e = "Roll-up"; 16117 var prefCaptionsPosition$e = "Position"; 16118 var captionsPositionOverlay$e = "Superposés"; 16119 var captionsPositionBelow$e = "Sous la vidéo"; 16120 var sampleCaptionText$e = "Ãchantillon de sous-titre"; 16121 var prefSuccess$e = "Vos changements ont été enregistrés."; 16122 var prefNoChange$e = "Vous nâavez pas fait de changements."; 16123 var save$e = "Enregistrer"; 16124 var cancel$e = "Annuler"; 16125 var dismissButton$e = "Dismiss"; 16126 var windowButtonLabel$e = "Options de fenêtre"; 16127 var windowMove$e = "Déplacer"; 16128 var windowMoveLeft$e = "Window moved left"; 16129 var windowMoveRight$e = "Window moved right"; 16130 var windowMoveUp$e = "Window moved up"; 16131 var windowMoveDown$e = "Window moved down"; 16132 var windowMoveStopped$e = "Window move stopped"; 16133 var transcriptControls$e = "Transcript Window Controls"; 16134 var signControls$e = "Sign Language Window Controls"; 16135 var windowMoveAlert$e = "Faites glisser avec la souris ou utilisez les touches fléchées pour déplacer la fenêtre; appuyez sur « Enter » pour arrêter."; 16136 var windowResize$e = "Redimensionner"; 16137 var windowResizeHeading$e = "Redimensionner la fenêtre de lâinterprète"; 16138 var closeButtonLabel$e = "Fermer"; 16139 var width$e = "Largeur"; 16140 var height$e = "Hauteur"; 16141 var resultsSummary1$e = "Vos résultats de recherche"; 16142 var resultsSummary2$e = "Trouvé %1 éléments correspondants."; 16143 var resultsSummary3$e = "Cliquez sur le temps associé à n'importe quel élément pour lire la vidéo à partir de ce point."; 16144 var noResultsFound$e = "Aucun résultat trouvé."; 16145 var searchButtonLabel$e = "Lecture à %1"; 16146 var hour$e = "heure"; 16147 var minute$e = "minute"; 16148 var second$e = "seconde"; 16149 var hours$e = "heures"; 16150 var minutes$e = "minutes"; 16151 var seconds$e = "seconds"; 16152 var vtsHeading$e = "Video Transcript Sorter"; 16153 var vtsInstructions1$e = "Use the Video Transcript Sorter to modify text tracks:"; 16154 var vtsInstructions2$e = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16155 var vtsInstructions3$e = "Modify content or start/end times (all are directly editable within the table)."; 16156 var vtsInstructions4$e = "Add new content, such as chapters or descriptions."; 16157 var vtsInstructions5$e = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 16158 var vtsSelectLanguage$e = "Select a language"; 16159 var vtsSave$e = "Generate new .vtt content"; 16160 var vtsReturn$e = "Return to Editor"; 16161 var vtsCancel$e = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16162 var vtsRow$e = "Row"; 16163 var vtsKind$e = "Kind"; 16164 var vtsStart$e = "Start"; 16165 var vtsEnd$e = "End"; 16166 var vtsContent$e = "Content"; 16167 var vtsActions$e = "Actions"; 16168 var vtsNewRow$e = "A new row %1 has been inserted."; 16169 var vtsDeletedRow$e = "Row %1 has been deleted."; 16170 var vtsMovedRow$e = "Row %1 has been moved %2 and is now Row %3."; 16171 var fr = { 16172 playerHeading: playerHeading$e, 16173 audioPlayer: audioPlayer$e, 16174 videoPlayer: videoPlayer$e, 16175 faster: faster$e, 16176 slower: slower$e, 16177 play: play$e, 16178 pause: pause$e, 16179 restart: restart$e, 16180 prevTrack: prevTrack$e, 16181 nextTrack: nextTrack$e, 16182 rewind: rewind$e, 16183 forward: forward$e, 16184 captions: captions$e, 16185 showCaptions: showCaptions$e, 16186 hideCaptions: hideCaptions$e, 16187 captionsOff: captionsOff$e, 16188 showTranscript: showTranscript$e, 16189 hideTranscript: hideTranscript$e, 16190 turnOnDescriptions: turnOnDescriptions$e, 16191 turnOffDescriptions: turnOffDescriptions$e, 16192 chapters: chapters$e, 16193 language: language$e, 16194 sign: sign$e, 16195 showSign: showSign$e, 16196 hideSign: hideSign$e, 16197 seekbarLabel: seekbarLabel$e, 16198 mute: mute$e, 16199 unmute: unmute$e, 16200 volume: volume$e, 16201 volumeUpDown: volumeUpDown$e, 16202 preferences: preferences$e, 16203 enterFullScreen: enterFullScreen$e, 16204 exitFullScreen: exitFullScreen$e, 16205 speed: speed$e, 16206 spacebar: spacebar$e, 16207 transcriptTitle: transcriptTitle$e, 16208 lyricsTitle: lyricsTitle$e, 16209 autoScroll: autoScroll$e, 16210 statusPlaying: statusPlaying$e, 16211 statusPaused: statusPaused$e, 16212 statusStopped: statusStopped$e, 16213 statusBuffering: statusBuffering$e, 16214 statusEnd: statusEnd$e, 16215 selectedTrack: selectedTrack$d, 16216 alertDescribedVersion: alertDescribedVersion$e, 16217 alertNonDescribedVersion: alertNonDescribedVersion$e, 16218 prefMenuCaptions: prefMenuCaptions$e, 16219 prefVoicedCaptions: prefVoicedCaptions$e, 16220 prefMenuDescriptions: prefMenuDescriptions$e, 16221 prefMenuKeyboard: prefMenuKeyboard$e, 16222 prefMenuTranscript: prefMenuTranscript$e, 16223 prefTitleCaptions: prefTitleCaptions$e, 16224 prefTitleDescriptions: prefTitleDescriptions$e, 16225 prefTitleKeyboard: prefTitleKeyboard$e, 16226 prefTitleTranscript: prefTitleTranscript$d, 16227 prefIntroDescription1: prefIntroDescription1$d, 16228 prefDescription1: prefDescription1$e, 16229 prefDescription2: prefDescription2$e, 16230 prefDescription3: prefDescription3$e, 16231 prefIntroDescriptionNone: prefIntroDescriptionNone$e, 16232 prefIntroDescription3: prefIntroDescription3$e, 16233 prefIntroDescription4: prefIntroDescription4$e, 16234 prefIntroKeyboard1: prefIntroKeyboard1$e, 16235 prefIntroKeyboard2: prefIntroKeyboard2$e, 16236 prefIntroKeyboard3: prefIntroKeyboard3$e, 16237 prefHeadingKeyboard1: prefHeadingKeyboard1$e, 16238 prefHeadingKeyboard2: prefHeadingKeyboard2$e, 16239 prefHeadingDescription: prefHeadingDescription$e, 16240 prefHeadingTextDescription: prefHeadingTextDescription$e, 16241 prefAltKey: prefAltKey$e, 16242 prefCtrlKey: prefCtrlKey$e, 16243 prefShiftKey: prefShiftKey$e, 16244 prefNoKeyShortcuts: prefNoKeyShortcuts$e, 16245 escapeKey: escapeKey$e, 16246 escapeKeyFunction: escapeKeyFunction$e, 16247 prefDescPause: prefDescPause$e, 16248 prefDescVisible: prefDescVisible$e, 16249 prefDescVoice: prefDescVoice$e, 16250 prefDescRate: prefDescRate$e, 16251 prefCaptionRate: prefCaptionRate$e, 16252 prefDescPitch: prefDescPitch$e, 16253 prefDescPitch1: prefDescPitch1$e, 16254 prefDescPitch2: prefDescPitch2$e, 16255 prefDescPitch3: prefDescPitch3$e, 16256 prefDescPitch4: prefDescPitch4$e, 16257 prefDescPitch5: prefDescPitch5$e, 16258 sampleDescriptionText: sampleDescriptionText$e, 16259 prefHighlight: prefHighlight$e, 16260 prefTabbable: prefTabbable$e, 16261 prefCaptionsFont: prefCaptionsFont$e, 16262 prefCaptionsColor: prefCaptionsColor$e, 16263 prefCaptionsBGColor: prefCaptionsBGColor$e, 16264 prefCaptionsSize: prefCaptionsSize$e, 16265 prefCaptionsOpacity: prefCaptionsOpacity$e, 16266 prefCaptionsStyle: prefCaptionsStyle$e, 16267 serif: serif$e, 16268 sans: sans$e, 16269 cursive: cursive$e, 16270 fantasy: fantasy$e, 16271 monospace: monospace$e, 16272 white: white$e, 16273 yellow: yellow$e, 16274 green: green$e, 16275 cyan: cyan$d, 16276 blue: blue$e, 16277 magenta: magenta$e, 16278 red: red$e, 16279 black: black$e, 16280 transparent: transparent$e, 16281 solid: solid$e, 16282 captionsStylePopOn: captionsStylePopOn$e, 16283 captionsStyleRollUp: captionsStyleRollUp$e, 16284 prefCaptionsPosition: prefCaptionsPosition$e, 16285 captionsPositionOverlay: captionsPositionOverlay$e, 16286 captionsPositionBelow: captionsPositionBelow$e, 16287 sampleCaptionText: sampleCaptionText$e, 16288 prefSuccess: prefSuccess$e, 16289 prefNoChange: prefNoChange$e, 16290 save: save$e, 16291 cancel: cancel$e, 16292 dismissButton: dismissButton$e, 16293 windowButtonLabel: windowButtonLabel$e, 16294 windowMove: windowMove$e, 16295 windowMoveLeft: windowMoveLeft$e, 16296 windowMoveRight: windowMoveRight$e, 16297 windowMoveUp: windowMoveUp$e, 16298 windowMoveDown: windowMoveDown$e, 16299 windowMoveStopped: windowMoveStopped$e, 16300 transcriptControls: transcriptControls$e, 16301 signControls: signControls$e, 16302 windowMoveAlert: windowMoveAlert$e, 16303 windowResize: windowResize$e, 16304 windowResizeHeading: windowResizeHeading$e, 16305 closeButtonLabel: closeButtonLabel$e, 16306 width: width$e, 16307 height: height$e, 16308 resultsSummary1: resultsSummary1$e, 16309 resultsSummary2: resultsSummary2$e, 16310 resultsSummary3: resultsSummary3$e, 16311 noResultsFound: noResultsFound$e, 16312 searchButtonLabel: searchButtonLabel$e, 16313 hour: hour$e, 16314 minute: minute$e, 16315 second: second$e, 16316 hours: hours$e, 16317 minutes: minutes$e, 16318 seconds: seconds$e, 16319 vtsHeading: vtsHeading$e, 16320 vtsInstructions1: vtsInstructions1$e, 16321 vtsInstructions2: vtsInstructions2$e, 16322 vtsInstructions3: vtsInstructions3$e, 16323 vtsInstructions4: vtsInstructions4$e, 16324 vtsInstructions5: vtsInstructions5$e, 16325 vtsSelectLanguage: vtsSelectLanguage$e, 16326 vtsSave: vtsSave$e, 16327 vtsReturn: vtsReturn$e, 16328 vtsCancel: vtsCancel$e, 16329 vtsRow: vtsRow$e, 16330 vtsKind: vtsKind$e, 16331 vtsStart: vtsStart$e, 16332 vtsEnd: vtsEnd$e, 16333 vtsContent: vtsContent$e, 16334 vtsActions: vtsActions$e, 16335 vtsNewRow: vtsNewRow$e, 16336 vtsDeletedRow: vtsDeletedRow$e, 16337 vtsMovedRow: vtsMovedRow$e 16338 }; 16339 16340 var playerHeading$d = "× ×× ××××"; 16341 var audioPlayer$d = "Audio player"; 16342 var videoPlayer$d = "Video player"; 16343 var faster$d = "××ר ××תר"; 16344 var slower$d = "××× ××תר"; 16345 var play$d = "× ××"; 16346 var pause$d = "×פסק×"; 16347 var restart$d = "×ת××× ×××ש"; 16348 var prevTrack$d = "××ס××× ××§×××"; 16349 var nextTrack$d = "××ס××× ×××"; 16350 var rewind$d = "××ר×"; 16351 var forward$d = "×§××××"; 16352 var captions$d = "××ת××××"; 16353 var showCaptions$d = "×צ×ת ××ת××××"; 16354 var hideCaptions$d = "×סתרת ××ת××××"; 16355 var captionsOff$d = "××× ××ת××××"; 16356 var showTranscript$d = "××¦× ×ª××××"; 16357 var hideTranscript$d = "×סתר ת××××"; 16358 var turnOnDescriptions$d = "××¤×¢× ×ª××××"; 16359 var turnOffDescriptions$d = "ס××ר ת××××"; 16360 var chapters$d = "פרק××"; 16361 var language$d = "שפ×"; 16362 var sign$d = "שפת ס××× ××"; 16363 var showSign$d = "××¦× ×©×¤×ª ס××× ××"; 16364 var hideSign$d = "×סתר שפת ס××× ××"; 16365 var seekbarLabel$d = "צ×ר ×××"; 16366 var mute$d = "×שתק×"; 16367 var unmute$d = "×פסקת ×שתק×"; 16368 var volume$d = "עצ××"; 16369 var volumeUpDown$d = "Volume up down"; 16370 var preferences$d = "××¢×פ×ת"; 16371 var enterFullScreen$d = "××¢×ר ×××¡× ×××"; 16372 var exitFullScreen$d = "×צ××× ×××¡× ×××"; 16373 var speed$d = "×××ר×ת"; 16374 var spacebar$d = "×קש ×ר×××"; 16375 var transcriptTitle$d = "ת××××"; 16376 var lyricsTitle$d = "×××××"; 16377 var autoScroll$d = "××××× ×××××××ת"; 16378 var statusPlaying$d = "×× ××"; 16379 var statusPaused$d = "××פסק"; 16380 var statusStopped$d = "× ×¢×¦×¨"; 16381 var statusBuffering$d = "×צ×צ×"; 16382 var statusEnd$d = "ס××£ ××ס×××"; 16383 var selectTrack = "×ס××× × ××ר"; 16384 var alertDescribedVersion$d = "ש×××ש ×××רסת ××××× ××ת××רת ×©× ×¡×¨××× ××"; 16385 var alertNonDescribedVersion$d = "ש×××ש ×××¨×¡× ××× ×ת××רת ×©× ×¡×¨××× ××"; 16386 var prefMenuCaptions$d = "××ת××××"; 16387 var prefVoicedCaptions$d = "Spoken Captions"; 16388 var prefMenuDescriptions$d = "ת×××ר××"; 16389 var prefMenuKeyboard$d = "××§××ת"; 16390 var prefMenuTranscript$d = "ת××××"; 16391 var prefTitleCaptions$d = "××¢×פ×ת ××ת××××"; 16392 var prefTitleDescriptions$d = "××¢×פ×ת ת×××ר ש××¢"; 16393 var prefTitleKeyboard$d = "××¢×פ×ת ××§××ת"; 16394 var PrefTitleTranscript = "××¢×פ×ת ת××××"; 16395 var prefIntrodescription1 = "× ×× ××××× ×ª××× ×ת×××ר ×ש××¢ ××©×ª× ×ר×××:"; 16396 var prefDescription1$d = "××××××× ×× ×××× ×ש ××¨×¡× ×××פ×ת ××ת××רתת×××ר ×××סס ×קס×"; 16397 var prefDescription2$d = "××××××× ×× ×××× ×ש××¨×¡× ×××פ×ת ××ת××רת ×©× ×××××"; 16398 var prefDescription3$d = "××××××× ×× ×××× ×ש ת×××ר ×××סס ×קס×, ×××××¢× ×¢× ××× ×§××¨× ×ס×"; 16399 var prefIntroDescriptionNone$d = "×סר××× ×× ×××× ××× ×ª×××ר ש××¢ ×ת×× ×ת."; 16400 var prefIntroDescription3$d = "×שת×ש ×××פס ××× ××× ××§×××¢ ×ת ×××¢×פ×ת ש×× ×קש×ר×ת ×ת×××ר ש××¢."; 16401 var prefIntroDescription4$d = "×××ר שתש××ר ×ת ××××ר×ת ש××, ת×××ר ×ש××¢ ×××× ××××ת ×××× ×¢× / ××××× ×××צע×ת ×××¦× ×ª×××ר."; 16402 var prefIntroKeyboard1$d = "× ×× ××××× ×××£ ××× ××¨× × ×× ×××× ××××ת ×××¤×¢× ××× ××§×× ×××£ ×××צע×ת ×§×צ××¨× ×קש×× (ר×× ×××× ×¨×©×××)."; 16403 var prefIntroKeyboard2$d = "× ××ª× ××©× ×ת ×ת ××§×©× ×ש×× ×× (Shift, Alt ×- Control)."; 16404 var prefIntroKeyboard3$d = "×ער×: ××ת×× ×©×××§ ×צ×ר××¤× ××קש×× ××ª× ×ש×× ×¢× ××קש×× ××ש×ש×× ×ת ××פ××¤× × / ×× ××ש××× ×ª××× × ××ר××, × ×¡× ×©×××××× ×©×× ×× ×©× ×פת××ת ש×× ×× ××× ××צ×× ××× ××ת××× ××."; 16405 var prefHeadingKeyboard1$d = "××§×©× ××©× × ××ש×ש×× ××§×צ××¨× ×ר×"; 16406 var prefHeadingKeyboard2$d = "×§×צ××¨× ×קש×× × ××××××"; 16407 var prefHeadingDescription$d = "ת×××ר ש××¢"; 16408 var prefHeadingTextDescription$d = "ת×××ר ×××סס ××§×¡× ×××סס ×קס×"; 16409 var prefAltKey$d = "Alt"; 16410 var prefCtrlKey$d = "Control"; 16411 var prefShiftKey$d = "Shift"; 16412 var prefNoKeyShortcuts$d = "Disable keyboard shortcuts"; 16413 var escapeKey$d = "Escape"; 16414 var escapeKeyFunction$d = "ס××ר ×ת ת××ת ××× ×©×× ×× ××××ת ×× ×ª×¤×¨×× ×§×פץ"; 16415 var prefDescPause$d = "×ש×× ×ת ××××××× ××××¤× ××××××× ××שר ×ת×××ר ×ת×××"; 16416 var prefDescVisible$d = "×פ×× ×ª×××ר ××××"; 16417 var prefDescVoice$d = "Voice"; 16418 var prefDescRate$d = "Spoken Description Rate"; 16419 var prefCaptionRate$d = "Spoken Caption Rate"; 16420 var prefDescPitch$d = "Pitch"; 16421 var prefDescPitch1$d = "Very low"; 16422 var prefDescPitch2$d = "Low"; 16423 var prefDescPitch3$d = "Default"; 16424 var prefDescPitch4$d = "High"; 16425 var prefDescPitch5$d = "Very high"; 16426 var sampleDescriptionText$d = "Adjust settings to hear this sample text."; 16427 var prefHighlight$d = "×××ש ת×××× ××× ×ש××§ ××××"; 16428 var prefTabbable$d = "××¤×¢× ×ª××××"; 16429 var prefCaptionsFont$d = "××פ×"; 16430 var prefCaptionsColor$d = "צ××¢ ×קס×"; 16431 var prefCaptionsBGColor$d = "רקע"; 16432 var prefCaptionsSize$d = "×××× ××פ×"; 16433 var prefCaptionsOpacity$d = "×××××ת"; 16434 var prefCaptionsStyle$d = "ס×× ××"; 16435 var serif$d = "serif"; 16436 var sans$d = "sans-serif"; 16437 var cursive$d = "cursive"; 16438 var fantasy$d = "fantasy"; 16439 var monospace$d = "monospace"; 16440 var white$d = "×××"; 16441 var yellow$d = "צ×××"; 16442 var green$d = "×ר××§"; 16443 var cyan$c = "××רק××"; 16444 var blue$d = "××××"; 16445 var magenta$d = "××× ××"; 16446 var red$d = "××××"; 16447 var black$d = "ש××ר"; 16448 var transparent$d = "שק××£"; 16449 var solid$d = "×××"; 16450 var captionsStylePopOn$d = "Pop-on"; 16451 var captionsStyleRollUp$d = "Roll-up"; 16452 var prefCaptionsPosition$d = "×××§××"; 16453 var captionsPositionOverlay$d = "Overlay"; 16454 var captionsPositionBelow$d = "×ת×ת ××××××"; 16455 var sampleCaptionText$d = "××××ת ××§×¡× ××ת××××ת"; 16456 var prefSuccess$d = "×ש×× ×××× ×©××× × ×©×ר×."; 16457 var prefNoChange$d = "×× ××¦×¢×ª× ×× ×©×× ××."; 16458 var save$d = "ש××ר×"; 16459 var cancel$d = "×××××"; 16460 var dismissButton$d = "Dismiss"; 16461 var windowButtonLabel$d = "×פשר×××ת ××××"; 16462 var windowMove$d = "××××"; 16463 var windowMoveLeft$d = "Window moved left"; 16464 var windowMoveRight$d = "Window moved right"; 16465 var windowMoveUp$d = "Window moved up"; 16466 var windowMoveDown$d = "Window moved down"; 16467 var windowMoveStopped$d = "Window move stopped"; 16468 var transcriptControls$d = "Transcript Window Controls"; 16469 var signControls$d = "Sign Language Window Controls"; 16470 var windowMoveAlert$d = "×ר×ר ×× ×שת××©× ××§×©× ××צ×× ××××ת ×××××; ×פת×ר Enter ××פסק×"; 16471 var windowResize$d = "ש××× ××××"; 16472 var windowResizeHeading$d = "ש×× ×× ×××× ××××"; 16473 var closeButtonLabel$d = "ס××רת"; 16474 var width$d = "ר×××"; 16475 var height$d = "××××"; 16476 var resultsSummary1$d = "You searched for:"; 16477 var resultsSummary2$d = "Found %1 matching items."; 16478 var resultsSummary3$d = "Click the time associated with any item to play the video from that point."; 16479 var noResultsFound$d = "No results found."; 16480 var searchButtonLabel$d = "Play at %1"; 16481 var hour$d = "hour"; 16482 var minute$d = "minute"; 16483 var second$d = "second"; 16484 var hours$d = "hours"; 16485 var minutes$d = "minutes"; 16486 var seconds$d = "seconds"; 16487 var vtsHeading$d = "Video Transcript Sorter"; 16488 var vtsInstructions1$d = "Use the Video Transcript Sorter to modify text tracks:"; 16489 var vtsInstructions2$d = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16490 var vtsInstructions3$d = "Modify content or start/end times (all are directly editable within the table)."; 16491 var vtsInstructions4$d = "Add new content, such as chapters or descriptions."; 16492 var vtsInstructions5$d = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 16493 var vtsSelectLanguage$d = "Select a language"; 16494 var vtsSave$d = "Generate new .vtt content"; 16495 var vtsReturn$d = "Return to Editor"; 16496 var vtsCancel$d = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16497 var vtsRow$d = "Row"; 16498 var vtsKind$d = "Kind"; 16499 var vtsStart$d = "Start"; 16500 var vtsEnd$d = "End"; 16501 var vtsContent$d = "Content"; 16502 var vtsActions$d = "Actions"; 16503 var vtsNewRow$d = "A new row %1 has been inserted."; 16504 var vtsDeletedRow$d = "Row %1 has been deleted."; 16505 var vtsMovedRow$d = "Row %1 has been moved %2 and is now Row %3."; 16506 var he = { 16507 playerHeading: playerHeading$d, 16508 audioPlayer: audioPlayer$d, 16509 videoPlayer: videoPlayer$d, 16510 faster: faster$d, 16511 slower: slower$d, 16512 play: play$d, 16513 pause: pause$d, 16514 restart: restart$d, 16515 prevTrack: prevTrack$d, 16516 nextTrack: nextTrack$d, 16517 rewind: rewind$d, 16518 forward: forward$d, 16519 captions: captions$d, 16520 showCaptions: showCaptions$d, 16521 hideCaptions: hideCaptions$d, 16522 captionsOff: captionsOff$d, 16523 showTranscript: showTranscript$d, 16524 hideTranscript: hideTranscript$d, 16525 turnOnDescriptions: turnOnDescriptions$d, 16526 turnOffDescriptions: turnOffDescriptions$d, 16527 chapters: chapters$d, 16528 language: language$d, 16529 sign: sign$d, 16530 showSign: showSign$d, 16531 hideSign: hideSign$d, 16532 seekbarLabel: seekbarLabel$d, 16533 mute: mute$d, 16534 unmute: unmute$d, 16535 volume: volume$d, 16536 volumeUpDown: volumeUpDown$d, 16537 preferences: preferences$d, 16538 enterFullScreen: enterFullScreen$d, 16539 exitFullScreen: exitFullScreen$d, 16540 speed: speed$d, 16541 spacebar: spacebar$d, 16542 transcriptTitle: transcriptTitle$d, 16543 lyricsTitle: lyricsTitle$d, 16544 autoScroll: autoScroll$d, 16545 statusPlaying: statusPlaying$d, 16546 statusPaused: statusPaused$d, 16547 statusStopped: statusStopped$d, 16548 statusBuffering: statusBuffering$d, 16549 statusEnd: statusEnd$d, 16550 selectTrack: selectTrack, 16551 alertDescribedVersion: alertDescribedVersion$d, 16552 alertNonDescribedVersion: alertNonDescribedVersion$d, 16553 prefMenuCaptions: prefMenuCaptions$d, 16554 prefVoicedCaptions: prefVoicedCaptions$d, 16555 prefMenuDescriptions: prefMenuDescriptions$d, 16556 prefMenuKeyboard: prefMenuKeyboard$d, 16557 prefMenuTranscript: prefMenuTranscript$d, 16558 prefTitleCaptions: prefTitleCaptions$d, 16559 prefTitleDescriptions: prefTitleDescriptions$d, 16560 prefTitleKeyboard: prefTitleKeyboard$d, 16561 PrefTitleTranscript: PrefTitleTranscript, 16562 prefIntrodescription1: prefIntrodescription1, 16563 prefDescription1: prefDescription1$d, 16564 prefDescription2: prefDescription2$d, 16565 prefDescription3: prefDescription3$d, 16566 prefIntroDescriptionNone: prefIntroDescriptionNone$d, 16567 prefIntroDescription3: prefIntroDescription3$d, 16568 prefIntroDescription4: prefIntroDescription4$d, 16569 prefIntroKeyboard1: prefIntroKeyboard1$d, 16570 prefIntroKeyboard2: prefIntroKeyboard2$d, 16571 prefIntroKeyboard3: prefIntroKeyboard3$d, 16572 prefHeadingKeyboard1: prefHeadingKeyboard1$d, 16573 prefHeadingKeyboard2: prefHeadingKeyboard2$d, 16574 prefHeadingDescription: prefHeadingDescription$d, 16575 prefHeadingTextDescription: prefHeadingTextDescription$d, 16576 prefAltKey: prefAltKey$d, 16577 prefCtrlKey: prefCtrlKey$d, 16578 prefShiftKey: prefShiftKey$d, 16579 prefNoKeyShortcuts: prefNoKeyShortcuts$d, 16580 escapeKey: escapeKey$d, 16581 escapeKeyFunction: escapeKeyFunction$d, 16582 prefDescPause: prefDescPause$d, 16583 prefDescVisible: prefDescVisible$d, 16584 prefDescVoice: prefDescVoice$d, 16585 prefDescRate: prefDescRate$d, 16586 prefCaptionRate: prefCaptionRate$d, 16587 prefDescPitch: prefDescPitch$d, 16588 prefDescPitch1: prefDescPitch1$d, 16589 prefDescPitch2: prefDescPitch2$d, 16590 prefDescPitch3: prefDescPitch3$d, 16591 prefDescPitch4: prefDescPitch4$d, 16592 prefDescPitch5: prefDescPitch5$d, 16593 sampleDescriptionText: sampleDescriptionText$d, 16594 prefHighlight: prefHighlight$d, 16595 prefTabbable: prefTabbable$d, 16596 prefCaptionsFont: prefCaptionsFont$d, 16597 prefCaptionsColor: prefCaptionsColor$d, 16598 prefCaptionsBGColor: prefCaptionsBGColor$d, 16599 prefCaptionsSize: prefCaptionsSize$d, 16600 prefCaptionsOpacity: prefCaptionsOpacity$d, 16601 prefCaptionsStyle: prefCaptionsStyle$d, 16602 serif: serif$d, 16603 sans: sans$d, 16604 cursive: cursive$d, 16605 fantasy: fantasy$d, 16606 monospace: monospace$d, 16607 white: white$d, 16608 yellow: yellow$d, 16609 green: green$d, 16610 cyan: cyan$c, 16611 blue: blue$d, 16612 magenta: magenta$d, 16613 red: red$d, 16614 black: black$d, 16615 transparent: transparent$d, 16616 solid: solid$d, 16617 captionsStylePopOn: captionsStylePopOn$d, 16618 captionsStyleRollUp: captionsStyleRollUp$d, 16619 prefCaptionsPosition: prefCaptionsPosition$d, 16620 captionsPositionOverlay: captionsPositionOverlay$d, 16621 captionsPositionBelow: captionsPositionBelow$d, 16622 sampleCaptionText: sampleCaptionText$d, 16623 prefSuccess: prefSuccess$d, 16624 prefNoChange: prefNoChange$d, 16625 save: save$d, 16626 cancel: cancel$d, 16627 dismissButton: dismissButton$d, 16628 windowButtonLabel: windowButtonLabel$d, 16629 windowMove: windowMove$d, 16630 windowMoveLeft: windowMoveLeft$d, 16631 windowMoveRight: windowMoveRight$d, 16632 windowMoveUp: windowMoveUp$d, 16633 windowMoveDown: windowMoveDown$d, 16634 windowMoveStopped: windowMoveStopped$d, 16635 transcriptControls: transcriptControls$d, 16636 signControls: signControls$d, 16637 windowMoveAlert: windowMoveAlert$d, 16638 windowResize: windowResize$d, 16639 windowResizeHeading: windowResizeHeading$d, 16640 closeButtonLabel: closeButtonLabel$d, 16641 width: width$d, 16642 height: height$d, 16643 resultsSummary1: resultsSummary1$d, 16644 resultsSummary2: resultsSummary2$d, 16645 resultsSummary3: resultsSummary3$d, 16646 noResultsFound: noResultsFound$d, 16647 searchButtonLabel: searchButtonLabel$d, 16648 hour: hour$d, 16649 minute: minute$d, 16650 second: second$d, 16651 hours: hours$d, 16652 minutes: minutes$d, 16653 seconds: seconds$d, 16654 vtsHeading: vtsHeading$d, 16655 vtsInstructions1: vtsInstructions1$d, 16656 vtsInstructions2: vtsInstructions2$d, 16657 vtsInstructions3: vtsInstructions3$d, 16658 vtsInstructions4: vtsInstructions4$d, 16659 vtsInstructions5: vtsInstructions5$d, 16660 vtsSelectLanguage: vtsSelectLanguage$d, 16661 vtsSave: vtsSave$d, 16662 vtsReturn: vtsReturn$d, 16663 vtsCancel: vtsCancel$d, 16664 vtsRow: vtsRow$d, 16665 vtsKind: vtsKind$d, 16666 vtsStart: vtsStart$d, 16667 vtsEnd: vtsEnd$d, 16668 vtsContent: vtsContent$d, 16669 vtsActions: vtsActions$d, 16670 vtsNewRow: vtsNewRow$d, 16671 vtsDeletedRow: vtsDeletedRow$d, 16672 vtsMovedRow: vtsMovedRow$d 16673 }; 16674 16675 var playerHeading$c = "Pemutar media"; 16676 var audioPlayer$c = "Audio player"; 16677 var videoPlayer$c = "Video player"; 16678 var faster$c = "Percepat"; 16679 var slower$c = "Perlambat"; 16680 var play$c = "Mulai"; 16681 var pause$c = "Jeda"; 16682 var restart$c = "Ulangi"; 16683 var prevTrack$c = "Trek sebelumnya"; 16684 var nextTrack$c = "Trek berikutnya"; 16685 var rewind$c = "Mundur"; 16686 var forward$c = "Maju"; 16687 var captions$c = "Takarir"; 16688 var showCaptions$c = "Tampilkan takarir"; 16689 var hideCaptions$c = "Sembunyikan takarir"; 16690 var captionsOff$c = "Tanpa takarir"; 16691 var showTranscript$c = "Tampilkan transkripsi"; 16692 var hideTranscript$c = "Sembunyikan transkripsi"; 16693 var turnOnDescriptions$c = "Nyalakan deskripsi"; 16694 var turnOffDescriptions$c = "Matikan deskripsi"; 16695 var chapters$c = "Bagian"; 16696 var language$c = "Bahasa"; 16697 var sign$c = "Bahasa isyarat"; 16698 var showSign$c = "Tampilkan bahasa isyarat"; 16699 var hideSign$c = "Sembunyikan bahasa isyarat"; 16700 var seekbarLabel$c = "lini masa"; 16701 var mute$c = "Matikan Suara"; 16702 var unmute$c = "Nyalakan Suara"; 16703 var volume$c = "Volume"; 16704 var volumeUpDown$c = "Naik turun volume"; 16705 var preferences$c = "Preferensi"; 16706 var enterFullScreen$c = "Masuk ke mode layar penuh"; 16707 var exitFullScreen$c = "Keluar dari mode layar penuh"; 16708 var speed$c = "Kecepatan"; 16709 var spacebar$c = "tombol spasi"; 16710 var transcriptTitle$c = "Transkripsi"; 16711 var lyricsTitle$c = "Lirik"; 16712 var autoScroll$c = "Gulir otomatis"; 16713 var statusPlaying$c = "Memutar"; 16714 var statusPaused$c = "Dijedakan"; 16715 var statusStopped$c = "Dihentikan"; 16716 var statusBuffering$c = "Sedang Bufer"; 16717 var statusEnd$c = "Akhir dari trek"; 16718 var selectedTrack$c = "Trek Pilihan"; 16719 var alertDescribedVersion$c = "Menggunakan versi dengan deskripsi audio pada video ini"; 16720 var alertNonDescribedVersion$c = "Menggunakan versi tanpa deskripsi pada video ini"; 16721 var prefMenuCaptions$c = "Takarir"; 16722 var prefVoicedCaptions$c = "Spoken Captions"; 16723 var prefMenuDescriptions$c = "Deskripsi"; 16724 var prefMenuKeyboard$c = "Kibor"; 16725 var prefMenuTranscript$c = "Transkripsi"; 16726 var prefTitleCaptions$c = "Preferensi Takarir"; 16727 var prefTitleDescriptions$c = "Preferensi Deskripsi Audio"; 16728 var prefTitleKeyboard$c = "Preferensi Kibor"; 16729 var prefTitleTranscript$c = "Preferensi Transkripsi"; 16730 var prefIntroDescription1$c = "Pemutar media ini mendukung deskripsi audio dengan dua cara: "; 16731 var prefDescription1$c = "Video ini memiliki versi alternatif berdeskripsi, deskripsi berbasis teks."; 16732 var prefDescription2$c = "Video ini memiliki versi alternatif video dengan deskripsi."; 16733 var prefDescription3$c = "Video ini memiliki deskripsi berbasis teks, dibacakan oleh pembaca layar."; 16734 var prefIntroDescriptionNone$c = "Video ini tidak memiliki deskripsi audio dalam format mana pun."; 16735 var prefIntroDescription3$c = "Gunakan isian berikut untuk memasang preferensi terkait deskripsi audio berbasis teks."; 16736 var prefIntroDescription4$c = "Setelah Anda menyimpan pengaturan, deskripsi audio dapat dinyala/matikan melalui tombol Deskripsi."; 16737 var prefIntroKeyboard1$c = "Pemutar media dalam halaman ini dapat dioperasikan dari bagian mana pun pada halaman dengan menggunakan pintasan kibor (lihat daftar di bawah)."; 16738 var prefIntroKeyboard2$c = "Fungsi tombol kombinasi (Shift, Alt, Control) dapat dipasangkan di bawah ini."; 16739 var prefIntroKeyboard3$c = "CATATAN: Beberapa kombinasi tombol mungkin akan bersinggungan dengan yang digunakan oleh browser dan/atau aplikasi perangkat lunak lainnya. Cobalah berbagai kombinasi untuk menemukan kombinasi yang bekerja untuk Anda."; 16740 var prefHeadingKeyboard1$c = "Tombol kombinasi yang digunakan untuk pintasan"; 16741 var prefHeadingKeyboard2$c = "Pintasan kibor saat ini"; 16742 var prefHeadingDescription$c = "Deskripsi audio"; 16743 var prefHeadingTextDescription$c = "Deskripsi audio berbasis teks"; 16744 var prefAltKey$c = "Alt"; 16745 var prefCtrlKey$c = "Control"; 16746 var prefShiftKey$c = "Shift"; 16747 var prefNoKeyShortcuts$c = "Disable keyboard shortcuts"; 16748 var escapeKey$c = "Escape"; 16749 var escapeKeyFunction$c = "Tutup dialog atau menu sembulan yang tampil"; 16750 var prefDescPause$c = "Otomatis jedakan video ketika deskripsi dimulai"; 16751 var prefDescVisible$c = "Tampilkan deskripsi"; 16752 var prefDescVoice$c = "Suara"; 16753 var prefDescRate$c = "Spoken Description Rate"; 16754 var prefCaptionRate$c = "Spoken Caption Rate"; 16755 var prefDescPitch$c = "Titinada"; 16756 var prefDescPitch1$c = "Sangat rendah"; 16757 var prefDescPitch2$c = "Rendah"; 16758 var prefDescPitch3$c = "Bawaan"; 16759 var prefDescPitch4$c = "Tinggi"; 16760 var prefDescPitch5$c = "Sangat tinggi"; 16761 var sampleDescriptionText$c = "Sesuaikan pengaturan untuk mendengarkan teks ini."; 16762 var prefHighlight$c = "Soroti transkripsi yang sesuai saat media diputar"; 16763 var prefTabbable$c = "Transkripsi dengan kontrol kibor"; 16764 var prefCaptionsFont$c = "Jenis Huruf"; 16765 var prefCaptionsColor$c = "Warna Teks"; 16766 var prefCaptionsBGColor$c = "Warna Latar"; 16767 var prefCaptionsSize$c = "Ukuran Huruf"; 16768 var prefCaptionsOpacity$c = "Opasitas"; 16769 var prefCaptionsStyle$c = "Gaya"; 16770 var serif$c = "serif"; 16771 var sans$c = "sans-serif"; 16772 var cursive$c = "kursif"; 16773 var fantasy$c = "fantasi"; 16774 var monospace$c = "monospasi"; 16775 var white$c = "putih"; 16776 var yellow$c = "kuning"; 16777 var green$c = "hijau"; 16778 var cyan$b = "sian"; 16779 var blue$c = "biru"; 16780 var magenta$c = "magenta"; 16781 var red$c = "merah"; 16782 var black$c = "hitam"; 16783 var transparent$c = "transparan"; 16784 var solid$c = "solid"; 16785 var captionsStylePopOn$c = "Muncul"; 16786 var captionsStyleRollUp$c = "Bergulir naik"; 16787 var prefCaptionsPosition$c = "Posisi"; 16788 var captionsPositionOverlay$c = "Di atas video"; 16789 var captionsPositionBelow$c = "Di bawah video"; 16790 var sampleCaptionText$c = "Contoh teks takarir"; 16791 var prefSuccess$c = "Perubahan telah disimpan."; 16792 var prefNoChange$c = "Anda tidak melakukan perubahan apa pun."; 16793 var save$c = "Simpan"; 16794 var cancel$c = "Batal"; 16795 var dismissButton$c = "Dismiss"; 16796 var windowButtonLabel$c = "Opsi jendela"; 16797 var windowMove$c = "Geser"; 16798 var windowMoveLeft$c = "Window moved left"; 16799 var windowMoveRight$c = "Window moved right"; 16800 var windowMoveUp$c = "Window moved up"; 16801 var windowMoveDown$c = "Window moved down"; 16802 var windowMoveStopped$c = "Window move stopped"; 16803 var transcriptControls$c = "Transcript Window Controls"; 16804 var signControls$c = "Sign Language Window Controls"; 16805 var windowMoveAlert$c = "Seret atau gunakan tombol panah untuk menggeser jendela; Tekan Enter untuk berhenti"; 16806 var windowResize$c = "Ubah ukuran"; 16807 var windowResizeHeading$c = "Ubah Ukuran Jendela"; 16808 var closeButtonLabel$c = "Tutup"; 16809 var width$c = "Lebar"; 16810 var height$c = "Tinggi"; 16811 var resultsSummary1$c = "Anda mencari:"; 16812 var resultsSummary2$c = "Ditemukan %1 hal yang sesuai."; 16813 var resultsSummary3$c = "Klik waktu yang terkait dengan suatu hal untuk memutar video dari titik tersebut."; 16814 var noResultsFound$c = "Tidak ada hasil yang ditemukan."; 16815 var searchButtonLabel$c = "Diputar pada %1"; 16816 var hour$c = "jam"; 16817 var minute$c = "menit"; 16818 var second$c = "detik"; 16819 var hours$c = "jam"; 16820 var minutes$c = "menit"; 16821 var seconds$c = "detik"; 16822 var vtsHeading$c = "Video Transcript Sorter"; 16823 var vtsInstructions1$c = "Use the Video Transcript Sorter to modify text tracks:"; 16824 var vtsInstructions2$c = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16825 var vtsInstructions3$c = "Modify content or start/end times (all are directly editable within the table)."; 16826 var vtsInstructions4$c = "Add new content, such as chapters or descriptions."; 16827 var vtsInstructions5$c = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 16828 var vtsSelectLanguage$c = "Select a language"; 16829 var vtsSave$c = "Generate new .vtt content"; 16830 var vtsReturn$c = "Return to Editor"; 16831 var vtsCancel$c = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16832 var vtsRow$c = "Row"; 16833 var vtsKind$c = "Kind"; 16834 var vtsStart$c = "Start"; 16835 var vtsEnd$c = "End"; 16836 var vtsContent$c = "Content"; 16837 var vtsActions$c = "Actions"; 16838 var vtsNewRow$c = "A new row %1 has been inserted."; 16839 var vtsDeletedRow$c = "Row %1 has been deleted."; 16840 var vtsMovedRow$c = "Row %1 has been moved %2 and is now Row %3."; 16841 var id = { 16842 playerHeading: playerHeading$c, 16843 audioPlayer: audioPlayer$c, 16844 videoPlayer: videoPlayer$c, 16845 faster: faster$c, 16846 slower: slower$c, 16847 play: play$c, 16848 pause: pause$c, 16849 restart: restart$c, 16850 prevTrack: prevTrack$c, 16851 nextTrack: nextTrack$c, 16852 rewind: rewind$c, 16853 forward: forward$c, 16854 captions: captions$c, 16855 showCaptions: showCaptions$c, 16856 hideCaptions: hideCaptions$c, 16857 captionsOff: captionsOff$c, 16858 showTranscript: showTranscript$c, 16859 hideTranscript: hideTranscript$c, 16860 turnOnDescriptions: turnOnDescriptions$c, 16861 turnOffDescriptions: turnOffDescriptions$c, 16862 chapters: chapters$c, 16863 language: language$c, 16864 sign: sign$c, 16865 showSign: showSign$c, 16866 hideSign: hideSign$c, 16867 seekbarLabel: seekbarLabel$c, 16868 mute: mute$c, 16869 unmute: unmute$c, 16870 volume: volume$c, 16871 volumeUpDown: volumeUpDown$c, 16872 preferences: preferences$c, 16873 enterFullScreen: enterFullScreen$c, 16874 exitFullScreen: exitFullScreen$c, 16875 speed: speed$c, 16876 spacebar: spacebar$c, 16877 transcriptTitle: transcriptTitle$c, 16878 lyricsTitle: lyricsTitle$c, 16879 autoScroll: autoScroll$c, 16880 statusPlaying: statusPlaying$c, 16881 statusPaused: statusPaused$c, 16882 statusStopped: statusStopped$c, 16883 statusBuffering: statusBuffering$c, 16884 statusEnd: statusEnd$c, 16885 selectedTrack: selectedTrack$c, 16886 alertDescribedVersion: alertDescribedVersion$c, 16887 alertNonDescribedVersion: alertNonDescribedVersion$c, 16888 prefMenuCaptions: prefMenuCaptions$c, 16889 prefVoicedCaptions: prefVoicedCaptions$c, 16890 prefMenuDescriptions: prefMenuDescriptions$c, 16891 prefMenuKeyboard: prefMenuKeyboard$c, 16892 prefMenuTranscript: prefMenuTranscript$c, 16893 prefTitleCaptions: prefTitleCaptions$c, 16894 prefTitleDescriptions: prefTitleDescriptions$c, 16895 prefTitleKeyboard: prefTitleKeyboard$c, 16896 prefTitleTranscript: prefTitleTranscript$c, 16897 prefIntroDescription1: prefIntroDescription1$c, 16898 prefDescription1: prefDescription1$c, 16899 prefDescription2: prefDescription2$c, 16900 prefDescription3: prefDescription3$c, 16901 prefIntroDescriptionNone: prefIntroDescriptionNone$c, 16902 prefIntroDescription3: prefIntroDescription3$c, 16903 prefIntroDescription4: prefIntroDescription4$c, 16904 prefIntroKeyboard1: prefIntroKeyboard1$c, 16905 prefIntroKeyboard2: prefIntroKeyboard2$c, 16906 prefIntroKeyboard3: prefIntroKeyboard3$c, 16907 prefHeadingKeyboard1: prefHeadingKeyboard1$c, 16908 prefHeadingKeyboard2: prefHeadingKeyboard2$c, 16909 prefHeadingDescription: prefHeadingDescription$c, 16910 prefHeadingTextDescription: prefHeadingTextDescription$c, 16911 prefAltKey: prefAltKey$c, 16912 prefCtrlKey: prefCtrlKey$c, 16913 prefShiftKey: prefShiftKey$c, 16914 prefNoKeyShortcuts: prefNoKeyShortcuts$c, 16915 escapeKey: escapeKey$c, 16916 escapeKeyFunction: escapeKeyFunction$c, 16917 prefDescPause: prefDescPause$c, 16918 prefDescVisible: prefDescVisible$c, 16919 prefDescVoice: prefDescVoice$c, 16920 prefDescRate: prefDescRate$c, 16921 prefCaptionRate: prefCaptionRate$c, 16922 prefDescPitch: prefDescPitch$c, 16923 prefDescPitch1: prefDescPitch1$c, 16924 prefDescPitch2: prefDescPitch2$c, 16925 prefDescPitch3: prefDescPitch3$c, 16926 prefDescPitch4: prefDescPitch4$c, 16927 prefDescPitch5: prefDescPitch5$c, 16928 sampleDescriptionText: sampleDescriptionText$c, 16929 prefHighlight: prefHighlight$c, 16930 prefTabbable: prefTabbable$c, 16931 prefCaptionsFont: prefCaptionsFont$c, 16932 prefCaptionsColor: prefCaptionsColor$c, 16933 prefCaptionsBGColor: prefCaptionsBGColor$c, 16934 prefCaptionsSize: prefCaptionsSize$c, 16935 prefCaptionsOpacity: prefCaptionsOpacity$c, 16936 prefCaptionsStyle: prefCaptionsStyle$c, 16937 serif: serif$c, 16938 sans: sans$c, 16939 cursive: cursive$c, 16940 fantasy: fantasy$c, 16941 monospace: monospace$c, 16942 white: white$c, 16943 yellow: yellow$c, 16944 green: green$c, 16945 cyan: cyan$b, 16946 blue: blue$c, 16947 magenta: magenta$c, 16948 red: red$c, 16949 black: black$c, 16950 transparent: transparent$c, 16951 solid: solid$c, 16952 captionsStylePopOn: captionsStylePopOn$c, 16953 captionsStyleRollUp: captionsStyleRollUp$c, 16954 prefCaptionsPosition: prefCaptionsPosition$c, 16955 captionsPositionOverlay: captionsPositionOverlay$c, 16956 captionsPositionBelow: captionsPositionBelow$c, 16957 sampleCaptionText: sampleCaptionText$c, 16958 prefSuccess: prefSuccess$c, 16959 prefNoChange: prefNoChange$c, 16960 save: save$c, 16961 cancel: cancel$c, 16962 dismissButton: dismissButton$c, 16963 windowButtonLabel: windowButtonLabel$c, 16964 windowMove: windowMove$c, 16965 windowMoveLeft: windowMoveLeft$c, 16966 windowMoveRight: windowMoveRight$c, 16967 windowMoveUp: windowMoveUp$c, 16968 windowMoveDown: windowMoveDown$c, 16969 windowMoveStopped: windowMoveStopped$c, 16970 transcriptControls: transcriptControls$c, 16971 signControls: signControls$c, 16972 windowMoveAlert: windowMoveAlert$c, 16973 windowResize: windowResize$c, 16974 windowResizeHeading: windowResizeHeading$c, 16975 closeButtonLabel: closeButtonLabel$c, 16976 width: width$c, 16977 height: height$c, 16978 resultsSummary1: resultsSummary1$c, 16979 resultsSummary2: resultsSummary2$c, 16980 resultsSummary3: resultsSummary3$c, 16981 noResultsFound: noResultsFound$c, 16982 searchButtonLabel: searchButtonLabel$c, 16983 hour: hour$c, 16984 minute: minute$c, 16985 second: second$c, 16986 hours: hours$c, 16987 minutes: minutes$c, 16988 seconds: seconds$c, 16989 vtsHeading: vtsHeading$c, 16990 vtsInstructions1: vtsInstructions1$c, 16991 vtsInstructions2: vtsInstructions2$c, 16992 vtsInstructions3: vtsInstructions3$c, 16993 vtsInstructions4: vtsInstructions4$c, 16994 vtsInstructions5: vtsInstructions5$c, 16995 vtsSelectLanguage: vtsSelectLanguage$c, 16996 vtsSave: vtsSave$c, 16997 vtsReturn: vtsReturn$c, 16998 vtsCancel: vtsCancel$c, 16999 vtsRow: vtsRow$c, 17000 vtsKind: vtsKind$c, 17001 vtsStart: vtsStart$c, 17002 vtsEnd: vtsEnd$c, 17003 vtsContent: vtsContent$c, 17004 vtsActions: vtsActions$c, 17005 vtsNewRow: vtsNewRow$c, 17006 vtsDeletedRow: vtsDeletedRow$c, 17007 vtsMovedRow: vtsMovedRow$c 17008 }; 17009 17010 var playerHeading$b = "Lettore multimediale"; 17011 var audioPlayer$b = "Audio player"; 17012 var videoPlayer$b = "Video player"; 17013 var faster$b = "Più veloce"; 17014 var slower$b = "Più lento"; 17015 var play$b = "Riproduci"; 17016 var pause$b = "Pausa"; 17017 var restart$b = "Torna all'inizio"; 17018 var prevTrack$b = "Traccia precedente"; 17019 var nextTrack$b = "Traccia successiva"; 17020 var rewind$b = "Indietro"; 17021 var forward$b = "Avanti"; 17022 var captions$b = "Sottotitoli"; 17023 var showCaptions$b = "Mostra sottotitoli"; 17024 var hideCaptions$b = "Nascondi sottotitoli"; 17025 var captionsOff$b = "Disattiva sottotitoli"; 17026 var showTranscript$b = "Mostra trascrizione"; 17027 var hideTranscript$b = "Nascondi trascrizione"; 17028 var turnOnDescriptions$b = "Attiva audiodescrizioni"; 17029 var turnOffDescriptions$b = "Disattiva audiodescrizioni"; 17030 var chapters$b = "Capitoli"; 17031 var language$b = "Lingua"; 17032 var sign$b = "Lingua dei segni"; 17033 var showSign$b = "Mostra lingua dei segni"; 17034 var hideSign$b = "Nascondi lingua dei segni"; 17035 var seekbarLabel$b = "Tempo trascorso"; 17036 var mute$b = "Muto"; 17037 var unmute$b = "Riattiva l'audio"; 17038 var volume$b = "Volume"; 17039 var volumeUpDown$b = "Incremento e diminuzione volume"; 17040 var preferences$b = "Preferenze"; 17041 var enterFullScreen$b = "Attiva schermo intero"; 17042 var exitFullScreen$b = "Disattiva schermo intero"; 17043 var speed$b = "Velocità "; 17044 var spacebar$b = "barra spaziatrice"; 17045 var transcriptTitle$b = "Trascrizione"; 17046 var lyricsTitle$b = "Testi"; 17047 var autoScroll$b = "Auto scorrimento"; 17048 var statusPlaying$b = "In riproduzione"; 17049 var statusPaused$b = "In pausa"; 17050 var statusStopped$b = "Fermato"; 17051 var statusBuffering$b = "Buffering"; 17052 var statusEnd$b = "Fine traccia"; 17053 var selectedTrack$b = "Traccia selezionata"; 17054 var alertDescribedVersion$b = "Sto usando la versione audiodescritta di questo video"; 17055 var alertNonDescribedVersion$b = "Sto usando la versione non audiodescritta di questo video"; 17056 var prefMenuCaptions$b = "Sottotitoli"; 17057 var prefVoicedCaptions$b = "Spoken Captions"; 17058 var prefMenuDescriptions$b = "Audioescrizioni"; 17059 var prefMenuKeyboard$b = "Tastiera"; 17060 var prefMenuTranscript$b = "Trascrizione"; 17061 var prefTitleCaptions$b = "Preferenze sottotitoli"; 17062 var prefTitleDescriptions$b = "Preferenze audiodescrizione"; 17063 var prefTitleKeyboard$b = "Preferenze tastiera"; 17064 var prefTitleTranscript$b = "Preferenze trascrizione"; 17065 var prefIntroDescription1$b = "Questo lettore multimediale supporta le audiodescrizioni in due modi:"; 17066 var prefDescription1$b = "il video corrente ha Una versione di descrizione alternativa, Descrizione testuale."; 17067 var prefDescription2$b = "il video corrente ha Versione di descrizione alternativa per il video."; 17068 var prefDescription3$b = "il video corrente ha Descrizione testuale, letta dal lettore di schermo."; 17069 var prefIntroDescriptionNone$b = "Il video corrente non ha audiodescrizioni."; 17070 var prefIntroDescription3$b = "Usa il seguente modulo per impostare le tue preferenze relative all'audiodescrizione testuale."; 17071 var prefIntroDescription4$b = "Dopo aver salvato le tue impostazioni, le audiodescrizioni possono essere attivate o disattivate usando i pulsanti descrizione."; 17072 var prefIntroKeyboard1$b = "Il lettore multimediale può essere usato dovunque in questa pagina, attraverso la tastiera. Vedi sotto per un elenco di tasti di scelta rapida."; 17073 var prefIntroKeyboard2$b = "I tasti di scelta rapida, (Shift, Alt, e Control) possono essere assegnati di seguito."; 17074 var prefIntroKeyboard3$b = "NOTA: Alcune combinazioni di tasti possono essere in conflitto con i tasti utilizzati dal tuo browser od altro software. Prova varie combinazioni di tasti per trovarne una che funzioni correttamente."; 17075 var prefHeadingKeyboard1$b = "Tasti di scelta rapida"; 17076 var prefHeadingKeyboard2$b = "Tasti di scelta rapida correnti"; 17077 var prefHeadingDescription$b = "Audiodescrizione"; 17078 var prefHeadingTextDescription$b = "audiodescrizione testuale"; 17079 var prefAltKey$b = "Alt"; 17080 var prefCtrlKey$b = "Control"; 17081 var prefShiftKey$b = "Shift"; 17082 var prefNoKeyShortcuts$b = "Disable keyboard shortcuts"; 17083 var escapeKey$b = "Escape"; 17084 var escapeKeyFunction$b = "Chiude il menu o la finestra corrente"; 17085 var prefDescPause$b = "Mette automaticamente il video in pausa, quando inizia la descrizione"; 17086 var prefDescVisible$b = "Rende la descrizione visibile"; 17087 var prefDescVoice$b = "Voice"; 17088 var prefDescRate$b = "Spoken Description Rate"; 17089 var prefCaptionRate$b = "Spoken Caption Rate"; 17090 var prefDescPitch$b = "Pitch"; 17091 var prefDescPitch1$b = "Very low"; 17092 var prefDescPitch2$b = "Low"; 17093 var prefDescPitch3$b = "Default"; 17094 var prefDescPitch4$b = "High"; 17095 var prefDescPitch5$b = "Very high"; 17096 var sampleDescriptionText$b = "Adjust settings to hear this sample text."; 17097 var prefHighlight$b = "Evidenzia la descrizione"; 17098 var prefTabbable$b = "Abilita la descrizione"; 17099 var prefCaptionsFont$b = "Carattere"; 17100 var prefCaptionsColor$b = "Colore del testo"; 17101 var prefCaptionsBGColor$b = "Colore di sfondo"; 17102 var prefCaptionsSize$b = "Dimensione del carattere"; 17103 var prefCaptionsOpacity$b = "Opacità "; 17104 var prefCaptionsStyle$b = "Stile"; 17105 var serif$b = "serif"; 17106 var sans$b = "sans-serif"; 17107 var cursive$b = "corsivo"; 17108 var fantasy$b = "fantasy"; 17109 var monospace$b = "monospace"; 17110 var white$b = "bianco"; 17111 var yellow$b = "giallo"; 17112 var green$b = "verde"; 17113 var ciano = "ciano"; 17114 var blue$b = "blue"; 17115 var magenta$b = "magenta"; 17116 var red$b = "rosso"; 17117 var black$b = "nero"; 17118 var transparent$b = "trasparente"; 17119 var solid$b = "solido"; 17120 var captionsStylePopOn$b = "A comparsa"; 17121 var captionsStyleRollUp$b = "A scorrimento"; 17122 var prefCaptionsPosition$b = "Posizione"; 17123 var captionsPositionOverlay$b = "Copre"; 17124 var captionsPositionBelow$b = "Sotto al video"; 17125 var sampleCaptionText$b = "Testo di esempio sottotitoli"; 17126 var prefSuccess$b = "I cambiamenti sono stati salvati."; 17127 var prefNoChange$b = "Non è stato effettuato alcun cambiamento."; 17128 var save$b = "Salva"; 17129 var cancel$b = "Annulla"; 17130 var dismissButton$b = "Dismiss"; 17131 var windowButtonLabel$b = "Opzioni finestra"; 17132 var windowMove$b = "Sposta"; 17133 var windowMoveLeft$b = "Window moved left"; 17134 var windowMoveRight$b = "Window moved right"; 17135 var windowMoveUp$b = "Window moved up"; 17136 var windowMoveDown$b = "Window moved down"; 17137 var windowMoveStopped$b = "Window move stopped"; 17138 var transcriptControls$b = "Transcript Window Controls"; 17139 var signControls$b = "Sign Language Window Controls"; 17140 var windowMoveAlert$b = "Trascina o usa le frecce per spostare la finestra. Invio per fermare."; 17141 var windowResize$b = "Ridimensiona"; 17142 var windowResizeHeading$b = "Ridimensiona finestra"; 17143 var closeButtonLabel$b = "Chiudi"; 17144 var width$b = "Larghezza"; 17145 var height$b = "Altezza"; 17146 var resultsSummary1$b = "You searched for:"; 17147 var resultsSummary2$b = "Found %1 matching items."; 17148 var resultsSummary3$b = "Click the time associated with any item to play the video from that point."; 17149 var noResultsFound$b = "No results found."; 17150 var searchButtonLabel$b = "Play at %1"; 17151 var hour$b = "hour"; 17152 var minute$b = "minute"; 17153 var second$b = "second"; 17154 var hours$b = "hours"; 17155 var minutes$b = "minutes"; 17156 var seconds$b = "seconds"; 17157 var vtsHeading$b = "Video Transcript Sorter"; 17158 var vtsInstructions1$b = "Use the Video Transcript Sorter to modify text tracks:"; 17159 var vtsInstructions2$b = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17160 var vtsInstructions3$b = "Modify content or start/end times (all are directly editable within the table)."; 17161 var vtsInstructions4$b = "Add new content, such as chapters or descriptions."; 17162 var vtsInstructions5$b = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 17163 var vtsSelectLanguage$b = "Select a language"; 17164 var vtsSave$b = "Generate new .vtt content"; 17165 var vtsReturn$b = "Return to Editor"; 17166 var vtsCancel$b = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17167 var vtsRow$b = "Row"; 17168 var vtsKind$b = "Kind"; 17169 var vtsStart$b = "Start"; 17170 var vtsEnd$b = "End"; 17171 var vtsContent$b = "Content"; 17172 var vtsActions$b = "Actions"; 17173 var vtsNewRow$b = "A new row %1 has been inserted."; 17174 var vtsDeletedRow$b = "Row %1 has been deleted."; 17175 var vtsMovedRow$b = "Row %1 has been moved %2 and is now Row %3."; 17176 var it = { 17177 playerHeading: playerHeading$b, 17178 audioPlayer: audioPlayer$b, 17179 videoPlayer: videoPlayer$b, 17180 faster: faster$b, 17181 slower: slower$b, 17182 play: play$b, 17183 pause: pause$b, 17184 restart: restart$b, 17185 prevTrack: prevTrack$b, 17186 nextTrack: nextTrack$b, 17187 rewind: rewind$b, 17188 forward: forward$b, 17189 captions: captions$b, 17190 showCaptions: showCaptions$b, 17191 hideCaptions: hideCaptions$b, 17192 captionsOff: captionsOff$b, 17193 showTranscript: showTranscript$b, 17194 hideTranscript: hideTranscript$b, 17195 turnOnDescriptions: turnOnDescriptions$b, 17196 turnOffDescriptions: turnOffDescriptions$b, 17197 chapters: chapters$b, 17198 language: language$b, 17199 sign: sign$b, 17200 showSign: showSign$b, 17201 hideSign: hideSign$b, 17202 seekbarLabel: seekbarLabel$b, 17203 mute: mute$b, 17204 unmute: unmute$b, 17205 volume: volume$b, 17206 volumeUpDown: volumeUpDown$b, 17207 preferences: preferences$b, 17208 enterFullScreen: enterFullScreen$b, 17209 exitFullScreen: exitFullScreen$b, 17210 speed: speed$b, 17211 spacebar: spacebar$b, 17212 transcriptTitle: transcriptTitle$b, 17213 lyricsTitle: lyricsTitle$b, 17214 autoScroll: autoScroll$b, 17215 statusPlaying: statusPlaying$b, 17216 statusPaused: statusPaused$b, 17217 statusStopped: statusStopped$b, 17218 statusBuffering: statusBuffering$b, 17219 statusEnd: statusEnd$b, 17220 selectedTrack: selectedTrack$b, 17221 alertDescribedVersion: alertDescribedVersion$b, 17222 alertNonDescribedVersion: alertNonDescribedVersion$b, 17223 prefMenuCaptions: prefMenuCaptions$b, 17224 prefVoicedCaptions: prefVoicedCaptions$b, 17225 prefMenuDescriptions: prefMenuDescriptions$b, 17226 prefMenuKeyboard: prefMenuKeyboard$b, 17227 prefMenuTranscript: prefMenuTranscript$b, 17228 prefTitleCaptions: prefTitleCaptions$b, 17229 prefTitleDescriptions: prefTitleDescriptions$b, 17230 prefTitleKeyboard: prefTitleKeyboard$b, 17231 prefTitleTranscript: prefTitleTranscript$b, 17232 prefIntroDescription1: prefIntroDescription1$b, 17233 prefDescription1: prefDescription1$b, 17234 prefDescription2: prefDescription2$b, 17235 prefDescription3: prefDescription3$b, 17236 prefIntroDescriptionNone: prefIntroDescriptionNone$b, 17237 prefIntroDescription3: prefIntroDescription3$b, 17238 prefIntroDescription4: prefIntroDescription4$b, 17239 prefIntroKeyboard1: prefIntroKeyboard1$b, 17240 prefIntroKeyboard2: prefIntroKeyboard2$b, 17241 prefIntroKeyboard3: prefIntroKeyboard3$b, 17242 prefHeadingKeyboard1: prefHeadingKeyboard1$b, 17243 prefHeadingKeyboard2: prefHeadingKeyboard2$b, 17244 prefHeadingDescription: prefHeadingDescription$b, 17245 prefHeadingTextDescription: prefHeadingTextDescription$b, 17246 prefAltKey: prefAltKey$b, 17247 prefCtrlKey: prefCtrlKey$b, 17248 prefShiftKey: prefShiftKey$b, 17249 prefNoKeyShortcuts: prefNoKeyShortcuts$b, 17250 escapeKey: escapeKey$b, 17251 escapeKeyFunction: escapeKeyFunction$b, 17252 prefDescPause: prefDescPause$b, 17253 prefDescVisible: prefDescVisible$b, 17254 prefDescVoice: prefDescVoice$b, 17255 prefDescRate: prefDescRate$b, 17256 prefCaptionRate: prefCaptionRate$b, 17257 prefDescPitch: prefDescPitch$b, 17258 prefDescPitch1: prefDescPitch1$b, 17259 prefDescPitch2: prefDescPitch2$b, 17260 prefDescPitch3: prefDescPitch3$b, 17261 prefDescPitch4: prefDescPitch4$b, 17262 prefDescPitch5: prefDescPitch5$b, 17263 sampleDescriptionText: sampleDescriptionText$b, 17264 prefHighlight: prefHighlight$b, 17265 prefTabbable: prefTabbable$b, 17266 prefCaptionsFont: prefCaptionsFont$b, 17267 prefCaptionsColor: prefCaptionsColor$b, 17268 prefCaptionsBGColor: prefCaptionsBGColor$b, 17269 prefCaptionsSize: prefCaptionsSize$b, 17270 prefCaptionsOpacity: prefCaptionsOpacity$b, 17271 prefCaptionsStyle: prefCaptionsStyle$b, 17272 serif: serif$b, 17273 sans: sans$b, 17274 cursive: cursive$b, 17275 fantasy: fantasy$b, 17276 monospace: monospace$b, 17277 white: white$b, 17278 yellow: yellow$b, 17279 green: green$b, 17280 ciano: ciano, 17281 blue: blue$b, 17282 magenta: magenta$b, 17283 red: red$b, 17284 black: black$b, 17285 transparent: transparent$b, 17286 solid: solid$b, 17287 captionsStylePopOn: captionsStylePopOn$b, 17288 captionsStyleRollUp: captionsStyleRollUp$b, 17289 prefCaptionsPosition: prefCaptionsPosition$b, 17290 captionsPositionOverlay: captionsPositionOverlay$b, 17291 captionsPositionBelow: captionsPositionBelow$b, 17292 sampleCaptionText: sampleCaptionText$b, 17293 prefSuccess: prefSuccess$b, 17294 prefNoChange: prefNoChange$b, 17295 save: save$b, 17296 cancel: cancel$b, 17297 dismissButton: dismissButton$b, 17298 windowButtonLabel: windowButtonLabel$b, 17299 windowMove: windowMove$b, 17300 windowMoveLeft: windowMoveLeft$b, 17301 windowMoveRight: windowMoveRight$b, 17302 windowMoveUp: windowMoveUp$b, 17303 windowMoveDown: windowMoveDown$b, 17304 windowMoveStopped: windowMoveStopped$b, 17305 transcriptControls: transcriptControls$b, 17306 signControls: signControls$b, 17307 windowMoveAlert: windowMoveAlert$b, 17308 windowResize: windowResize$b, 17309 windowResizeHeading: windowResizeHeading$b, 17310 closeButtonLabel: closeButtonLabel$b, 17311 width: width$b, 17312 height: height$b, 17313 resultsSummary1: resultsSummary1$b, 17314 resultsSummary2: resultsSummary2$b, 17315 resultsSummary3: resultsSummary3$b, 17316 noResultsFound: noResultsFound$b, 17317 searchButtonLabel: searchButtonLabel$b, 17318 hour: hour$b, 17319 minute: minute$b, 17320 second: second$b, 17321 hours: hours$b, 17322 minutes: minutes$b, 17323 seconds: seconds$b, 17324 vtsHeading: vtsHeading$b, 17325 vtsInstructions1: vtsInstructions1$b, 17326 vtsInstructions2: vtsInstructions2$b, 17327 vtsInstructions3: vtsInstructions3$b, 17328 vtsInstructions4: vtsInstructions4$b, 17329 vtsInstructions5: vtsInstructions5$b, 17330 vtsSelectLanguage: vtsSelectLanguage$b, 17331 vtsSave: vtsSave$b, 17332 vtsReturn: vtsReturn$b, 17333 vtsCancel: vtsCancel$b, 17334 vtsRow: vtsRow$b, 17335 vtsKind: vtsKind$b, 17336 vtsStart: vtsStart$b, 17337 vtsEnd: vtsEnd$b, 17338 vtsContent: vtsContent$b, 17339 vtsActions: vtsActions$b, 17340 vtsNewRow: vtsNewRow$b, 17341 vtsDeletedRow: vtsDeletedRow$b, 17342 vtsMovedRow: vtsMovedRow$b 17343 }; 17344 17345 var playerHeading$a = "ã¡ãã£ã¢ãã¬ã¤ã¤ã¼"; 17346 var audioPlayer$a = "Audio player"; 17347 var videoPlayer$a = "Video player"; 17348 var faster$a = "ã¯ãã"; 17349 var slower$a = "ããã"; 17350 var play$a = "åç"; 17351 var pause$a = "䏿忢"; 17352 var restart$a = "åé"; 17353 var prevTrack$a = "åã®ãã©ãã¯"; 17354 var nextTrack$a = "次ã®ãã©ãã¯"; 17355 var rewind$a = "å·»ãæ»ã"; 17356 var forward$a = "æ©éã"; 17357 var captions$a = "ãã£ãã·ã§ã³"; 17358 var showCaptions$a = "ãã£ãã·ã§ã³ã表示ãã"; 17359 var hideCaptions$a = "ãã£ãã·ã§ã³ãé表示ã«ãã"; 17360 var captionsOff$a = "ãã£ãã·ã§ã³ãæ¶ã"; 17361 var showTranscript$a = "æ¸ãèµ·ããã®è¡¨ç¤º"; 17362 var hideTranscript$a = "æ¸ãèµ·ãããé表示ã«ãã"; 17363 var turnOnDescriptions$a = "é³å£°è§£èª¬ãåºã"; 17364 var turnOffDescriptions$a = "é³å£°è§£èª¬ãåºããªã"; 17365 var chapters$a = "ãã£ãã¿ã¼"; 17366 var language$a = "è¨èª"; 17367 var sign$a = "æè©±"; 17368 var showSign$a = "æè©±ã表示"; 17369 var hideSign$a = "æè©±ãé表示"; 17370 var seekbarLabel$a = "ã¿ã¤ã ã©ã¤ã³"; 17371 var mute$a = "æ¶é³"; 17372 var unmute$a = "æ¶é³è§£é¤"; 17373 var volume$a = "é³é"; 17374 var volumeUpDown$a = "é³éã®ä¸ä¸"; 17375 var preferences$a = "è¨å®"; 17376 var enterFullScreen$a = "å ¨ç»é¢è¡¨ç¤º"; 17377 var exitFullScreen$a = "å ¨ç»é¢è¡¨ç¤ºã®çµäº"; 17378 var speed$a = "åçé度"; 17379 var spacebar$a = "ã¹ãã¼ã¹ãã¼"; 17380 var transcriptTitle$a = "æ¸ãèµ·ãã"; 17381 var lyricsTitle$a = "æè©"; 17382 var autoScroll$a = "èªåã¹ã¯ãã¼ã«"; 17383 var statusPlaying$a = "åçä¸"; 17384 var statusPaused$a = "䏿忢ä¸"; 17385 var statusStopped$a = "忢"; 17386 var statusBuffering$a = "ãããã¡ãªã³ã°ä¸"; 17387 var statusEnd$a = "ãã©ãã¯ã®çµãã"; 17388 var selectedTrack$a = "鏿ããããã©ãã¯"; 17389 var alertDescribedVersion$a = "ãã®åç»ã®é³å£°è§£èª¬ä»ããã¼ã¸ã§ã³ã使ãã¾ã"; 17390 var alertNonDescribedVersion$a = "ãã®åç»ã®è§£èª¬ãªããã¼ã¸ã§ã³ã使ãã¾ã"; 17391 var prefMenuCaptions$a = "ãã£ãã·ã§ã³"; 17392 var prefVoicedCaptions$a = "Spoken Captions"; 17393 var prefMenuDescriptions$a = "é³å£°è§£èª¬"; 17394 var prefMenuKeyboard$a = "ãã¼ãã¼ã"; 17395 var prefMenuTranscript$a = "åå¹"; 17396 var prefTitleCaptions$a = "ãã£ãã·ã§ã³ã®è¨å®"; 17397 var prefTitleDescriptions$a = "é³å£°è§£èª¬ã®è¨å®"; 17398 var prefTitleKeyboard$a = "ãã¼ãã¼ãã®è¨å®"; 17399 var prefTitleTranscript$a = "åå¹ã®è¨å®"; 17400 var prefIntroDescription1$a = "ãã®ã¡ãã£ã¢ãã¬ã¤ã¤ã¼ã¯æ¬¡ã®2ã¤ã®æ¹æ³ã§é³å£°è§£èª¬ããµãã¼ããã¾ã: "; 17401 var prefDescription1$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: 解説ä»ãã®ä»£æ¿ãã¼ã¸ã§ã³ ããã¹ãã«ãã解説"; 17402 var prefDescription2$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: 解説ä»ãã®ä»£æ¿ãã¼ã¸ã§ã³ã®ãããª"; 17403 var prefDescription3$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: ããã¹ãã«ãã解説(ã¹ã¯ãªã¼ã³ãªã¼ãã¼ã«ãã£ã¦èªã¿ä¸ãããã)"; 17404 var prefIntroDescriptionNone$a = "ç¾å¨ã®åç»ã«ã¯ã©ã¡ãã®å½¢å¼ã®é³å£°è§£èª¬ãå«ã¾ãã¦ãã¾ããã"; 17405 var prefIntroDescription3$a = "次ã®ãã©ã¼ã ã使ã£ã¦ãé³å£°è§£èª¬ã«é¢é£ããè¨å®ãä¿åã§ãã¾ãã"; 17406 var prefIntroDescription4$a = "è¨å®ãä¿åãããããé³å£°è§£èª¬ãã¿ã³ã«ãã£ã¦é³å£°è§£èª¬ã®è¡¨ç¤ºã»é表示ãåãæ¿ãããã¨ãã§ãã¾ãã"; 17407 var prefIntroKeyboard1$a = "ãã®ãã¼ã¸ã®ã¡ãã£ã¢ãã¬ã¤ã¤ã¼ã¯ããã¼ãã¼ãã»ã·ã§ã¼ãã«ããã使ã£ã¦ãã®ãã¼ã¸ã®ã©ãããã§ãæä½ã§ãã¾ã(ä¸ã®ä¸è¦§ãåç §ãã¦ãã ãã)ã"; 17408 var prefIntroKeyboard2$a = "修飾ãã¼(ShiftãAlt 㨠Control) ã¯ä»¥ä¸ã§å²ãå½ã¦ããã¨ãã§ãã¾ãã"; 17409 var prefIntroKeyboard3$a = "注æ: ããã¤ãã®ãã¼ã®çµã¿åããã¯ããã©ã¦ã¶ãã¢ããªã±ã¼ã·ã§ã³ã»ã½ããã§ä½¿ããã¦ãããã®ã¨è¡çªããå¯è½æ§ãããã¾ãããå©ç¨ã®ç°å¢ã§æ£ããåä½ãããæ§ã ãªãã¼ã®çµã¿åããã試ãã¦ãã ããã"; 17410 var prefHeadingKeyboard1$a = "ã·ã§ã¼ãã«ããã®ä¿®é£¾ãã¼"; 17411 var prefHeadingKeyboard2$a = "ç¾å¨ã®ãã¼ãã¼ãã»ã·ã§ã¼ãã«ãã"; 17412 var prefHeadingDescription$a = "é³å£°è§£èª¬"; 17413 var prefHeadingTextDescription$a = "ããã¹ãã«ããé³å£°è§£èª¬"; 17414 var prefAltKey$a = "Alt"; 17415 var prefCtrlKey$a = "Control"; 17416 var prefShiftKey$a = "Shift"; 17417 var prefNoKeyShortcuts$a = "Disable keyboard shortcuts"; 17418 var escapeKey$a = "ã¨ã¹ã±ã¼ã"; 17419 var escapeKeyFunction$a = "ç¾å¨ã®ãã¤ã¢ãã°ããããã¢ããã¡ãã¥ã¼ãéãã"; 17420 var prefDescPause$a = "解説ã表示ããããåç»ãèªåçã«åæ¢ãã"; 17421 var prefDescVisible$a = "解説ãè¦ããããã«ãã"; 17422 var prefDescVoice$a = "é³"; 17423 var prefDescRate$a = "Spoken Description Rate"; 17424 var prefCaptionRate$a = "Spoken Caption Rate"; 17425 var prefDescPitch$a = "ããã"; 17426 var prefDescPitch1$a = "é常ã«ä½ã"; 17427 var prefDescPitch2$a = "ä½ã"; 17428 var prefDescPitch3$a = "æ¨æº"; 17429 var prefDescPitch4$a = "é«ã"; 17430 var prefDescPitch5$a = "é常ã«é«ã"; 17431 var sampleDescriptionText$a = "ãã®ãµã³ãã«ã»ããã¹ããèãããã«è¨å®ãåããã¾ãã"; 17432 var prefHighlight$a = "ã¡ãã£ã¢ã®åçã«åããã¦åå¹ããã¤ã©ã¤ããã"; 17433 var prefTabbable$a = "ãã¼ãã¼ãæä½å¯è½ãªåå¹"; 17434 var prefCaptionsFont$a = "ãã©ã³ã"; 17435 var prefCaptionsColor$a = "æåè²"; 17436 var prefCaptionsBGColor$a = "èæ¯è²"; 17437 var prefCaptionsSize$a = "ãã©ã³ããµã¤ãº"; 17438 var prefCaptionsOpacity$a = "ä¸éæåº¦"; 17439 var prefCaptionsStyle$a = "æ¸å¼"; 17440 var serif$a = "ã»ãªã"; 17441 var sans$a = "ãµã³ã»ãªã"; 17442 var cursive$a = "cursive"; 17443 var fantasy$a = "fantasy"; 17444 var monospace$a = "monospace"; 17445 var white$a = "ç½"; 17446 var yellow$a = "é»è²"; 17447 var green$a = "ç·"; 17448 var cyan$a = "ã·ã¢ã³"; 17449 var blue$a = "é"; 17450 var magenta$a = "ãã¼ã³ã¿"; 17451 var red$a = "赤"; 17452 var black$a = "é»"; 17453 var transparent$a = "éæ"; 17454 var solid$a = "ä¸éæ"; 17455 var captionsStylePopOn$a = "ãããã»ãªã³"; 17456 var captionsStyleRollUp$a = "ãã¼ã«ã»ã¢ãã"; 17457 var prefCaptionsPosition$a = "ä½ç½®"; 17458 var captionsPositionOverlay$a = "ãªã¼ãã¼ã¬ã¤è¡¨ç¤º"; 17459 var captionsPositionBelow$a = "åç»ã®ä¸é¨"; 17460 var sampleCaptionText$a = "ãã£ãã·ã§ã³è¡¨ç¤ºã®ä¾"; 17461 var prefSuccess$a = "夿´ãä¿åããã¾ããã"; 17462 var prefNoChange$a = "è¨å®ã夿´ããã¦ãã¾ããã"; 17463 var save$a = "ä¿å"; 17464 var cancel$a = "ãã£ã³ã»ã«"; 17465 var dismissButton$a = "Dismiss"; 17466 var windowButtonLabel$a = "ã¦ã£ã³ãã¦ã®è¨å®"; 17467 var windowMove$a = "ç§»å"; 17468 var windowMoveLeft$a = "Window moved left"; 17469 var windowMoveRight$a = "Window moved right"; 17470 var windowMoveUp$a = "Window moved up"; 17471 var windowMoveDown$a = "Window moved down"; 17472 var windowMoveStopped$a = "Window move stopped"; 17473 var transcriptControls$a = "Transcript Window Controls"; 17474 var signControls$a = "Sign Language Window Controls"; 17475 var windowMoveAlert$a = "ãã¦ã¹ããã©ãã°ãããç¢å°ãã¼ã§ã¦ã£ã³ãã¦ãç§»åã§ãã¾ã; Enterã§çµäº"; 17476 var windowResize$a = "ãµã¤ãºãå¤ãã"; 17477 var windowResizeHeading$a = "ã¦ã£ã³ãã¦ã®ãµã¤ãºå¤æ´"; 17478 var closeButtonLabel$a = "éãã"; 17479 var width$a = "å¹ "; 17480 var height$a = "é«ã"; 17481 var resultsSummary1$a = "æ¬¡ãæ¤ç´¢:"; 17482 var resultsSummary2$a = "è¦ã¤ããã¾ãã %1 åã®æ¤ç´¢çµæ"; 17483 var resultsSummary3$a = "ã¢ã¤ãã ã«é¢é£ä»ãããã¦ããæéãã¯ãªãã¯ãã¦ããã®æç¹ãããããªãåçãã¾ãã"; 17484 var noResultsFound$a = "è¦ã¤ããã¾ããã§ããã"; 17485 var searchButtonLabel$a = "次ããåç %1"; 17486 var hour$a = "æé"; 17487 var minute$a = "å"; 17488 var second$a = "ç§"; 17489 var hours$a = "æé"; 17490 var minutes$a = "å"; 17491 var seconds$a = "ç§"; 17492 var vtsHeading$a = "Video Transcript Sorter"; 17493 var vtsInstructions1$a = "Use the Video Transcript Sorter to modify text tracks:"; 17494 var vtsInstructions2$a = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17495 var vtsInstructions3$a = "Modify content or start/end times (all are directly editable within the table)."; 17496 var vtsInstructions4$a = "Add new content, such as chapters or descriptions."; 17497 var vtsInstructions5$a = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 17498 var vtsSelectLanguage$a = "Select a language"; 17499 var vtsSave$a = "Generate new .vtt content"; 17500 var vtsReturn$a = "Return to Editor"; 17501 var vtsCancel$a = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17502 var vtsRow$a = "Row"; 17503 var vtsKind$a = "Kind"; 17504 var vtsStart$a = "Start"; 17505 var vtsEnd$a = "End"; 17506 var vtsContent$a = "Content"; 17507 var vtsActions$a = "Actions"; 17508 var vtsNewRow$a = "A new row %1 has been inserted."; 17509 var vtsDeletedRow$a = "Row %1 has been deleted."; 17510 var vtsMovedRow$a = "Row %1 has been moved %2 and is now Row %3."; 17511 var ja = { 17512 playerHeading: playerHeading$a, 17513 audioPlayer: audioPlayer$a, 17514 videoPlayer: videoPlayer$a, 17515 faster: faster$a, 17516 slower: slower$a, 17517 play: play$a, 17518 pause: pause$a, 17519 restart: restart$a, 17520 prevTrack: prevTrack$a, 17521 nextTrack: nextTrack$a, 17522 rewind: rewind$a, 17523 forward: forward$a, 17524 captions: captions$a, 17525 showCaptions: showCaptions$a, 17526 hideCaptions: hideCaptions$a, 17527 captionsOff: captionsOff$a, 17528 showTranscript: showTranscript$a, 17529 hideTranscript: hideTranscript$a, 17530 turnOnDescriptions: turnOnDescriptions$a, 17531 turnOffDescriptions: turnOffDescriptions$a, 17532 chapters: chapters$a, 17533 language: language$a, 17534 sign: sign$a, 17535 showSign: showSign$a, 17536 hideSign: hideSign$a, 17537 seekbarLabel: seekbarLabel$a, 17538 mute: mute$a, 17539 unmute: unmute$a, 17540 volume: volume$a, 17541 volumeUpDown: volumeUpDown$a, 17542 preferences: preferences$a, 17543 enterFullScreen: enterFullScreen$a, 17544 exitFullScreen: exitFullScreen$a, 17545 speed: speed$a, 17546 spacebar: spacebar$a, 17547 transcriptTitle: transcriptTitle$a, 17548 lyricsTitle: lyricsTitle$a, 17549 autoScroll: autoScroll$a, 17550 statusPlaying: statusPlaying$a, 17551 statusPaused: statusPaused$a, 17552 statusStopped: statusStopped$a, 17553 statusBuffering: statusBuffering$a, 17554 statusEnd: statusEnd$a, 17555 selectedTrack: selectedTrack$a, 17556 alertDescribedVersion: alertDescribedVersion$a, 17557 alertNonDescribedVersion: alertNonDescribedVersion$a, 17558 prefMenuCaptions: prefMenuCaptions$a, 17559 prefVoicedCaptions: prefVoicedCaptions$a, 17560 prefMenuDescriptions: prefMenuDescriptions$a, 17561 prefMenuKeyboard: prefMenuKeyboard$a, 17562 prefMenuTranscript: prefMenuTranscript$a, 17563 prefTitleCaptions: prefTitleCaptions$a, 17564 prefTitleDescriptions: prefTitleDescriptions$a, 17565 prefTitleKeyboard: prefTitleKeyboard$a, 17566 prefTitleTranscript: prefTitleTranscript$a, 17567 prefIntroDescription1: prefIntroDescription1$a, 17568 prefDescription1: prefDescription1$a, 17569 prefDescription2: prefDescription2$a, 17570 prefDescription3: prefDescription3$a, 17571 prefIntroDescriptionNone: prefIntroDescriptionNone$a, 17572 prefIntroDescription3: prefIntroDescription3$a, 17573 prefIntroDescription4: prefIntroDescription4$a, 17574 prefIntroKeyboard1: prefIntroKeyboard1$a, 17575 prefIntroKeyboard2: prefIntroKeyboard2$a, 17576 prefIntroKeyboard3: prefIntroKeyboard3$a, 17577 prefHeadingKeyboard1: prefHeadingKeyboard1$a, 17578 prefHeadingKeyboard2: prefHeadingKeyboard2$a, 17579 prefHeadingDescription: prefHeadingDescription$a, 17580 prefHeadingTextDescription: prefHeadingTextDescription$a, 17581 prefAltKey: prefAltKey$a, 17582 prefCtrlKey: prefCtrlKey$a, 17583 prefShiftKey: prefShiftKey$a, 17584 prefNoKeyShortcuts: prefNoKeyShortcuts$a, 17585 escapeKey: escapeKey$a, 17586 escapeKeyFunction: escapeKeyFunction$a, 17587 prefDescPause: prefDescPause$a, 17588 prefDescVisible: prefDescVisible$a, 17589 prefDescVoice: prefDescVoice$a, 17590 prefDescRate: prefDescRate$a, 17591 prefCaptionRate: prefCaptionRate$a, 17592 prefDescPitch: prefDescPitch$a, 17593 prefDescPitch1: prefDescPitch1$a, 17594 prefDescPitch2: prefDescPitch2$a, 17595 prefDescPitch3: prefDescPitch3$a, 17596 prefDescPitch4: prefDescPitch4$a, 17597 prefDescPitch5: prefDescPitch5$a, 17598 sampleDescriptionText: sampleDescriptionText$a, 17599 prefHighlight: prefHighlight$a, 17600 prefTabbable: prefTabbable$a, 17601 prefCaptionsFont: prefCaptionsFont$a, 17602 prefCaptionsColor: prefCaptionsColor$a, 17603 prefCaptionsBGColor: prefCaptionsBGColor$a, 17604 prefCaptionsSize: prefCaptionsSize$a, 17605 prefCaptionsOpacity: prefCaptionsOpacity$a, 17606 prefCaptionsStyle: prefCaptionsStyle$a, 17607 serif: serif$a, 17608 sans: sans$a, 17609 cursive: cursive$a, 17610 fantasy: fantasy$a, 17611 monospace: monospace$a, 17612 white: white$a, 17613 yellow: yellow$a, 17614 green: green$a, 17615 cyan: cyan$a, 17616 blue: blue$a, 17617 magenta: magenta$a, 17618 red: red$a, 17619 black: black$a, 17620 transparent: transparent$a, 17621 solid: solid$a, 17622 captionsStylePopOn: captionsStylePopOn$a, 17623 captionsStyleRollUp: captionsStyleRollUp$a, 17624 prefCaptionsPosition: prefCaptionsPosition$a, 17625 captionsPositionOverlay: captionsPositionOverlay$a, 17626 captionsPositionBelow: captionsPositionBelow$a, 17627 sampleCaptionText: sampleCaptionText$a, 17628 prefSuccess: prefSuccess$a, 17629 prefNoChange: prefNoChange$a, 17630 save: save$a, 17631 cancel: cancel$a, 17632 dismissButton: dismissButton$a, 17633 windowButtonLabel: windowButtonLabel$a, 17634 windowMove: windowMove$a, 17635 windowMoveLeft: windowMoveLeft$a, 17636 windowMoveRight: windowMoveRight$a, 17637 windowMoveUp: windowMoveUp$a, 17638 windowMoveDown: windowMoveDown$a, 17639 windowMoveStopped: windowMoveStopped$a, 17640 transcriptControls: transcriptControls$a, 17641 signControls: signControls$a, 17642 windowMoveAlert: windowMoveAlert$a, 17643 windowResize: windowResize$a, 17644 windowResizeHeading: windowResizeHeading$a, 17645 closeButtonLabel: closeButtonLabel$a, 17646 width: width$a, 17647 height: height$a, 17648 resultsSummary1: resultsSummary1$a, 17649 resultsSummary2: resultsSummary2$a, 17650 resultsSummary3: resultsSummary3$a, 17651 noResultsFound: noResultsFound$a, 17652 searchButtonLabel: searchButtonLabel$a, 17653 hour: hour$a, 17654 minute: minute$a, 17655 second: second$a, 17656 hours: hours$a, 17657 minutes: minutes$a, 17658 seconds: seconds$a, 17659 vtsHeading: vtsHeading$a, 17660 vtsInstructions1: vtsInstructions1$a, 17661 vtsInstructions2: vtsInstructions2$a, 17662 vtsInstructions3: vtsInstructions3$a, 17663 vtsInstructions4: vtsInstructions4$a, 17664 vtsInstructions5: vtsInstructions5$a, 17665 vtsSelectLanguage: vtsSelectLanguage$a, 17666 vtsSave: vtsSave$a, 17667 vtsReturn: vtsReturn$a, 17668 vtsCancel: vtsCancel$a, 17669 vtsRow: vtsRow$a, 17670 vtsKind: vtsKind$a, 17671 vtsStart: vtsStart$a, 17672 vtsEnd: vtsEnd$a, 17673 vtsContent: vtsContent$a, 17674 vtsActions: vtsActions$a, 17675 vtsNewRow: vtsNewRow$a, 17676 vtsDeletedRow: vtsDeletedRow$a, 17677 vtsMovedRow: vtsMovedRow$a 17678 }; 17679 17680 var playerHeading$9 = "Pemain media"; 17681 var audioPlayer$9 = "Audio player"; 17682 var videoPlayer$9 = "Video player"; 17683 var faster$9 = "Lebih laju"; 17684 var slower$9 = "Lebih perlahan"; 17685 var play$9 = "Main"; 17686 var pause$9 = "Jeda"; 17687 var restart$9 = "Mula semula"; 17688 var prevTrack$9 = "Trek sebelumnya"; 17689 var nextTrack$9 = "Trek seterusnya"; 17690 var rewind$9 = "Undur"; 17691 var forward$9 = "Majukan"; 17692 var captions$9 = "Sarikata"; 17693 var showCaptions$9 = "Tunjuk sarikata"; 17694 var hideCaptions$9 = "Sembunyi sarikata"; 17695 var captionsOff$9 = "Sarikata dimatikan"; 17696 var showTranscript$9 = "Tunjuk transkrip"; 17697 var hideTranscript$9 = "Sembunyi transkrip"; 17698 var turnOnDescriptions$9 = "Hidupkan deskripsi"; 17699 var turnOffDescriptions$9 = "Matikan deskripsi"; 17700 var chapters$9 = "Bab"; 17701 var language$9 = "Bahasa"; 17702 var sign$9 = "Bahasa isyarat"; 17703 var showSign$9 = "Tunjuk bahasa isyarat"; 17704 var hideSign$9 = "Sembunyi bahasa isyarat"; 17705 var seekbarLabel$9 = "garis masa"; 17706 var mute$9 = "Bisukan"; 17707 var unmute$9 = "Nyahbisukan"; 17708 var volume$9 = "Volum"; 17709 var volumeUpDown$9 = "Naik turun volum"; 17710 var preferences$9 = "Keutamaan"; 17711 var enterFullScreen$9 = "Masuk skrin penuh"; 17712 var exitFullScreen$9 = "Keluar skrin penuh"; 17713 var speed$9 = "Kelajuan"; 17714 var spacebar$9 = "bar ruang"; 17715 var transcriptTitle$9 = "Transkrip"; 17716 var lyricsTitle$9 = "Lirik"; 17717 var autoScroll$9 = "Skrol automatik"; 17718 var statusPlaying$9 = "Sedang dimainkan"; 17719 var statusPaused$9 = "Dijeda"; 17720 var statusStopped$9 = "Dihentikan"; 17721 var statusBuffering$9 = "Memuatkan"; 17722 var statusEnd$9 = "Akhir trek"; 17723 var selectedTrack$9 = "Trek dipilih"; 17724 var alertDescribedVersion$9 = "Menggunakan versi video dengan deskripsi audio"; 17725 var alertNonDescribedVersion$9 = "Menggunakan versi video tanpa deskripsi audio"; 17726 var prefMenuCaptions$9 = "Sarikata"; 17727 var prefVoicedCaptions$9 = "Spoken Captions"; 17728 var prefMenuDescriptions$9 = "Deskripsi"; 17729 var prefMenuKeyboard$9 = "Papan kekunci"; 17730 var prefMenuTranscript$9 = "Transkrip"; 17731 var prefTitleCaptions$9 = "Keutamaan Sarikata"; 17732 var prefTitleDescriptions$9 = "Keutamaan Deskripsi Audio"; 17733 var prefTitleKeyboard$9 = "Keutamaan Papan Kekunci"; 17734 var prefTitleTranscript$9 = "Keutamaan Transkrip"; 17735 var prefIntroDescription1$9 = "Pemain media ini menyokong deskripsi audio dalam dua cara: "; 17736 var prefDescription1$9 = "Video semasa mempunyai versi berdeskripsi alternatif, deskripsi berasaskan teks."; 17737 var prefDescription2$9 = "Video semasa mempunyai versi video berdeskripsi alternatif."; 17738 var prefDescription3$9 = "Video semasa mempunyai deskripsi berasaskan teks, diumumkan oleh pembaca skrin."; 17739 var prefIntroDescriptionNone$9 = "Video semasa tiada deskripsi audio dalam mana-mana format."; 17740 var prefIntroDescription3$9 = "Gunakan borang berikut untuk menetapkan keutamaan berkaitan deskripsi audio berasaskan teks."; 17741 var prefIntroDescription4$9 = "Selepas anda menyimpan tetapan, deskripsi audio boleh dihidupkan/dimatikan menggunakan butang Deskripsi."; 17742 var prefIntroKeyboard1$9 = "Pemain media di laman web ini boleh dikendalikan dari mana-mana sahaja di halaman menggunakan pintasan papan kekunci (lihat di bawah untuk senarai)."; 17743 var prefIntroKeyboard2$9 = "Kekunci pengubah (Shift, Alt, dan Control) boleh ditetapkan di bawah."; 17744 var prefIntroKeyboard3$9 = "NOTA: Sesetengah kombinasi kekunci mungkin bertindah dengan kekunci yang digunakan oleh pelayar dan/atau aplikasi lain. Cuba pelbagai kombinasi kekunci pengubah untuk mencari yang sesuai untuk anda."; 17745 var prefHeadingKeyboard1$9 = "Kekunci pengubah untuk pintasan"; 17746 var prefHeadingKeyboard2$9 = "Pintasan papan kekunci semasa"; 17747 var prefHeadingDescription$9 = "Deskripsi audio"; 17748 var prefHeadingTextDescription$9 = "Deskripsi audio berasaskan teks"; 17749 var prefAltKey$9 = "Alt"; 17750 var prefCtrlKey$9 = "Control"; 17751 var prefShiftKey$9 = "Shift"; 17752 var prefNoKeyShortcuts$9 = "Disable keyboard shortcuts"; 17753 var escapeKey$9 = "Escape"; 17754 var escapeKeyFunction$9 = "Tutup dialog atau menu popup semasa"; 17755 var prefDescPause$9 = "Jeda video secara automatik apabila deskripsi bermula"; 17756 var prefDescVisible$9 = "Paparkan deskripsi"; 17757 var prefDescVoice$9 = "Suara"; 17758 var prefDescRate$9 = "Spoken Description Rate"; 17759 var prefCaptionRate$9 = "Spoken Caption Rate"; 17760 var prefDescPitch$9 = "Nada"; 17761 var prefDescPitch1$9 = "Sangat rendah"; 17762 var prefDescPitch2$9 = "Rendah"; 17763 var prefDescPitch3$9 = "Lalai"; 17764 var prefDescPitch4$9 = "Tinggi"; 17765 var prefDescPitch5$9 = "Sangat tinggi"; 17766 var sampleDescriptionText$9 = "Laraskan tetapan untuk mendengar teks contoh ini."; 17767 var prefHighlight$9 = "Serlahkan transkrip semasa media dimainkan"; 17768 var prefTabbable$9 = "Aktifkan transkrip dengan papan kekunci"; 17769 var prefCaptionsFont$9 = "Fon"; 17770 var prefCaptionsColor$9 = "Warna Teks"; 17771 var prefCaptionsBGColor$9 = "Latar Belakang"; 17772 var prefCaptionsSize$9 = "Saiz Fon"; 17773 var prefCaptionsOpacity$9 = "Kelegapan"; 17774 var prefCaptionsStyle$9 = "Gaya"; 17775 var serif$9 = "serif"; 17776 var sans$9 = "sans-serif"; 17777 var cursive$9 = "cursive"; 17778 var fantasy$9 = "fantasy"; 17779 var monospace$9 = "monospace"; 17780 var white$9 = "putih"; 17781 var yellow$9 = "kuning"; 17782 var green$9 = "hijau"; 17783 var cyan$9 = "sian"; 17784 var blue$9 = "biru"; 17785 var magenta$9 = "magenta"; 17786 var red$9 = "merah"; 17787 var black$9 = "hitam"; 17788 var transparent$9 = "telus"; 17789 var solid$9 = "pepejal"; 17790 var captionsStylePopOn$9 = "Pop-on"; 17791 var captionsStyleRollUp$9 = "Roll-up"; 17792 var prefCaptionsPosition$9 = "Kedudukan"; 17793 var captionsPositionOverlay$9 = "Lapisan atas"; 17794 var captionsPositionBelow$9 = "Di bawah video"; 17795 var sampleCaptionText$9 = "Teks contoh sarikata"; 17796 var prefSuccess$9 = "Perubahan anda telah disimpan."; 17797 var prefNoChange$9 = "Anda tidak membuat sebarang perubahan."; 17798 var save$9 = "Simpan"; 17799 var cancel$9 = "Batal"; 17800 var dismissButton$9 = "Dismiss"; 17801 var windowButtonLabel$9 = "Pilihan tetingkap"; 17802 var windowMove$9 = "Alih"; 17803 var windowMoveLeft$9 = "Window moved left"; 17804 var windowMoveRight$9 = "Window moved right"; 17805 var windowMoveUp$9 = "Window moved up"; 17806 var windowMoveDown$9 = "Window moved down"; 17807 var windowMoveStopped$9 = "Window move stopped"; 17808 var transcriptControls$9 = "Transcript Window Controls"; 17809 var signControls$9 = "Sign Language Window Controls"; 17810 var windowMoveAlert$9 = "Seret atau gunakan kekunci anak panah untuk mengalih tetingkap; Enter untuk berhenti"; 17811 var windowResize$9 = "Ubah saiz"; 17812 var windowResizeHeading$9 = "Ubah Saiz Tetingkap"; 17813 var closeButtonLabel$9 = "Tutup"; 17814 var width$9 = "Lebar"; 17815 var height$9 = "Tinggi"; 17816 var resultsSummary1$9 = "Anda mencari:"; 17817 var resultsSummary2$9 = "Ditemui %1 item sepadan."; 17818 var resultsSummary3$9 = "Klik masa yang berkaitan dengan mana-mana item untuk mainkan video dari titik itu."; 17819 var noResultsFound$9 = "Tiada hasil ditemui."; 17820 var searchButtonLabel$9 = "Main pada %1"; 17821 var hour$9 = "jam"; 17822 var minute$9 = "minit"; 17823 var second$9 = "saat"; 17824 var hours$9 = "jam"; 17825 var minutes$9 = "minit"; 17826 var seconds$9 = "saat"; 17827 var vtsHeading$9 = "Video Transcript Sorter"; 17828 var vtsInstructions1$9 = "Use the Video Transcript Sorter to modify text tracks:"; 17829 var vtsInstructions2$9 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17830 var vtsInstructions3$9 = "Modify content or start/end times (all are directly editable within the table)."; 17831 var vtsInstructions4$9 = "Add new content, such as chapters or descriptions."; 17832 var vtsInstructions5$9 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 17833 var vtsSelectLanguage$9 = "Select a language"; 17834 var vtsSave$9 = "Generate new .vtt content"; 17835 var vtsReturn$9 = "Return to Editor"; 17836 var vtsCancel$9 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17837 var vtsRow$9 = "Row"; 17838 var vtsKind$9 = "Kind"; 17839 var vtsStart$9 = "Start"; 17840 var vtsEnd$9 = "End"; 17841 var vtsContent$9 = "Content"; 17842 var vtsActions$9 = "Actions"; 17843 var vtsNewRow$9 = "A new row %1 has been inserted."; 17844 var vtsDeletedRow$9 = "Row %1 has been deleted."; 17845 var vtsMovedRow$9 = "Row %1 has been moved %2 and is now Row %3."; 17846 var ms = { 17847 playerHeading: playerHeading$9, 17848 audioPlayer: audioPlayer$9, 17849 videoPlayer: videoPlayer$9, 17850 faster: faster$9, 17851 slower: slower$9, 17852 play: play$9, 17853 pause: pause$9, 17854 restart: restart$9, 17855 prevTrack: prevTrack$9, 17856 nextTrack: nextTrack$9, 17857 rewind: rewind$9, 17858 forward: forward$9, 17859 captions: captions$9, 17860 showCaptions: showCaptions$9, 17861 hideCaptions: hideCaptions$9, 17862 captionsOff: captionsOff$9, 17863 showTranscript: showTranscript$9, 17864 hideTranscript: hideTranscript$9, 17865 turnOnDescriptions: turnOnDescriptions$9, 17866 turnOffDescriptions: turnOffDescriptions$9, 17867 chapters: chapters$9, 17868 language: language$9, 17869 sign: sign$9, 17870 showSign: showSign$9, 17871 hideSign: hideSign$9, 17872 seekbarLabel: seekbarLabel$9, 17873 mute: mute$9, 17874 unmute: unmute$9, 17875 volume: volume$9, 17876 volumeUpDown: volumeUpDown$9, 17877 preferences: preferences$9, 17878 enterFullScreen: enterFullScreen$9, 17879 exitFullScreen: exitFullScreen$9, 17880 speed: speed$9, 17881 spacebar: spacebar$9, 17882 transcriptTitle: transcriptTitle$9, 17883 lyricsTitle: lyricsTitle$9, 17884 autoScroll: autoScroll$9, 17885 statusPlaying: statusPlaying$9, 17886 statusPaused: statusPaused$9, 17887 statusStopped: statusStopped$9, 17888 statusBuffering: statusBuffering$9, 17889 statusEnd: statusEnd$9, 17890 selectedTrack: selectedTrack$9, 17891 alertDescribedVersion: alertDescribedVersion$9, 17892 alertNonDescribedVersion: alertNonDescribedVersion$9, 17893 prefMenuCaptions: prefMenuCaptions$9, 17894 prefVoicedCaptions: prefVoicedCaptions$9, 17895 prefMenuDescriptions: prefMenuDescriptions$9, 17896 prefMenuKeyboard: prefMenuKeyboard$9, 17897 prefMenuTranscript: prefMenuTranscript$9, 17898 prefTitleCaptions: prefTitleCaptions$9, 17899 prefTitleDescriptions: prefTitleDescriptions$9, 17900 prefTitleKeyboard: prefTitleKeyboard$9, 17901 prefTitleTranscript: prefTitleTranscript$9, 17902 prefIntroDescription1: prefIntroDescription1$9, 17903 prefDescription1: prefDescription1$9, 17904 prefDescription2: prefDescription2$9, 17905 prefDescription3: prefDescription3$9, 17906 prefIntroDescriptionNone: prefIntroDescriptionNone$9, 17907 prefIntroDescription3: prefIntroDescription3$9, 17908 prefIntroDescription4: prefIntroDescription4$9, 17909 prefIntroKeyboard1: prefIntroKeyboard1$9, 17910 prefIntroKeyboard2: prefIntroKeyboard2$9, 17911 prefIntroKeyboard3: prefIntroKeyboard3$9, 17912 prefHeadingKeyboard1: prefHeadingKeyboard1$9, 17913 prefHeadingKeyboard2: prefHeadingKeyboard2$9, 17914 prefHeadingDescription: prefHeadingDescription$9, 17915 prefHeadingTextDescription: prefHeadingTextDescription$9, 17916 prefAltKey: prefAltKey$9, 17917 prefCtrlKey: prefCtrlKey$9, 17918 prefShiftKey: prefShiftKey$9, 17919 prefNoKeyShortcuts: prefNoKeyShortcuts$9, 17920 escapeKey: escapeKey$9, 17921 escapeKeyFunction: escapeKeyFunction$9, 17922 prefDescPause: prefDescPause$9, 17923 prefDescVisible: prefDescVisible$9, 17924 prefDescVoice: prefDescVoice$9, 17925 prefDescRate: prefDescRate$9, 17926 prefCaptionRate: prefCaptionRate$9, 17927 prefDescPitch: prefDescPitch$9, 17928 prefDescPitch1: prefDescPitch1$9, 17929 prefDescPitch2: prefDescPitch2$9, 17930 prefDescPitch3: prefDescPitch3$9, 17931 prefDescPitch4: prefDescPitch4$9, 17932 prefDescPitch5: prefDescPitch5$9, 17933 sampleDescriptionText: sampleDescriptionText$9, 17934 prefHighlight: prefHighlight$9, 17935 prefTabbable: prefTabbable$9, 17936 prefCaptionsFont: prefCaptionsFont$9, 17937 prefCaptionsColor: prefCaptionsColor$9, 17938 prefCaptionsBGColor: prefCaptionsBGColor$9, 17939 prefCaptionsSize: prefCaptionsSize$9, 17940 prefCaptionsOpacity: prefCaptionsOpacity$9, 17941 prefCaptionsStyle: prefCaptionsStyle$9, 17942 serif: serif$9, 17943 sans: sans$9, 17944 cursive: cursive$9, 17945 fantasy: fantasy$9, 17946 monospace: monospace$9, 17947 white: white$9, 17948 yellow: yellow$9, 17949 green: green$9, 17950 cyan: cyan$9, 17951 blue: blue$9, 17952 magenta: magenta$9, 17953 red: red$9, 17954 black: black$9, 17955 transparent: transparent$9, 17956 solid: solid$9, 17957 captionsStylePopOn: captionsStylePopOn$9, 17958 captionsStyleRollUp: captionsStyleRollUp$9, 17959 prefCaptionsPosition: prefCaptionsPosition$9, 17960 captionsPositionOverlay: captionsPositionOverlay$9, 17961 captionsPositionBelow: captionsPositionBelow$9, 17962 sampleCaptionText: sampleCaptionText$9, 17963 prefSuccess: prefSuccess$9, 17964 prefNoChange: prefNoChange$9, 17965 save: save$9, 17966 cancel: cancel$9, 17967 dismissButton: dismissButton$9, 17968 windowButtonLabel: windowButtonLabel$9, 17969 windowMove: windowMove$9, 17970 windowMoveLeft: windowMoveLeft$9, 17971 windowMoveRight: windowMoveRight$9, 17972 windowMoveUp: windowMoveUp$9, 17973 windowMoveDown: windowMoveDown$9, 17974 windowMoveStopped: windowMoveStopped$9, 17975 transcriptControls: transcriptControls$9, 17976 signControls: signControls$9, 17977 windowMoveAlert: windowMoveAlert$9, 17978 windowResize: windowResize$9, 17979 windowResizeHeading: windowResizeHeading$9, 17980 closeButtonLabel: closeButtonLabel$9, 17981 width: width$9, 17982 height: height$9, 17983 resultsSummary1: resultsSummary1$9, 17984 resultsSummary2: resultsSummary2$9, 17985 resultsSummary3: resultsSummary3$9, 17986 noResultsFound: noResultsFound$9, 17987 searchButtonLabel: searchButtonLabel$9, 17988 hour: hour$9, 17989 minute: minute$9, 17990 second: second$9, 17991 hours: hours$9, 17992 minutes: minutes$9, 17993 seconds: seconds$9, 17994 vtsHeading: vtsHeading$9, 17995 vtsInstructions1: vtsInstructions1$9, 17996 vtsInstructions2: vtsInstructions2$9, 17997 vtsInstructions3: vtsInstructions3$9, 17998 vtsInstructions4: vtsInstructions4$9, 17999 vtsInstructions5: vtsInstructions5$9, 18000 vtsSelectLanguage: vtsSelectLanguage$9, 18001 vtsSave: vtsSave$9, 18002 vtsReturn: vtsReturn$9, 18003 vtsCancel: vtsCancel$9, 18004 vtsRow: vtsRow$9, 18005 vtsKind: vtsKind$9, 18006 vtsStart: vtsStart$9, 18007 vtsEnd: vtsEnd$9, 18008 vtsContent: vtsContent$9, 18009 vtsActions: vtsActions$9, 18010 vtsNewRow: vtsNewRow$9, 18011 vtsDeletedRow: vtsDeletedRow$9, 18012 vtsMovedRow: vtsMovedRow$9 18013 }; 18014 18015 var playerHeading$8 = "Mediespiller"; 18016 var audioPlayer$8 = "Audio player"; 18017 var videoPlayer$8 = "Video player"; 18018 var faster$8 = "Raskere"; 18019 var slower$8 = "Saktere"; 18020 var play$8 = "Spill av"; 18021 var pause$8 = "Pause"; 18022 var restart$8 = "Start pÃ¥ nytt"; 18023 var prevTrack$8 = "Forrige spor"; 18024 var nextTrack$8 = "Neste spor"; 18025 var rewind$8 = "Spol tilbake"; 18026 var forward$8 = "Spol fremover"; 18027 var captions$8 = "Undertekster"; 18028 var showCaptions$8 = "Vis undertekster"; 18029 var hideCaptions$8 = "Skjul undertekster"; 18030 var captionsOff$8 = "SlÃ¥ av undertekster"; 18031 var showTranscript$8 = "Vis transkripsjon"; 18032 var hideTranscript$8 = "Skjul transkripsjon"; 18033 var turnOnDescriptions$8 = "SlÃ¥ pÃ¥ synstolking"; 18034 var turnOffDescriptions$8 = "SlÃ¥ av synstolking"; 18035 var chapters$8 = "Kapitler"; 18036 var language$8 = "SprÃ¥k"; 18037 var sign$8 = "TegnsprÃ¥k"; 18038 var showSign$8 = "Vis tegnsprÃ¥k"; 18039 var hideSign$8 = "Skjul tegnsprÃ¥k"; 18040 var seekbarLabel$8 = "tidslinje"; 18041 var mute$8 = "Lyd av"; 18042 var unmute$8 = "Lyd pÃ¥"; 18043 var volume$8 = "Volum"; 18044 var volumeUpDown$8 = "Volumkontroll"; 18045 var preferences$8 = "Innstillinger"; 18046 var enterFullScreen$8 = "Vis fullskjerm"; 18047 var exitFullScreen$8 = "Avslutt fullskjerm"; 18048 var speed$8 = "Hastighet"; 18049 var spacebar$8 = "ordskiller"; 18050 var transcriptTitle$8 = "Transkripsjon"; 18051 var lyricsTitle$8 = "Teksting, verselinjer"; 18052 var autoScroll$8 = "Automatisk rulling"; 18053 var statusPlaying$8 = "Spiller av"; 18054 var statusPaused$8 = "Satt pÃ¥ pause"; 18055 var statusStopped$8 = "Stoppet"; 18056 var statusBuffering$8 = "Mottar data"; 18057 var statusEnd$8 = "Slutt pÃ¥ sporet"; 18058 var selectedTrack$8 = "Valgt spor"; 18059 var alertDescribedVersion$8 = "Bruker videoversjon med synstolking"; 18060 var alertNonDescribedVersion$8 = "Bruker videoversjon uten synstolking"; 18061 var prefMenuCaptions$8 = "Undertekster"; 18062 var prefVoicedCaptions$8 = "Spoken Captions"; 18063 var prefMenuDescriptions$8 = "Synstolking"; 18064 var prefMenuKeyboard$8 = "Tastatur"; 18065 var prefMenuTranscript$8 = "Transkripsjon"; 18066 var prefTitleCaptions$8 = "Undertekstinnstillinger"; 18067 var prefTitleDescriptions$8 = "Synstolkingsinnstillinger"; 18068 var prefTitleKeyboard$8 = "Tastaturinnstillinger"; 18069 var prefTitleTranscript$8 = "Transkripsjonsinnstillinger"; 18070 var prefIntroDescription1$8 = "Denne mediespilleren støtter lydbeskrivelse pÃ¥ to mÃ¥ter: "; 18071 var prefDescription1$8 = "Denne videoen har en alternativ synstolket versjon, tekstbasert synstolking."; 18072 var prefDescription2$8 = "Denne videoen har alternativ synstolket versjon av video."; 18073 var prefDescription3$8 = "Denne videoen har tekstbasert synstolket versjon opplest av skjermleser."; 18074 var prefIntroDescriptionNone$8 = "Denne videoen har ikke synstolking i noen av formatene."; 18075 var prefIntroDescription3$8 = "Bruk følgende skjema for Ã¥ gjøre dine valg angÃ¥ende tekstbasert synstolking."; 18076 var prefIntroDescription4$8 = "Etter at du har lagret dine valg kan synstolking slÃ¥s av og pÃ¥ med synstolkingsknappen."; 18077 var prefIntroKeyboard1$8 = "Mediespilleren pÃ¥ denne nettsiden kan styres fra hvor som helst pÃ¥ siden ved hjelp av tastatursnarveier (se liste nedenfor)."; 18078 var prefIntroKeyboard2$8 = "Metataster (Shift, Alt, og Control) kan legges til nedenfor."; 18079 var prefIntroKeyboard3$8 = "NB! Noen tastekombinasjoner kan komme i konflikt med taster som brukes av nettleseren din og/eller andre programmer. Prøv forskjellige tastekombinasjoner for Ã¥ finne de som fungerer for deg."; 18080 var prefHeadingKeyboard1$8 = "Metataster som brukes til tastekombinasjoner"; 18081 var prefHeadingKeyboard2$8 = "NÃ¥værende tastekombinasjoner"; 18082 var prefHeadingDescription$8 = "Synstolking"; 18083 var prefHeadingTextDescription$8 = "Tekstbasert synstolking"; 18084 var prefAltKey$8 = "Alt"; 18085 var prefCtrlKey$8 = "Control"; 18086 var prefShiftKey$8 = "Shift"; 18087 var prefNoKeyShortcuts$8 = "Disable keyboard shortcuts"; 18088 var escapeKey$8 = "Escape"; 18089 var escapeKeyFunction$8 = "Lukk dialogboks eller popup-meny"; 18090 var prefDescPause$8 = "Sett video automatisk pÃ¥ pause nÃ¥r synstolking starter"; 18091 var prefDescVisible$8 = "Vis synstolking"; 18092 var prefDescVoice$8 = "Stemme"; 18093 var prefDescRate$8 = "Spoken Description Rate"; 18094 var prefCaptionRate$8 = "Spoken Caption Rate"; 18095 var prefDescPitch$8 = "Toneleie"; 18096 var prefDescPitch1$8 = "Meget lavt"; 18097 var prefDescPitch2$8 = "Lavt"; 18098 var prefDescPitch3$8 = "Normalt"; 18099 var prefDescPitch4$8 = "Høyt"; 18100 var prefDescPitch5$8 = "Meget høyt"; 18101 var sampleDescriptionText$8 = "Juster innstillingene for Ã¥ høre denne eksempelteksten."; 18102 var prefHighlight$8 = "Marker det som leses i transkripsjon ved avspilling"; 18103 var prefTabbable$8 = "Tastaturaktivert transkripsjon"; 18104 var prefCaptionsFont$8 = "Skrifttype"; 18105 var prefCaptionsColor$8 = "Skriftfarge"; 18106 var prefCaptionsBGColor$8 = "Bakgrunn"; 18107 var prefCaptionsSize$8 = "Skriftstørrelse"; 18108 var prefCaptionsOpacity$8 = "Ugjennomsiktighet"; 18109 var prefCaptionsStyle$8 = "Stil"; 18110 var serif$8 = "serif"; 18111 var sans$8 = "sans-serif"; 18112 var cursive$8 = "kursiv"; 18113 var fantasy$8 = "fantasi"; 18114 var monospace$8 = "ikke proporsjonal"; 18115 var white$8 = "hvit"; 18116 var yellow$8 = "gul"; 18117 var green$8 = "grønn"; 18118 var cyan$8 = "cyan"; 18119 var blue$8 = "blÃ¥"; 18120 var magenta$8 = "magenta"; 18121 var red$8 = "rød"; 18122 var black$8 = "sort"; 18123 var transparent$8 = "gjennomsiktig"; 18124 var solid$8 = "ugjennomsiktig"; 18125 var captionsStylePopOn$8 = "Dukker opp"; 18126 var captionsStyleRollUp$8 = "Ruller opp"; 18127 var prefCaptionsPosition$8 = "Posisjon"; 18128 var captionsPositionOverlay$8 = "Legg oppÃ¥ video"; 18129 var captionsPositionBelow$8 = "Legg under video"; 18130 var sampleCaptionText$8 = "Eksempel pÃ¥ undertekst"; 18131 var prefSuccess$8 = "Dine endringer er lagret."; 18132 var prefNoChange$8 = "Du gjorde ingen endringer."; 18133 var save$8 = "Lagre"; 18134 var cancel$8 = "Avbryt"; 18135 var dismissButton$8 = "Dismiss"; 18136 var windowButtonLabel$8 = "Vindusinnstillinger"; 18137 var windowMove$8 = "Flytt"; 18138 var windowMoveLeft$8 = "Window moved left"; 18139 var windowMoveRight$8 = "Window moved right"; 18140 var windowMoveUp$8 = "Window moved up"; 18141 var windowMoveDown$8 = "Window moved down"; 18142 var windowMoveStopped$8 = "Window move stopped"; 18143 var transcriptControls$8 = "Transcript Window Controls"; 18144 var signControls$8 = "Sign Language Window Controls"; 18145 var windowMoveAlert$8 = "Dra eller bruk piltaster for Ã¥ flytte vinduet; Enter-tast for Ã¥ stoppe"; 18146 var windowResize$8 = "Endre størrelse"; 18147 var windowResizeHeading$8 = "Endre vindusstørrelse"; 18148 var closeButtonLabel$8 = "Lukk"; 18149 var width$8 = "Bredde"; 18150 var height$8 = "Høyde"; 18151 var resultsSummary1$8 = "Du søkte etter:"; 18152 var resultsSummary2$8 = "Funnet %1 passende treff."; 18153 var resultsSummary3$8 = "Klikk tidspunkt pÃ¥ aktuelt treff for Ã¥ avspille video derfra."; 18154 var noResultsFound$8 = "Ingen treff funnet."; 18155 var searchButtonLabel$8 = "Spill av fra %1"; 18156 var hour$8 = "time"; 18157 var minute$8 = "minutt"; 18158 var second$8 = "sekund"; 18159 var hours$8 = "timer"; 18160 var minutes$8 = "minutter"; 18161 var seconds$8 = "sekunder"; 18162 var vtsHeading$8 = "Video Transcript Sorter"; 18163 var vtsInstructions1$8 = "Use the Video Transcript Sorter to modify text tracks:"; 18164 var vtsInstructions2$8 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 18165 var vtsInstructions3$8 = "Modify content or start/end times (all are directly editable within the table)."; 18166 var vtsInstructions4$8 = "Add new content, such as chapters or descriptions."; 18167 var vtsInstructions5$8 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 18168 var vtsSelectLanguage$8 = "Select a language"; 18169 var vtsSave$8 = "Generate new .vtt content"; 18170 var vtsReturn$8 = "Return to Editor"; 18171 var vtsCancel$8 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 18172 var vtsRow$8 = "Row"; 18173 var vtsKind$8 = "Kind"; 18174 var vtsStart$8 = "Start"; 18175 var vtsEnd$8 = "End"; 18176 var vtsContent$8 = "Content"; 18177 var vtsActions$8 = "Actions"; 18178 var vtsNewRow$8 = "A new row %1 has been inserted."; 18179 var vtsDeletedRow$8 = "Row %1 has been deleted."; 18180 var vtsMovedRow$8 = "Row %1 has been moved %2 and is now Row %3."; 18181 var nb = { 18182 playerHeading: playerHeading$8, 18183 audioPlayer: audioPlayer$8, 18184 videoPlayer: videoPlayer$8, 18185 faster: faster$8, 18186 slower: slower$8, 18187 play: play$8, 18188 pause: pause$8, 18189 restart: restart$8, 18190 prevTrack: prevTrack$8, 18191 nextTrack: nextTrack$8, 18192 rewind: rewind$8, 18193 forward: forward$8, 18194 captions: captions$8, 18195 showCaptions: showCaptions$8, 18196 hideCaptions: hideCaptions$8, 18197 captionsOff: captionsOff$8, 18198 showTranscript: showTranscript$8, 18199 hideTranscript: hideTranscript$8, 18200 turnOnDescriptions: turnOnDescriptions$8, 18201 turnOffDescriptions: turnOffDescriptions$8, 18202 chapters: chapters$8, 18203 language: language$8, 18204 sign: sign$8, 18205 showSign: showSign$8, 18206 hideSign: hideSign$8, 18207 seekbarLabel: seekbarLabel$8, 18208 mute: mute$8, 18209 unmute: unmute$8, 18210 volume: volume$8, 18211 volumeUpDown: volumeUpDown$8, 18212 preferences: preferences$8, 18213 enterFullScreen: enterFullScreen$8, 18214 exitFullScreen: exitFullScreen$8, 18215 speed: speed$8, 18216 spacebar: spacebar$8, 18217 transcriptTitle: transcriptTitle$8, 18218 lyricsTitle: lyricsTitle$8, 18219 autoScroll: autoScroll$8, 18220 statusPlaying: statusPlaying$8, 18221 statusPaused: statusPaused$8, 18222 statusStopped: statusStopped$8, 18223 statusBuffering: statusBuffering$8, 18224 statusEnd: statusEnd$8, 18225 selectedTrack: selectedTrack$8, 18226 alertDescribedVersion: alertDescribedVersion$8, 18227 alertNonDescribedVersion: alertNonDescribedVersion$8, 18228 prefMenuCaptions: prefMenuCaptions$8, 18229 prefVoicedCaptions: prefVoicedCaptions$8, 18230 prefMenuDescriptions: prefMenuDescriptions$8, 18231 prefMenuKeyboard: prefMenuKeyboard$8, 18232 prefMenuTranscript: prefMenuTranscript$8, 18233 prefTitleCaptions: prefTitleCaptions$8, 18234 prefTitleDescriptions: prefTitleDescriptions$8, 18235 prefTitleKeyboard: prefTitleKeyboard$8, 18236 prefTitleTranscript: prefTitleTranscript$8, 18237 prefIntroDescription1: prefIntroDescription1$8, 18238 prefDescription1: prefDescription1$8, 18239 prefDescription2: prefDescription2$8, 18240 prefDescription3: prefDescription3$8, 18241 prefIntroDescriptionNone: prefIntroDescriptionNone$8, 18242 prefIntroDescription3: prefIntroDescription3$8, 18243 prefIntroDescription4: prefIntroDescription4$8, 18244 prefIntroKeyboard1: prefIntroKeyboard1$8, 18245 prefIntroKeyboard2: prefIntroKeyboard2$8, 18246 prefIntroKeyboard3: prefIntroKeyboard3$8, 18247 prefHeadingKeyboard1: prefHeadingKeyboard1$8, 18248 prefHeadingKeyboard2: prefHeadingKeyboard2$8, 18249 prefHeadingDescription: prefHeadingDescription$8, 18250 prefHeadingTextDescription: prefHeadingTextDescription$8, 18251 prefAltKey: prefAltKey$8, 18252 prefCtrlKey: prefCtrlKey$8, 18253 prefShiftKey: prefShiftKey$8, 18254 prefNoKeyShortcuts: prefNoKeyShortcuts$8, 18255 escapeKey: escapeKey$8, 18256 escapeKeyFunction: escapeKeyFunction$8, 18257 prefDescPause: prefDescPause$8, 18258 prefDescVisible: prefDescVisible$8, 18259 prefDescVoice: prefDescVoice$8, 18260 prefDescRate: prefDescRate$8, 18261 prefCaptionRate: prefCaptionRate$8, 18262 prefDescPitch: prefDescPitch$8, 18263 prefDescPitch1: prefDescPitch1$8, 18264 prefDescPitch2: prefDescPitch2$8, 18265 prefDescPitch3: prefDescPitch3$8, 18266 prefDescPitch4: prefDescPitch4$8, 18267 prefDescPitch5: prefDescPitch5$8, 18268 sampleDescriptionText: sampleDescriptionText$8, 18269 prefHighlight: prefHighlight$8, 18270 prefTabbable: prefTabbable$8, 18271 prefCaptionsFont: prefCaptionsFont$8, 18272 prefCaptionsColor: prefCaptionsColor$8, 18273 prefCaptionsBGColor: prefCaptionsBGColor$8, 18274 prefCaptionsSize: prefCaptionsSize$8, 18275 prefCaptionsOpacity: prefCaptionsOpacity$8, 18276 prefCaptionsStyle: prefCaptionsStyle$8, 18277 serif: serif$8, 18278 sans: sans$8, 18279 cursive: cursive$8, 18280 fantasy: fantasy$8, 18281 monospace: monospace$8, 18282 white: white$8, 18283 yellow: yellow$8, 18284 green: green$8, 18285 cyan: cyan$8, 18286 blue: blue$8, 18287 magenta: magenta$8, 18288 red: red$8, 18289 black: black$8, 18290 transparent: transparent$8, 18291 solid: solid$8, 18292 captionsStylePopOn: captionsStylePopOn$8, 18293 captionsStyleRollUp: captionsStyleRollUp$8, 18294 prefCaptionsPosition: prefCaptionsPosition$8, 18295 captionsPositionOverlay: captionsPositionOverlay$8, 18296 captionsPositionBelow: captionsPositionBelow$8, 18297 sampleCaptionText: sampleCaptionText$8, 18298 prefSuccess: prefSuccess$8, 18299 prefNoChange: prefNoChange$8, 18300 save: save$8, 18301 cancel: cancel$8, 18302 dismissButton: dismissButton$8, 18303 windowButtonLabel: windowButtonLabel$8, 18304 windowMove: windowMove$8, 18305 windowMoveLeft: windowMoveLeft$8, 18306 windowMoveRight: windowMoveRight$8, 18307 windowMoveUp: windowMoveUp$8, 18308 windowMoveDown: windowMoveDown$8, 18309 windowMoveStopped: windowMoveStopped$8, 18310 transcriptControls: transcriptControls$8, 18311 signControls: signControls$8, 18312 windowMoveAlert: windowMoveAlert$8, 18313 windowResize: windowResize$8, 18314 windowResizeHeading: windowResizeHeading$8, 18315 closeButtonLabel: closeButtonLabel$8, 18316 width: width$8, 18317 height: height$8, 18318 resultsSummary1: resultsSummary1$8, 18319 resultsSummary2: resultsSummary2$8, 18320 resultsSummary3: resultsSummary3$8, 18321 noResultsFound: noResultsFound$8, 18322 searchButtonLabel: searchButtonLabel$8, 18323 hour: hour$8, 18324 minute: minute$8, 18325 second: second$8, 18326 hours: hours$8, 18327 minutes: minutes$8, 18328 seconds: seconds$8, 18329 vtsHeading: vtsHeading$8, 18330 vtsInstructions1: vtsInstructions1$8, 18331 vtsInstructions2: vtsInstructions2$8, 18332 vtsInstructions3: vtsInstructions3$8, 18333 vtsInstructions4: vtsInstructions4$8, 18334 vtsInstructions5: vtsInstructions5$8, 18335 vtsSelectLanguage: vtsSelectLanguage$8, 18336 vtsSave: vtsSave$8, 18337 vtsReturn: vtsReturn$8, 18338 vtsCancel: vtsCancel$8, 18339 vtsRow: vtsRow$8, 18340 vtsKind: vtsKind$8, 18341 vtsStart: vtsStart$8, 18342 vtsEnd: vtsEnd$8, 18343 vtsContent: vtsContent$8, 18344 vtsActions: vtsActions$8, 18345 vtsNewRow: vtsNewRow$8, 18346 vtsDeletedRow: vtsDeletedRow$8, 18347 vtsMovedRow: vtsMovedRow$8 18348 }; 18349 18350 var playerHeading$7 = "Mediaspeler"; 18351 var audioPlayer$7 = "Audiospeler"; 18352 var videoPlayer$7 = "Videospeler"; 18353 var faster$7 = "Sneller"; 18354 var slower$7 = "Langzamer"; 18355 var play$7 = "Afspelen"; 18356 var pause$7 = "Pauzeren"; 18357 var restart$7 = "Herstarten"; 18358 var prevTrack$7 = "Vorige track"; 18359 var nextTrack$7 = "Volgend track"; 18360 var rewind$7 = "Terug"; 18361 var forward$7 = "Verder"; 18362 var captions$7 = "Ondertiteling"; 18363 var showCaptions$7 = "Toon ondertiteling"; 18364 var hideCaptions$7 = "Verberg ondertiteling"; 18365 var captionsOff$7 = "Ondertiteling uit"; 18366 var showTranscript$7 = "Toon transcript"; 18367 var hideTranscript$7 = "Verberg transcript"; 18368 var turnOnDescriptions$7 = "Beschrijvingen aanzetten"; 18369 var turnOffDescriptions$7 = "Beschrijvingen uitzetten"; 18370 var chapters$7 = "Hoofdstukken"; 18371 var language$7 = "Taal"; 18372 var sign$7 = "Gebarentaal"; 18373 var showSign$7 = "Toon gebarentaal"; 18374 var seekbarLabel$7 = "tijdlijn"; 18375 var hideSign$7 = "Verberg gebarentaal"; 18376 var mute$7 = "Dempen"; 18377 var unmute$7 = "Dempen uit"; 18378 var volume$7 = "Volume"; 18379 var volumeUpDown$7 = "Volume hoger lager"; 18380 var preferences$7 = "Voorkeuren"; 18381 var enterFullScreen$7 = "Ga naar volledig scherm"; 18382 var exitFullScreen$7 = "Verlaat volledig scherm"; 18383 var speed$7 = "Snelheid"; 18384 var spacebar$7 = "spatietoets"; 18385 var transcriptTitle$7 = "Transcript"; 18386 var lyricsTitle$7 = "Tekst"; 18387 var autoScroll$7 = "Auto-scroll"; 18388 var statusPlaying$7 = "Aan het spelen"; 18389 var statusPaused$7 = "Gepauzeerd"; 18390 var statusStopped$7 = "Gestopt"; 18391 var statusBuffering$7 = "Aan het bufferen"; 18392 var statusEnd$7 = "Einde van track"; 18393 var selectedTrack$7 = "Geselecteerde track"; 18394 var alertDescribedVersion$7 = "Versie met audiobeschrijving wordt gebruikt"; 18395 var alertNonDescribedVersion$7 = "Versie zonder audiobeschrijving wordt gebruikt"; 18396 var prefMenuCaptions$7 = "Ondertiteling"; 18397 var prefVoicedCaptions$7 = "Spoken Captions"; 18398 var prefMenuDescriptions$7 = "Beschrijvingen"; 18399 var prefMenuKeyboard$7 = "Toetsenbord"; 18400 var prefMenuTranscript$7 = "Transcript"; 18401 var prefTitleCaptions$7 = "Voorkeuren ondertiteling"; 18402 var prefTitleDescriptions$7 = "Voorkeuren audiobeschrijving"; 18403 var prefTitleKeyboard$7 = "Voorkeuren toetsenbord"; 18404 var prefTitleTranscript$7 = "Voorkeuren transcript"; 18405 var prefIntroDescription1$7 = "Deze videospeler ondersteund audiobeschrijving op twee manieren: "; 18406 var prefDescription1$7 = "De huidige video heeft een alternatief beschreven versie, op tekst gebaseerde beschrijving."; 18407 var prefDescription2$7 = "De huidige video heeft Alternatieve beschreven versie van de video."; 18408 var prefDescription3$7 = "De huidige video heeft op tekst gebaseerde beschrijving, uitgesproken door de schermlezer."; 18409 var prefIntroDescriptionNone$7 = "De huidige video heeft in beide formaten geen audiobeschrijving."; 18410 var prefIntroDescription3$7 = "Gebruik het volgende formulier om je voorkeuren gerelateerd aan tekst-gebaseerde audiobeschrijving in te stellen."; 18411 var prefIntroDescription4$7 = "Na het opslaan van je instellingen, kan audiobeschrijving aan of uit gezet worden met de Beschrijving-knop."; 18412 var prefIntroKeyboard1$7 = "De mediaspeler op deze pagina kan vanaf elke plek binnen de pagina bestuurd worden met de toetsenbord sneltoetsen (zie de lijst hieronder)."; 18413 var prefIntroKeyboard2$7 = "Mutatie knoppen (Shift, Alt, and Control) kunnen hier onder toegekend worden."; 18414 var prefIntroKeyboard3$7 = "LET OP: Sommige toetsencombinaties kunnen conflicteren met de toetsen die gebruikt worden door de browser en/of andere software applicaties. Probeer verscheidene combinaties van de mutatie knoppen om een combinatie te vinden die voor jou werkt."; 18415 var prefHeadingKeyboard1$7 = "Mutatie knoppen gebruikt voor sneltoetsen"; 18416 var prefHeadingKeyboard2$7 = "Huidige toetsenbord sneltoetsen"; 18417 var prefHeadingDescription$7 = "Audiobeschrijving"; 18418 var prefHeadingTextDescription$7 = "Tekst-gebaseerde audiobeschrijving"; 18419 var prefAltKey$7 = "Alt"; 18420 var prefCtrlKey$7 = "Control"; 18421 var prefShiftKey$7 = "Shift"; 18422 var prefNoKeyShortcuts$7 = "Schakel sneltoetsen uit"; 18423 var escapeKey$7 = "Escape"; 18424 var escapeKeyFunction$7 = "Sluit huidige dialoog of pop-up menu"; 18425 var prefDescPause$7 = "Pauzeer de video automatisch als de beschrijving aan wordt gezet"; 18426 var prefDescVisible$7 = "Als er een tekst-gebaseerde beschrijving is, maak deze dan zichtbaar"; 18427 var prefDescVoice$7 = "Stem"; 18428 var prefDescRate$7 = "Spoken Description Rate"; 18429 var prefCaptionRate$7 = "Spoken Caption Rate"; 18430 var prefDescPitch$7 = "Toonhoogte"; 18431 var prefDescPitch1$7 = "Zeer laag"; 18432 var prefDescPitch2$7 = "Laag"; 18433 var prefDescPitch3$7 = "Normaal"; 18434 var prefDescPitch4$7 = "Hoog"; 18435 var prefDescPitch5$7 = "Zeer hoog"; 18436 var sampleDescriptionText$7 = "Pas de instellingen aan om deze voorbeeldtekst te beluisteren."; 18437 var prefHighlight$7 = "Highlight transcript terwijl media speelt"; 18438 var prefTabbable$7 = "Maak transcript bedienbaar met toetsenbord"; 18439 var prefCaptionsFont$7 = "Lettertype"; 18440 var prefCaptionsColor$7 = "Tekstkleur"; 18441 var prefCaptionsBGColor$7 = "Achtergrondkleur"; 18442 var prefCaptionsSize$7 = "Grootte lettertype "; 18443 var prefCaptionsOpacity$7 = "Ondoorzichtigheid"; 18444 var prefCaptionsStyle$7 = "Stijl"; 18445 var serif$7 = "serif"; 18446 var sans$7 = "sans-serif"; 18447 var cursive$7 = "cursief"; 18448 var fantasy$7 = "fantasy"; 18449 var monospace$7 = "monospace"; 18450 var white$7 = "wit"; 18451 var yellow$7 = "geel"; 18452 var green$7 = "groen"; 18453 var cyan$7 = "cyaan"; 18454 var blue$7 = "blauw"; 18455 var magenta$7 = "magenta"; 18456 var red$7 = "rood"; 18457 var black$7 = "zwart"; 18458 var transparent$7 = "transparant"; 18459 var solid$7 = "solide"; 18460 var captionsStylePopOn$7 = "Verschijn"; 18461 var captionsStyleRollUp$7 = "Rol omhoog"; 18462 var prefCaptionsPosition$7 = "Positie"; 18463 var captionsPositionOverlay$7 = "Overlay"; 18464 var captionsPositionBelow$7 = "Onder video"; 18465 var sampleCaptionText$7 = "Voorbeeld ondertiteling"; 18466 var prefSuccess$7 = "Je wijzigingen zijn opgeslagen."; 18467 var prefNoChange$7 = "Je hebt geen wijzigingen gemaakt."; 18468 var save$7 = "Opslaan"; 18469 var cancel$7 = "Annuleren"; 18470 var dismissButton$7 = "Verwijder"; 18471 var windowButtonLabel$7 = "Instellingen venster"; 18472 var windowMove$7 = "Verplaats"; 18473 var windowMoveLeft$7 = "Venster naar links verschoven"; 18474 var windowMoveRight$7 = "Venster naar rechts verschoven"; 18475 var windowMoveUp$7 = "Venster omhoog geschoven."; 18476 var windowMoveDown$7 = "Venster omlaag geschoven."; 18477 var windowMoveStopped$7 = "Verplaatsing venster gestopt"; 18478 var transcriptControls$7 = "Bediening venster transcript"; 18479 var signControls$7 = "Bediening venster gebarentaal"; 18480 var windowMoveAlert$7 = "Versleep of gebruik de pijltjestoetsen om te verplaatsen. Druk op Enter om te stoppen."; 18481 var windowResize$7 = "Verkleinen of vergroten"; 18482 var windowResizeHeading$7 = "Verander grootte van venster"; 18483 var closeButtonLabel$7 = "Sluit"; 18484 var width$7 = "Breedte"; 18485 var height$7 = "Hoogte"; 18486 var resultsSummary1$7 = "Je zocht naar:"; 18487 var resultsSummary2$7 = "Er zijn %1 overeenkomende items gevonden."; 18488 var resultsSummary3$7 = "Klik op de tijd die bij een item staat om de video vanaf dat punt af te spelen."; 18489 var noResultsFound$7 = "Geen resultaten gevonden."; 18490 var searchButtonLabel$7 = "Speel af op %1"; 18491 var hour$7 = "uur"; 18492 var minute$7 = "minuut"; 18493 var second$7 = "seconde"; 18494 var hours$7 = "hours"; 18495 var minutes$7 = "minuten"; 18496 var seconds$7 = "seconden"; 18497 var vtsHeading$7 = "Video Transcript Sorter"; 18498 var vtsInstructions1$7 = "Gebruik de Video Transcript Sorter om teksttracks te wijzigen:"; 18499 var vtsInstructions2$7 = "Herschik hoofdstukken, beschrijvingen, bijschriften en/of ondertiteling zodat ze in de juiste volgorde verschijnen in het automatisch gegenereerde transcript van Able Player."; 18500 var vtsInstructions3$7 = "Wijzig de inhoud of de begin-/eindtijden (alles is direct bewerkbaar in de tabel)."; 18501 var vtsInstructions4$7 = "Voeg nieuwe inhoud toe, zoals hoofdstukken of beschrijvingen."; 18502 var vtsInstructions5$7 = "Klik na het bewerken op de knop 'Wijzigingen opslaan' om nieuwe inhoud te genereren voor alle relevante getimede tekstbestanden. De nieuwe tekst kan worden gekopieerd en geplakt in nieuwe WebVTT-bestanden."; 18503 var vtsSelectLanguage$7 = "Selecteer een taal"; 18504 var vtsSave$7 = "Genereer nieuwe .vtt inhoud"; 18505 var vtsReturn$7 = "Terug naar de editor"; 18506 var vtsCancel$7 = "Opslaan annuleren. Alle bewerkingen die je hebt gedaan, zijn hersteld in de VTS-tabel"; 18507 var vtsRow$7 = "Rij"; 18508 var vtsKind$7 = "Sorteer"; 18509 var vtsStart$7 = "Start"; 18510 var vtsEnd$7 = "Eind"; 18511 var vtsContent$7 = "Inhoud"; 18512 var vtsActions$7 = "Acties"; 18513 var vtsNewRow$7 = "Er is een nieuwe rij %1 ingevoegd."; 18514 var vtsDeletedRow$7 = "Rij %1 is verwijderd."; 18515 var vtsMovedRow$7 = "Rij %1 is verplaatst naar %2 en is nu rij %3."; 18516 var nl = { 18517 playerHeading: playerHeading$7, 18518 audioPlayer: audioPlayer$7, 18519 videoPlayer: videoPlayer$7, 18520 faster: faster$7, 18521 slower: slower$7, 18522 play: play$7, 18523 pause: pause$7, 18524 restart: restart$7, 18525 prevTrack: prevTrack$7, 18526 nextTrack: nextTrack$7, 18527 rewind: rewind$7, 18528 forward: forward$7, 18529 captions: captions$7, 18530 showCaptions: showCaptions$7, 18531 hideCaptions: hideCaptions$7, 18532 captionsOff: captionsOff$7, 18533 showTranscript: showTranscript$7, 18534 hideTranscript: hideTranscript$7, 18535 turnOnDescriptions: turnOnDescriptions$7, 18536 turnOffDescriptions: turnOffDescriptions$7, 18537 chapters: chapters$7, 18538 language: language$7, 18539 sign: sign$7, 18540 showSign: showSign$7, 18541 seekbarLabel: seekbarLabel$7, 18542 hideSign: hideSign$7, 18543 mute: mute$7, 18544 unmute: unmute$7, 18545 volume: volume$7, 18546 volumeUpDown: volumeUpDown$7, 18547 preferences: preferences$7, 18548 enterFullScreen: enterFullScreen$7, 18549 exitFullScreen: exitFullScreen$7, 18550 speed: speed$7, 18551 spacebar: spacebar$7, 18552 transcriptTitle: transcriptTitle$7, 18553 lyricsTitle: lyricsTitle$7, 18554 autoScroll: autoScroll$7, 18555 statusPlaying: statusPlaying$7, 18556 statusPaused: statusPaused$7, 18557 statusStopped: statusStopped$7, 18558 statusBuffering: statusBuffering$7, 18559 statusEnd: statusEnd$7, 18560 selectedTrack: selectedTrack$7, 18561 alertDescribedVersion: alertDescribedVersion$7, 18562 alertNonDescribedVersion: alertNonDescribedVersion$7, 18563 prefMenuCaptions: prefMenuCaptions$7, 18564 prefVoicedCaptions: prefVoicedCaptions$7, 18565 prefMenuDescriptions: prefMenuDescriptions$7, 18566 prefMenuKeyboard: prefMenuKeyboard$7, 18567 prefMenuTranscript: prefMenuTranscript$7, 18568 prefTitleCaptions: prefTitleCaptions$7, 18569 prefTitleDescriptions: prefTitleDescriptions$7, 18570 prefTitleKeyboard: prefTitleKeyboard$7, 18571 prefTitleTranscript: prefTitleTranscript$7, 18572 prefIntroDescription1: prefIntroDescription1$7, 18573 prefDescription1: prefDescription1$7, 18574 prefDescription2: prefDescription2$7, 18575 prefDescription3: prefDescription3$7, 18576 prefIntroDescriptionNone: prefIntroDescriptionNone$7, 18577 prefIntroDescription3: prefIntroDescription3$7, 18578 prefIntroDescription4: prefIntroDescription4$7, 18579 prefIntroKeyboard1: prefIntroKeyboard1$7, 18580 prefIntroKeyboard2: prefIntroKeyboard2$7, 18581 prefIntroKeyboard3: prefIntroKeyboard3$7, 18582 prefHeadingKeyboard1: prefHeadingKeyboard1$7, 18583 prefHeadingKeyboard2: prefHeadingKeyboard2$7, 18584 prefHeadingDescription: prefHeadingDescription$7, 18585 prefHeadingTextDescription: prefHeadingTextDescription$7, 18586 prefAltKey: prefAltKey$7, 18587 prefCtrlKey: prefCtrlKey$7, 18588 prefShiftKey: prefShiftKey$7, 18589 prefNoKeyShortcuts: prefNoKeyShortcuts$7, 18590 escapeKey: escapeKey$7, 18591 escapeKeyFunction: escapeKeyFunction$7, 18592 prefDescPause: prefDescPause$7, 18593 prefDescVisible: prefDescVisible$7, 18594 prefDescVoice: prefDescVoice$7, 18595 prefDescRate: prefDescRate$7, 18596 prefCaptionRate: prefCaptionRate$7, 18597 prefDescPitch: prefDescPitch$7, 18598 prefDescPitch1: prefDescPitch1$7, 18599 prefDescPitch2: prefDescPitch2$7, 18600 prefDescPitch3: prefDescPitch3$7, 18601 prefDescPitch4: prefDescPitch4$7, 18602 prefDescPitch5: prefDescPitch5$7, 18603 sampleDescriptionText: sampleDescriptionText$7, 18604 prefHighlight: prefHighlight$7, 18605 prefTabbable: prefTabbable$7, 18606 prefCaptionsFont: prefCaptionsFont$7, 18607 prefCaptionsColor: prefCaptionsColor$7, 18608 prefCaptionsBGColor: prefCaptionsBGColor$7, 18609 prefCaptionsSize: prefCaptionsSize$7, 18610 prefCaptionsOpacity: prefCaptionsOpacity$7, 18611 prefCaptionsStyle: prefCaptionsStyle$7, 18612 serif: serif$7, 18613 sans: sans$7, 18614 cursive: cursive$7, 18615 fantasy: fantasy$7, 18616 monospace: monospace$7, 18617 white: white$7, 18618 yellow: yellow$7, 18619 green: green$7, 18620 cyan: cyan$7, 18621 blue: blue$7, 18622 magenta: magenta$7, 18623 red: red$7, 18624 black: black$7, 18625 transparent: transparent$7, 18626 solid: solid$7, 18627 captionsStylePopOn: captionsStylePopOn$7, 18628 captionsStyleRollUp: captionsStyleRollUp$7, 18629 prefCaptionsPosition: prefCaptionsPosition$7, 18630 captionsPositionOverlay: captionsPositionOverlay$7, 18631 captionsPositionBelow: captionsPositionBelow$7, 18632 sampleCaptionText: sampleCaptionText$7, 18633 prefSuccess: prefSuccess$7, 18634 prefNoChange: prefNoChange$7, 18635 save: save$7, 18636 cancel: cancel$7, 18637 dismissButton: dismissButton$7, 18638 windowButtonLabel: windowButtonLabel$7, 18639 windowMove: windowMove$7, 18640 windowMoveLeft: windowMoveLeft$7, 18641 windowMoveRight: windowMoveRight$7, 18642 windowMoveUp: windowMoveUp$7, 18643 windowMoveDown: windowMoveDown$7, 18644 windowMoveStopped: windowMoveStopped$7, 18645 transcriptControls: transcriptControls$7, 18646 signControls: signControls$7, 18647 windowMoveAlert: windowMoveAlert$7, 18648 windowResize: windowResize$7, 18649 windowResizeHeading: windowResizeHeading$7, 18650 closeButtonLabel: closeButtonLabel$7, 18651 width: width$7, 18652 height: height$7, 18653 resultsSummary1: resultsSummary1$7, 18654 resultsSummary2: resultsSummary2$7, 18655 resultsSummary3: resultsSummary3$7, 18656 noResultsFound: noResultsFound$7, 18657 searchButtonLabel: searchButtonLabel$7, 18658 hour: hour$7, 18659 minute: minute$7, 18660 second: second$7, 18661 hours: hours$7, 18662 minutes: minutes$7, 18663 seconds: seconds$7, 18664 vtsHeading: vtsHeading$7, 18665 vtsInstructions1: vtsInstructions1$7, 18666 vtsInstructions2: vtsInstructions2$7, 18667 vtsInstructions3: vtsInstructions3$7, 18668 vtsInstructions4: vtsInstructions4$7, 18669 vtsInstructions5: vtsInstructions5$7, 18670 vtsSelectLanguage: vtsSelectLanguage$7, 18671 vtsSave: vtsSave$7, 18672 vtsReturn: vtsReturn$7, 18673 vtsCancel: vtsCancel$7, 18674 vtsRow: vtsRow$7, 18675 vtsKind: vtsKind$7, 18676 vtsStart: vtsStart$7, 18677 vtsEnd: vtsEnd$7, 18678 vtsContent: vtsContent$7, 18679 vtsActions: vtsActions$7, 18680 vtsNewRow: vtsNewRow$7, 18681 vtsDeletedRow: vtsDeletedRow$7, 18682 vtsMovedRow: vtsMovedRow$7 18683 }; 18684 18685 var playerHeading$6 = "Odtwarzacz mediów"; 18686 var audioPlayer$6 = "Audio player"; 18687 var videoPlayer$6 = "Video player"; 18688 var faster$6 = "Szybciej"; 18689 var slower$6 = "Wolniej"; 18690 var play$6 = "Odtwarzaj"; 18691 var pause$6 = "Pauza"; 18692 var restart$6 = "Restart"; 18693 var prevTrack$6 = "Poprzednia Åcieżka"; 18694 var nextTrack$6 = "NastÄpna Åcieżka"; 18695 var rewind$6 = "PrzewiÅ w tyÅ"; 18696 var forward$6 = "PrzewiÅ w przód"; 18697 var captions$6 = "Napisy"; 18698 var showCaptions$6 = "Pokaż napisy"; 18699 var hideCaptions$6 = "Ukryj napisy"; 18700 var captionsOff$6 = "WyÅÄ cz napisy"; 18701 var showTranscript$6 = "Pokaż transkrypcjÄ"; 18702 var hideTranscript$6 = "Ukryj transkrypcjÄ"; 18703 var turnOnDescriptions$6 = "WÅÄ cz opis audio"; 18704 var turnOffDescriptions$6 = "WyÅÄ cz opis audio"; 18705 var chapters$6 = "RozdziaÅy"; 18706 var language$6 = "JÄzyk"; 18707 var sign$6 = "JÄzyk migowy"; 18708 var showSign$6 = "Pokaż jÄzyk migowy"; 18709 var hideSign$6 = "Ukryj jÄzyk migowy"; 18710 var seekbarLabel$6 = "Linia czasu"; 18711 var mute$6 = "Wycisz"; 18712 var unmute$6 = "PogÅoÅnij"; 18713 var volume$6 = "GÅoÅnoÅÄ"; 18714 var volumeUpDown$6 = "GÅoÅniej/Ciszej"; 18715 var preferences$6 = "Preferencje"; 18716 var enterFullScreen$6 = "Wejdź na peÅny ekran"; 18717 var exitFullScreen$6 = "Wyjdź z peÅnego ekranu"; 18718 var speed$6 = "Tempo"; 18719 var spacebar$6 = "spacja"; 18720 var transcriptTitle$6 = "Transkrypcja"; 18721 var lyricsTitle$6 = "Tekst"; 18722 var autoScroll$6 = "Auto przewijanie"; 18723 var statusPlaying$6 = "Odtwarzanie"; 18724 var statusPaused$6 = "Wstrzymane"; 18725 var statusStopped$6 = "Zatrzymane"; 18726 var statusBuffering$6 = "Buforowanie"; 18727 var statusEnd$6 = "Koniec Åcieżki"; 18728 var selectedTrack$6 = "Wybrana Åcieżka"; 18729 var alertDescribedVersion$6 = "Użyj wersji filmu z opisem audio"; 18730 var alertNonDescribedVersion$6 = "Użyj wersji filmu bez opisu audio"; 18731 var prefMenuCaptions$6 = "Napisy"; 18732 var prefVoicedCaptions$6 = "Spoken Captions"; 18733 var prefMenuDescriptions$6 = "Audiodeskrypcja"; 18734 var prefMenuKeyboard$6 = "Klawiatura"; 18735 var prefMenuTranscript$6 = "Transkrypcja"; 18736 var prefTitleCaptions$6 = "Preferencje napisów"; 18737 var prefTitleDescriptions$6 = "Preferencje audiodeskrypcji"; 18738 var prefTitleKeyboard$6 = "Preferencje klawiatury"; 18739 var prefTitleTranscript$6 = "Preferencje transkrypcji"; 18740 var prefIntroDescription1$6 = "Ten odtwarzacz mediów obsÅuguje audiodeskrypcjÄ na dwa sposoby: "; 18741 var prefDescription1$6 = "BieÅ¼Ä cy film ma alternatywna wersja opisu audio, tekst opisu audio."; 18742 var prefDescription2$6 = "BieÅ¼Ä cy film ma alternatywna wersja wideo z opisem audio."; 18743 var prefDescription3$6 = "BieÅ¼Ä cy film ma tekst opisu audio, ogÅaszany przez czytnik ekranu."; 18744 var prefIntroDescriptionNone$6 = "BieÅ¼Ä cy film nie ma audiodeskprycji."; 18745 var prefIntroDescription3$6 = "Użyj formularza poniższej, aby okreÅliÄ ustawienia odtwarzania tekstowego opisu audio"; 18746 var prefIntroDescription4$6 = "Po zapisaniu ustawieÅ opisu audio można wÅÄ czaÄ/wyÅÄ czaÄ za pomocÄ przycisku Audiodeskrypcja."; 18747 var prefIntroKeyboard1$6 = "Odtwarzaczem mediów na tej stronie można sterowaÄ z dowolnego miejsca na stronie za pomocÄ skrótów klawiaturowych (lista poniżej)."; 18748 var prefIntroKeyboard2$6 = "Klawisze modyfikujÄ ce (Shift, Alt i Control) można przypisaÄ poniżej"; 18749 var prefIntroKeyboard3$6 = "UWAGA: Niektóre kombinacje klawiszy mogÄ kolidowaÄ z klawiszami używanymi przez przeglÄ darkÄ i/lub inne aplikacje. Wypróbuj różne kombinacje klawiszy modyfikujÄ cych, aby znaleÅºÄ takÄ , która bÄdzie dla Ciebie odpowiednia."; 18750 var prefHeadingKeyboard1$6 = "Klawisze modyfikujÄ ce używane do skrótów"; 18751 var prefHeadingKeyboard2$6 = "BieÅ¼Ä ce skróty klawiaturowe"; 18752 var prefHeadingDescription$6 = "Audiodeskrypcja"; 18753 var prefHeadingTextDescription$6 = "Tekst oparty na audiodeskrypcji"; 18754 var prefAltKey$6 = "Alt"; 18755 var prefCtrlKey$6 = "Control"; 18756 var prefShiftKey$6 = "Shift"; 18757 var prefNoKeyShortcuts$6 = "Disable keyboard shortcuts"; 18758 var escapeKey$6 = "Escape"; 18759 var escapeKeyFunction$6 = "Zamknij bieÅ¼Ä ce okno dialogowe lub menu podrÄczne"; 18760 var prefDescPause$6 = "Automatycznie wstrzymuj wideo po rozpoczÄciu audiodeskrypcji"; 18761 var prefDescVisible$6 = "WyÅwietlaj opis audio"; 18762 var prefDescVoice$6 = "GÅos"; 18763 var prefDescRate$6 = "Spoken Description Rate"; 18764 var prefCaptionRate$6 = "Spoken Caption Rate"; 18765 var prefDescPitch$6 = "WysokoÅÄ"; 18766 var prefDescPitch1$6 = "Bardzo niska"; 18767 var prefDescPitch2$6 = "Niska"; 18768 var prefDescPitch3$6 = "DomyÅlna"; 18769 var prefDescPitch4$6 = "Wysoka"; 18770 var prefDescPitch5$6 = "Bardzo wysoko"; 18771 var sampleDescriptionText$6 = "Edytuj ustawienia, aby odczytaÄ ten tekst na gÅos."; 18772 var prefHighlight$6 = "PodÅwietl transkrypcjÄ podczas odtwarzania mediów"; 18773 var prefTabbable$6 = "WÅÄ cz transkrypcjÄ za pomocÄ klawiatury"; 18774 var prefCaptionsFont$6 = "Czcionka"; 18775 var prefCaptionsColor$6 = "Kolor tekstu"; 18776 var prefCaptionsBGColor$6 = "Kolor tÅa"; 18777 var prefCaptionsSize$6 = "Rozmiar czcionki"; 18778 var prefCaptionsOpacity$6 = "Krycie"; 18779 var prefCaptionsStyle$6 = "Styl"; 18780 var serif$6 = "szeryfowa"; 18781 var sans$6 = "bezszeryfowa"; 18782 var cursive$6 = "pochyÅa"; 18783 var fantasy$6 = "fantazyjna"; 18784 var monospace$6 = "staÅa szerokoÅÄ"; 18785 var white$6 = "biaÅy"; 18786 var yellow$6 = "żóÅty"; 18787 var green$6 = "zielony"; 18788 var cyan$6 = "cyjan"; 18789 var blue$6 = "niebieski"; 18790 var magenta$6 = "magenta"; 18791 var red$6 = "czerwony"; 18792 var black$6 = "czarny"; 18793 var transparent$6 = "transparentne"; 18794 var solid$6 = "solidne"; 18795 var captionsStylePopOn$6 = "RozwiÅ"; 18796 var captionsStyleRollUp$6 = "ZwiÅ"; 18797 var prefCaptionsPosition$6 = "Pozycja"; 18798 var captionsPositionOverlay$6 = "NakÅadka"; 18799 var captionsPositionBelow$6 = "Poniżej wideo"; 18800 var sampleCaptionText$6 = "PrzykÅadowy tekst napisu"; 18801 var prefSuccess$6 = "Zmiany zostaÅy zapisane."; 18802 var prefNoChange$6 = "Nie wprowadzono żadnych zmian."; 18803 var save$6 = "Zapisz"; 18804 var cancel$6 = "Anuluj"; 18805 var dismissButton$6 = "Dismiss"; 18806 var windowButtonLabel$6 = "Opcje okna"; 18807 var windowMove$6 = "PrzenieÅ"; 18808 var windowMoveLeft$6 = "Window moved left"; 18809 var windowMoveRight$6 = "Window moved right"; 18810 var windowMoveUp$6 = "Window moved up"; 18811 var windowMoveDown$6 = "Window moved down"; 18812 var windowMoveStopped$6 = "Window move stopped"; 18813 var transcriptControls$6 = "Transcript Window Controls"; 18814 var signControls$6 = "Sign Language Window Controls"; 18815 var windowMoveAlert$6 = "PrzeciÄ gnij lub użyj strzaÅek, aby przesunÄ Ä okno; Enter, aby zatrzymaÄ"; 18816 var windowResize$6 = "ZmieÅ rozmiar"; 18817 var windowResizeHeading$6 = "ZmieÅ rozmiar okna"; 18818 var closeButtonLabel$6 = "Zamknij"; 18819 var width$6 = "SzerokoÅÄ"; 18820 var height$6 = "WysokoÅÄ"; 18821 var resultsSummary1$6 = "Szukano:"; 18822 var resultsSummary2$6 = "Znaleziono %1 pasujÄ ce elementy."; 18823 var resultsSummary3$6 = "Kliknij czas powiÄ zany z dowolnym elementem, aby odtworzyÄ wideo od tego momentu."; 18824 var noResultsFound$6 = "Nie znaleziono żadnych wyników"; 18825 var searchButtonLabel$6 = "Odtwarzaj od %1"; 18826 var hour$6 = "godzina"; 18827 var minute$6 = "minuta"; 18828 var second$6 = "sekunda"; 18829 var hours$6 = "godziny"; 18830 var minutes$6 = "minuty"; 18831 var seconds$6 = "sekundy"; 18832 var vtsHeading$6 = "Video Transcript Sorter"; 18833 var vtsInstructions1$6 = "Use the Video Transcript Sorter to modify text tracks:"; 18834 var vtsInstructions2$6 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 18835 var vtsInstructions3$6 = "Modify content or start/end times (all are directly editable within the table)."; 18836 var vtsInstructions4$6 = "Add new content, such as chapters or descriptions."; 18837 var vtsInstructions5$6 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 18838 var vtsSelectLanguage$6 = "Select a language"; 18839 var vtsSave$6 = "Generate new .vtt content"; 18840 var vtsReturn$6 = "Return to Editor"; 18841 var vtsCancel$6 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 18842 var vtsRow$6 = "Row"; 18843 var vtsKind$6 = "Kind"; 18844 var vtsStart$6 = "Start"; 18845 var vtsEnd$6 = "End"; 18846 var vtsContent$6 = "Content"; 18847 var vtsActions$6 = "Actions"; 18848 var vtsNewRow$6 = "A new row %1 has been inserted."; 18849 var vtsDeletedRow$6 = "Row %1 has been deleted."; 18850 var vtsMovedRow$6 = "Row %1 has been moved %2 and is now Row %3."; 18851 var pl = { 18852 playerHeading: playerHeading$6, 18853 audioPlayer: audioPlayer$6, 18854 videoPlayer: videoPlayer$6, 18855 faster: faster$6, 18856 slower: slower$6, 18857 play: play$6, 18858 pause: pause$6, 18859 restart: restart$6, 18860 prevTrack: prevTrack$6, 18861 nextTrack: nextTrack$6, 18862 rewind: rewind$6, 18863 forward: forward$6, 18864 captions: captions$6, 18865 showCaptions: showCaptions$6, 18866 hideCaptions: hideCaptions$6, 18867 captionsOff: captionsOff$6, 18868 showTranscript: showTranscript$6, 18869 hideTranscript: hideTranscript$6, 18870 turnOnDescriptions: turnOnDescriptions$6, 18871 turnOffDescriptions: turnOffDescriptions$6, 18872 chapters: chapters$6, 18873 language: language$6, 18874 sign: sign$6, 18875 showSign: showSign$6, 18876 hideSign: hideSign$6, 18877 seekbarLabel: seekbarLabel$6, 18878 mute: mute$6, 18879 unmute: unmute$6, 18880 volume: volume$6, 18881 volumeUpDown: volumeUpDown$6, 18882 preferences: preferences$6, 18883 enterFullScreen: enterFullScreen$6, 18884 exitFullScreen: exitFullScreen$6, 18885 speed: speed$6, 18886 spacebar: spacebar$6, 18887 transcriptTitle: transcriptTitle$6, 18888 lyricsTitle: lyricsTitle$6, 18889 autoScroll: autoScroll$6, 18890 statusPlaying: statusPlaying$6, 18891 statusPaused: statusPaused$6, 18892 statusStopped: statusStopped$6, 18893 statusBuffering: statusBuffering$6, 18894 statusEnd: statusEnd$6, 18895 selectedTrack: selectedTrack$6, 18896 alertDescribedVersion: alertDescribedVersion$6, 18897 alertNonDescribedVersion: alertNonDescribedVersion$6, 18898 prefMenuCaptions: prefMenuCaptions$6, 18899 prefVoicedCaptions: prefVoicedCaptions$6, 18900 prefMenuDescriptions: prefMenuDescriptions$6, 18901 prefMenuKeyboard: prefMenuKeyboard$6, 18902 prefMenuTranscript: prefMenuTranscript$6, 18903 prefTitleCaptions: prefTitleCaptions$6, 18904 prefTitleDescriptions: prefTitleDescriptions$6, 18905 prefTitleKeyboard: prefTitleKeyboard$6, 18906 prefTitleTranscript: prefTitleTranscript$6, 18907 prefIntroDescription1: prefIntroDescription1$6, 18908 prefDescription1: prefDescription1$6, 18909 prefDescription2: prefDescription2$6, 18910 prefDescription3: prefDescription3$6, 18911 prefIntroDescriptionNone: prefIntroDescriptionNone$6, 18912 prefIntroDescription3: prefIntroDescription3$6, 18913 prefIntroDescription4: prefIntroDescription4$6, 18914 prefIntroKeyboard1: prefIntroKeyboard1$6, 18915 prefIntroKeyboard2: prefIntroKeyboard2$6, 18916 prefIntroKeyboard3: prefIntroKeyboard3$6, 18917 prefHeadingKeyboard1: prefHeadingKeyboard1$6, 18918 prefHeadingKeyboard2: prefHeadingKeyboard2$6, 18919 prefHeadingDescription: prefHeadingDescription$6, 18920 prefHeadingTextDescription: prefHeadingTextDescription$6, 18921 prefAltKey: prefAltKey$6, 18922 prefCtrlKey: prefCtrlKey$6, 18923 prefShiftKey: prefShiftKey$6, 18924 prefNoKeyShortcuts: prefNoKeyShortcuts$6, 18925 escapeKey: escapeKey$6, 18926 escapeKeyFunction: escapeKeyFunction$6, 18927 prefDescPause: prefDescPause$6, 18928 prefDescVisible: prefDescVisible$6, 18929 prefDescVoice: prefDescVoice$6, 18930 prefDescRate: prefDescRate$6, 18931 prefCaptionRate: prefCaptionRate$6, 18932 prefDescPitch: prefDescPitch$6, 18933 prefDescPitch1: prefDescPitch1$6, 18934 prefDescPitch2: prefDescPitch2$6, 18935 prefDescPitch3: prefDescPitch3$6, 18936 prefDescPitch4: prefDescPitch4$6, 18937 prefDescPitch5: prefDescPitch5$6, 18938 sampleDescriptionText: sampleDescriptionText$6, 18939 prefHighlight: prefHighlight$6, 18940 prefTabbable: prefTabbable$6, 18941 prefCaptionsFont: prefCaptionsFont$6, 18942 prefCaptionsColor: prefCaptionsColor$6, 18943 prefCaptionsBGColor: prefCaptionsBGColor$6, 18944 prefCaptionsSize: prefCaptionsSize$6, 18945 prefCaptionsOpacity: prefCaptionsOpacity$6, 18946 prefCaptionsStyle: prefCaptionsStyle$6, 18947 serif: serif$6, 18948 sans: sans$6, 18949 cursive: cursive$6, 18950 fantasy: fantasy$6, 18951 monospace: monospace$6, 18952 white: white$6, 18953 yellow: yellow$6, 18954 green: green$6, 18955 cyan: cyan$6, 18956 blue: blue$6, 18957 magenta: magenta$6, 18958 red: red$6, 18959 black: black$6, 18960 transparent: transparent$6, 18961 solid: solid$6, 18962 captionsStylePopOn: captionsStylePopOn$6, 18963 captionsStyleRollUp: captionsStyleRollUp$6, 18964 prefCaptionsPosition: prefCaptionsPosition$6, 18965 captionsPositionOverlay: captionsPositionOverlay$6, 18966 captionsPositionBelow: captionsPositionBelow$6, 18967 sampleCaptionText: sampleCaptionText$6, 18968 prefSuccess: prefSuccess$6, 18969 prefNoChange: prefNoChange$6, 18970 save: save$6, 18971 cancel: cancel$6, 18972 dismissButton: dismissButton$6, 18973 windowButtonLabel: windowButtonLabel$6, 18974 windowMove: windowMove$6, 18975 windowMoveLeft: windowMoveLeft$6, 18976 windowMoveRight: windowMoveRight$6, 18977 windowMoveUp: windowMoveUp$6, 18978 windowMoveDown: windowMoveDown$6, 18979 windowMoveStopped: windowMoveStopped$6, 18980 transcriptControls: transcriptControls$6, 18981 signControls: signControls$6, 18982 windowMoveAlert: windowMoveAlert$6, 18983 windowResize: windowResize$6, 18984 windowResizeHeading: windowResizeHeading$6, 18985 closeButtonLabel: closeButtonLabel$6, 18986 width: width$6, 18987 height: height$6, 18988 resultsSummary1: resultsSummary1$6, 18989 resultsSummary2: resultsSummary2$6, 18990 resultsSummary3: resultsSummary3$6, 18991 noResultsFound: noResultsFound$6, 18992 searchButtonLabel: searchButtonLabel$6, 18993 hour: hour$6, 18994 minute: minute$6, 18995 second: second$6, 18996 hours: hours$6, 18997 minutes: minutes$6, 18998 seconds: seconds$6, 18999 vtsHeading: vtsHeading$6, 19000 vtsInstructions1: vtsInstructions1$6, 19001 vtsInstructions2: vtsInstructions2$6, 19002 vtsInstructions3: vtsInstructions3$6, 19003 vtsInstructions4: vtsInstructions4$6, 19004 vtsInstructions5: vtsInstructions5$6, 19005 vtsSelectLanguage: vtsSelectLanguage$6, 19006 vtsSave: vtsSave$6, 19007 vtsReturn: vtsReturn$6, 19008 vtsCancel: vtsCancel$6, 19009 vtsRow: vtsRow$6, 19010 vtsKind: vtsKind$6, 19011 vtsStart: vtsStart$6, 19012 vtsEnd: vtsEnd$6, 19013 vtsContent: vtsContent$6, 19014 vtsActions: vtsActions$6, 19015 vtsNewRow: vtsNewRow$6, 19016 vtsDeletedRow: vtsDeletedRow$6, 19017 vtsMovedRow: vtsMovedRow$6 19018 }; 19019 19020 var playerHeading$5 = "Reprodutor de mÃdias"; 19021 var audioPlayer$5 = "Audio player"; 19022 var videoPlayer$5 = "Video player"; 19023 var faster$5 = "Rápido"; 19024 var slower$5 = "Lento"; 19025 var play$5 = "Reproduzir"; 19026 var pause$5 = "Pausar"; 19027 var restart$5 = "Reiniciar"; 19028 var prevTrack$5 = "Faixa anterior"; 19029 var nextTrack$5 = "Próxima faixa"; 19030 var rewind$5 = "Retroceder"; 19031 var forward$5 = "Avançar"; 19032 var captions$5 = "Legendas"; 19033 var showCaptions$5 = "Mostrar legendas"; 19034 var hideCaptions$5 = "Ocultar legendas"; 19035 var captionsOff$5 = "Legendas desligadas"; 19036 var showTranscript$5 = "Mostrar transcrição"; 19037 var hideTranscript$5 = "Ocultar transcrição"; 19038 var turnOnDescriptions$5 = "Habilitar descrições"; 19039 var turnOffDescriptions$5 = "Desabilitar descrições"; 19040 var chapters$5 = "CapÃtulos"; 19041 var language$5 = "Idioma"; 19042 var sign$5 = "LÃngua de sinais"; 19043 var showSign$5 = "Mostrar lÃngua de sinais"; 19044 var hideSign$5 = "Ocultar lÃngua de sinais"; 19045 var seekbarLabel$5 = "Linha do tempo"; 19046 var mute$5 = "Silenciar"; 19047 var unmute$5 = "Ativar som"; 19048 var volume$5 = "Volume"; 19049 var volumeUpDown$5 = "Ampliar/reduzir volume"; 19050 var preferences$5 = "Preferências"; 19051 var enterFullScreen$5 = "Entrar em tela cheia"; 19052 var exitFullScreen$5 = "Sair da tela cheia"; 19053 var speed$5 = "Velocidade"; 19054 var spacebar$5 = "barra de espaço"; 19055 var transcriptTitle$5 = "Transcrição"; 19056 var lyricsTitle$5 = "Letras"; 19057 var autoScroll$5 = "Rolagem automática"; 19058 var statusPlaying$5 = "Reproduzindo"; 19059 var statusPaused$5 = "Pausado"; 19060 var statusStopped$5 = "Parado"; 19061 var statusBuffering$5 = "Carregando"; 19062 var statusEnd$5 = "Fim da faixa"; 19063 var selectedTrack$5 = "Faixa Selecionada"; 19064 var alertDescribedVersion$5 = "Usando a versão com audiodescrição deste vÃdeo"; 19065 var alertNonDescribedVersion$5 = "Utilizando a versão sem audiodescrição desse vÃdeo"; 19066 var prefMenuCaptions$5 = "CapÃtulos"; 19067 var prefVoicedCaptions$5 = "Spoken Captions"; 19068 var prefMenuDescriptions$5 = "Descrições"; 19069 var prefMenuKeyboard$5 = "Teclado"; 19070 var prefMenuTranscript$5 = "Transcrição"; 19071 var prefTitleCaptions$5 = "Preferências de Legenda"; 19072 var prefTitleDescriptions$5 = "Preferências da Audiodescrição"; 19073 var prefTitleKeyboard$5 = "Preferências do Teclado"; 19074 var prefTitleTranscript$5 = "Preferências da Transcrição"; 19075 var prefIntroDescription1$5 = "Esse reprodutor de mÃdias suportam audiodescrição de duas maneiras: "; 19076 var prefDescription1$5 = "O vÃdeo atual está uma versão alternativa de descrição, texto baseado em descrição."; 19077 var prefDescription2$5 = "O vÃdeo atual está versão alternativa de descrição do vÃdeo."; 19078 var prefDescription3$5 = "O vÃdeo atual está texto baseado em descrição, anunciado pelo leitor de tela."; 19079 var prefIntroDescriptionNone$5 = "O vÃdeo atual não possui audiodescrição em nenhum formato."; 19080 var prefIntroDescription3$5 = "Use o formulário a seguir para definir suas preferências relacionadas à texto baseado em audiodescrição."; 19081 var prefIntroDescription4$5 = "Depois que salvar suas configurações, a audiodescrição poderá ser ligada/desligada usando o botão Descrição."; 19082 var prefIntroKeyboard1$5 = "O reprodutor de mÃdias desse página web pode ser operado de qualquer lugar nessa página, usando os atalhos de teclado (veja a lista abaixo)."; 19083 var prefIntroKeyboard2$5 = "Modificar teclas (Shift, Alt and Control) podem ser gravadas abaixo."; 19084 var prefIntroKeyboard3$5 = "OBSERVE: Algumas combinações de teclado podem entrar em conflito com atalhos usados pelo seu navegador e/ou outro software. Teste várias combinações de atalhos de teclado para encontrar uma que funcione bem para você."; 19085 var prefHeadingKeyboard1$5 = "Teclas modificadas dos atalhos"; 19086 var prefHeadingKeyboard2$5 = "Atual atalho de teclado"; 19087 var prefHeadingDescription$5 = "Audiodescrição"; 19088 var prefHeadingTextDescription$5 = "Texto baseado em audiodescrição"; 19089 var prefAltKey$5 = "Alt"; 19090 var prefCtrlKey$5 = "Control"; 19091 var prefShiftKey$5 = "Shift"; 19092 var prefNoKeyShortcuts$5 = "Disable keyboard shortcuts"; 19093 var escapeKey$5 = "ESC"; 19094 var escapeKeyFunction$5 = "Fechar diálogo ou caixa pop-up atual"; 19095 var prefDescPause$5 = "Automaticamente pausar um vÃdeo quando a descrição iniciar"; 19096 var prefDescVisible$5 = "Fazer com que a descrição esteja visÃvel"; 19097 var prefDescVoice$5 = "Voz"; 19098 var prefDescRate$5 = "Spoken Description Rate"; 19099 var prefCaptionRate$5 = "Spoken Caption Rate"; 19100 var prefDescPitch$5 = "Tom"; 19101 var prefDescPitch1$5 = "Muito baixo"; 19102 var prefDescPitch2$5 = "Baixo"; 19103 var prefDescPitch3$5 = "Padrão"; 19104 var prefDescPitch4$5 = "Alto"; 19105 var prefDescPitch5$5 = "Muito alto"; 19106 var sampleDescriptionText$5 = "Ajuste as configurações para ouvir este exemplo de texto."; 19107 var prefHighlight$5 = "Destacar a descrição conforme a mÃdia for reproduzida"; 19108 var prefTabbable$5 = "Transcrição de teclado habilitada"; 19109 var prefCaptionsFont$5 = "Fonte"; 19110 var prefCaptionsColor$5 = "Cor do Texto"; 19111 var prefCaptionsBGColor$5 = "Cor do plano de Fundo"; 19112 var prefCaptionsSize$5 = "Tamanho da Fonte"; 19113 var prefCaptionsOpacity$5 = "transparência"; 19114 var prefCaptionsStyle$5 = "Estilo"; 19115 var serif$5 = "serifada"; 19116 var sans$5 = "sem serifa"; 19117 var cursive$5 = "cursiva"; 19118 var fantasy$5 = "fantasiosa"; 19119 var monospace$5 = "monoespaçada"; 19120 var white$5 = "branca"; 19121 var yellow$5 = "amarela"; 19122 var green$5 = "verde"; 19123 var cyan$5 = "ciano"; 19124 var blue$5 = "azul"; 19125 var magenta$5 = "magenta"; 19126 var red$5 = "vermelha"; 19127 var black$5 = "preta"; 19128 var transparent$5 = "transparente"; 19129 var solid$5 = "sólida"; 19130 var captionsStylePopOn$5 = "Aparecer"; 19131 var captionsStyleRollUp$5 = "Rolar pra cima"; 19132 var prefCaptionsPosition$5 = "Posição"; 19133 var captionsPositionOverlay$5 = "Sobrepôr"; 19134 var captionsPositionBelow$5 = "Abaixo do vÃdeo"; 19135 var sampleCaptionText$5 = "Um texto de exemplo para legendas"; 19136 var prefSuccess$5 = "Suas alterações foram salvas"; 19137 var prefNoChange$5 = "Você não fez nenhuma alteração"; 19138 var save$5 = "Salvar"; 19139 var cancel$5 = "Cancelar"; 19140 var dismissButton$5 = "Dismiss"; 19141 var windowButtonLabel$5 = "Opções de janela"; 19142 var windowMove$5 = "Mover"; 19143 var windowMoveLeft$5 = "Window moved left"; 19144 var windowMoveRight$5 = "Window moved right"; 19145 var windowMoveUp$5 = "Window moved up"; 19146 var windowMoveDown$5 = "Window moved down"; 19147 var windowMoveStopped$5 = "Window move stopped"; 19148 var transcriptControls$5 = "Transcript Window Controls"; 19149 var signControls$5 = "Sign Language Window Controls"; 19150 var windowMoveAlert$5 = "Arraste ou use as setas do seu teclado para mover; Enter para parar"; 19151 var windowResize$5 = "Redimensionar"; 19152 var windowResizeHeading$5 = "Redimensionar Janela"; 19153 var closeButtonLabel$5 = "Fechar"; 19154 var width$5 = "Largura"; 19155 var height$5 = "Altura"; 19156 var resultsSummary1$5 = "Você buscou por:"; 19157 var resultsSummary2$5 = "Encontrado %1 itens correspondentes"; 19158 var resultsSummary3$5 = "Ative o item de tempo associado para reproduzir o vÃdeo a partir desse ponto."; 19159 var noResultsFound$5 = "Nenhum resultado encontrado"; 19160 var searchButtonLabel$5 = "Tocar no %1"; 19161 var hour$5 = "hora"; 19162 var minute$5 = "minuto"; 19163 var second$5 = "segundos"; 19164 var hours$5 = "horas"; 19165 var minutes$5 = "minutos"; 19166 var seconds$5 = "segundos"; 19167 var vtsHeading$5 = "Video Transcript Sorter"; 19168 var vtsInstructions1$5 = "Use the Video Transcript Sorter to modify text tracks:"; 19169 var vtsInstructions2$5 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 19170 var vtsInstructions3$5 = "Modify content or start/end times (all are directly editable within the table)."; 19171 var vtsInstructions4$5 = "Add new content, such as chapters or descriptions."; 19172 var vtsInstructions5$5 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 19173 var vtsSelectLanguage$5 = "Select a language"; 19174 var vtsSave$5 = "Generate new .vtt content"; 19175 var vtsReturn$5 = "Return to Editor"; 19176 var vtsCancel$5 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 19177 var vtsRow$5 = "Row"; 19178 var vtsKind$5 = "Kind"; 19179 var vtsStart$5 = "Start"; 19180 var vtsEnd$5 = "End"; 19181 var vtsContent$5 = "Content"; 19182 var vtsActions$5 = "Actions"; 19183 var vtsNewRow$5 = "A new row %1 has been inserted."; 19184 var vtsDeletedRow$5 = "Row %1 has been deleted."; 19185 var vtsMovedRow$5 = "Row %1 has been moved %2 and is now Row %3."; 19186 var pt_br = { 19187 playerHeading: playerHeading$5, 19188 audioPlayer: audioPlayer$5, 19189 videoPlayer: videoPlayer$5, 19190 faster: faster$5, 19191 slower: slower$5, 19192 play: play$5, 19193 pause: pause$5, 19194 restart: restart$5, 19195 prevTrack: prevTrack$5, 19196 nextTrack: nextTrack$5, 19197 rewind: rewind$5, 19198 forward: forward$5, 19199 captions: captions$5, 19200 showCaptions: showCaptions$5, 19201 hideCaptions: hideCaptions$5, 19202 captionsOff: captionsOff$5, 19203 showTranscript: showTranscript$5, 19204 hideTranscript: hideTranscript$5, 19205 turnOnDescriptions: turnOnDescriptions$5, 19206 turnOffDescriptions: turnOffDescriptions$5, 19207 chapters: chapters$5, 19208 language: language$5, 19209 sign: sign$5, 19210 showSign: showSign$5, 19211 hideSign: hideSign$5, 19212 seekbarLabel: seekbarLabel$5, 19213 mute: mute$5, 19214 unmute: unmute$5, 19215 volume: volume$5, 19216 volumeUpDown: volumeUpDown$5, 19217 preferences: preferences$5, 19218 enterFullScreen: enterFullScreen$5, 19219 exitFullScreen: exitFullScreen$5, 19220 speed: speed$5, 19221 spacebar: spacebar$5, 19222 transcriptTitle: transcriptTitle$5, 19223 lyricsTitle: lyricsTitle$5, 19224 autoScroll: autoScroll$5, 19225 statusPlaying: statusPlaying$5, 19226 statusPaused: statusPaused$5, 19227 statusStopped: statusStopped$5, 19228 statusBuffering: statusBuffering$5, 19229 statusEnd: statusEnd$5, 19230 selectedTrack: selectedTrack$5, 19231 alertDescribedVersion: alertDescribedVersion$5, 19232 alertNonDescribedVersion: alertNonDescribedVersion$5, 19233 prefMenuCaptions: prefMenuCaptions$5, 19234 prefVoicedCaptions: prefVoicedCaptions$5, 19235 prefMenuDescriptions: prefMenuDescriptions$5, 19236 prefMenuKeyboard: prefMenuKeyboard$5, 19237 prefMenuTranscript: prefMenuTranscript$5, 19238 prefTitleCaptions: prefTitleCaptions$5, 19239 prefTitleDescriptions: prefTitleDescriptions$5, 19240 prefTitleKeyboard: prefTitleKeyboard$5, 19241 prefTitleTranscript: prefTitleTranscript$5, 19242 prefIntroDescription1: prefIntroDescription1$5, 19243 prefDescription1: prefDescription1$5, 19244 prefDescription2: prefDescription2$5, 19245 prefDescription3: prefDescription3$5, 19246 prefIntroDescriptionNone: prefIntroDescriptionNone$5, 19247 prefIntroDescription3: prefIntroDescription3$5, 19248 prefIntroDescription4: prefIntroDescription4$5, 19249 prefIntroKeyboard1: prefIntroKeyboard1$5, 19250 prefIntroKeyboard2: prefIntroKeyboard2$5, 19251 prefIntroKeyboard3: prefIntroKeyboard3$5, 19252 prefHeadingKeyboard1: prefHeadingKeyboard1$5, 19253 prefHeadingKeyboard2: prefHeadingKeyboard2$5, 19254 prefHeadingDescription: prefHeadingDescription$5, 19255 prefHeadingTextDescription: prefHeadingTextDescription$5, 19256 prefAltKey: prefAltKey$5, 19257 prefCtrlKey: prefCtrlKey$5, 19258 prefShiftKey: prefShiftKey$5, 19259 prefNoKeyShortcuts: prefNoKeyShortcuts$5, 19260 escapeKey: escapeKey$5, 19261 escapeKeyFunction: escapeKeyFunction$5, 19262 prefDescPause: prefDescPause$5, 19263 prefDescVisible: prefDescVisible$5, 19264 prefDescVoice: prefDescVoice$5, 19265 prefDescRate: prefDescRate$5, 19266 prefCaptionRate: prefCaptionRate$5, 19267 prefDescPitch: prefDescPitch$5, 19268 prefDescPitch1: prefDescPitch1$5, 19269 prefDescPitch2: prefDescPitch2$5, 19270 prefDescPitch3: prefDescPitch3$5, 19271 prefDescPitch4: prefDescPitch4$5, 19272 prefDescPitch5: prefDescPitch5$5, 19273 sampleDescriptionText: sampleDescriptionText$5, 19274 prefHighlight: prefHighlight$5, 19275 prefTabbable: prefTabbable$5, 19276 prefCaptionsFont: prefCaptionsFont$5, 19277 prefCaptionsColor: prefCaptionsColor$5, 19278 prefCaptionsBGColor: prefCaptionsBGColor$5, 19279 prefCaptionsSize: prefCaptionsSize$5, 19280 prefCaptionsOpacity: prefCaptionsOpacity$5, 19281 prefCaptionsStyle: prefCaptionsStyle$5, 19282 serif: serif$5, 19283 sans: sans$5, 19284 cursive: cursive$5, 19285 fantasy: fantasy$5, 19286 monospace: monospace$5, 19287 white: white$5, 19288 yellow: yellow$5, 19289 green: green$5, 19290 cyan: cyan$5, 19291 blue: blue$5, 19292 magenta: magenta$5, 19293 red: red$5, 19294 black: black$5, 19295 transparent: transparent$5, 19296 solid: solid$5, 19297 captionsStylePopOn: captionsStylePopOn$5, 19298 captionsStyleRollUp: captionsStyleRollUp$5, 19299 prefCaptionsPosition: prefCaptionsPosition$5, 19300 captionsPositionOverlay: captionsPositionOverlay$5, 19301 captionsPositionBelow: captionsPositionBelow$5, 19302 sampleCaptionText: sampleCaptionText$5, 19303 prefSuccess: prefSuccess$5, 19304 prefNoChange: prefNoChange$5, 19305 save: save$5, 19306 cancel: cancel$5, 19307 dismissButton: dismissButton$5, 19308 windowButtonLabel: windowButtonLabel$5, 19309 windowMove: windowMove$5, 19310 windowMoveLeft: windowMoveLeft$5, 19311 windowMoveRight: windowMoveRight$5, 19312 windowMoveUp: windowMoveUp$5, 19313 windowMoveDown: windowMoveDown$5, 19314 windowMoveStopped: windowMoveStopped$5, 19315 transcriptControls: transcriptControls$5, 19316 signControls: signControls$5, 19317 windowMoveAlert: windowMoveAlert$5, 19318 windowResize: windowResize$5, 19319 windowResizeHeading: windowResizeHeading$5, 19320 closeButtonLabel: closeButtonLabel$5, 19321 width: width$5, 19322 height: height$5, 19323 resultsSummary1: resultsSummary1$5, 19324 resultsSummary2: resultsSummary2$5, 19325 resultsSummary3: resultsSummary3$5, 19326 noResultsFound: noResultsFound$5, 19327 searchButtonLabel: searchButtonLabel$5, 19328 hour: hour$5, 19329 minute: minute$5, 19330 second: second$5, 19331 hours: hours$5, 19332 minutes: minutes$5, 19333 seconds: seconds$5, 19334 vtsHeading: vtsHeading$5, 19335 vtsInstructions1: vtsInstructions1$5, 19336 vtsInstructions2: vtsInstructions2$5, 19337 vtsInstructions3: vtsInstructions3$5, 19338 vtsInstructions4: vtsInstructions4$5, 19339 vtsInstructions5: vtsInstructions5$5, 19340 vtsSelectLanguage: vtsSelectLanguage$5, 19341 vtsSave: vtsSave$5, 19342 vtsReturn: vtsReturn$5, 19343 vtsCancel: vtsCancel$5, 19344 vtsRow: vtsRow$5, 19345 vtsKind: vtsKind$5, 19346 vtsStart: vtsStart$5, 19347 vtsEnd: vtsEnd$5, 19348 vtsContent: vtsContent$5, 19349 vtsActions: vtsActions$5, 19350 vtsNewRow: vtsNewRow$5, 19351 vtsDeletedRow: vtsDeletedRow$5, 19352 vtsMovedRow: vtsMovedRow$5 19353 }; 19354 19355 var playerHeading$4 = "Leitor multimédia"; 19356 var audioPlayer$4 = "Audio player"; 19357 var videoPlayer$4 = "Video player"; 19358 var faster$4 = "Mais rápido"; 19359 var slower$4 = "Mais lento"; 19360 var play$4 = "Reproduzir"; 19361 var pause$4 = "Pausa"; 19362 var restart$4 = "Reiniciar"; 19363 var prevTrack$4 = "Faixa anterior"; 19364 var nextTrack$4 = "Faixa seguinte"; 19365 var rewind$4 = "Retroceder"; 19366 var forward$4 = "Avançar"; 19367 var captions$4 = "Legendas"; 19368 var showCaptions$4 = "Mostrar legendas"; 19369 var hideCaptions$4 = "Esconder legendas"; 19370 var captionsOff$4 = "Desativar legendas"; 19371 var showTranscript$4 = "Mostrar transcrição"; 19372 var hideTranscript$4 = "Esconder transcrição"; 19373 var turnOnDescriptions$4 = "Ativar descrições"; 19374 var turnOffDescriptions$4 = "Desativar descrições"; 19375 var chapters$4 = "CapÃtulos"; 19376 var language$4 = "Idioma"; 19377 var sign$4 = "Linguagem gestual"; 19378 var showSign$4 = "Mostrar linguagem gestual"; 19379 var hideSign$4 = "Esconder linguagem gestual"; 19380 var seekbarLabel$4 = "linha temporal"; 19381 var mute$4 = "Desativar som"; 19382 var unmute$4 = "Ativar som"; 19383 var volume$4 = "Volume"; 19384 var volumeUpDown$4 = "Volume up down"; 19385 var preferences$4 = "Preferências"; 19386 var enterFullScreen$4 = "Ativar a vista de ecrã inteiro"; 19387 var exitFullScreen$4 = "Sair da vista de ecrã inteiro"; 19388 var speed$4 = "Velocidade"; 19389 var spacebar$4 = "barra de espaço"; 19390 var transcriptTitle$4 = "Transcrição"; 19391 var lyricsTitle$4 = "Letra"; 19392 var autoScroll$4 = "Scroll automático"; 19393 var statusPlaying$4 = "A reproduzir"; 19394 var statusPaused$4 = "Pausado"; 19395 var statusStopped$4 = "Parado"; 19396 var statusBuffering$4 = "A carregar"; 19397 var statusEnd$4 = "Fim da faixa"; 19398 var selectedTrack$4 = "Faixa selecionada"; 19399 var alertDescribedVersion$4 = "A usar versão de áudio descrita deste vÃdeo"; 19400 var alertNonDescribedVersion$4 = "A usar versão não descrita deste vÃdeo"; 19401 var prefMenuCaptions$4 = "Legendas"; 19402 var prefVoicedCaptions$4 = "Spoken Captions"; 19403 var prefMenuDescriptions$4 = "Descrições"; 19404 var prefMenuKeyboard$4 = "Teclado"; 19405 var prefMenuTranscript$4 = "Transcrição"; 19406 var prefTitleCaptions$4 = "Preferências das Legendas"; 19407 var prefTitleDescriptions$4 = "Preferências da Descrição de Aúdio"; 19408 var prefTitleKeyboard$4 = "Preferências de Peclado"; 19409 var prefTitleTranscript$4 = "Preferências de Transcrição"; 19410 var prefIntroDescription1$4 = "Este leitor multimédia suporta descrição de áudio de duas formas: "; 19411 var prefDescription1$4 = "O vÃdeo atual tem uma versão descrita alternativa, descrição à base de texto."; 19412 var prefDescription2$4 = "O vÃdeo atual tem versão descrita alernativa do vÃdeo."; 19413 var prefDescription3$4 = "O vÃdeo atual tem descrição à base de texto, anunciada pelo leitor de ecrãs."; 19414 var prefIntroDescriptionNone$4 = "O vÃdeo atual não tem descrição de áudio em nenhum formato."; 19415 var prefIntroDescription3$4 = "Utiliza o seguinte formulário para definir as tuas preferências relacionadas com descrição de áudio à base de texto."; 19416 var prefIntroDescription4$4 = "Depois de guardar as tuas definições, descrição de áudio pode ser alternada on/off usando o botão Descrição."; 19417 var prefIntroKeyboard1$4 = "O leitor multimédia nesta página pode ser operado de qualquer lugar na página utilizando os atalhos de teclado (vê em baixo a lista)."; 19418 var prefIntroKeyboard2$4 = "Teclas modificadoras (Shift, Alt, and Control) podem ser atribuÃdas em baixo."; 19419 var prefIntroKeyboard3$4 = "NOTA: Algumas combinações de teclas podem entrar em conflito com teclas usadas pelo teu navegador e/ou outras aplicações de software. Tenta várias combinações de teclas modificadoras para encontrar uma que funciona para ti."; 19420 var prefHeadingKeyboard1$4 = "Teclas modificadoras usadas para atalhos"; 19421 var prefHeadingKeyboard2$4 = "Atalhos de teclado em uso"; 19422 var prefHeadingDescription$4 = "Descrição de áudio"; 19423 var prefHeadingTextDescription$4 = "Descrição de áudio à base de texto"; 19424 var prefAltKey$4 = "Alt"; 19425 var prefCtrlKey$4 = "Control"; 19426 var prefShiftKey$4 = "Shift"; 19427 var prefNoKeyShortcuts$4 = "Disable keyboard shortcuts"; 19428 var escapeKey$4 = "Escape"; 19429 var escapeKeyFunction$4 = "Fecha o diálogo ou menu popup"; 19430 var prefDescPause$4 = "Pausar vÃdeo automaticamente quando a descrição começa"; 19431 var prefDescVisible$4 = "Torna a descrição visÃvel"; 19432 var prefDescVoice$4 = "Voz"; 19433 var prefDescRate$4 = "Spoken Description Rate"; 19434 var prefCaptionRate$4 = "Spoken Caption Rate"; 19435 var prefDescPitch$4 = "Tom"; 19436 var prefDescPitch1$4 = "Muito baixo"; 19437 var prefDescPitch2$4 = "Baixo"; 19438 var prefDescPitch3$4 = "Padrão"; 19439 var prefDescPitch4$4 = "Alto"; 19440 var prefDescPitch5$4 = "Muito Alto"; 19441 var sampleDescriptionText$4 = "Ajusta as preferências para ouvir esta amostra de texto."; 19442 var prefHighlight$4 = "Destacar transcrição enquanto o leito está a reproduzir."; 19443 var prefTabbable$4 = "Transcrição ativada por teclado"; 19444 var prefCaptionsFont$4 = "Tipo de letra"; 19445 var prefCaptionsColor$4 = "Cor de texto"; 19446 var prefCaptionsBGColor$4 = "Fundo"; 19447 var prefCaptionsSize$4 = "Tamanho de letra"; 19448 var prefCaptionsOpacity$4 = "Opacidade"; 19449 var prefCaptionsStyle$4 = "Estilo"; 19450 var serif$4 = "serifada"; 19451 var sans$4 = "sem serifa"; 19452 var cursive$4 = "cursivo"; 19453 var fantasy$4 = "fantasia"; 19454 var monospace$4 = "mono-espaçada"; 19455 var white$4 = "branco"; 19456 var yellow$4 = "amarelo"; 19457 var green$4 = "verde"; 19458 var cyan$4 = "ciano"; 19459 var blue$4 = "azul"; 19460 var magenta$4 = "magenta"; 19461 var red$4 = "vermelho"; 19462 var black$4 = "preto"; 19463 var transparent$4 = "transparente"; 19464 var solid$4 = "sólido"; 19465 var captionsStylePopOn$4 = "Pop-on"; 19466 var captionsStyleRollUp$4 = "Roll-up"; 19467 var prefCaptionsPosition$4 = "Posição"; 19468 var captionsPositionOverlay$4 = "Sobreposição"; 19469 var captionsPositionBelow$4 = "Por baixo do vÃdeo"; 19470 var sampleCaptionText$4 = "Amostra texto de legenda"; 19471 var prefSuccess$4 = "As tuas alterações foram guardadas."; 19472 var prefNoChange$4 = "Não fizeste nenhuma alteração."; 19473 var save$4 = "Guardar"; 19474 var cancel$4 = "Cancelar"; 19475 var dismissButton$4 = "Dismiss"; 19476 var windowButtonLabel$4 = "Opções de janela"; 19477 var windowMove$4 = "Mover"; 19478 var windowMoveLeft$4 = "Window moved left"; 19479 var windowMoveRight$4 = "Window moved right"; 19480 var windowMoveUp$4 = "Window moved up"; 19481 var windowMoveDown$4 = "Window moved down"; 19482 var windowMoveStopped$4 = "Window move stopped"; 19483 var transcriptControls$4 = "Transcript Window Controls"; 19484 var signControls$4 = "Sign Language Window Controls"; 19485 var windowMoveAlert$4 = "Arrasta ou usa as teclas de seta para mover a janela; Enter para parar."; 19486 var windowResize$4 = "Redimencionar"; 19487 var windowResizeHeading$4 = "Redimencionar janela"; 19488 var closeButtonLabel$4 = "Fechar"; 19489 var width$4 = "Largura"; 19490 var height$4 = "Altura"; 19491 var resultsSummary1$4 = "Pesquisaste por:"; 19492 var resultsSummary2$4 = "Encontrado %1 itens correspondentes."; 19493 var resultsSummary3$4 = "Clica no tempo associado a qualquer item para reproduzir o vÃdeo a partir daquele ponto."; 19494 var noResultsFound$4 = "Nenhum resultado encontrado."; 19495 var searchButtonLabel$4 = "Reproduzir a partir de %1"; 19496 var hour$4 = "hora"; 19497 var minute$4 = "minuto"; 19498 var second$4 = "segundo"; 19499 var hours$4 = "horas"; 19500 var minutes$4 = "minutos"; 19501 var seconds$4 = "segundos"; 19502 var vtsHeading$4 = "Video Transcript Sorter"; 19503 var vtsInstructions1$4 = "Use the Video Transcript Sorter to modify text tracks:"; 19504 var vtsInstructions2$4 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 19505 var vtsInstructions3$4 = "Modify content or start/end times (all are directly editable within the table)."; 19506 var vtsInstructions4$4 = "Add new content, such as chapters or descriptions."; 19507 var vtsInstructions5$4 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 19508 var vtsSelectLanguage$4 = "Select a language"; 19509 var vtsSave$4 = "Generate new .vtt content"; 19510 var vtsReturn$4 = "Return to Editor"; 19511 var vtsCancel$4 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 19512 var vtsRow$4 = "Row"; 19513 var vtsKind$4 = "Kind"; 19514 var vtsStart$4 = "Start"; 19515 var vtsEnd$4 = "End"; 19516 var vtsContent$4 = "Content"; 19517 var vtsActions$4 = "Actions"; 19518 var vtsNewRow$4 = "A new row %1 has been inserted."; 19519 var vtsDeletedRow$4 = "Row %1 has been deleted."; 19520 var vtsMovedRow$4 = "Row %1 has been moved %2 and is now Row %3."; 19521 var pt = { 19522 playerHeading: playerHeading$4, 19523 audioPlayer: audioPlayer$4, 19524 videoPlayer: videoPlayer$4, 19525 faster: faster$4, 19526 slower: slower$4, 19527 play: play$4, 19528 pause: pause$4, 19529 restart: restart$4, 19530 prevTrack: prevTrack$4, 19531 nextTrack: nextTrack$4, 19532 rewind: rewind$4, 19533 forward: forward$4, 19534 captions: captions$4, 19535 showCaptions: showCaptions$4, 19536 hideCaptions: hideCaptions$4, 19537 captionsOff: captionsOff$4, 19538 showTranscript: showTranscript$4, 19539 hideTranscript: hideTranscript$4, 19540 turnOnDescriptions: turnOnDescriptions$4, 19541 turnOffDescriptions: turnOffDescriptions$4, 19542 chapters: chapters$4, 19543 language: language$4, 19544 sign: sign$4, 19545 showSign: showSign$4, 19546 hideSign: hideSign$4, 19547 seekbarLabel: seekbarLabel$4, 19548 mute: mute$4, 19549 unmute: unmute$4, 19550 volume: volume$4, 19551 volumeUpDown: volumeUpDown$4, 19552 preferences: preferences$4, 19553 enterFullScreen: enterFullScreen$4, 19554 exitFullScreen: exitFullScreen$4, 19555 speed: speed$4, 19556 spacebar: spacebar$4, 19557 transcriptTitle: transcriptTitle$4, 19558 lyricsTitle: lyricsTitle$4, 19559 autoScroll: autoScroll$4, 19560 statusPlaying: statusPlaying$4, 19561 statusPaused: statusPaused$4, 19562 statusStopped: statusStopped$4, 19563 statusBuffering: statusBuffering$4, 19564 statusEnd: statusEnd$4, 19565 selectedTrack: selectedTrack$4, 19566 alertDescribedVersion: alertDescribedVersion$4, 19567 alertNonDescribedVersion: alertNonDescribedVersion$4, 19568 prefMenuCaptions: prefMenuCaptions$4, 19569 prefVoicedCaptions: prefVoicedCaptions$4, 19570 prefMenuDescriptions: prefMenuDescriptions$4, 19571 prefMenuKeyboard: prefMenuKeyboard$4, 19572 prefMenuTranscript: prefMenuTranscript$4, 19573 prefTitleCaptions: prefTitleCaptions$4, 19574 prefTitleDescriptions: prefTitleDescriptions$4, 19575 prefTitleKeyboard: prefTitleKeyboard$4, 19576 prefTitleTranscript: prefTitleTranscript$4, 19577 prefIntroDescription1: prefIntroDescription1$4, 19578 prefDescription1: prefDescription1$4, 19579 prefDescription2: prefDescription2$4, 19580 prefDescription3: prefDescription3$4, 19581 prefIntroDescriptionNone: prefIntroDescriptionNone$4, 19582 prefIntroDescription3: prefIntroDescription3$4, 19583 prefIntroDescription4: prefIntroDescription4$4, 19584 prefIntroKeyboard1: prefIntroKeyboard1$4, 19585 prefIntroKeyboard2: prefIntroKeyboard2$4, 19586 prefIntroKeyboard3: prefIntroKeyboard3$4, 19587 prefHeadingKeyboard1: prefHeadingKeyboard1$4, 19588 prefHeadingKeyboard2: prefHeadingKeyboard2$4, 19589 prefHeadingDescription: prefHeadingDescription$4, 19590 prefHeadingTextDescription: prefHeadingTextDescription$4, 19591 prefAltKey: prefAltKey$4, 19592 prefCtrlKey: prefCtrlKey$4, 19593 prefShiftKey: prefShiftKey$4, 19594 prefNoKeyShortcuts: prefNoKeyShortcuts$4, 19595 escapeKey: escapeKey$4, 19596 escapeKeyFunction: escapeKeyFunction$4, 19597 prefDescPause: prefDescPause$4, 19598 prefDescVisible: prefDescVisible$4, 19599 prefDescVoice: prefDescVoice$4, 19600 prefDescRate: prefDescRate$4, 19601 prefCaptionRate: prefCaptionRate$4, 19602 prefDescPitch: prefDescPitch$4, 19603 prefDescPitch1: prefDescPitch1$4, 19604 prefDescPitch2: prefDescPitch2$4, 19605 prefDescPitch3: prefDescPitch3$4, 19606 prefDescPitch4: prefDescPitch4$4, 19607 prefDescPitch5: prefDescPitch5$4, 19608 sampleDescriptionText: sampleDescriptionText$4, 19609 prefHighlight: prefHighlight$4, 19610 prefTabbable: prefTabbable$4, 19611 prefCaptionsFont: prefCaptionsFont$4, 19612 prefCaptionsColor: prefCaptionsColor$4, 19613 prefCaptionsBGColor: prefCaptionsBGColor$4, 19614 prefCaptionsSize: prefCaptionsSize$4, 19615 prefCaptionsOpacity: prefCaptionsOpacity$4, 19616 prefCaptionsStyle: prefCaptionsStyle$4, 19617 serif: serif$4, 19618 sans: sans$4, 19619 cursive: cursive$4, 19620 fantasy: fantasy$4, 19621 monospace: monospace$4, 19622 white: white$4, 19623 yellow: yellow$4, 19624 green: green$4, 19625 cyan: cyan$4, 19626 blue: blue$4, 19627 magenta: magenta$4, 19628 red: red$4, 19629 black: black$4, 19630 transparent: transparent$4, 19631 solid: solid$4, 19632 captionsStylePopOn: captionsStylePopOn$4, 19633 captionsStyleRollUp: captionsStyleRollUp$4, 19634 prefCaptionsPosition: prefCaptionsPosition$4, 19635 captionsPositionOverlay: captionsPositionOverlay$4, 19636 captionsPositionBelow: captionsPositionBelow$4, 19637 sampleCaptionText: sampleCaptionText$4, 19638 prefSuccess: prefSuccess$4, 19639 prefNoChange: prefNoChange$4, 19640 save: save$4, 19641 cancel: cancel$4, 19642 dismissButton: dismissButton$4, 19643 windowButtonLabel: windowButtonLabel$4, 19644 windowMove: windowMove$4, 19645 windowMoveLeft: windowMoveLeft$4, 19646 windowMoveRight: windowMoveRight$4, 19647 windowMoveUp: windowMoveUp$4, 19648 windowMoveDown: windowMoveDown$4, 19649 windowMoveStopped: windowMoveStopped$4, 19650 transcriptControls: transcriptControls$4, 19651 signControls: signControls$4, 19652 windowMoveAlert: windowMoveAlert$4, 19653 windowResize: windowResize$4, 19654 windowResizeHeading: windowResizeHeading$4, 19655 closeButtonLabel: closeButtonLabel$4, 19656 width: width$4, 19657 height: height$4, 19658 resultsSummary1: resultsSummary1$4, 19659 resultsSummary2: resultsSummary2$4, 19660 resultsSummary3: resultsSummary3$4, 19661 noResultsFound: noResultsFound$4, 19662 searchButtonLabel: searchButtonLabel$4, 19663 hour: hour$4, 19664 minute: minute$4, 19665 second: second$4, 19666 hours: hours$4, 19667 minutes: minutes$4, 19668 seconds: seconds$4, 19669 vtsHeading: vtsHeading$4, 19670 vtsInstructions1: vtsInstructions1$4, 19671 vtsInstructions2: vtsInstructions2$4, 19672 vtsInstructions3: vtsInstructions3$4, 19673 vtsInstructions4: vtsInstructions4$4, 19674 vtsInstructions5: vtsInstructions5$4, 19675 vtsSelectLanguage: vtsSelectLanguage$4, 19676 vtsSave: vtsSave$4, 19677 vtsReturn: vtsReturn$4, 19678 vtsCancel: vtsCancel$4, 19679 vtsRow: vtsRow$4, 19680 vtsKind: vtsKind$4, 19681 vtsStart: vtsStart$4, 19682 vtsEnd: vtsEnd$4, 19683 vtsContent: vtsContent$4, 19684 vtsActions: vtsActions$4, 19685 vtsNewRow: vtsNewRow$4, 19686 vtsDeletedRow: vtsDeletedRow$4, 19687 vtsMovedRow: vtsMovedRow$4 19688 }; 19689 19690 var __author__ = "Slovak translation by Radoslav ÄuraÄ (@rraddatch)"; 19691 var playerHeading$3 = "PrehrávaÄ médiÃ"; 19692 var audioPlayer$3 = "PrehrávaÄ zvuku"; 19693 var videoPlayer$3 = "PrehrávaÄ videa"; 19694 var faster$3 = "RýchlejÅ¡ie"; 19695 var slower$3 = "PomalÅ¡ie"; 19696 var play$3 = "PrehraÅ¥"; 19697 var pause$3 = "PozastaviÅ¥"; 19698 var restart$3 = "PrehraÅ¥ odznova"; 19699 var prevTrack$3 = "Predchádzajúca stopa"; 19700 var nextTrack$3 = "Nasledujúca stopa"; 19701 var rewind$3 = "PretoÄiÅ¥ dozadu"; 19702 var forward$3 = "PretoÄiÅ¥ dopredu"; 19703 var captions$3 = "Skryté titulky"; 19704 var showCaptions$3 = "ZobraziÅ¥ skryté titulky"; 19705 var hideCaptions$3 = "Vypnúť skryté titulky"; 19706 var captionsOff$3 = "Vypnúť skryté titulky"; 19707 var showTranscript$3 = "ZobraziÅ¥ prepis"; 19708 var hideTranscript$3 = "SkryÅ¥ prepis"; 19709 var turnOnDescriptions$3 = "Zapnúť audiokomentár"; 19710 var turnOffDescriptions$3 = "Vypnúť audiokomentár"; 19711 var chapters$3 = "Kapitoly"; 19712 var language$3 = "Jazyk"; 19713 var sign$3 = "Posunkový jazyk"; 19714 var showSign$3 = "ZobraziÅ¥ posunkový jazyk"; 19715 var hideSign$3 = "SkryÅ¥ posunkový jazyk"; 19716 var seekbarLabel$3 = "Äasová os"; 19717 var mute$3 = "StlmiÅ¥"; 19718 var unmute$3 = "ZruÅ¡iÅ¥ stlmenie"; 19719 var volume$3 = "HlasitosÅ¥"; 19720 var volumeUpDown$3 = "HlasitosÅ¥ hore dole"; 19721 var preferences$3 = "Nastavenia"; 19722 var enterFullScreen$3 = "PrejsÅ¥ na celú obrazovku"; 19723 var exitFullScreen$3 = "UkonÄiÅ¥ celú obrazovku"; 19724 var speed$3 = "RýchlosÅ¥"; 19725 var spacebar$3 = "medzernÃk"; 19726 var transcriptTitle$3 = "Prepis"; 19727 var lyricsTitle$3 = "Text piesne"; 19728 var autoScroll$3 = "Automatické posúvanie"; 19729 var statusPlaying$3 = "Prehráva sa"; 19730 var statusPaused$3 = "Pozastavené"; 19731 var statusStopped$3 = "Zastavené"; 19732 var statusBuffering$3 = "NaÄÃtava sa"; 19733 var statusEnd$3 = "Koniec stopy"; 19734 var selectedTrack$3 = "Vybraná stopa"; 19735 var alertDescribedVersion$3 = "PoužÃva sa verzia videa s audiokomentárom"; 19736 var alertNonDescribedVersion$3 = "PoužÃva sa verzia videa bez audiokomentára"; 19737 var prefMenuCaptions$3 = "Skryté titulky"; 19738 var prefVoicedCaptions$3 = "Hovorené skryté titulky"; 19739 var prefMenuDescriptions$3 = "Audiokomentár"; 19740 var prefMenuKeyboard$3 = "Klávesnica"; 19741 var prefMenuTranscript$3 = "Prepis"; 19742 var prefTitleCaptions$3 = "Nastavenia skrytých titulkov"; 19743 var prefTitleDescriptions$3 = "Nastavenia audiokomentára"; 19744 var prefTitleKeyboard$3 = "Nastavenia klávesnice"; 19745 var prefTitleTranscript$3 = "Nastavenia prepisu"; 19746 var prefIntroDescription1$3 = "Tento prehrávaÄ médià podporuje audiokomentár dvoma spôsobmi: "; 19747 var prefDescription1$3 = "Aktuálne video má alternatÃvnu verziu s audiokomentárom a textový audiokomentár oznamovaný ÄÃtaÄkou obrazovky."; 19748 var prefDescription2$3 = "Aktuálne video má textový audiokomentár."; 19749 var prefDescription3$3 = "Aktuálne video má alternatÃvnu verziu s audiokomentárom."; 19750 var prefIntroDescriptionNone$3 = "Aktuálne video nemá audiokomentár v žiadnom formáte."; 19751 var prefIntroDescription3$3 = "Pomocou nasledujúceho formulára nastavte svoje predvoľby týkajúce sa textového audiokomentára."; 19752 var prefIntroDescription4$3 = "Po uloženà nastavenà je možné audiokomentár zapnúť/vypnúť pomocou prÃsluÅ¡ného tlaÄidla."; 19753 var prefIntroKeyboard1$3 = "PrehrávaÄ médià na tejto webovej stránke možno ovládaÅ¥ odkiaľkoľvek na stránke pomocou klávesových skratiek (zoznam nájdete nižšie)."; 19754 var prefIntroKeyboard2$3 = "ModifikaÄné klávesy (Shift, Alt a Control) je možné priradiÅ¥ nižšie."; 19755 var prefIntroKeyboard3$3 = "POZNÃMKA: Niektoré kombinácie klávesov môžu byÅ¥ v konflikte s klávesmi použÃvanými vaÅ¡Ãm prehliadaÄom a/alebo inými softvérovými aplikáciami. Vyskúšajte rôzne kombinácie modifikaÄných klávesov, aby ste naÅ¡li tú, ktorá vám vyhovuje."; 19756 var prefHeadingKeyboard1$3 = "ModifikaÄné klávesy použÃvané pre skratky"; 19757 var prefHeadingKeyboard2$3 = "Aktuálne klávesové skratky"; 19758 var prefHeadingDescription$3 = "Audiokomentár"; 19759 var prefHeadingTextDescription$3 = "Textový audiokomentár"; 19760 var prefAltKey$3 = "Alt"; 19761 var prefCtrlKey$3 = "Control"; 19762 var prefShiftKey$3 = "Shift"; 19763 var prefNoKeyShortcuts$3 = "ZakázaÅ¥ klávesové skratky"; 19764 var escapeKey$3 = "Escape"; 19765 var escapeKeyFunction$3 = "ZatvoriÅ¥ aktuálny dialóg alebo vyskakovacie menu"; 19766 var prefDescPause$3 = "Automaticky pozastaviÅ¥ video, keÄ sa spustà audiokomentár"; 19767 var prefDescVisible$3 = "ZobraziÅ¥ audiokomentár"; 19768 var prefDescVoice$3 = "Hlas"; 19769 var prefDescRate$3 = "RýchlosÅ¥ hovoreného audiokomentára"; 19770 var prefCaptionRate$3 = "RýchlosÅ¥ hovorených skrytých titulkov"; 19771 var prefDescPitch$3 = "Výška tónu"; 19772 var prefDescPitch1$3 = "Veľmi nÃzka"; 19773 var prefDescPitch2$3 = "NÃzka"; 19774 var prefDescPitch3$3 = "Predvolená"; 19775 var prefDescPitch4$3 = "Vysoká"; 19776 var prefDescPitch5$3 = "Veľmi vysoká"; 19777 var sampleDescriptionText$3 = "Upravte nastavenia, aby ste poÄuli tento vzorový text."; 19778 var prefHighlight$3 = "ZvýrazniÅ¥ prepis poÄas prehrávania médiÃ"; 19779 var prefTabbable$3 = "PovoliÅ¥ ovládanie prepisu klávesnicou"; 19780 var prefCaptionsFont$3 = "PÃsmo"; 19781 var prefCaptionsColor$3 = "Farba textu"; 19782 var prefCaptionsBGColor$3 = "Pozadie"; 19783 var prefCaptionsSize$3 = "VeľkosÅ¥ pÃsma"; 19784 var prefCaptionsOpacity$3 = "NepriehľadnosÅ¥"; 19785 var prefCaptionsStyle$3 = "Å týl"; 19786 var serif$3 = "pätkové (serif)"; 19787 var sans$3 = "bezpätkové (sans-serif)"; 19788 var cursive$3 = "kurzÃva"; 19789 var fantasy$3 = "ozdobné"; 19790 var monospace$3 = "stála Å¡Ãrka (monospace)"; 19791 var white$3 = "biela"; 19792 var yellow$3 = "žltá"; 19793 var green$3 = "zelená"; 19794 var cyan$3 = "azúrová"; 19795 var blue$3 = "modrá"; 19796 var magenta$3 = "fialová"; 19797 var red$3 = "Äervená"; 19798 var black$3 = "Äierna"; 19799 var transparent$3 = "priehľadné"; 19800 var solid$3 = "nepriehľadné"; 19801 var captionsStylePopOn$3 = "Pop-on"; 19802 var captionsStyleRollUp$3 = "Roll-up"; 19803 var prefCaptionsPosition$3 = "PozÃcia"; 19804 var captionsPositionOverlay$3 = "Prekrytie"; 19805 var captionsPositionBelow$3 = "Pod videom"; 19806 var sampleCaptionText$3 = "Vzorový text skrytých titulkov"; 19807 var prefSuccess$3 = "VaÅ¡e zmeny boli uložené."; 19808 var prefNoChange$3 = "Neurobili ste žiadne zmeny."; 19809 var save$3 = "UložiÅ¥"; 19810 var cancel$3 = "ZruÅ¡iÅ¥"; 19811 var dismissButton$3 = "ZatvoriÅ¥"; 19812 var windowButtonLabel$3 = "Možnosti okna"; 19813 var windowMove$3 = "Presunúť"; 19814 var windowMoveLeft$3 = "Okno sa presunulo doľava"; 19815 var windowMoveRight$3 = "Okno sa presunulo doprava"; 19816 var windowMoveUp$3 = "Okno sa presunulo hore"; 19817 var windowMoveDown$3 = "Okno sa presunulo dole"; 19818 var windowMoveStopped$3 = "Presúvanie okna zastavené"; 19819 var transcriptControls$3 = "Ovládacie prvky okna prepisu"; 19820 var signControls$3 = "Ovládacie prvky okna posunkového jazyka"; 19821 var windowMoveAlert$3 = "Potiahnite alebo použite Å¡Ãpky na presun okna; stlaÄenÃm Enter zastavte"; 19822 var windowResize$3 = "ZmeniÅ¥ veľkosÅ¥"; 19823 var windowResizeHeading$3 = "ZmeniÅ¥ veľkosÅ¥ okna"; 19824 var closeButtonLabel$3 = "ZatvoriÅ¥"; 19825 var width$3 = "Å Ãrka"; 19826 var height$3 = "Výška"; 19827 var resultsSummary1$3 = "Hľadali ste:"; 19828 var resultsSummary2$3 = "Nájdených %1 zodpovedajúcich položiek."; 19829 var resultsSummary3$3 = "KliknutÃm na Äas priradený k ľubovoľnej položke prehráte video od daného bodu."; 19830 var noResultsFound$3 = "NenaÅ¡li sa žiadne výsledky."; 19831 var searchButtonLabel$3 = "PrehraÅ¥ od %1"; 19832 var hour$3 = "hodina"; 19833 var minute$3 = "minúta"; 19834 var second$3 = "sekunda"; 19835 var hours$3 = "hodiny"; 19836 var minutes$3 = "minúty"; 19837 var seconds$3 = "sekundy"; 19838 var vtsHeading$3 = "TriediÄ prepisu videa"; 19839 var vtsInstructions1$3 = "Použite TriediÄ prepisu videa na úpravu textových stôp:"; 19840 var vtsInstructions2$3 = "ZmeÅte poradie kapitol, audiokomentárov, titulkov a/alebo podtitulkov tak, aby sa zobrazovali v správnom poradà v automaticky generovanom prepise Able Player."; 19841 var vtsInstructions3$3 = "Upravte obsah alebo Äasy zaÄiatku/konca (vÅ¡etky sú priamo upraviteľné v tabuľke)."; 19842 var vtsInstructions4$3 = "Pridajte nový obsah, naprÃklad kapitoly alebo audiokomentáre."; 19843 var vtsInstructions5$3 = "Po úprave kliknite na tlaÄidlo \"UložiÅ¥ zmeny\", ÄÃm vygenerujete nový obsah pre vÅ¡etky prÃsluÅ¡né Äasované textové súbory. Nový text možno skopÃrovaÅ¥ a vložiÅ¥ do nových súborov WebVTT."; 19844 var vtsSelectLanguage$3 = "Vyberte jazyk"; 19845 var vtsSave$3 = "VygenerovaÅ¥ nový obsah .vtt"; 19846 var vtsReturn$3 = "VrátiÅ¥ sa do editora"; 19847 var vtsCancel$3 = "Ukladanie sa ruÅ¡Ã. VÅ¡etky vykonané úpravy boli v tabuľke VTS obnovené."; 19848 var vtsRow$3 = "Riadok"; 19849 var vtsKind$3 = "Typ"; 19850 var vtsStart$3 = "ZaÄiatok"; 19851 var vtsEnd$3 = "Koniec"; 19852 var vtsContent$3 = "Obsah"; 19853 var vtsActions$3 = "Akcie"; 19854 var vtsNewRow$3 = "Bol vložený nový riadok %1."; 19855 var vtsDeletedRow$3 = "Riadok %1 bol odstránený."; 19856 var vtsMovedRow$3 = "Riadok %1 bol presunutý %2 a teraz je riadkom %3."; 19857 var sk = { 19858 __author__: __author__, 19859 playerHeading: playerHeading$3, 19860 audioPlayer: audioPlayer$3, 19861 videoPlayer: videoPlayer$3, 19862 faster: faster$3, 19863 slower: slower$3, 19864 play: play$3, 19865 pause: pause$3, 19866 restart: restart$3, 19867 prevTrack: prevTrack$3, 19868 nextTrack: nextTrack$3, 19869 rewind: rewind$3, 19870 forward: forward$3, 19871 captions: captions$3, 19872 showCaptions: showCaptions$3, 19873 hideCaptions: hideCaptions$3, 19874 captionsOff: captionsOff$3, 19875 showTranscript: showTranscript$3, 19876 hideTranscript: hideTranscript$3, 19877 turnOnDescriptions: turnOnDescriptions$3, 19878 turnOffDescriptions: turnOffDescriptions$3, 19879 chapters: chapters$3, 19880 language: language$3, 19881 sign: sign$3, 19882 showSign: showSign$3, 19883 hideSign: hideSign$3, 19884 seekbarLabel: seekbarLabel$3, 19885 mute: mute$3, 19886 unmute: unmute$3, 19887 volume: volume$3, 19888 volumeUpDown: volumeUpDown$3, 19889 preferences: preferences$3, 19890 enterFullScreen: enterFullScreen$3, 19891 exitFullScreen: exitFullScreen$3, 19892 speed: speed$3, 19893 spacebar: spacebar$3, 19894 transcriptTitle: transcriptTitle$3, 19895 lyricsTitle: lyricsTitle$3, 19896 autoScroll: autoScroll$3, 19897 statusPlaying: statusPlaying$3, 19898 statusPaused: statusPaused$3, 19899 statusStopped: statusStopped$3, 19900 statusBuffering: statusBuffering$3, 19901 statusEnd: statusEnd$3, 19902 selectedTrack: selectedTrack$3, 19903 alertDescribedVersion: alertDescribedVersion$3, 19904 alertNonDescribedVersion: alertNonDescribedVersion$3, 19905 prefMenuCaptions: prefMenuCaptions$3, 19906 prefVoicedCaptions: prefVoicedCaptions$3, 19907 prefMenuDescriptions: prefMenuDescriptions$3, 19908 prefMenuKeyboard: prefMenuKeyboard$3, 19909 prefMenuTranscript: prefMenuTranscript$3, 19910 prefTitleCaptions: prefTitleCaptions$3, 19911 prefTitleDescriptions: prefTitleDescriptions$3, 19912 prefTitleKeyboard: prefTitleKeyboard$3, 19913 prefTitleTranscript: prefTitleTranscript$3, 19914 prefIntroDescription1: prefIntroDescription1$3, 19915 prefDescription1: prefDescription1$3, 19916 prefDescription2: prefDescription2$3, 19917 prefDescription3: prefDescription3$3, 19918 prefIntroDescriptionNone: prefIntroDescriptionNone$3, 19919 prefIntroDescription3: prefIntroDescription3$3, 19920 prefIntroDescription4: prefIntroDescription4$3, 19921 prefIntroKeyboard1: prefIntroKeyboard1$3, 19922 prefIntroKeyboard2: prefIntroKeyboard2$3, 19923 prefIntroKeyboard3: prefIntroKeyboard3$3, 19924 prefHeadingKeyboard1: prefHeadingKeyboard1$3, 19925 prefHeadingKeyboard2: prefHeadingKeyboard2$3, 19926 prefHeadingDescription: prefHeadingDescription$3, 19927 prefHeadingTextDescription: prefHeadingTextDescription$3, 19928 prefAltKey: prefAltKey$3, 19929 prefCtrlKey: prefCtrlKey$3, 19930 prefShiftKey: prefShiftKey$3, 19931 prefNoKeyShortcuts: prefNoKeyShortcuts$3, 19932 escapeKey: escapeKey$3, 19933 escapeKeyFunction: escapeKeyFunction$3, 19934 prefDescPause: prefDescPause$3, 19935 prefDescVisible: prefDescVisible$3, 19936 prefDescVoice: prefDescVoice$3, 19937 prefDescRate: prefDescRate$3, 19938 prefCaptionRate: prefCaptionRate$3, 19939 prefDescPitch: prefDescPitch$3, 19940 prefDescPitch1: prefDescPitch1$3, 19941 prefDescPitch2: prefDescPitch2$3, 19942 prefDescPitch3: prefDescPitch3$3, 19943 prefDescPitch4: prefDescPitch4$3, 19944 prefDescPitch5: prefDescPitch5$3, 19945 sampleDescriptionText: sampleDescriptionText$3, 19946 prefHighlight: prefHighlight$3, 19947 prefTabbable: prefTabbable$3, 19948 prefCaptionsFont: prefCaptionsFont$3, 19949 prefCaptionsColor: prefCaptionsColor$3, 19950 prefCaptionsBGColor: prefCaptionsBGColor$3, 19951 prefCaptionsSize: prefCaptionsSize$3, 19952 prefCaptionsOpacity: prefCaptionsOpacity$3, 19953 prefCaptionsStyle: prefCaptionsStyle$3, 19954 serif: serif$3, 19955 sans: sans$3, 19956 cursive: cursive$3, 19957 fantasy: fantasy$3, 19958 monospace: monospace$3, 19959 white: white$3, 19960 yellow: yellow$3, 19961 green: green$3, 19962 cyan: cyan$3, 19963 blue: blue$3, 19964 magenta: magenta$3, 19965 red: red$3, 19966 black: black$3, 19967 transparent: transparent$3, 19968 solid: solid$3, 19969 captionsStylePopOn: captionsStylePopOn$3, 19970 captionsStyleRollUp: captionsStyleRollUp$3, 19971 prefCaptionsPosition: prefCaptionsPosition$3, 19972 captionsPositionOverlay: captionsPositionOverlay$3, 19973 captionsPositionBelow: captionsPositionBelow$3, 19974 sampleCaptionText: sampleCaptionText$3, 19975 prefSuccess: prefSuccess$3, 19976 prefNoChange: prefNoChange$3, 19977 save: save$3, 19978 cancel: cancel$3, 19979 dismissButton: dismissButton$3, 19980 windowButtonLabel: windowButtonLabel$3, 19981 windowMove: windowMove$3, 19982 windowMoveLeft: windowMoveLeft$3, 19983 windowMoveRight: windowMoveRight$3, 19984 windowMoveUp: windowMoveUp$3, 19985 windowMoveDown: windowMoveDown$3, 19986 windowMoveStopped: windowMoveStopped$3, 19987 transcriptControls: transcriptControls$3, 19988 signControls: signControls$3, 19989 windowMoveAlert: windowMoveAlert$3, 19990 windowResize: windowResize$3, 19991 windowResizeHeading: windowResizeHeading$3, 19992 closeButtonLabel: closeButtonLabel$3, 19993 width: width$3, 19994 height: height$3, 19995 resultsSummary1: resultsSummary1$3, 19996 resultsSummary2: resultsSummary2$3, 19997 resultsSummary3: resultsSummary3$3, 19998 noResultsFound: noResultsFound$3, 19999 searchButtonLabel: searchButtonLabel$3, 20000 hour: hour$3, 20001 minute: minute$3, 20002 second: second$3, 20003 hours: hours$3, 20004 minutes: minutes$3, 20005 seconds: seconds$3, 20006 vtsHeading: vtsHeading$3, 20007 vtsInstructions1: vtsInstructions1$3, 20008 vtsInstructions2: vtsInstructions2$3, 20009 vtsInstructions3: vtsInstructions3$3, 20010 vtsInstructions4: vtsInstructions4$3, 20011 vtsInstructions5: vtsInstructions5$3, 20012 vtsSelectLanguage: vtsSelectLanguage$3, 20013 vtsSave: vtsSave$3, 20014 vtsReturn: vtsReturn$3, 20015 vtsCancel: vtsCancel$3, 20016 vtsRow: vtsRow$3, 20017 vtsKind: vtsKind$3, 20018 vtsStart: vtsStart$3, 20019 vtsEnd: vtsEnd$3, 20020 vtsContent: vtsContent$3, 20021 vtsActions: vtsActions$3, 20022 vtsNewRow: vtsNewRow$3, 20023 vtsDeletedRow: vtsDeletedRow$3, 20024 vtsMovedRow: vtsMovedRow$3 20025 }; 20026 20027 var playerHeading$2 = "Mediaspelare"; 20028 var audioPlayer$2 = "Audio player"; 20029 var videoPlayer$2 = "Video player"; 20030 var faster$2 = "Snabbare"; 20031 var slower$2 = "LÃ¥ngsammare"; 20032 var play$2 = "Spela upp"; 20033 var pause$2 = "Pausa"; 20034 var restart$2 = "Starta om"; 20035 var prevTrack$2 = "FöregÃ¥ende spÃ¥r"; 20036 var nextTrack$2 = "Nästa spÃ¥r"; 20037 var rewind$2 = "Spola tillbaka"; 20038 var forward$2 = "Spola framÃ¥t"; 20039 var captions$2 = "Undertexter"; 20040 var showCaptions$2 = "Visa undertexter"; 20041 var hideCaptions$2 = "Göm undertexter"; 20042 var captionsOff$2 = "Stäng av undertexter"; 20043 var showTranscript$2 = "Visa transkript"; 20044 var hideTranscript$2 = "Göm transkript"; 20045 var turnOnDescriptions$2 = "Sätt pÃ¥ syntolkning"; 20046 var turnOffDescriptions$2 = "Stäng av syntolkning"; 20047 var chapters$2 = "Kapitel"; 20048 var language$2 = "SprÃ¥k"; 20049 var sign$2 = "TeckensprÃ¥k"; 20050 var showSign$2 = "Visa teckensprÃ¥k"; 20051 var hideSign$2 = "Göm teckensprÃ¥k"; 20052 var seekbarLabel$2 = "tidslinje"; 20053 var mute$2 = "SlÃ¥ av ljud"; 20054 var unmute$2 = "SlÃ¥ pÃ¥ ljud"; 20055 var volume$2 = "Volym"; 20056 var volumeUpDown$2 = "Volym up ner"; 20057 var preferences$2 = "Preferenser"; 20058 var enterFullScreen$2 = "Visa i fullskärmsläge"; 20059 var exitFullScreen$2 = "GÃ¥ ur fullskärmsläge"; 20060 var speed$2 = "Hastighet"; 20061 var spacebar$2 = "mellanslag"; 20062 var transcriptTitle$2 = "Transkript"; 20063 var lyricsTitle$2 = "Lyrik"; 20064 var autoScroll$2 = "Auto-scrolla"; 20065 var statusPlaying$2 = "Spelar upp"; 20066 var statusPaused$2 = "Pausad"; 20067 var statusStopped$2 = "Stoppad"; 20068 var statusBuffering$2 = "Buffrar"; 20069 var statusEnd$2 = "Slut pÃ¥ spÃ¥ret"; 20070 var selectedTrack$2 = "Valt spÃ¥r"; 20071 var alertDescribedVersion$2 = "Använder syntolkad version av denna video"; 20072 var alertNonDescribedVersion$2 = "Använder ej syntolkad version av denna video"; 20073 var prefMenuCaptions$2 = "Undertexter"; 20074 var prefVoicedCaptions$2 = "Spoken Captions"; 20075 var prefMenuDescriptions$2 = "Syntolkning"; 20076 var prefMenuKeyboard$2 = "Tangentbord"; 20077 var prefMenuTranscript$2 = "Transkript"; 20078 var prefTitleCaptions$2 = "Undertextprefenser"; 20079 var prefTitleDescriptions$2 = "Syntolkningspreferenser"; 20080 var prefTitleKeyboard$2 = "Tangentbordspreferenser"; 20081 var prefTitleTranscript$2 = "Transkriptpreferenser"; 20082 var prefIntroDescription1$2 = "Denna mediaspelare stöder syntolkning pÃ¥ tvÃ¥ sätt: "; 20083 var prefDescription1$2 = "Följande video har en alternativ syntolkad version, textbaserad syntolkning."; 20084 var prefDescription2$2 = "Följande video har alternativ syntolkad version av videon."; 20085 var prefDescription3$2 = "Följande video har textbaserad syntolkning, uppläst av skärmläsare."; 20086 var prefIntroDescriptionNone$2 = "Nuvarande video har ingen syntolkning i nÃ¥got format."; 20087 var prefIntroDescription3$2 = "Använd följande formulär för att ställa in dina prefenser gällande textbaserad syntolkning."; 20088 var prefIntroDescription4$2 = "Efter du sparar dina prefenser, kan syntolkning växlas av och pÃ¥ med syntolkningsknappen."; 20089 var prefIntroKeyboard1$2 = "Mediaspelaren pÃ¥ denna webbsida kan styras frÃ¥n varsomhelst pÃ¥ sidan med hjälp av tangentbordsgenvägar (se nedan för en lista)."; 20090 var prefIntroKeyboard2$2 = "Metatangenter (Shift, Alt, och Ctrl) kan tilldelas nedan."; 20091 var prefIntroKeyboard3$2 = "NOTERA: Vissa tangentkombinationer kan vara i konflikt med din webbläsare eller andra mjukvarors inställningar. Prova olika kombinationer av metatangeter för att hitta en som fungerar för dig."; 20092 var prefHeadingKeyboard1$2 = "Metatangenter använda till genvägar"; 20093 var prefHeadingKeyboard2$2 = "Nuvarande tangentbordsgenvägar"; 20094 var prefHeadingDescription$2 = "Syntolkning"; 20095 var prefHeadingTextDescription$2 = "Textbaserad syntolkning"; 20096 var prefAltKey$2 = "Alt"; 20097 var prefCtrlKey$2 = "Ctrl"; 20098 var prefShiftKey$2 = "Shift"; 20099 var prefNoKeyShortcuts$2 = "Disable keyboard shortcuts"; 20100 var escapeKey$2 = "Escape"; 20101 var escapeKeyFunction$2 = "Stäng nuvarande dialog eller popupmeny"; 20102 var prefDescPause$2 = "Pausa automatiskt video när syntolkning startar"; 20103 var prefDescVisible$2 = "Synliggör syntolkning"; 20104 var prefDescVoice$2 = "Röst"; 20105 var prefDescRate$2 = "Spoken Description Rate"; 20106 var prefCaptionRate$2 = "Spoken Caption Rate"; 20107 var prefDescPitch$2 = "Tonhöjd"; 20108 var prefDescPitch1$2 = "Mycket lÃ¥gt"; 20109 var prefDescPitch2$2 = "LÃ¥g"; 20110 var prefDescPitch3$2 = "Normal"; 20111 var prefDescPitch4$2 = "Högt"; 20112 var prefDescPitch5$2 = "Mycket högt"; 20113 var sampleDescriptionText$2 = "Justera inställningarna för att höra denna exempeltext."; 20114 var prefHighlight$2 = "Markera det som läses upp i transkriptet"; 20115 var prefTabbable$2 = "Tangentbordsaktiverat transkript"; 20116 var prefCaptionsFont$2 = "Typsnitt"; 20117 var prefCaptionsColor$2 = "Textfärg"; 20118 var prefCaptionsBGColor$2 = "Bakgrund"; 20119 var prefCaptionsSize$2 = "Textstorlek"; 20120 var prefCaptionsOpacity$2 = "Ogenomskinlighet"; 20121 var prefCaptionsStyle$2 = "Stil"; 20122 var serif$2 = "serif"; 20123 var sans$2 = "sans-serif"; 20124 var cursive$2 = "kursiv"; 20125 var fantasy$2 = "fantasi"; 20126 var monospace$2 = "monospace"; 20127 var white$2 = "vit"; 20128 var yellow$2 = "gul"; 20129 var green$2 = "grön"; 20130 var cyan$2 = "ljusblÃ¥"; 20131 var blue$2 = "blÃ¥"; 20132 var magenta$2 = "lila"; 20133 var red$2 = "röd"; 20134 var black$2 = "svart"; 20135 var transparent$2 = "genomskinligt"; 20136 var solid$2 = "massiv"; 20137 var captionsStylePopOn$2 = "Dyker upp"; 20138 var captionsStyleRollUp$2 = "Rullar upp"; 20139 var prefCaptionsPosition$2 = "Position"; 20140 var captionsPositionOverlay$2 = "Täck över videon"; 20141 var captionsPositionBelow$2 = "Under videon"; 20142 var sampleCaptionText$2 = "Exempel pÃ¥ undertext"; 20143 var prefSuccess$2 = "Dina ändringar har blivit sparade."; 20144 var prefNoChange$2 = "Du gjorde inga ändringar."; 20145 var save$2 = "Spara"; 20146 var cancel$2 = "Avbryt"; 20147 var dismissButton$2 = "Dismiss"; 20148 var windowButtonLabel$2 = "Fönsteralternativ"; 20149 var windowMove$2 = "Flytta"; 20150 var windowMoveLeft$2 = "Window moved left"; 20151 var windowMoveRight$2 = "Window moved right"; 20152 var windowMoveUp$2 = "Window moved up"; 20153 var windowMoveDown$2 = "Window moved down"; 20154 var windowMoveStopped$2 = "Window move stopped"; 20155 var transcriptControls$2 = "Transcript Window Controls"; 20156 var signControls$2 = "Sign Language Window Controls"; 20157 var windowMoveAlert$2 = "Drag eller använd piltangenter för att flytta fönstret; Enter för att sluta"; 20158 var windowResize$2 = "Ãndra storlek"; 20159 var windowResizeHeading$2 = "Ãndra fönsterstorlek"; 20160 var closeButtonLabel$2 = "Stäng"; 20161 var width$2 = "Bredd"; 20162 var height$2 = "Höjd"; 20163 var resultsSummary1$2 = "Du sökte efter:"; 20164 var resultsSummary2$2 = "Hittade %1 träffar."; 20165 var resultsSummary3$2 = "Klicka pÃ¥ tiden för en punkt för att spela video frÃ¥n den tiden."; 20166 var noResultsFound$2 = "Inga träffar hittade."; 20167 var searchButtonLabel$2 = "Spela frÃ¥n %1"; 20168 var hour$2 = "timme"; 20169 var minute$2 = "minut"; 20170 var second$2 = "sekund"; 20171 var hours$2 = "timmar"; 20172 var minutes$2 = "minuter"; 20173 var seconds$2 = "sekunder"; 20174 var vtsHeading$2 = "Video Transcript Sorter"; 20175 var vtsInstructions1$2 = "Use the Video Transcript Sorter to modify text tracks:"; 20176 var vtsInstructions2$2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20177 var vtsInstructions3$2 = "Modify content or start/end times (all are directly editable within the table)."; 20178 var vtsInstructions4$2 = "Add new content, such as chapters or descriptions."; 20179 var vtsInstructions5$2 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 20180 var vtsSelectLanguage$2 = "Select a language"; 20181 var vtsSave$2 = "Generate new .vtt content"; 20182 var vtsReturn$2 = "Return to Editor"; 20183 var vtsCancel$2 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20184 var vtsRow$2 = "Row"; 20185 var vtsKind$2 = "Kind"; 20186 var vtsStart$2 = "Start"; 20187 var vtsEnd$2 = "End"; 20188 var vtsContent$2 = "Content"; 20189 var vtsActions$2 = "Actions"; 20190 var vtsNewRow$2 = "A new row %1 has been inserted."; 20191 var vtsDeletedRow$2 = "Row %1 has been deleted."; 20192 var vtsMovedRow$2 = "Row %1 has been moved %2 and is now Row %3."; 20193 var sv = { 20194 playerHeading: playerHeading$2, 20195 audioPlayer: audioPlayer$2, 20196 videoPlayer: videoPlayer$2, 20197 faster: faster$2, 20198 slower: slower$2, 20199 play: play$2, 20200 pause: pause$2, 20201 restart: restart$2, 20202 prevTrack: prevTrack$2, 20203 nextTrack: nextTrack$2, 20204 rewind: rewind$2, 20205 forward: forward$2, 20206 captions: captions$2, 20207 showCaptions: showCaptions$2, 20208 hideCaptions: hideCaptions$2, 20209 captionsOff: captionsOff$2, 20210 showTranscript: showTranscript$2, 20211 hideTranscript: hideTranscript$2, 20212 turnOnDescriptions: turnOnDescriptions$2, 20213 turnOffDescriptions: turnOffDescriptions$2, 20214 chapters: chapters$2, 20215 language: language$2, 20216 sign: sign$2, 20217 showSign: showSign$2, 20218 hideSign: hideSign$2, 20219 seekbarLabel: seekbarLabel$2, 20220 mute: mute$2, 20221 unmute: unmute$2, 20222 volume: volume$2, 20223 volumeUpDown: volumeUpDown$2, 20224 preferences: preferences$2, 20225 enterFullScreen: enterFullScreen$2, 20226 exitFullScreen: exitFullScreen$2, 20227 speed: speed$2, 20228 spacebar: spacebar$2, 20229 transcriptTitle: transcriptTitle$2, 20230 lyricsTitle: lyricsTitle$2, 20231 autoScroll: autoScroll$2, 20232 statusPlaying: statusPlaying$2, 20233 statusPaused: statusPaused$2, 20234 statusStopped: statusStopped$2, 20235 statusBuffering: statusBuffering$2, 20236 statusEnd: statusEnd$2, 20237 selectedTrack: selectedTrack$2, 20238 alertDescribedVersion: alertDescribedVersion$2, 20239 alertNonDescribedVersion: alertNonDescribedVersion$2, 20240 prefMenuCaptions: prefMenuCaptions$2, 20241 prefVoicedCaptions: prefVoicedCaptions$2, 20242 prefMenuDescriptions: prefMenuDescriptions$2, 20243 prefMenuKeyboard: prefMenuKeyboard$2, 20244 prefMenuTranscript: prefMenuTranscript$2, 20245 prefTitleCaptions: prefTitleCaptions$2, 20246 prefTitleDescriptions: prefTitleDescriptions$2, 20247 prefTitleKeyboard: prefTitleKeyboard$2, 20248 prefTitleTranscript: prefTitleTranscript$2, 20249 prefIntroDescription1: prefIntroDescription1$2, 20250 prefDescription1: prefDescription1$2, 20251 prefDescription2: prefDescription2$2, 20252 prefDescription3: prefDescription3$2, 20253 prefIntroDescriptionNone: prefIntroDescriptionNone$2, 20254 prefIntroDescription3: prefIntroDescription3$2, 20255 prefIntroDescription4: prefIntroDescription4$2, 20256 prefIntroKeyboard1: prefIntroKeyboard1$2, 20257 prefIntroKeyboard2: prefIntroKeyboard2$2, 20258 prefIntroKeyboard3: prefIntroKeyboard3$2, 20259 prefHeadingKeyboard1: prefHeadingKeyboard1$2, 20260 prefHeadingKeyboard2: prefHeadingKeyboard2$2, 20261 prefHeadingDescription: prefHeadingDescription$2, 20262 prefHeadingTextDescription: prefHeadingTextDescription$2, 20263 prefAltKey: prefAltKey$2, 20264 prefCtrlKey: prefCtrlKey$2, 20265 prefShiftKey: prefShiftKey$2, 20266 prefNoKeyShortcuts: prefNoKeyShortcuts$2, 20267 escapeKey: escapeKey$2, 20268 escapeKeyFunction: escapeKeyFunction$2, 20269 prefDescPause: prefDescPause$2, 20270 prefDescVisible: prefDescVisible$2, 20271 prefDescVoice: prefDescVoice$2, 20272 prefDescRate: prefDescRate$2, 20273 prefCaptionRate: prefCaptionRate$2, 20274 prefDescPitch: prefDescPitch$2, 20275 prefDescPitch1: prefDescPitch1$2, 20276 prefDescPitch2: prefDescPitch2$2, 20277 prefDescPitch3: prefDescPitch3$2, 20278 prefDescPitch4: prefDescPitch4$2, 20279 prefDescPitch5: prefDescPitch5$2, 20280 sampleDescriptionText: sampleDescriptionText$2, 20281 prefHighlight: prefHighlight$2, 20282 prefTabbable: prefTabbable$2, 20283 prefCaptionsFont: prefCaptionsFont$2, 20284 prefCaptionsColor: prefCaptionsColor$2, 20285 prefCaptionsBGColor: prefCaptionsBGColor$2, 20286 prefCaptionsSize: prefCaptionsSize$2, 20287 prefCaptionsOpacity: prefCaptionsOpacity$2, 20288 prefCaptionsStyle: prefCaptionsStyle$2, 20289 serif: serif$2, 20290 sans: sans$2, 20291 cursive: cursive$2, 20292 fantasy: fantasy$2, 20293 monospace: monospace$2, 20294 white: white$2, 20295 yellow: yellow$2, 20296 green: green$2, 20297 cyan: cyan$2, 20298 blue: blue$2, 20299 magenta: magenta$2, 20300 red: red$2, 20301 black: black$2, 20302 transparent: transparent$2, 20303 solid: solid$2, 20304 captionsStylePopOn: captionsStylePopOn$2, 20305 captionsStyleRollUp: captionsStyleRollUp$2, 20306 prefCaptionsPosition: prefCaptionsPosition$2, 20307 captionsPositionOverlay: captionsPositionOverlay$2, 20308 captionsPositionBelow: captionsPositionBelow$2, 20309 sampleCaptionText: sampleCaptionText$2, 20310 prefSuccess: prefSuccess$2, 20311 prefNoChange: prefNoChange$2, 20312 save: save$2, 20313 cancel: cancel$2, 20314 dismissButton: dismissButton$2, 20315 windowButtonLabel: windowButtonLabel$2, 20316 windowMove: windowMove$2, 20317 windowMoveLeft: windowMoveLeft$2, 20318 windowMoveRight: windowMoveRight$2, 20319 windowMoveUp: windowMoveUp$2, 20320 windowMoveDown: windowMoveDown$2, 20321 windowMoveStopped: windowMoveStopped$2, 20322 transcriptControls: transcriptControls$2, 20323 signControls: signControls$2, 20324 windowMoveAlert: windowMoveAlert$2, 20325 windowResize: windowResize$2, 20326 windowResizeHeading: windowResizeHeading$2, 20327 closeButtonLabel: closeButtonLabel$2, 20328 width: width$2, 20329 height: height$2, 20330 resultsSummary1: resultsSummary1$2, 20331 resultsSummary2: resultsSummary2$2, 20332 resultsSummary3: resultsSummary3$2, 20333 noResultsFound: noResultsFound$2, 20334 searchButtonLabel: searchButtonLabel$2, 20335 hour: hour$2, 20336 minute: minute$2, 20337 second: second$2, 20338 hours: hours$2, 20339 minutes: minutes$2, 20340 seconds: seconds$2, 20341 vtsHeading: vtsHeading$2, 20342 vtsInstructions1: vtsInstructions1$2, 20343 vtsInstructions2: vtsInstructions2$2, 20344 vtsInstructions3: vtsInstructions3$2, 20345 vtsInstructions4: vtsInstructions4$2, 20346 vtsInstructions5: vtsInstructions5$2, 20347 vtsSelectLanguage: vtsSelectLanguage$2, 20348 vtsSave: vtsSave$2, 20349 vtsReturn: vtsReturn$2, 20350 vtsCancel: vtsCancel$2, 20351 vtsRow: vtsRow$2, 20352 vtsKind: vtsKind$2, 20353 vtsStart: vtsStart$2, 20354 vtsEnd: vtsEnd$2, 20355 vtsContent: vtsContent$2, 20356 vtsActions: vtsActions$2, 20357 vtsNewRow: vtsNewRow$2, 20358 vtsDeletedRow: vtsDeletedRow$2, 20359 vtsMovedRow: vtsMovedRow$2 20360 }; 20361 20362 var playerHeading$1 = "Medya Oynatıcı"; 20363 var audioPlayer$1 = "Audio player"; 20364 var videoPlayer$1 = "Video player"; 20365 var faster$1 = "Hızlandır"; 20366 var slower$1 = "YavaÅlat"; 20367 var play$1 = "Oynat"; 20368 var pause$1 = "Duraklat"; 20369 var restart$1 = "Yeniden Oynat"; 20370 var prevTrack$1 = "Ãnceki Parça"; 20371 var nextTrack$1 = "Gelecek Parça"; 20372 var rewind$1 = "Geri Sar"; 20373 var forward$1 = "İleri Sar"; 20374 var captions$1 = "Altyazılar"; 20375 var showCaptions$1 = "Altyazıları Göster"; 20376 var hideCaptions$1 = "Altyazıları Gizle"; 20377 var captionsOff$1 = "Altyazıkarı Kapat"; 20378 var showTranscript$1 = "Belgeyi Göster"; 20379 var hideTranscript$1 = "HBelgeyi Gizle"; 20380 var turnOnDescriptions$1 = "Açıklamaları Aç"; 20381 var turnOffDescriptions$1 = "Açıklamaları Kapa"; 20382 var chapters$1 = "Bölümler"; 20383 var language$1 = "Dil"; 20384 var sign$1 = "İÅaret Dili"; 20385 var showSign$1 = "İÅaret Dilini Göster"; 20386 var hideSign$1 = "İÅaret Dilini Gizle"; 20387 var seekbarLabel$1 = "Zaman Cetveli"; 20388 var mute$1 = "Sessize Al"; 20389 var unmute$1 = "Sesi Aç"; 20390 var volume$1 = "Ses Ayarı"; 20391 var volumeUpDown$1 = "Sesi yükselt veya alçalt"; 20392 var preferences$1 = "Seçenekler"; 20393 var enterFullScreen$1 = "Tam Ekranı EtkinleÅtir"; 20394 var exitFullScreen$1 = "Tam Ekranı Kapa"; 20395 var speed$1 = "Hız"; 20396 var spacebar$1 = "boÅluk tuÅu"; 20397 var transcriptTitle$1 = "Belge"; 20398 var lyricsTitle$1 = "Sözler"; 20399 var autoScroll$1 = "Otomatik Kaydırma"; 20400 var statusPlaying$1 = "Yürütülüyor"; 20401 var statusPaused$1 = "Duraklat"; 20402 var statusStopped$1 = "Durdur"; 20403 var statusBuffering$1 = "ArabelleÄe Alınıyor"; 20404 var statusEnd$1 = "Parça sonlandı"; 20405 var selectedTrack$1 = "Seçilen Parça"; 20406 var alertDescribedVersion$1 = "Tanımlı video versiyonu için ses kullanılıyor"; 20407 var alertNonDescribedVersion$1 = "TanımlanmamıŠvideo versiyonu için kullanılıyor"; 20408 var prefMenuCaptions$1 = "Altyazılar"; 20409 var prefVoicedCaptions$1 = "Spoken Captions"; 20410 var prefMenuDescriptions$1 = "Açıklamalar"; 20411 var prefMenuKeyboard$1 = "Klavye"; 20412 var prefMenuTranscript$1 = "Belgeler"; 20413 var prefTitleCaptions$1 = "Altyazı Seçenekleri"; 20414 var prefTitleDescriptions$1 = "Ses Açıklama Ayarları"; 20415 var prefTitleKeyboard$1 = "Klavye Tercihleri"; 20416 var prefTitleTranscript$1 = "Belge Tercihleri"; 20417 var prefIntroDescription1$1 = "Bu video oynatıcı iki farklı ses açıklaması destekler: "; 20418 var prefDescription1$1 = "Åu anki video'da mevcut alternatif tanımlı versiyon, metin tabanlı açıklama."; 20419 var prefDescription2$1 = "Åu anki video'da mevcut Video versiyonu için tanımlı alternatifler."; 20420 var prefDescription3$1 = "Åu anki video'da mevcut metin tabanlı açıklama, ekran okuyucusu tarafından duyurulan."; 20421 var prefIntroDescriptionNone$1 = "Åu anki video'da her iki format için de ses açıklaması mevcut deÄil."; 20422 var prefIntroDescription3$1 = "Ses açıklamalarını tercihlerinle iliÅkilendirmek için belirtilen biçimi kullan."; 20423 var prefIntroDescription4$1 = "Tercihlerini kaydettikten sonra, ses açıklamasını Açıklama butonu aracılıÄıyla açıp kapatabilirsin."; 20424 var prefIntroKeyboard1$1 = "Bu web sayfasındaki medya oynatıcıyı, klavye kısayolları yardımıyla, sayfanın herhangi bir kısmından da yönetebilirsin (Liste için AÅaÄıyı İncele)."; 20425 var prefIntroKeyboard2$1 = "Niteleme TuÅları (Shift, Alt, ve Control) aÅaÄınan atanabilir."; 20426 var prefIntroKeyboard3$1 = "NOT: Bazı tuÅ kombinasyonları, tarayıcında veya farklı bir programda kullandıÄın tuÅlarla çakıÅabilir. Uygun niteleme tuÅlarını oluÅturmak için farklı tuÅ kombinasyonları kullanmayı dene."; 20427 var prefHeadingKeyboard1$1 = "Niteleme TuÅları kısayollar için kullanılır"; 20428 var prefHeadingKeyboard2$1 = "Åu anki klavye kısayolları"; 20429 var prefHeadingDescription$1 = "Ses açıklaması"; 20430 var prefHeadingTextDescription$1 = "Metin tabanlı ses açıklaması"; 20431 var prefAltKey$1 = "Alt"; 20432 var prefCtrlKey$1 = "Control"; 20433 var prefShiftKey$1 = "Shift"; 20434 var prefNoKeyShortcuts$1 = "Disable keyboard shortcuts"; 20435 var escapeKey$1 = "Escape"; 20436 var escapeKeyFunction$1 = "Åu anki diyalogu veya açılır menüyü kapa"; 20437 var prefDescPause$1 = "Açıklama baÅladıÄında video'yu otomatik olarak durdur"; 20438 var prefDescVisible$1 = "Açıklamayı görünür yap"; 20439 var prefDescVoice$1 = "Voice"; 20440 var prefDescRate$1 = "Spoken Description Rate"; 20441 var prefCaptionRate$1 = "Spoken Caption Rate"; 20442 var prefDescPitch$1 = "Pitch"; 20443 var prefDescPitch1$1 = "Very low"; 20444 var prefDescPitch2$1 = "Low"; 20445 var prefDescPitch3$1 = "Default"; 20446 var prefDescPitch4$1 = "High"; 20447 var prefDescPitch5$1 = "Very high"; 20448 var sampleDescriptionText$1 = "Adjust settings to hear this sample text."; 20449 var prefHighlight$1 = "Medya'daki belgeyi öne çıkar"; 20450 var prefTabbable$1 = "Klavye ile aktif edilen belge"; 20451 var prefCaptionsFont$1 = "Yazı Tipi"; 20452 var prefCaptionsColor$1 = "Metin Rengi"; 20453 var prefCaptionsBGColor$1 = "Arkaplan"; 20454 var prefCaptionsSize$1 = "Yazı Boyutu"; 20455 var prefCaptionsOpacity$1 = "Opasite"; 20456 var prefCaptionsStyle$1 = "Stil"; 20457 var serif$1 = "serif"; 20458 var sans$1 = "sans-serif"; 20459 var cursive$1 = "el yazısı"; 20460 var fantasy$1 = "fantezi"; 20461 var monospace$1 = "tek hacimli"; 20462 var white$1 = "beyaz"; 20463 var yellow$1 = "sarı"; 20464 var green$1 = "yeÅik"; 20465 var cyan$1 = "açık mavi"; 20466 var blue$1 = "mavi"; 20467 var magenta$1 = "mor"; 20468 var red$1 = "kırmızı"; 20469 var black$1 = "siyah"; 20470 var transparent$1 = "transparan"; 20471 var solid$1 = "düz renk"; 20472 var captionsStylePopOn$1 = "Pop-on"; 20473 var captionsStyleRollUp$1 = "Roll-up"; 20474 var prefCaptionsPosition$1 = "Pozisyon"; 20475 var captionsPositionOverlay$1 = "Kaplama"; 20476 var captionsPositionBelow$1 = "Video'nun altında"; 20477 var sampleCaptionText$1 = "Ãrnek altyaı metni"; 20478 var prefSuccess$1 = "DeÄiÅikliklerin kaydedildi."; 20479 var prefNoChange$1 = "Hiçbir deÄiÅiklik yapmadın."; 20480 var save$1 = "Kaydet"; 20481 var cancel$1 = "İptal Et"; 20482 var dismissButton$1 = "Dismiss"; 20483 var windowButtonLabel$1 = "Pencere Seçenekleri"; 20484 var windowMove$1 = "Hareket"; 20485 var windowMoveLeft$1 = "Window moved left"; 20486 var windowMoveRight$1 = "Window moved right"; 20487 var windowMoveUp$1 = "Window moved up"; 20488 var windowMoveDown$1 = "Window moved down"; 20489 var windowMoveStopped$1 = "Window move stopped"; 20490 var transcriptControls$1 = "Transcript Window Controls"; 20491 var signControls$1 = "Sign Language Window Controls"; 20492 var windowMoveAlert$1 = "Pencereyi hareket ettirmek için mouse'unu sürü veya ok tuÅlarını kullan; Durdurmak için Enter'a bas"; 20493 var windowResize$1 = "Yeniden Boyutlandır"; 20494 var windowResizeHeading$1 = "Pencereyi yeniden Boyutlandır"; 20495 var closeButtonLabel$1 = "Kapat"; 20496 var width$1 = "En"; 20497 var height$1 = "Boy"; 20498 var resultsSummary1$1 = "You searched for:"; 20499 var resultsSummary2$1 = "Found %1 matching items."; 20500 var resultsSummary3$1 = "Click the time associated with any item to play the video from that point."; 20501 var noResultsFound$1 = "No results found."; 20502 var searchButtonLabel$1 = "Play at %1"; 20503 var hour$1 = "hour"; 20504 var minute$1 = "minute"; 20505 var second$1 = "second"; 20506 var hours$1 = "hours"; 20507 var minutes$1 = "minutes"; 20508 var seconds$1 = "seconds"; 20509 var vtsHeading$1 = "Video Transcript Sorter"; 20510 var vtsInstructions1$1 = "Use the Video Transcript Sorter to modify text tracks:"; 20511 var vtsInstructions2$1 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20512 var vtsInstructions3$1 = "Modify content or start/end times (all are directly editable within the table)."; 20513 var vtsInstructions4$1 = "Add new content, such as chapters or descriptions."; 20514 var vtsInstructions5$1 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 20515 var vtsSelectLanguage$1 = "Select a language"; 20516 var vtsSave$1 = "Generate new .vtt content"; 20517 var vtsReturn$1 = "Return to Editor"; 20518 var vtsCancel$1 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20519 var vtsRow$1 = "Row"; 20520 var vtsKind$1 = "Kind"; 20521 var vtsStart$1 = "Start"; 20522 var vtsEnd$1 = "End"; 20523 var vtsContent$1 = "Content"; 20524 var vtsActions$1 = "Actions"; 20525 var vtsNewRow$1 = "A new row %1 has been inserted."; 20526 var vtsDeletedRow$1 = "Row %1 has been deleted."; 20527 var vtsMovedRow$1 = "Row %1 has been moved %2 and is now Row %3."; 20528 var tr = { 20529 playerHeading: playerHeading$1, 20530 audioPlayer: audioPlayer$1, 20531 videoPlayer: videoPlayer$1, 20532 faster: faster$1, 20533 slower: slower$1, 20534 play: play$1, 20535 pause: pause$1, 20536 restart: restart$1, 20537 prevTrack: prevTrack$1, 20538 nextTrack: nextTrack$1, 20539 rewind: rewind$1, 20540 forward: forward$1, 20541 captions: captions$1, 20542 showCaptions: showCaptions$1, 20543 hideCaptions: hideCaptions$1, 20544 captionsOff: captionsOff$1, 20545 showTranscript: showTranscript$1, 20546 hideTranscript: hideTranscript$1, 20547 turnOnDescriptions: turnOnDescriptions$1, 20548 turnOffDescriptions: turnOffDescriptions$1, 20549 chapters: chapters$1, 20550 language: language$1, 20551 sign: sign$1, 20552 showSign: showSign$1, 20553 hideSign: hideSign$1, 20554 seekbarLabel: seekbarLabel$1, 20555 mute: mute$1, 20556 unmute: unmute$1, 20557 volume: volume$1, 20558 volumeUpDown: volumeUpDown$1, 20559 preferences: preferences$1, 20560 enterFullScreen: enterFullScreen$1, 20561 exitFullScreen: exitFullScreen$1, 20562 speed: speed$1, 20563 spacebar: spacebar$1, 20564 transcriptTitle: transcriptTitle$1, 20565 lyricsTitle: lyricsTitle$1, 20566 autoScroll: autoScroll$1, 20567 statusPlaying: statusPlaying$1, 20568 statusPaused: statusPaused$1, 20569 statusStopped: statusStopped$1, 20570 statusBuffering: statusBuffering$1, 20571 statusEnd: statusEnd$1, 20572 selectedTrack: selectedTrack$1, 20573 alertDescribedVersion: alertDescribedVersion$1, 20574 alertNonDescribedVersion: alertNonDescribedVersion$1, 20575 prefMenuCaptions: prefMenuCaptions$1, 20576 prefVoicedCaptions: prefVoicedCaptions$1, 20577 prefMenuDescriptions: prefMenuDescriptions$1, 20578 prefMenuKeyboard: prefMenuKeyboard$1, 20579 prefMenuTranscript: prefMenuTranscript$1, 20580 prefTitleCaptions: prefTitleCaptions$1, 20581 prefTitleDescriptions: prefTitleDescriptions$1, 20582 prefTitleKeyboard: prefTitleKeyboard$1, 20583 prefTitleTranscript: prefTitleTranscript$1, 20584 prefIntroDescription1: prefIntroDescription1$1, 20585 prefDescription1: prefDescription1$1, 20586 prefDescription2: prefDescription2$1, 20587 prefDescription3: prefDescription3$1, 20588 prefIntroDescriptionNone: prefIntroDescriptionNone$1, 20589 prefIntroDescription3: prefIntroDescription3$1, 20590 prefIntroDescription4: prefIntroDescription4$1, 20591 prefIntroKeyboard1: prefIntroKeyboard1$1, 20592 prefIntroKeyboard2: prefIntroKeyboard2$1, 20593 prefIntroKeyboard3: prefIntroKeyboard3$1, 20594 prefHeadingKeyboard1: prefHeadingKeyboard1$1, 20595 prefHeadingKeyboard2: prefHeadingKeyboard2$1, 20596 prefHeadingDescription: prefHeadingDescription$1, 20597 prefHeadingTextDescription: prefHeadingTextDescription$1, 20598 prefAltKey: prefAltKey$1, 20599 prefCtrlKey: prefCtrlKey$1, 20600 prefShiftKey: prefShiftKey$1, 20601 prefNoKeyShortcuts: prefNoKeyShortcuts$1, 20602 escapeKey: escapeKey$1, 20603 escapeKeyFunction: escapeKeyFunction$1, 20604 prefDescPause: prefDescPause$1, 20605 prefDescVisible: prefDescVisible$1, 20606 prefDescVoice: prefDescVoice$1, 20607 prefDescRate: prefDescRate$1, 20608 prefCaptionRate: prefCaptionRate$1, 20609 prefDescPitch: prefDescPitch$1, 20610 prefDescPitch1: prefDescPitch1$1, 20611 prefDescPitch2: prefDescPitch2$1, 20612 prefDescPitch3: prefDescPitch3$1, 20613 prefDescPitch4: prefDescPitch4$1, 20614 prefDescPitch5: prefDescPitch5$1, 20615 sampleDescriptionText: sampleDescriptionText$1, 20616 prefHighlight: prefHighlight$1, 20617 prefTabbable: prefTabbable$1, 20618 prefCaptionsFont: prefCaptionsFont$1, 20619 prefCaptionsColor: prefCaptionsColor$1, 20620 prefCaptionsBGColor: prefCaptionsBGColor$1, 20621 prefCaptionsSize: prefCaptionsSize$1, 20622 prefCaptionsOpacity: prefCaptionsOpacity$1, 20623 prefCaptionsStyle: prefCaptionsStyle$1, 20624 serif: serif$1, 20625 sans: sans$1, 20626 cursive: cursive$1, 20627 fantasy: fantasy$1, 20628 monospace: monospace$1, 20629 white: white$1, 20630 yellow: yellow$1, 20631 green: green$1, 20632 cyan: cyan$1, 20633 blue: blue$1, 20634 magenta: magenta$1, 20635 red: red$1, 20636 black: black$1, 20637 transparent: transparent$1, 20638 solid: solid$1, 20639 captionsStylePopOn: captionsStylePopOn$1, 20640 captionsStyleRollUp: captionsStyleRollUp$1, 20641 prefCaptionsPosition: prefCaptionsPosition$1, 20642 captionsPositionOverlay: captionsPositionOverlay$1, 20643 captionsPositionBelow: captionsPositionBelow$1, 20644 sampleCaptionText: sampleCaptionText$1, 20645 prefSuccess: prefSuccess$1, 20646 prefNoChange: prefNoChange$1, 20647 save: save$1, 20648 cancel: cancel$1, 20649 dismissButton: dismissButton$1, 20650 windowButtonLabel: windowButtonLabel$1, 20651 windowMove: windowMove$1, 20652 windowMoveLeft: windowMoveLeft$1, 20653 windowMoveRight: windowMoveRight$1, 20654 windowMoveUp: windowMoveUp$1, 20655 windowMoveDown: windowMoveDown$1, 20656 windowMoveStopped: windowMoveStopped$1, 20657 transcriptControls: transcriptControls$1, 20658 signControls: signControls$1, 20659 windowMoveAlert: windowMoveAlert$1, 20660 windowResize: windowResize$1, 20661 windowResizeHeading: windowResizeHeading$1, 20662 closeButtonLabel: closeButtonLabel$1, 20663 width: width$1, 20664 height: height$1, 20665 resultsSummary1: resultsSummary1$1, 20666 resultsSummary2: resultsSummary2$1, 20667 resultsSummary3: resultsSummary3$1, 20668 noResultsFound: noResultsFound$1, 20669 searchButtonLabel: searchButtonLabel$1, 20670 hour: hour$1, 20671 minute: minute$1, 20672 second: second$1, 20673 hours: hours$1, 20674 minutes: minutes$1, 20675 seconds: seconds$1, 20676 vtsHeading: vtsHeading$1, 20677 vtsInstructions1: vtsInstructions1$1, 20678 vtsInstructions2: vtsInstructions2$1, 20679 vtsInstructions3: vtsInstructions3$1, 20680 vtsInstructions4: vtsInstructions4$1, 20681 vtsInstructions5: vtsInstructions5$1, 20682 vtsSelectLanguage: vtsSelectLanguage$1, 20683 vtsSave: vtsSave$1, 20684 vtsReturn: vtsReturn$1, 20685 vtsCancel: vtsCancel$1, 20686 vtsRow: vtsRow$1, 20687 vtsKind: vtsKind$1, 20688 vtsStart: vtsStart$1, 20689 vtsEnd: vtsEnd$1, 20690 vtsContent: vtsContent$1, 20691 vtsActions: vtsActions$1, 20692 vtsNewRow: vtsNewRow$1, 20693 vtsDeletedRow: vtsDeletedRow$1, 20694 vtsMovedRow: vtsMovedRow$1 20695 }; 20696 20697 var playerHeading = "åªé«ææ¾å¨"; 20698 var audioPlayer = "Audio player"; 20699 var videoPlayer = "Video player"; 20700 var faster = "å å¿«"; 20701 var slower = "æ¸æ ¢"; 20702 var play = "ææ¾"; 20703 var pause = "æ«å"; 20704 var restart = "å¾é éå§"; 20705 var prevTrack = "åä¸è»"; 20706 var nextTrack = "å¾ä¸è»"; 20707 var rewind = "åå"; 20708 var forward = "å¿«è½"; 20709 var captions = "åå¹"; 20710 var showCaptions = "顯示åå¹"; 20711 var hideCaptions = "é±èåå¹"; 20712 var captionsOff = "åå¹éé"; 20713 var showTranscript = "顯示éå稿"; 20714 var hideTranscript = "é±èéå稿"; 20715 var turnOnDescriptions = "éåå£è¿°å½±å"; 20716 var turnOffDescriptions = "ééå£è¿°å½±å"; 20717 var chapters = "ç« ç¯"; 20718 var language = "èªè¨"; 20719 var sign = "æèª"; 20720 var showSign = "顯示æèª"; 20721 var hideSign = "é±èæèª"; 20722 var seekbarLabel = "æé軸"; 20723 var mute = "éé³"; 20724 var unmute = "åæ¶éé³"; 20725 var volume = "é³é"; 20726 var volumeUpDown = "é³é墿¸"; 20727 var preferences = "å好è¨å®"; 20728 var enterFullScreen = "é²å ¥å ¨è¢å¹æ¨¡å¼"; 20729 var exitFullScreen = "é¢éå ¨è¢å¹æ¨¡å¼"; 20730 var speed = "ææ¾é度"; 20731 var spacebar = "空ç½éµ"; 20732 var transcriptTitle = "éå稿"; 20733 var lyricsTitle = "æè©"; 20734 var autoScroll = "èªåæ²å"; 20735 var statusPlaying = "æ£å¨ææ¾"; 20736 var statusPaused = "å·²æ«å"; 20737 var statusStopped = "已忢"; 20738 var statusBuffering = "æ£å¨ç·©è¡"; 20739 var statusEnd = "è»æ®µçµæ"; 20740 var selectedTrack = "å·²é¸è»æ®µ"; 20741 var alertDescribedVersion = "æ£å¨ä½¿ç¨æ¬å½±ççå£è¿°å½±åçæ¬"; 20742 var alertNonDescribedVersion = "æ£å¨ä½¿ç¨æ¬å½±ççéå£è¿°å½±åçæ¬"; 20743 var prefMenuCaptions = "åå¹"; 20744 var prefVoicedCaptions = "Spoken Captions"; 20745 var prefMenuDescriptions = "å£è¿°å½±å"; 20746 var prefMenuKeyboard = "éµç¤"; 20747 var prefMenuTranscript = "éå稿"; 20748 var prefTitleCaptions = "åå¹å好è¨å®"; 20749 var prefTitleDescriptions = "å£è¿°å½±åå好è¨å®"; 20750 var prefTitleKeyboard = "éµç¤å好è¨å®"; 20751 var prefTitleTranscript = "éå稿å好è¨å®"; 20752 var prefIntroDescription1 = "æ¬åªé«ææ¾å¨æ¯æ´å ©ç¨®æ¯æ´å£è¿°å½±åæ ¼å¼ï¼"; 20753 var prefDescription1 = "ç®åçå½±çæ é製å£è¿°å½±åçæ¿ä»£çæ¬ æåå¼å£è¿°å½±å"; 20754 var prefDescription2 = "ç®åçå½±çæ å½±ççå£è¿°å½±åæ¿ä»£çæ¬"; 20755 var prefDescription3 = "ç®åçå½±çæ æåå¼å£è¿°å½±åï¼éæé è¢å¹å ±è®è»é«"; 20756 var prefIntroDescriptionNone = "ç®åçå½±çå ©ç¨®æ ¼å¼é½æ²æä¾ã"; 20757 var prefIntroDescription3 = "è«ä½¿ç¨ä¸å表å®è¨å®å£è¿°å½±åçç¸éå好ã"; 20758 var prefIntroDescription4 = "å²åè¨å®å¾ï¼å£è¿°å½±ååè½å¯ä»¥ç±å£è¿°å½±åæéåæééã"; 20759 var prefIntroKeyboard1 = "ä¸è«æ¨èº«èç¶²é ä½èï¼é½å¯ä»¥éç¨ä¸åéµç¤å¿«é鵿ä½é é¢ä¸çéååªé«ææ¾å¨ï¼å¿«é鵿¸ å®è«è¦åºä¸ï¼ã"; 20760 var prefIntroKeyboard2 = "以ä¸é½å¯ä»¥ææ´¾çµåéµï¼ShiftãAltãCtrlï¼ã"; 20761 var prefIntroKeyboard3 = "è«æ³¨æï¼æäºå¿«ééµçµåå¯è½æè·æ¨ççè¦½å¨æå ¶ä»æç¨ç¨å¼ç¸è¡çªãè«èªè¡å¤å å試ï¼ç¢ºèªåªäºæéµçµåå¯ä»¥ä½¿ç¨ã"; 20762 var prefHeadingKeyboard1 = "ç¨æ¼å¿«ééµççµåéµ"; 20763 var prefHeadingKeyboard2 = "ç®åçå¿«ééµ"; 20764 var prefHeadingDescription = "å£è¿°å½±å"; 20765 var prefHeadingTextDescription = "æåå¼å£è¿°å½±å"; 20766 var prefAltKey = "Alt"; 20767 var prefCtrlKey = "Ctrl"; 20768 var prefShiftKey = "Shift"; 20769 var prefNoKeyShortcuts = "Disable keyboard shortcuts"; 20770 var escapeKey = "Esc"; 20771 var escapeKeyFunction = "ééç®åçå°è©±è¦çªæå½åºå¼é¸å®"; 20772 var prefDescPause = "éå§å£è¿°æèªåæ«åå½±ç"; 20773 var prefDescVisible = "åæä»¥è¦è¦ºæ¹å¼åç¾å£è¿°å½±å"; 20774 var prefDescVoice = "èªé³"; 20775 var prefDescRate = "Spoken Description Rate"; 20776 var prefCaptionRate = "Spoken Caption Rate"; 20777 var prefDescPitch = "é³èª¿"; 20778 var prefDescPitch1 = "é叏使²"; 20779 var prefDescPitch2 = "使²"; 20780 var prefDescPitch3 = "é è¨"; 20781 var prefDescPitch4 = "é«äº¢"; 20782 var prefDescPitch5 = "é常é«äº¢"; 20783 var sampleDescriptionText = "調æ´è¨å®ä»¥èè½é段ç¯ä¾æåã"; 20784 var prefHighlight = "ææ¾åªé«å §å®¹æå¸é¡¯ç¸å°æçéå稿段è½"; 20785 var prefTabbable = "åç¨éµç¤çéå稿"; 20786 var prefCaptionsFont = "åé«è®å"; 20787 var prefCaptionsColor = "æåè²å½©"; 20788 var prefCaptionsBGColor = "èæ¯"; 20789 var prefCaptionsSize = "æå尺寸"; 20790 var prefCaptionsOpacity = "ä¸éæåº¦"; 20791 var prefCaptionsStyle = "å忍£å¼"; 20792 var serif = "襯ç·åå"; 20793 var sans = "ç¡è¥¯ç·åå"; 20794 var cursive = "æå¯«åå"; 20795 var fantasy = "è¯éºåå"; 20796 var monospace = "ç寬åå"; 20797 var white = "ç½è²"; 20798 var yellow = "é»è²"; 20799 var green = "ç¶ è²"; 20800 var cyan = "éè²"; 20801 var blue = "èè²"; 20802 var magenta = "æ´ç´ è²"; 20803 var red = "ç´ è²"; 20804 var black = "é»è²"; 20805 var transparent = "éæ"; 20806 var solid = "ä¸éæ"; 20807 var captionsStylePopOn = "å½åº"; 20808 var captionsStyleRollUp = "å¾ä¸æ²å"; 20809 var prefCaptionsPosition = "ä½ç½®"; 20810 var captionsPositionOverlay = "è¦è"; 20811 var captionsPositionBelow = "å¨å½±çä¹ä¸"; 20812 var sampleCaptionText = "åå¹ç¯ä¾"; 20813 var prefSuccess = "å·²å²åæ¨æåºçè®åã"; 20814 var prefNoChange = "æ¨ä¸¦æªæåºä»»ä½è®åã"; 20815 var save = "å²å"; 20816 var cancel = "åæ¶"; 20817 var dismissButton = "Dismiss"; 20818 var windowButtonLabel = "è¦çªé¸é "; 20819 var windowMove = "ç§»å"; 20820 var windowMoveLeft = "Window moved left"; 20821 var windowMoveRight = "Window moved right"; 20822 var windowMoveUp = "Window moved up"; 20823 var windowMoveDown = "Window moved down"; 20824 var windowMoveStopped = "Window move stopped"; 20825 var transcriptControls = "Transcript Window Controls"; 20826 var signControls = "Sign Language Window Controls"; 20827 var windowMoveAlert = "ææ³è¦çªæä»¥æ¹åéµç§»åè¦çªï¼ç¢ºå®å¾æ Enter"; 20828 var windowResize = "è®æ´å¤§å°"; 20829 var windowResizeHeading = "è®æ´è¦çªå¤§å°"; 20830 var closeButtonLabel = "é"; 20831 var width = "寬度"; 20832 var height = "é«åº¦"; 20833 var resultsSummary1 = "You searched for:"; 20834 var resultsSummary2 = "Found %1 matching items."; 20835 var resultsSummary3 = "Click the time associated with any item to play the video from that point."; 20836 var noResultsFound = "No results found."; 20837 var searchButtonLabel = "Play at %1"; 20838 var hour = "hour"; 20839 var minute = "minute"; 20840 var second = "second"; 20841 var hours = "hours"; 20842 var minutes = "minutes"; 20843 var seconds = "seconds"; 20844 var vtsHeading = "Video Transcript Sorter"; 20845 var vtsInstructions1 = "Use the Video Transcript Sorter to modify text tracks:"; 20846 var vtsInstructions2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20847 var vtsInstructions3 = "Modify content or start/end times (all are directly editable within the table)."; 20848 var vtsInstructions4 = "Add new content, such as chapters or descriptions."; 20849 var vtsInstructions5 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files."; 20850 var vtsSelectLanguage = "Select a language"; 20851 var vtsSave = "Generate new .vtt content"; 20852 var vtsReturn = "Return to Editor"; 20853 var vtsCancel = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20854 var vtsRow = "Row"; 20855 var vtsKind = "Kind"; 20856 var vtsStart = "Start"; 20857 var vtsEnd = "End"; 20858 var vtsContent = "Content"; 20859 var vtsActions = "Actions"; 20860 var vtsNewRow = "A new row %1 has been inserted."; 20861 var vtsDeletedRow = "Row %1 has been deleted."; 20862 var vtsMovedRow = "Row %1 has been moved %2 and is now Row %3."; 20863 var zh_tw = { 20864 playerHeading: playerHeading, 20865 audioPlayer: audioPlayer, 20866 videoPlayer: videoPlayer, 20867 faster: faster, 20868 slower: slower, 20869 play: play, 20870 pause: pause, 20871 restart: restart, 20872 prevTrack: prevTrack, 20873 nextTrack: nextTrack, 20874 rewind: rewind, 20875 forward: forward, 20876 captions: captions, 20877 showCaptions: showCaptions, 20878 hideCaptions: hideCaptions, 20879 captionsOff: captionsOff, 20880 showTranscript: showTranscript, 20881 hideTranscript: hideTranscript, 20882 turnOnDescriptions: turnOnDescriptions, 20883 turnOffDescriptions: turnOffDescriptions, 20884 chapters: chapters, 20885 language: language, 20886 sign: sign, 20887 showSign: showSign, 20888 hideSign: hideSign, 20889 seekbarLabel: seekbarLabel, 20890 mute: mute, 20891 unmute: unmute, 20892 volume: volume, 20893 volumeUpDown: volumeUpDown, 20894 preferences: preferences, 20895 enterFullScreen: enterFullScreen, 20896 exitFullScreen: exitFullScreen, 20897 speed: speed, 20898 spacebar: spacebar, 20899 transcriptTitle: transcriptTitle, 20900 lyricsTitle: lyricsTitle, 20901 autoScroll: autoScroll, 20902 statusPlaying: statusPlaying, 20903 statusPaused: statusPaused, 20904 statusStopped: statusStopped, 20905 statusBuffering: statusBuffering, 20906 statusEnd: statusEnd, 20907 selectedTrack: selectedTrack, 20908 alertDescribedVersion: alertDescribedVersion, 20909 alertNonDescribedVersion: alertNonDescribedVersion, 20910 prefMenuCaptions: prefMenuCaptions, 20911 prefVoicedCaptions: prefVoicedCaptions, 20912 prefMenuDescriptions: prefMenuDescriptions, 20913 prefMenuKeyboard: prefMenuKeyboard, 20914 prefMenuTranscript: prefMenuTranscript, 20915 prefTitleCaptions: prefTitleCaptions, 20916 prefTitleDescriptions: prefTitleDescriptions, 20917 prefTitleKeyboard: prefTitleKeyboard, 20918 prefTitleTranscript: prefTitleTranscript, 20919 prefIntroDescription1: prefIntroDescription1, 20920 prefDescription1: prefDescription1, 20921 prefDescription2: prefDescription2, 20922 prefDescription3: prefDescription3, 20923 prefIntroDescriptionNone: prefIntroDescriptionNone, 20924 prefIntroDescription3: prefIntroDescription3, 20925 prefIntroDescription4: prefIntroDescription4, 20926 prefIntroKeyboard1: prefIntroKeyboard1, 20927 prefIntroKeyboard2: prefIntroKeyboard2, 20928 prefIntroKeyboard3: prefIntroKeyboard3, 20929 prefHeadingKeyboard1: prefHeadingKeyboard1, 20930 prefHeadingKeyboard2: prefHeadingKeyboard2, 20931 prefHeadingDescription: prefHeadingDescription, 20932 prefHeadingTextDescription: prefHeadingTextDescription, 20933 prefAltKey: prefAltKey, 20934 prefCtrlKey: prefCtrlKey, 20935 prefShiftKey: prefShiftKey, 20936 prefNoKeyShortcuts: prefNoKeyShortcuts, 20937 escapeKey: escapeKey, 20938 escapeKeyFunction: escapeKeyFunction, 20939 prefDescPause: prefDescPause, 20940 prefDescVisible: prefDescVisible, 20941 prefDescVoice: prefDescVoice, 20942 prefDescRate: prefDescRate, 20943 prefCaptionRate: prefCaptionRate, 20944 prefDescPitch: prefDescPitch, 20945 prefDescPitch1: prefDescPitch1, 20946 prefDescPitch2: prefDescPitch2, 20947 prefDescPitch3: prefDescPitch3, 20948 prefDescPitch4: prefDescPitch4, 20949 prefDescPitch5: prefDescPitch5, 20950 sampleDescriptionText: sampleDescriptionText, 20951 prefHighlight: prefHighlight, 20952 prefTabbable: prefTabbable, 20953 prefCaptionsFont: prefCaptionsFont, 20954 prefCaptionsColor: prefCaptionsColor, 20955 prefCaptionsBGColor: prefCaptionsBGColor, 20956 prefCaptionsSize: prefCaptionsSize, 20957 prefCaptionsOpacity: prefCaptionsOpacity, 20958 prefCaptionsStyle: prefCaptionsStyle, 20959 serif: serif, 20960 sans: sans, 20961 cursive: cursive, 20962 fantasy: fantasy, 20963 monospace: monospace, 20964 white: white, 20965 yellow: yellow, 20966 green: green, 20967 cyan: cyan, 20968 blue: blue, 20969 magenta: magenta, 20970 red: red, 20971 black: black, 20972 transparent: transparent, 20973 solid: solid, 20974 captionsStylePopOn: captionsStylePopOn, 20975 captionsStyleRollUp: captionsStyleRollUp, 20976 prefCaptionsPosition: prefCaptionsPosition, 20977 captionsPositionOverlay: captionsPositionOverlay, 20978 captionsPositionBelow: captionsPositionBelow, 20979 sampleCaptionText: sampleCaptionText, 20980 prefSuccess: prefSuccess, 20981 prefNoChange: prefNoChange, 20982 save: save, 20983 cancel: cancel, 20984 dismissButton: dismissButton, 20985 windowButtonLabel: windowButtonLabel, 20986 windowMove: windowMove, 20987 windowMoveLeft: windowMoveLeft, 20988 windowMoveRight: windowMoveRight, 20989 windowMoveUp: windowMoveUp, 20990 windowMoveDown: windowMoveDown, 20991 windowMoveStopped: windowMoveStopped, 20992 transcriptControls: transcriptControls, 20993 signControls: signControls, 20994 windowMoveAlert: windowMoveAlert, 20995 windowResize: windowResize, 20996 windowResizeHeading: windowResizeHeading, 20997 closeButtonLabel: closeButtonLabel, 20998 width: width, 20999 height: height, 21000 resultsSummary1: resultsSummary1, 21001 resultsSummary2: resultsSummary2, 21002 resultsSummary3: resultsSummary3, 21003 noResultsFound: noResultsFound, 21004 searchButtonLabel: searchButtonLabel, 21005 hour: hour, 21006 minute: minute, 21007 second: second, 21008 hours: hours, 21009 minutes: minutes, 21010 seconds: seconds, 21011 vtsHeading: vtsHeading, 21012 vtsInstructions1: vtsInstructions1, 21013 vtsInstructions2: vtsInstructions2, 21014 vtsInstructions3: vtsInstructions3, 21015 vtsInstructions4: vtsInstructions4, 21016 vtsInstructions5: vtsInstructions5, 21017 vtsSelectLanguage: vtsSelectLanguage, 21018 vtsSave: vtsSave, 21019 vtsReturn: vtsReturn, 21020 vtsCancel: vtsCancel, 21021 vtsRow: vtsRow, 21022 vtsKind: vtsKind, 21023 vtsStart: vtsStart, 21024 vtsEnd: vtsEnd, 21025 vtsContent: vtsContent, 21026 vtsActions: vtsActions, 21027 vtsNewRow: vtsNewRow, 21028 vtsDeletedRow: vtsDeletedRow, 21029 vtsMovedRow: vtsMovedRow 21030 }; 21031 21032 const moduleFromTag = { 21033 ca, 21034 cs, 21035 da, 21036 de, 21037 en, 21038 es, 21039 fr, 21040 he, 21041 id, 21042 it, 21043 ja, 21044 ms, 21045 nb, 21046 nl, 21047 pl, 21048 pt, 21049 'pt-BR': pt_br, 21050 sk, 21051 sv, 21052 tr, 21053 'zh-TW': zh_tw, 21054 }; 21055 21056 function addTranslationFunctions(AblePlayer) { 21057 AblePlayer.prototype.getSupportedLangs = function() { 21058 // returns an array of languages for which AblePlayer has translation tables 21059 var langs = { 21060 'ca' : 'Catalan', 21061 'cs' : 'Czech', 21062 'da' : 'Danish', 21063 'de' : 'German', 21064 'en' : 'English', 21065 'es' : 'Spanish', 21066 'fr' : 'French', 21067 'he' : 'Hebrew', 21068 'id' : 'Indonesian', 21069 'it' : 'Italian', 21070 'ja' : 'Japanese', 21071 'ms' : 'Malay', 21072 'nb' : 'Norwegian BokmÃ¥l', 21073 'nl' : 'Dutch', 21074 'pl' : 'Polish', 21075 'pt' : 'Portuguese', 21076 'pt-BR' : 'Brazilian Portuguese', 21077 'sk' : 'Slovak', 21078 'sv' : 'Swedish', 21079 'tr' : 'Turkish', 21080 'zh-TW' : 'Chinese (Taiwan)' 21081 }; 21082 21083 return langs; 21084 }; 21085 21086 /** 21087 * Fetch a translated string if it exists. 21088 * 21089 * @param {string} key JSON key to locate translated string. 21090 * @param {string} fallback Default language if no translation found. 21091 * @param {Array} args Ordered array of arguments to replace in string. 21092 * @returns 21093 */ 21094 AblePlayer.prototype.translate = function( key, fallback, args = Array() ) { 21095 let translation = ''; 21096 if ( this.tt[ key ] ) { 21097 translation = this.tt[ key ]; 21098 } else { 21099 translation = fallback; 21100 } 21101 if ( args.length > 0 ) { 21102 args.forEach( ( val, index ) => { 21103 let ref = index + 1; 21104 translation = translation.replace( '%' + ref, val ); 21105 }); 21106 } 21107 21108 return translation; 21109 }; 21110 21111 AblePlayer.prototype.getTranslationText = function() { 21112 21113 // determine language, then get labels and prompts from corresponding translation var 21114 var thisObj, supportedLangs, docLang, similarLangFound; 21115 thisObj = this; 21116 21117 supportedLangs = this.getSupportedLangs(); // returns an array 21118 21119 if (this.lang) { // a data-lang attribute is included on the media element 21120 this.lang; 21121 if ( ! Object.hasOwn( supportedLangs,this.lang ) ) { 21122 // the specified language code is not in the index 21123 if ( this.lang.indexOf('-') == 2 ) { 21124 // this is a localized lang attribute (e.g., fr-CA) 21125 // try the parent language, given the first two characters 21126 // if parent lang is supported. Use that, else null. 21127 this.lang = ( Object.hasOwn(supportedLangs,this.lang.substring(0,2)) !== -1 ) ? this.lang.substring(0,2) : null; 21128 } else { 21129 // this is not a localized language. 21130 // but maybe there's a similar localized language supported 21131 // that has the same parent? 21132 similarLangFound = false; 21133 for ( const [key,value] of Object.entries(supportedLangs) ) { 21134 if ( key.substring(0,2) == this.lang ) { 21135 this.lang = key; 21136 similarLangFound = true; 21137 } 21138 } 21139 if ( !similarLangFound ) { 21140 // language requested via data-lang is not supported 21141 this.lang = null; 21142 } 21143 } 21144 } 21145 } 21146 21147 if (!this.lang) { 21148 // try the language of the web page, if specified 21149 if ($('body').attr('lang')) { 21150 docLang = $('body').attr('lang').toLowerCase(); 21151 } else if ($('html').attr('lang')) { 21152 docLang = $('html').attr('lang').toLowerCase(); 21153 } else { 21154 docLang = null; 21155 } 21156 if (docLang) { 21157 if ( Object.hasOwn( supportedLangs,docLang ) ) { 21158 // the document language is supported 21159 this.lang = docLang; 21160 } else { 21161 // the document language is not supported 21162 if (docLang.indexOf('-') == 2) { 21163 // this is a localized lang attribute (e.g., fr-CA) 21164 // try the parent language, given the first two characters 21165 if ( Object.hasOwn(supportedLangs,docLang.substring(0,2)) ) { 21166 // the parent language is supported. use that. 21167 this.lang = docLang.substring(0,2); 21168 } 21169 } 21170 } 21171 } 21172 } 21173 21174 if (!this.lang) { 21175 // No supported language has been specified by any means 21176 // Fallback to English 21177 this.lang = 'en'; 21178 } 21179 21180 if (!this.searchLang) { 21181 this.searchLang = this.lang; 21182 } 21183 const ttModule = moduleFromTag[this.lang]; 21184 if (!ttModule) { 21185 console.log( "Error: Unable to load translation module for language:", this.lang); 21186 thisObj.tt = {}; 21187 thisObj.translationFiles = false; 21188 } else { 21189 thisObj.tt = ttModule; 21190 thisObj.translationFiles = true; 21191 } 21192 }; 21193 21194 AblePlayer.prototype.getSampleDescriptionText = function() { 21195 if ( ! this.translationFiles ) { 21196 this.sampleText = []; 21197 let translation = { 'lang':'en', 'text': this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) }; 21198 this.sampleText.push(translation); 21199 } else { 21200 // Create an array of sample description text in all languages 21201 // This needs to be readily available for testing different voices 21202 // in the Description Preferences dialog 21203 var supportedLangs, thisText, translation; 21204 21205 supportedLangs = this.getSupportedLangs(); 21206 21207 this.sampleText = []; 21208 for ( const [tag,name] of Object.entries(supportedLangs) ) { 21209 const ttModule = moduleFromTag[tag]; 21210 thisText = ttModule.sampleDescriptionText; 21211 translation = {'lang':name, 'text': thisText}; 21212 this.sampleText.push(translation); 21213 } 21214 } 21215 }; 21216 21217 } 21218 21219 /* global Vimeo */ 21220 21221 function addVimeoFunctions(AblePlayer) { 21222 21223 AblePlayer.prototype.initVimeoPlayer = function () { 21224 21225 var thisObj, deferred, promise, containerId, vimeoId, options; 21226 thisObj = this; 21227 21228 deferred = new this.defer(); 21229 promise = deferred.promise(); 21230 21231 containerId = this.mediaId + '_vimeo'; 21232 21233 // add container to which Vimeo player iframe will be appended 21234 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 21235 21236 // if a described version is available && user prefers description 21237 // init player using the described version 21238 vimeoId = (this.vimeoDescId && this.prefDesc) ? this.vimeoDescId : this.vimeoId; 21239 21240 this.activeVimeoId = vimeoId; 21241 21242 // Notes re. Vimeo Embed Options: 21243 // If a video is owned by a user with a paid Plus, PRO, or Business account, 21244 // setting the "controls" option to "false" will hide the default controls, without hiding captions. 21245 // This is a new option from Vimeo; previously used "background:true" to hide the controller, 21246 // but that had unwanted side effects: 21247 // - In addition to hiding the controls, it also hides captions 21248 // - It automatically autoplays (initializing the player with autoplay:false does not override this) 21249 // - It automatically loops (but this can be overridden by initializing the player with loop:false) 21250 // - It automatically sets volume to 0 (not sure if this can be overridden, since no longer using the background option) 21251 21252 if (this.playerWidth) { 21253 if (this.vimeoUrlHasParams) { 21254 // use url param, not id 21255 options = { 21256 url: vimeoId, 21257 width: this.playerWidth, 21258 controls: false 21259 }; 21260 } else { 21261 options = { 21262 id: vimeoId, 21263 width: this.playerWidth, 21264 controls: false 21265 }; 21266 } 21267 } else { 21268 // initialize without width & set width later 21269 if (this.vimeoUrlHasParams) { 21270 options = { 21271 url: vimeoId, 21272 controls: false 21273 }; 21274 } else { 21275 options = { 21276 id: vimeoId, 21277 controls: false 21278 }; 21279 } 21280 } 21281 21282 this.vimeoPlayer = new Vimeo.Player(containerId, options); 21283 21284 this.vimeoPlayer.ready().then(function() { 21285 // add tabindex -1 on iframe so vimeo frame cannot be focused on 21286 $('#'+containerId).children('iframe').attr({ 21287 'tabindex': '-1', 21288 'aria-hidden': true 21289 }); 21290 21291 // get video's intrinsic size and initiate player dimensions 21292 thisObj.vimeoPlayer.getVideoWidth().then(function(width) { 21293 if (width) { 21294 // also get height 21295 thisObj.vimeoPlayer.getVideoHeight().then(function(height) { 21296 if (height) { 21297 thisObj.resizePlayer(width,height); 21298 } 21299 }); 21300 } 21301 }).catch(function(error) { 21302 // an error occurred getting height or width 21303 // TODO: Test this to see how gracefully it organically recovers 21304 }); 21305 21306 if (!thisObj.hasPlaylist) { 21307 // remove the media element, since Vimeo replaces that with its own element in an iframe 21308 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 21309 thisObj.$media.remove(); 21310 21311 // define variables that will impact player setup 21312 21313 // vimeoSupportsPlaybackRateChange 21314 // changing playbackRate is only supported if the video is hosted on a Pro or Business account 21315 // unfortunately there is no direct way to query for that information. 21316 // this.vimeoPlayer.getPlaybackRate() returns a value, regardless of account type 21317 // This is a hack: 21318 // Attempt to change the playbackRate. If it results in an error, assume changing playbackRate is not supported. 21319 // Supported playbackRate values are 0.5 to 2. 21320 thisObj.vimeoPlaybackRate = 1; 21321 thisObj.vimeoPlayer.setPlaybackRate(thisObj.vimeoPlaybackRate).then(function(playbackRate) { 21322 // playback rate was set 21323 thisObj.vimeoSupportsPlaybackRateChange = true; 21324 }).catch(function(error) { 21325 thisObj.vimeoSupportsPlaybackRateChange = false; 21326 }); 21327 deferred.resolve(); 21328 } 21329 }); 21330 return promise; 21331 }; 21332 21333 AblePlayer.prototype.getVimeoPaused = function () { 21334 21335 var deferred, promise; 21336 deferred = new this.defer(); 21337 promise = deferred.promise(); 21338 21339 this.vimeoPlayer.getPaused().then(function (paused) { 21340 // paused is Boolean 21341 deferred.resolve(paused); 21342 }); 21343 21344 return promise; 21345 }; 21346 21347 AblePlayer.prototype.getVimeoEnded = function () { 21348 21349 var deferred, promise; 21350 deferred = new this.defer(); 21351 promise = deferred.promise(); 21352 21353 this.vimeoPlayer.getEnded().then(function (ended) { 21354 // ended is Boolean 21355 deferred.resolve(ended); 21356 }); 21357 21358 return promise; 21359 }; 21360 21361 AblePlayer.prototype.getVimeoState = function () { 21362 21363 var deferred, promise, promises, gettingPausedPromise, gettingEndedPromise; 21364 21365 deferred = new this.defer(); 21366 promise = deferred.promise(); 21367 promises = []; 21368 21369 gettingPausedPromise = this.vimeoPlayer.getPaused(); 21370 gettingEndedPromise = this.vimeoPlayer.getEnded(); 21371 21372 promises.push(gettingPausedPromise); 21373 promises.push(gettingEndedPromise); 21374 21375 gettingPausedPromise.then(function (paused) { 21376 deferred.resolve(paused); 21377 }); 21378 gettingEndedPromise.then(function (ended) { 21379 deferred.resolve(ended); 21380 }); 21381 $.when.apply($, promises).then(function () { 21382 deferred.resolve(); 21383 }); 21384 return promise; 21385 }; 21386 21387 AblePlayer.prototype.getVimeoCaptionTracks = function () { 21388 21389 // get data via Vimeo Player API, and push data to this.captions 21390 // Note: Vimeo doesn't expose the caption cues themselves 21391 // so this.captions will only include metadata about caption tracks; not cues 21392 var deferred = new this.defer(); 21393 var promise = deferred.promise(); 21394 21395 var thisObj, i, isDefaultTrack; 21396 21397 thisObj = this; 21398 21399 this.vimeoPlayer.getTextTracks().then(function(tracks) { 21400 21401 // each Vimeo track includes the following: 21402 // label (local name of the language) 21403 // language (2-character code) 21404 // kind (captions or subtitles, as declared by video owner) 21405 // mode ('disabled' or 'showing') 21406 21407 if (tracks.length) { 21408 21409 // create a new button for each caption track 21410 for (i=0; i<tracks.length; i++) { 21411 21412 thisObj.hasCaptions = true; 21413 if (thisObj.prefCaptions === 1) { 21414 thisObj.captionsOn = true; 21415 } else { 21416 thisObj.captionsOn = false; 21417 } 21418 // assign the default track based on language of the player 21419 if (tracks[i]['language'] === thisObj.lang) { 21420 isDefaultTrack = true; 21421 } else { 21422 isDefaultTrack = false; 21423 } 21424 thisObj.tracks.push({ 21425 'kind': tracks[i]['kind'], 21426 'language': tracks[i]['language'], 21427 'label': tracks[i]['label'], 21428 'def': isDefaultTrack 21429 }); 21430 } 21431 thisObj.captions = thisObj.tracks; 21432 thisObj.hasCaptions = true; 21433 21434 // setupPopups again with new captions array, replacing original 21435 thisObj.setupPopups('captions'); 21436 deferred.resolve(); 21437 } else { 21438 thisObj.hasCaptions = false; 21439 thisObj.usingVimeoCaptions = false; 21440 deferred.resolve(); 21441 } 21442 }); 21443 21444 return promise; 21445 }; 21446 21447 AblePlayer.prototype.getVimeoPosterUrl = function (vimeoId) { 21448 const thisObj = this; 21449 21450 // Vimeo Oembed only returns a 640px width image. Hope at some point there's an alternative. 21451 var url = 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/' + vimeoId, imageUrl = ''; 21452 console.log( url ); 21453 fetch( url ).then( response => { 21454 21455 return response.json(); 21456 }) 21457 .then( json => { 21458 imageUrl = json.thumbnail_url; 21459 }) 21460 .catch( error => { 21461 if (thisObj.debug) { 21462 console.log( 'Vimeo API query: ' + error ); 21463 } 21464 }); 21465 21466 return imageUrl; 21467 }; 21468 21469 AblePlayer.prototype.getVimeoId = function (url) { 21470 21471 // return a Vimeo ID, extracted from a full Vimeo URL 21472 // Supported URL patterns are anything containing 'vimeo.com' 21473 // and ending with a '/' followed by the ID. 21474 // (Vimeo IDs do not have predicatable lengths) 21475 21476 // Update: If URL contains parameters, return the full url 21477 // This will need to be passed to the Vimeo Player API 21478 // as a url parameter, not as an id parameter 21479 this.vimeoUrlHasParams = false; 21480 21481 let urlObject; 21482 if (typeof url === 'number') { 21483 // this is likely already a vimeo ID 21484 return url; 21485 } else { 21486 urlObject = new URL(url); 21487 } 21488 if ( 'vimeo.com' === urlObject.hostname || 'player.vimeo.com' === urlObject.hostname ) { 21489 // this is a full Vimeo URL 21490 if ( '' !== urlObject.search ) { 21491 // URL contains parameters 21492 this.vimeoUrlHasParams = true; 21493 return url; 21494 } else { 21495 if ( 'player.vimeo.com' === urlObject.hostname ) { 21496 return urlObject.pathname.replace( '/video/', '' ); 21497 } else { 21498 return urlObject.pathname.replace( '/', '' ); 21499 } 21500 } 21501 } else { 21502 return url; 21503 } 21504 }; 21505 21506 } 21507 21508 function addVolumeFunctions(AblePlayer) { 21509 21510 AblePlayer.prototype.addVolumeSlider = function($div) { 21511 21512 // Prior to v4.4.64, we were using a custom-build vertical volunme slider 21513 // Changed to input type="range" because it's standard and gaining more widespread support 21514 // including screen reader support 21515 // TODO: Improve presentation of vertical slider. That requires some CSS finesse. 21516 21517 var thisObj, volumeSliderId, volumeHelpId, volumePct, volumeLabel, volumeHeight; 21518 21519 thisObj = this; 21520 21521 // define a few variables 21522 volumeSliderId = this.mediaId + '-volume-slider'; 21523 volumeHelpId = this.mediaId + '-volume-help'; 21524 21525 this.$volumeSlider = $('<div>',{ 21526 'id': volumeSliderId, 21527 'class': 'able-volume-slider', 21528 'aria-hidden': 'true' 21529 }).hide(); 21530 this.$volumeSliderTooltip = $('<div>',{ 21531 'class': 'able-tooltip', 21532 'role': 'tooltip' 21533 }).hide(); 21534 this.$volumeRange = $('<input>',{ 21535 'type': 'range', 21536 'min': '0', 21537 'max': '10', 21538 'step': '1', 21539 'orient': 'vertical', // non-standard, but required for Firefox 21540 'aria-label': this.translate( 'volumeUpDown', 'Volume up down' ), 21541 'value': this.volume 21542 }); 21543 volumePct = parseInt(thisObj.volume) / 10 * 100; 21544 this.$volumeHelp = $('<div>',{ 21545 'id': volumeHelpId, 21546 'class': 'able-volume-help', 21547 'aria-live': 'polite' 21548 }).text(volumePct + '%'); 21549 volumeLabel = this.$volumeButton.attr( 'aria-label' ); 21550 this.$volumeButton.attr( 'aria-label', volumeLabel + ' ' + volumePct + '%'); 21551 this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp); 21552 volumeHeight = this.$volumeButton.parents( '.able-control-row' )[0]; 21553 this.$volumeSlider.css( 'bottom', volumeHeight.offsetHeight ); 21554 21555 $div.append(this.$volumeSlider); 21556 21557 // add event listeners 21558 this.$volumeRange.on('change',function (e) { 21559 thisObj.handleVolumeChange($(this).val()); 21560 }); 21561 21562 this.$volumeRange.on('input',function (e) { 21563 thisObj.handleVolumeChange($(this).val()); 21564 }); 21565 21566 this.$volumeRange.on('keydown',function (e) { 21567 21568 if (e.key === 'Escape' || e.key === 'Tab' || e.key === 'Enter') { 21569 // close popup 21570 if (thisObj.$volumeSlider.is(':visible')) { 21571 thisObj.closingVolume = true; // stopgap 21572 thisObj.hideVolumePopup(); 21573 } else { 21574 if (!thisObj.closingVolume) { 21575 thisObj.showVolumePopup(); 21576 } 21577 } 21578 } else { 21579 return; 21580 } 21581 }); 21582 }; 21583 21584 AblePlayer.prototype.refreshVolumeHelp = function(volume) { 21585 21586 // make adjustments based on current volume 21587 var volumePct; 21588 volumePct = (volume/10) * 100; 21589 21590 // Update help text 21591 if (this.$volumeHelp) { 21592 this.$volumeHelp.text(volumePct + '%'); 21593 } 21594 21595 // Update the default value of the volume slider input field 21596 // This doesn't seem to be necessary; browsers remember the previous setting during a session 21597 // but this is a fallback in case they don't 21598 this.$volumeRange.attr('value',volume); 21599 }; 21600 21601 AblePlayer.prototype.refreshVolumeButton = function(volume) { 21602 21603 var volumeName, volumePct, volumeLabel; 21604 21605 volumeName = this.getVolumeName(volume); 21606 volumePct = (volume/10) * 100; 21607 volumeLabel = this.translate( 'volume', 'Volume' ) + ' ' + volumePct + '%'; 21608 21609 this.getIcon( this.$volumeButton, 'volume-' + volumeName ); 21610 this.$volumeButton.attr( 'aria-label', volumeLabel ); 21611 }; 21612 21613 AblePlayer.prototype.handleVolumeButtonClick = function() { 21614 21615 if (this.$volumeSlider.is(':visible')) { 21616 this.hideVolumePopup(); 21617 } else { 21618 this.showVolumePopup(); 21619 } 21620 }; 21621 21622 AblePlayer.prototype.handleVolumeKeystroke = function(volume) { 21623 // keyboard shortcuts for changing volume 21624 if (this.isMuted() && volume > 0) { 21625 this.setMute(false); 21626 } else if (volume === 0) { 21627 this.setMute(true); 21628 } else { 21629 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 21630 this.refreshVolumeHelp(volume); 21631 this.refreshVolumeButton(volume); 21632 } 21633 }; 21634 21635 21636 AblePlayer.prototype.handleVolumeChange = function(volume) { 21637 21638 // handle volume change using the volume input slider 21639 21640 if (this.isMuted() && volume > 0) { 21641 this.setMute(false); 21642 } else if (volume === 0) { 21643 this.setMute(true); 21644 } else { 21645 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 21646 this.refreshVolumeHelp(volume); 21647 this.refreshVolumeButton(volume); 21648 } 21649 }; 21650 21651 AblePlayer.prototype.handleMute = function() { 21652 21653 if (this.isMuted()) { 21654 this.setMute(false); 21655 } else { 21656 this.setMute(true); 21657 } 21658 }; 21659 21660 AblePlayer.prototype.showVolumePopup = function() { 21661 21662 this.closePopups(); 21663 this.$tooltipDiv.hide(); 21664 this.$volumeSlider.show().attr('aria-hidden','false'); 21665 this.$volumeButton.attr('aria-expanded','true'); 21666 this.$volumeButton.focus(); // for screen reader expanded state to be read 21667 this.waitThenFocus(this.$volumeRange); 21668 }; 21669 21670 AblePlayer.prototype.hideVolumePopup = function() { 21671 21672 var thisObj = this; 21673 21674 this.$volumeSlider.hide().attr('aria-hidden','true'); 21675 this.$volumeButton.attr('aria-expanded','false').focus(); 21676 // wait a second before resetting stopgap var 21677 // otherwise the keypress used to close volume popup will trigger the volume button 21678 setTimeout(function() { 21679 thisObj.closingVolume = false; 21680 }, 1000); 21681 }; 21682 21683 AblePlayer.prototype.isMuted = function () { 21684 21685 if (this.player === 'html5') { 21686 return this.media.muted; 21687 } else if (this.player === 'youtube') { 21688 return this.youTubePlayer.isMuted(); 21689 } 21690 }; 21691 21692 AblePlayer.prototype.setMute = function(mute) { 21693 21694 // mute is either true (muting) or false (unmuting) 21695 if (mute) { 21696 // save current volume so it can be restored after unmute 21697 this.lastVolume = this.volume; 21698 this.volume = 0; 21699 } else { // restore to previous volume 21700 if (typeof this.lastVolume !== 'undefined') { 21701 this.volume = this.lastVolume; 21702 } 21703 } 21704 21705 if (this.player === 'html5') { 21706 this.media.muted = mute; 21707 } else if (this.player === 'youtube') { 21708 if (mute) { 21709 this.youTubePlayer.mute(); 21710 } else { 21711 this.youTubePlayer.unMute(); 21712 } 21713 } 21714 this.setVolume(this.volume); 21715 this.refreshVolumeHelp(this.volume); 21716 this.refreshVolumeButton(this.volume); 21717 }; 21718 21719 AblePlayer.prototype.setVolume = function (volume) { 21720 21721 // volume is 1 to 10 21722 // convert as needed depending on player 21723 21724 var newVolume; 21725 this.syncSignVideo( {'volume' : 0 } ); 21726 if (this.player === 'html5') { 21727 // volume is 0 to 1 21728 newVolume = volume / 10; 21729 this.media.volume = newVolume; 21730 } else if (this.player === 'youtube') { 21731 // volume is 0 to 100 21732 newVolume = volume * 10; 21733 this.youTubePlayer.setVolume(newVolume); 21734 this.volume = volume; 21735 } else if (this.player === 'vimeo') { 21736 // volume is 0 to 1 21737 newVolume = volume / 10; 21738 this.vimeoPlayer.setVolume(newVolume).then(function() { 21739 // setVolume finished. 21740 // successful completion also fires a 'volumechange' event (see event.js) 21741 }); 21742 } 21743 this.lastVolume = volume; 21744 }; 21745 21746 AblePlayer.prototype.getVolume = function (volume) { 21747 21748 // return volume using common audio control scale 1 to 10 21749 if (this.player === 'html5') { 21750 // uses 0 to 1 scale 21751 return this.media.volume * 10; 21752 } else if (this.player === 'youtube') { 21753 // uses 0 to 100 scale 21754 if (this.youTubePlayerReady) { 21755 return this.youTubePlayer.getVolume() / 10; 21756 } 21757 } 21758 if (this.player === 'vimeo') { 21759 // uses 0 to 1 scale 21760 // this.vimeoPlayer.getVolume() takes too long to resolve with a value 21761 // Just use variable that's already been defined (should be the same value anyway) 21762 return this.volume; 21763 } 21764 }; 21765 21766 AblePlayer.prototype.getVolumeName = function (volume) { 21767 21768 // returns 'mute','soft','medium', or 'loud' depending on volume level 21769 if (volume == 0) { 21770 return 'mute'; 21771 } else if (volume == 10) { 21772 return 'loud'; 21773 } else if (volume < 5) { 21774 return 'soft'; 21775 } else { 21776 return 'medium'; 21777 } 21778 }; 21779 21780 } 21781 21782 /* Video Transcript Sorter (VTS) 21783 * Used to synchronize time stamps from WebVTT resources 21784 * so they appear in the proper sequence within an auto-generated interactive transcript 21785 */ 21786 21787 21788 function addVtsFunctions(AblePlayer) { 21789 AblePlayer.prototype.injectVTS = function() { 21790 21791 var thisObj, $heading, $instructions, $p1, $p2, $ul, $li1, $li2, $li3, 21792 $fieldset, $legend, i, $radioDiv, radioId, $label, $radio, $saveButton, $savedTable; 21793 21794 thisObj = this; 21795 21796 if ( null !== document.getElementById( 'able-vts' ) ) { 21797 // Are they qualifying tracks? 21798 if (this.vtsTracks.length) { 21799 // Build an array of unique languages 21800 this.langs = []; 21801 this.getAllLangs(this.vtsTracks); 21802 21803 // Set the default VTS language 21804 this.vtsLang = this.lang; 21805 21806 // Inject a heading 21807 let heading = this.translate( 'vtsHeading', 'Video Transcript Sorter' ); 21808 $heading = $('<h2>').text( heading ); // TODO: intelligently assign proper heading level 21809 $('#able-vts').append($heading); 21810 21811 // Inject an empty div for writing messages 21812 this.$vtsAlert = $('<div>',{ 21813 'id': 'able-vts-alert', 21814 'aria-live': 'polite', 21815 'aria-atomic': 'true' 21816 }); 21817 $('#able-vts').append(this.$vtsAlert); 21818 21819 // Inject instructions (TODO: Localize) 21820 $instructions = $('<div>',{ 21821 'id': 'able-vts-instructions' 21822 }); 21823 $p1 = $('<p>').text( this.translate( 'vtsInstructions1', 'Use the Video Transcript Sorter to modify text tracks:' ) ); 21824 $ul = $('<ul>'); 21825 $li1 = $('<li>').text( this.translate( 'vtsInstructions2', 'Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player\'s auto-generated transcript.' ) ); 21826 $li2 = $('<li>').text( this.translate( 'vtsInstructions3', 'Modify content or start/end times (all are directly editable within the table).' ) ); 21827 $li3 = $('<li>').text( this.translate( 'vtsInstructions4', 'Add new content, such as chapters or descriptions.' ) ); 21828 $p2 = $('<p>').text( this.translate( 'vtsInstructions5', 'After editing, click the "Save Changes" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.' ) ); 21829 $ul.append($li1,$li2,$li3); 21830 $instructions.append($p1,$ul,$p2); 21831 $('#able-vts').append($instructions); 21832 21833 // Inject a fieldset with radio buttons for each language 21834 $fieldset = $('<fieldset>'); 21835 $legend = $('<legend>').text( this.translate( 'vtsSelectLanguage', 'Select a language' ) ); 21836 $fieldset.append($legend); 21837 let $fieldWrapper = $( '<div class="vts-lang-selector"></div>' ); 21838 for (i in this.langs) { 21839 radioId = 'vts-lang-radio-' + this.langs[i]; 21840 $radioDiv = $('<div>',{ 21841 // uncomment the following if label is native name 21842 // 'lang': this.langs[i] 21843 }); 21844 $radio = $('<input>', { 21845 'type': 'radio', 21846 'name': 'vts-lang', 21847 'id': radioId, 21848 'value': this.langs[i] 21849 }).on('click',function() { 21850 thisObj.vtsLang = $(this).val(); 21851 thisObj.showVtsAlert('Loading ' + thisObj.getLanguageName(thisObj.vtsLang) + ' tracks'); 21852 thisObj.injectVtsTable('update',thisObj.vtsLang); 21853 }); 21854 if (this.langs[i] == this.lang) { 21855 // this is the default language. 21856 $radio.prop('checked',true); 21857 } 21858 $label = $('<label>', { 21859 'for': radioId 21860 // Two options for label: 21861 // getLanguageName() - with second parameter "local" would return native name, otherwise returns English; 21862 // TODO: if using this be sure to add lang attr to <div> (see above) 21863 }).text(this.getLanguageName(this.langs[i])); 21864 $radioDiv.append($radio,$label); 21865 $fieldWrapper.append($radioDiv); 21866 } 21867 $fieldset.append( $fieldWrapper ); 21868 $('#able-vts').append($fieldset); 21869 let vtsSave = this.translate( 'vtsSave', 'Generate new .vtt content' ); 21870 // Inject a button to generate new files. 21871 $saveButton = $('<button>',{ 21872 'type': 'button', 21873 'id': 'able-vts-save', 21874 'value': 'save' 21875 }).text( vtsSave ); 21876 $('#able-vts').append($saveButton); 21877 21878 // Inject a table with one row for each cue in the default language 21879 this.injectVtsTable('add',this.vtsLang); 21880 21881 // TODO: Add drag/drop functionality for mousers 21882 // Add event listeners for contenteditable cells 21883 var kindOptions, beforeEditing, editedCell, editedContent; 21884 kindOptions = ['captions','chapters','descriptions','subtitles']; 21885 $('td[contenteditable="true"]').on('focus',function() { 21886 beforeEditing = $(this).text(); 21887 }).on('blur',function() { 21888 if (beforeEditing != $(this).text()) { 21889 editedCell = $(this).index(); 21890 editedContent = $(this).text(); 21891 if (editedCell === 1) { 21892 // do some simple spelling auto-correct 21893 if ($.inArray(editedContent,kindOptions) === -1) { 21894 // whatever user typed is not a valid kind 21895 // assume they correctly typed the first character 21896 if (editedContent.substring(0,1) === 's') { 21897 $(this).text('subtitles'); 21898 } else if (editedContent.substring(0,1) === 'd') { 21899 $(this).text('descriptions'); 21900 } else if (editedContent.substring(0,2) === 'ch') { 21901 $(this).text('chapters'); 21902 } else { 21903 // whatever else they types, assume 'captions' 21904 $(this).text('captions'); 21905 } 21906 } 21907 } else if (editedCell === 2 || editedCell === 3) { 21908 // start or end time 21909 // ensure proper formatting (with 3 decimal places) 21910 $(this).text(thisObj.formatTimestamp(editedContent)); 21911 } 21912 } 21913 }).on('keydown',function(e) { 21914 // don't allow keystrokes to trigger Able Player (or other) functions 21915 // while user is editing 21916 e.stopPropagation(); 21917 }); 21918 21919 // handle click on the Save button 21920 $('#able-vts-save').on('click',function(e) { 21921 e.stopPropagation(); 21922 if ($(this).attr('value') == 'save') { 21923 // replace table with WebVTT output in textarea fields (for copying/pasting) 21924 $(this).attr('value','cancel').text( thisObj.translate( 'vtsReturn', 'Return to Editor' ) ); 21925 $savedTable = $('#able-vts table'); 21926 $('#able-vts-instructions').hide(); 21927 $('#able-vts > fieldset').hide(); 21928 $('#able-vts table').remove(); 21929 $('#able-vts-icon-credit').remove(); 21930 thisObj.parseVtsOutput($savedTable); 21931 } else { 21932 // cancel saving, and restore the table using edited content 21933 $(this).attr('value','save').text( vtsSave ); 21934 $('#able-vts-output').remove(); 21935 $('#able-vts-instructions').show(); 21936 $('#able-vts > fieldset').show(); 21937 $('#able-vts').append($savedTable); 21938 $('#able-vts').append(thisObj.getIconCredit()); 21939 thisObj.showVtsAlert( thisObj.translate( 'vtsCancel', 'Cancelling saving. Any edits you made have been restored in the VTS table.' ) ); 21940 } 21941 }); 21942 } 21943 } 21944 }; 21945 21946 AblePlayer.prototype.setupVtsTracks = function(kind, lang, trackDesc, label, src, contents) { 21947 21948 // TODO: Add support for trackDesc 21949 // (to destinguish between tracks for the decribed vs non-described versions) 21950 var srcFile, vtsCues; 21951 21952 srcFile = this.getFilenameFromPath(src); 21953 vtsCues = this.parseVtsTracks(contents); 21954 21955 this.vtsTracks.push({ 21956 'kind': kind, 21957 'language': lang, 21958 'label': label, 21959 'srcFile': srcFile, 21960 'cues': vtsCues 21961 }); 21962 }; 21963 21964 AblePlayer.prototype.getFilenameFromPath = function(path) { 21965 21966 var lastSlash; 21967 lastSlash = path.lastIndexOf('/'); 21968 // fix slashes. 21969 return (lastSlash === -1) ? path : path.substring(lastSlash+1); 21970 }; 21971 21972 AblePlayer.prototype.getFilenameFromTracks = function(kind,lang) { 21973 21974 for (var i=0; i<this.vtsTracks.length; i++) { 21975 if (this.vtsTracks[i].kind === kind && this.vtsTracks[i].language === lang) { 21976 // this is a matching track 21977 // srcFile has already been converted to filename from path before saving to vtsTracks 21978 return this.vtsTracks[i].srcFile; 21979 } 21980 } 21981 // no matching track found 21982 return false; 21983 }; 21984 21985 AblePlayer.prototype.parseVtsTracks = function(contents) { 21986 21987 var rows, timeParts, cues, i, j, thisRow, nextRow, content, blankRow; 21988 rows = contents.split("\n"); 21989 cues = []; 21990 i = 0; 21991 while (i < rows.length) { 21992 thisRow = rows[i]; 21993 if (thisRow.indexOf(' --> ') !== -1) { 21994 // this is probably a time row 21995 timeParts = thisRow.trim().split(' '); 21996 if (this.isValidTimestamp(timeParts[0]) && this.isValidTimestamp(timeParts[2])) { 21997 // both timestamps are valid. This is definitely a time row 21998 content = ''; 21999 j = i+1; 22000 blankRow = false; 22001 while (j < rows.length && !blankRow) { 22002 nextRow = rows[j].trim(); 22003 if (nextRow.length > 0) { 22004 if (content.length > 0) { 22005 // add back the EOL between rows of content 22006 content += "\n" + nextRow; 22007 } else { 22008 // this is the first row of content. No need for an EOL 22009 content += nextRow; 22010 } 22011 } else { 22012 blankRow = true; 22013 } 22014 j++; 22015 } 22016 cues.push({ 22017 'start': timeParts[0], 22018 'end': timeParts[2], 22019 'content': content 22020 }); 22021 i = j; //skip ahead 22022 } 22023 } else { 22024 i++; 22025 } 22026 } 22027 return cues; 22028 }; 22029 22030 AblePlayer.prototype.isValidTimestamp = function(timestamp) { 22031 22032 // return true if timestamp contains only numbers or expected punctuation 22033 return (/^[0-9:,.]*$/.test(timestamp)) ? true : false; 22034 }; 22035 22036 AblePlayer.prototype.formatTimestamp = function(timestamp) { 22037 22038 // timestamp is a string in the form "HH:MM:SS.xxx" 22039 // Take some simple steps to ensure edited timestamp values still adhere to expected format 22040 22041 var firstPart, lastPart; 22042 22043 firstPart = timestamp.substring(0,timestamp.lastIndexOf('.')+1); 22044 lastPart = timestamp.substring(timestamp.lastIndexOf('.')+1); 22045 22046 // TODO: Be sure each component within firstPart has only exactly two digits 22047 // Probably can't justify doing this automatically 22048 // If users enters '5' for minutes, that could be either '05' or '50' 22049 // This should trigger an error and prompt the user to correct the value before proceeding 22050 22051 // Be sure lastPart has exactly three digits 22052 if (lastPart.length > 3) { 22053 // chop off any extra digits 22054 lastPart = lastPart.substring(0,3); 22055 } else if (lastPart.length < 3) { 22056 // add trailing zeros 22057 while (lastPart.length < 3) { 22058 lastPart += '0'; 22059 } 22060 } 22061 return firstPart + lastPart; 22062 }; 22063 22064 22065 AblePlayer.prototype.injectVtsTable = function(action,lang) { 22066 22067 // action is either 'add' (for a new table) or 'update' (if user has selected a new lang) 22068 22069 var $table, $thead, headers, i, $tr, $th, $td, rows, rowNum, rowId; 22070 22071 if (action === 'update') { 22072 // remove existing table 22073 $('#able-vts table').remove(); 22074 $('#able-vts-icon-credit').remove(); 22075 } 22076 22077 $table = $('<table>',{ 22078 'lang': lang 22079 }); 22080 $thead = $( '<thead>' ); 22081 $tr = $( '<tr>' ); 22082 headers = [ 22083 this.translate( 'vtsRow', 'Row' ), 22084 this.translate( 'vtsKind', 'Kind' ), 22085 this.translate( 'vtsStart', 'Start' ), 22086 this.translate( 'vtsEnd', 'End' ), 22087 this.translate( 'vtsContent', 'Content' ), 22088 this.translate( 'vtsActions', 'Actions' ) 22089 ]; 22090 for (i=0; i < headers.length; i++) { 22091 $th = $('<th>', { 22092 'scope': 'col' 22093 }).text(headers[i]); 22094 if (headers[i] === 'Actions') { 22095 $th.addClass('actions'); 22096 } 22097 $tr.append($th); 22098 } 22099 $thead.append($tr); 22100 $table.append($thead); 22101 22102 // Get all rows (sorted by start time), and inject them into table 22103 rows = this.getAllRows(lang); 22104 for (i=0; i < rows.length; i++) { 22105 rowNum = i + 1; 22106 rowId = 'able-vts-row-' + rowNum; 22107 $tr = $('<tr>',{ 22108 'id': rowId, 22109 'class': 'kind-' + rows[i].kind 22110 }); 22111 // Row # 22112 $td = $('<td>').text(rowNum); 22113 $tr.append($td); 22114 22115 // Kind 22116 $td = $('<td>',{ 22117 'contenteditable': 'true' 22118 }).text(rows[i].kind); 22119 $tr.append($td); 22120 22121 // Start 22122 $td = $('<td>',{ 22123 'contenteditable': 'true' 22124 }).text(rows[i].start); 22125 $tr.append($td); 22126 22127 // End 22128 $td = $('<td>',{ 22129 'contenteditable': 'true' 22130 }).text(rows[i].end); 22131 $tr.append($td); 22132 22133 // Content 22134 $td = $('<td>',{ 22135 'contenteditable': 'true' 22136 }).text(rows[i].content); // TODO: Preserve tags 22137 $tr.append($td); 22138 22139 // Actions 22140 $td = this.addVtsActionButtons(rowNum,rows.length); 22141 $tr.append($td); 22142 22143 $table.append($tr); 22144 } 22145 $('#able-vts').append($table); 22146 22147 // Add credit for action button SVG icons 22148 $('#able-vts').append(this.getIconCredit()); 22149 22150 }; 22151 22152 AblePlayer.prototype.addVtsActionButtons = function(rowNum,numRows) { 22153 22154 // rowNum is the number of the current table row (starting with 1) 22155 // numRows is the total number of rows (excluding the header row) 22156 // TODO: Position buttons so they're vertically aligned, even if missing an Up or Down button 22157 var thisObj, $td, buttons, i, button, $button, $svg, $g, pathString, pathString2, $path, $path2; 22158 thisObj = this; 22159 $td = $('<td>'); 22160 buttons = ['up','down','insert','delete']; 22161 22162 for (i=0; i < buttons.length; i++) { 22163 button = buttons[i]; 22164 if (button === 'up') { 22165 if (rowNum > 1) { 22166 $button = $('<button>',{ 22167 'id': 'able-vts-button-up-' + rowNum, 22168 'title': 'Move up', 22169 'aria-label': 'Move Row ' + rowNum + ' up' 22170 }).on('click', function(el) { 22171 thisObj.onClickVtsActionButton(el.currentTarget); 22172 }); 22173 $svg = $('<svg>',{ 22174 'focusable': 'false', 22175 'aria-hidden': 'true', 22176 'x': '0px', 22177 'y': '0px', 22178 'width': '254.296px', 22179 'height': '254.296px', 22180 'viewBox': '0 0 254.296 254.296', 22181 'style': 'enable-background:new 0 0 254.296 254.296' 22182 }); 22183 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' 22184 + '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' 22185 + 'l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z'; 22186 $path = $('<path>',{ 22187 'd': pathString 22188 }); 22189 $g = $('<g>').append($path); 22190 $svg.append($g); 22191 $button.append($svg); 22192 // Refresh button in the DOM in order for browser to process & display the SVG 22193 $button.html($button.html()); 22194 $td.append($button); 22195 } 22196 } else if (button === 'down') { 22197 if (rowNum < numRows) { 22198 $button = $('<button>',{ 22199 'id': 'able-vts-button-down-' + rowNum, 22200 'title': 'Move down', 22201 'aria-label': 'Move Row ' + rowNum + ' down' 22202 }).on('click', function(el) { 22203 thisObj.onClickVtsActionButton(el.currentTarget); 22204 }); 22205 $svg = $('<svg>',{ 22206 'focusable': 'false', 22207 'aria-hidden': 'true', 22208 'x': '0px', 22209 'y': '0px', 22210 'width': '292.362px', 22211 'height': '292.362px', 22212 'viewBox': '0 0 292.362 292.362', 22213 'style': 'enable-background:new 0 0 292.362 292.362' 22214 }); 22215 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' 22216 + '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' 22217 + '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'; 22218 $path = $('<path>',{ 22219 'd': pathString 22220 }); 22221 $g = $('<g>').append($path); 22222 $svg.append($g); 22223 $button.append($svg); 22224 // Refresh button in the DOM in order for browser to process & display the SVG 22225 $button.html($button.html()); 22226 $td.append($button); 22227 } 22228 } else if (button === 'insert') { 22229 // Add Insert button to all rows 22230 $button = $('<button>',{ 22231 'id': 'able-vts-button-insert-' + rowNum, 22232 'title': 'Insert row below', 22233 'aria-label': 'Insert row before Row ' + rowNum 22234 }).on('click', function(el) { 22235 thisObj.onClickVtsActionButton(el.currentTarget); 22236 }); 22237 $svg = $('<svg>',{ 22238 'focusable': 'false', 22239 'aria-hidden': 'true', 22240 'x': '0px', 22241 'y': '0px', 22242 'width': '401.994px', 22243 'height': '401.994px', 22244 'viewBox': '0 0 401.994 401.994', 22245 'style': 'enable-background:new 0 0 401.994 401.994' 22246 }); 22247 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' 22248 + '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' 22249 + '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' 22250 + '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' 22251 + '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' 22252 + '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'; 22253 $path = $('<path>',{ 22254 'd': pathString 22255 }); 22256 $g = $('<g>').append($path); 22257 $svg.append($g); 22258 $button.append($svg); 22259 // Refresh button in the DOM in order for browser to process & display the SVG 22260 $button.html($button.html()); 22261 $td.append($button); 22262 } else if (button === 'delete') { 22263 // Add Delete button to all rows 22264 $button = $('<button>',{ 22265 'id': 'able-vts-button-delete-' + rowNum, 22266 'title': 'Delete row ', 22267 'aria-label': 'Delete Row ' + rowNum 22268 }).on('click', function(el) { 22269 thisObj.onClickVtsActionButton(el.currentTarget); 22270 }); 22271 $svg = $('<svg>',{ 22272 'focusable': 'false', 22273 'aria-hidden': 'true', 22274 'x': '0px', 22275 'y': '0px', 22276 'width': '508.52px', 22277 'height': '508.52px', 22278 'viewBox': '0 0 508.52 508.52', 22279 'style': 'enable-background:new 0 0 508.52 508.52' 22280 }); 22281 pathString = 'M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347' 22282 + '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' 22283 + 'C429.063,46.021,414.825,31.782,397.281,31.782z'; 22284 $path = $('<path>',{ 22285 'd': pathString 22286 }); 22287 pathString2 = 'M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042' 22288 + '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' 22289 + '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' 22290 + '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' 22291 + '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' 22292 + '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' 22293 + 'c-8.772,0-15.891-7.151-15.891-15.891V174.804z'; 22294 $path2 = $('<path>',{ 22295 'd': pathString2 22296 }); 22297 22298 $g = $('<g>').append($path,$path2); 22299 $svg.append($g); 22300 $button.append($svg); 22301 // Refresh button in the DOM in order for browser to process & display the SVG 22302 $button.html($button.html()); 22303 $td.append($button); 22304 } 22305 } 22306 return $td; 22307 }; 22308 22309 AblePlayer.prototype.updateVtsActionButtons = function($buttons,nextRowNum) { 22310 22311 // TODO: Add some filters to this function to add or delete 'Up' and 'Down' buttons 22312 // if row is moved to/from the first/last rows 22313 var i, $thisButton, id, label, newId, newLabel; 22314 for (i=0; i < $buttons.length; i++) { 22315 $thisButton = $buttons.eq(i); 22316 id = $thisButton.attr('id'); 22317 label = $thisButton.attr('aria-label'); 22318 // replace the integer (id) within each of the above strings 22319 newId = id.replace(/[0-9]+/g, nextRowNum); 22320 newLabel = label.replace(/[0-9]+/g, nextRowNum); 22321 $thisButton.attr('id',newId); 22322 $thisButton.attr('aria-label',newLabel); 22323 } 22324 }; 22325 22326 AblePlayer.prototype.getIconCredit = function() { 22327 22328 var credit 22329 = 'Action buttons made by <a target="_blank" rel="noreferrer" href="https://www.elegantthemes.com">Elegant Themes</a>' 22330 + ' from <a target="_blank" rel="noreferrer" href="https://www.flaticon.com">flaticon</a>' 22331 + ' are licensed by <a target="_blank" rel="noreferrer" href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC 3.0 BY</a>' 22332 ; 22333 return '<div id="able-vts-icon-credit">' + credit + '</div>'; 22334 }; 22335 22336 AblePlayer.prototype.getAllLangs = function(tracks) { 22337 22338 // update this.langs with any unique languages found in tracks 22339 var i; 22340 for (i in tracks) { 22341 if (Object.hasOwn(tracks[i], 'language')) { 22342 if ($.inArray(tracks[i].language,this.langs) === -1) { 22343 // this language is not already in the langs array. Add it. 22344 this.langs[this.langs.length] = tracks[i].language; 22345 } 22346 } 22347 } 22348 }; 22349 22350 AblePlayer.prototype.getAllRows = function(lang) { 22351 22352 // returns an array of data to be displayed in VTS table 22353 // includes all cues for tracks of any type with matching lang 22354 // cues are sorted by start time 22355 var i, track, c, cues; 22356 cues = []; 22357 for (i=0; i < this.vtsTracks.length; i++) { 22358 track = this.vtsTracks[i]; 22359 if (track.language == lang) { 22360 // this track matches the language. Add its cues to array 22361 for (c in track.cues) { 22362 cues.push({ 22363 'kind': track.kind, 22364 'lang': lang, 22365 'id': track.cues[c].id, 22366 'start': track.cues[c].start, 22367 'end': track.cues[c].end, 22368 'content': track.cues[c].content 22369 }); 22370 } 22371 } 22372 } 22373 // Now sort cues by start time 22374 cues.sort(function(a,b) { 22375 return a.start > b.start ? 1 : -1; 22376 }); 22377 return cues; 22378 }; 22379 22380 22381 AblePlayer.prototype.onClickVtsActionButton = function(el) { 22382 22383 // handle click on up, down, insert, or delete button 22384 var idParts, action, rowNum; 22385 idParts = $(el).attr('id').split('-'); 22386 action = idParts[3]; 22387 rowNum = idParts[4]; 22388 if (action == 'up') { 22389 // move the row up 22390 this.moveRow(rowNum,'up'); 22391 } else if (action == 'down') { 22392 // move the row down 22393 this.moveRow(rowNum,'down'); 22394 } else if (action == 'insert') { 22395 // insert a row below 22396 this.insertRow(rowNum); 22397 } else if (action == 'delete') { 22398 // delete the row 22399 this.deleteRow(rowNum); 22400 } 22401 }; 22402 22403 AblePlayer.prototype.insertRow = function(rowNum) { 22404 22405 // Insert empty row below rowNum 22406 var $table, $rows, numRows, newRowNum, newRowId, $tr, $td, $select, 22407 options, i, $option, newKind, newClass, $parentRow, nextRowNum, $buttons; 22408 22409 $table = $('#able-vts table'); 22410 $rows = $table.find('tr'); 22411 22412 numRows = $rows.length - 1; // exclude header row 22413 22414 newRowNum = parseInt(rowNum) + 1; 22415 newRowId = 'able-vts-row-' + newRowNum; 22416 22417 // Create an empty row 22418 $tr = $('<tr>',{ 22419 'id': newRowId 22420 }); 22421 22422 // Row # 22423 $td = $('<td>').text(newRowNum); 22424 $tr.append($td); 22425 22426 // Kind (add a select field for chosing a kind) 22427 newKind = null; 22428 $select = $('<select>',{ 22429 'id': 'able-vts-kind-' + newRowNum, 22430 'aria-label': 'What kind of track is this?', 22431 'placeholder': 'Select a kind' 22432 }).on('change',function() { 22433 newKind = $(this).val(); 22434 newClass = 'kind-' + newKind; 22435 $parentRow = $(this).closest('tr'); 22436 // replace the select field with the chosen value as text 22437 $(this).parent().text(newKind); 22438 // add a class to the parent row 22439 $parentRow.addClass(newClass); 22440 }); 22441 options = ['','captions','chapters','descriptions','subtitles']; 22442 for (i=0; i<options.length; i++) { 22443 $option = $('<option>',{ 22444 'value': options[i] 22445 }).text(options[i]); 22446 $select.append($option); 22447 } 22448 $td = $('<td>').append($select); 22449 $tr.append($td); 22450 22451 // Start 22452 $td = $('<td>',{ 22453 'contenteditable': 'true' 22454 }); // TODO; Intelligently assign a new start time (see getAdjustedTimes()) 22455 $tr.append($td); 22456 22457 // End 22458 $td = $('<td>',{ 22459 'contenteditable': 'true' 22460 }); // TODO; Intelligently assign a new end time (see getAdjustedTimes()) 22461 $tr.append($td); 22462 22463 // Content 22464 $td = $('<td>',{ 22465 'contenteditable': 'true' 22466 }); 22467 $tr.append($td); 22468 22469 // Actions 22470 $td = this.addVtsActionButtons(newRowNum,numRows); 22471 $tr.append($td); 22472 22473 // Now insert the new row 22474 $table.find('tr').eq(rowNum).after($tr); 22475 22476 // Update row.id, Row # cell, & action items for all rows after the inserted one 22477 for (i=newRowNum; i <= numRows; i++) { 22478 nextRowNum = i + 1; 22479 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 22480 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 22481 $buttons = $rows.eq(i).find('button'); 22482 this.updateVtsActionButtons($buttons,nextRowNum); 22483 } 22484 22485 // Auto-adjust times 22486 this.adjustTimes(newRowNum); 22487 22488 // Announce the insertion 22489 let newAlert = this.translate( 'vtsNewRow', 'A new row %1 has been inserted.', [ newRowNum ] ); 22490 this.showVtsAlert( newAlert ); 22491 22492 // Place focus in new select field 22493 $select.trigger('focus'); 22494 22495 }; 22496 22497 AblePlayer.prototype.deleteRow = function(rowNum) { 22498 22499 var $table, $rows, numRows, i, nextRowNum, $buttons; 22500 22501 $table = $('#able-vts table'); 22502 $table[0].deleteRow(rowNum); 22503 $rows = $table.find('tr'); // this does not include the deleted row 22504 numRows = $rows.length - 1; // exclude header row 22505 22506 // Update row.id, Row # cell, & action buttons for all rows after the deleted one 22507 for (i=rowNum; i <= numRows; i++) { 22508 nextRowNum = i; 22509 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 22510 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 22511 $buttons = $rows.eq(i).find('button'); 22512 this.updateVtsActionButtons($buttons,nextRowNum); 22513 } 22514 22515 // Announce the deletion 22516 let newAlert = this.translate( 'vtsDeletedRow', 'Row %1 has been deleted.', [ rowNum ] ); 22517 this.showVtsAlert( newAlert ); 22518 22519 }; 22520 22521 AblePlayer.prototype.moveRow = function(rowNum,direction) { 22522 22523 // swap two rows 22524 var $thisRow, otherRowNum, $otherRow, msg; 22525 22526 $thisRow = $('#able-vts table').find('tr').eq(rowNum); 22527 if (direction == 'up') { 22528 otherRowNum = parseInt(rowNum) - 1; 22529 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 22530 $otherRow.before($thisRow); 22531 } else if (direction == 'down') { 22532 otherRowNum = parseInt(rowNum) + 1; 22533 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 22534 $otherRow.after($thisRow); 22535 } 22536 // Update row.id, Row # cell, & action buttons for the two swapped rows 22537 $thisRow.attr('id','able-vts-row-' + otherRowNum); 22538 $thisRow.find('td').eq(0).text(otherRowNum); 22539 this.updateVtsActionButtons($thisRow.find('button'),otherRowNum); 22540 $otherRow.attr('id','able-vts-row-' + rowNum); 22541 $otherRow.find('td').eq(0).text(rowNum); 22542 this.updateVtsActionButtons($otherRow.find('button'),rowNum); 22543 22544 // auto-adjust times 22545 this.adjustTimes(otherRowNum); 22546 22547 // Announce the move 22548 msg = this.translate( 'vtsMovedRow', 'Row %1 has been moved %2 and is now Row %3.', [ rowNum, direction, otherRowNum ] ); 22549 this.showVtsAlert(msg); 22550 }; 22551 22552 AblePlayer.prototype.adjustTimes = function(rowNum) { 22553 22554 // Adjusts start and end times of the current, previous, and next rows in VTS table 22555 // after a move or insert 22556 // NOTE: Fully automating this process would be extraordinarily complicated 22557 // The goal here is simply to make subtle tweaks to ensure rows appear 22558 // in the new order within the Able Player transcript 22559 // Additional tweaking will likely be required by the user 22560 22561 // HISTORY: Originally set minDuration to 2 seconds for captions and .500 for descriptions 22562 // However, this can results in significant changes to existing caption timing, 22563 // with not-so-positive results. 22564 // As of 3.1.15, setting minDuration to .001 for all track kinds 22565 // Users will have to make further adjustments manually if needed 22566 22567 // TODO: Add WebVTT validation on save, since tweaking times is risky 22568 22569 var minDuration, $rows, prevRowNum, nextRowNum, $row, $prevRow, $nextRow, 22570 kind, prevKind, nextKind, 22571 start, prevStart, nextStart, 22572 end, prevEnd, nextEnd; 22573 22574 // Define minimum duration (in seconds) for each kind of track 22575 minDuration = []; 22576 minDuration['captions'] = .001; 22577 minDuration['descriptions'] = .001; 22578 minDuration['chapters'] = .001; 22579 22580 // refresh rows object 22581 $rows = $('#able-vts table').find('tr'); 22582 22583 // Get kind, start, and end from current row 22584 $row = $rows.eq(rowNum); 22585 // row has a class that starts with "kind-" 22586 // Extract kind from the class name 22587 kind = ($row.is('[class^="kind-"]')) ? this.getKindFromClass($row.attr('class')) : 'captions'; 22588 22589 start = this.getSecondsFromColonTime($row.find('td').eq(2).text()); 22590 end = this.getSecondsFromColonTime($row.find('td').eq(3).text()); 22591 22592 // Get kind, start, and end from previous row 22593 if (rowNum > 1) { 22594 // this is not the first row. Include the previous row 22595 prevRowNum = rowNum - 1; 22596 $prevRow = $rows.eq(prevRowNum); 22597 // row has a class that starts with "kind-" 22598 // Extract kind from the class name 22599 prevKind = ($prevRow.is('[class^="kind-"]')) ? this.getKindFromClass($prevRow.attr('class')) : null; 22600 prevStart = this.getSecondsFromColonTime($prevRow.find('td').eq(2).text()); 22601 prevEnd = this.getSecondsFromColonTime($prevRow.find('td').eq(3).text()); 22602 } else { 22603 // this is the first row 22604 $prevRow = null; 22605 prevKind = null; 22606 prevStart = null; 22607 prevEnd = null; 22608 } 22609 22610 // Get kind, start, and end from next row 22611 if (rowNum < ($rows.length - 1)) { 22612 // this is not the last row. Include the next row 22613 nextRowNum = rowNum + 1; 22614 $nextRow = $rows.eq(nextRowNum); 22615 // row has a class that starts with "kind-" 22616 // Extract kind from the class name 22617 nextKind = ($nextRow.is('[class^="kind-"]')) ? this.getKindFromClass($nextRow.attr('class')) : null; 22618 nextStart = this.getSecondsFromColonTime($nextRow.find('td').eq(2).text()); 22619 nextEnd = this.getSecondsFromColonTime($nextRow.find('td').eq(3).text()); 22620 } else { 22621 // this is the last row 22622 $nextRow = null; 22623 nextKind = null; 22624 nextStart = null; 22625 nextEnd = null; 22626 } 22627 22628 if (isNaN(start)) { 22629 if (prevKind == null) { 22630 // The previous row was probably inserted, and user has not yet selected a kind 22631 // automatically set it to captions 22632 prevKind = 'captions'; 22633 $prevRow.attr('class','kind-captions'); 22634 $prevRow.find('td').eq(1).html('captions'); 22635 } 22636 // Current row has no start time (i.e., it's an inserted row) 22637 if (prevKind === 'captions') { 22638 // start the new row immediately after the captions end 22639 start = (parseFloat(prevEnd) + .001).toFixed(3); 22640 // end the new row immediately before the next row starts 22641 end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3); 22642 } else if (prevKind === 'chapters') { 22643 // start the new row immediately after the chapter start (not end) 22644 start = (parseFloat(prevStart) + .001).toFixed(3); 22645 // end the new row immediately before the next row starts 22646 end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3); 22647 } else if (prevKind === 'descriptions') { 22648 // start the new row minDuration['descriptions'] after the description starts 22649 // this will theoretically allow at least a small cushion for the description to be read 22650 start = (parseFloat(prevStart) + minDuration['descriptions']).toFixed(3); 22651 end = (parseFloat(start) + minDuration['descriptions']).toFixed(3); 22652 } 22653 } else { 22654 // current row has a start time (i.e., an existing row has been moved)) 22655 if (prevStart) { 22656 // this is not the first row. 22657 if (prevStart < start) { 22658 if (start < nextStart) ; else { 22659 // nextStart needs to be incremented 22660 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 22661 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 22662 // TODO: Ensure nextEnd does not exceed the following start (nextNextStart) 22663 // Or... maybe this is getting too complicated and should be left up to the user 22664 } 22665 } else { 22666 // start needs to be incremented 22667 start = (parseFloat(prevStart) + minDuration[prevKind]).toFixed(3); 22668 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 22669 } 22670 } else { 22671 // this is the first row 22672 if (start < nextStart) ; else { 22673 // nextStart needs to be incremented 22674 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 22675 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 22676 } 22677 } 22678 } 22679 22680 // check to be sure there is sufficient duration between new start & end times 22681 if (end - start < minDuration[kind]) { 22682 // duration is too short. Change end time 22683 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 22684 if (nextStart) { 22685 // this is not the last row 22686 // increase start time of next row 22687 nextStart = (parseFloat(end) + .001).toFixed(3); 22688 } 22689 } 22690 22691 // Update all affected start/end times 22692 $row.find('td').eq(2).text(this.formatSecondsAsColonTime(start,true)); 22693 $row.find('td').eq(3).text(this.formatSecondsAsColonTime(end,true)); 22694 if ($prevRow) { 22695 $prevRow.find('td').eq(2).text(this.formatSecondsAsColonTime(prevStart,true)); 22696 $prevRow.find('td').eq(3).text(this.formatSecondsAsColonTime(prevEnd,true)); 22697 } 22698 if ($nextRow) { 22699 $nextRow.find('td').eq(2).text(this.formatSecondsAsColonTime(nextStart,true)); 22700 $nextRow.find('td').eq(3).text(this.formatSecondsAsColonTime(nextEnd,true)); 22701 } 22702 }; 22703 22704 AblePlayer.prototype.getKindFromClass = function(myclass) { 22705 22706 // This function is called when a class with prefix "kind-" is found in the class attribute 22707 22708 var kindStart, kindEnd; 22709 22710 kindStart = myclass.indexOf('kind-')+5; 22711 kindEnd = myclass.indexOf(' ',kindStart); 22712 if (kindEnd == -1) { 22713 // no spaces found, "kind-" must be the only myclass 22714 return myclass.substring(kindStart); 22715 } else { 22716 // kind-* is one of multiple classes 22717 // the following will find it regardless of position of "kind-*" within the class string 22718 return myclass.substring(kindStart,kindEnd); 22719 } 22720 }; 22721 22722 AblePlayer.prototype.showVtsAlert = function(message) { 22723 22724 // this is distinct from greater Able Player showAlert() 22725 // because it's positioning needs are unique 22726 // For now, alertDiv is fixed at top left of screen 22727 // but could ultimately be modified to appear near the point of action in the VTS table 22728 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 22729 this.$vtsAlert.text(message).show(); 22730 delay(10000).then(() => { 22731 this.$vtsAlert.text(message).hide(); 22732 }); 22733 }; 22734 22735 AblePlayer.prototype.parseVtsOutput = function($table) { 22736 22737 // parse table into arrays, then into WebVTT content, for each kind 22738 // Display the WebVTT content in textarea fields for users to copy and paste 22739 var lang, i, kinds, kind, vtt, $rows, start, end, content, $output; 22740 22741 lang = $table.attr('lang'); 22742 kinds = ['captions','chapters','descriptions','subtitles']; 22743 vtt = {}; 22744 for (i=0; i < kinds.length; i++) { 22745 kind = kinds[i]; 22746 vtt[kind] = 'WEBVTT' + "\n\n"; 22747 } 22748 $rows = $table.find('tr'); 22749 if ($rows.length > 0) { 22750 for (i=0; i < $rows.length; i++) { 22751 kind = $rows.eq(i).find('td').eq(1).text(); 22752 if ($.inArray(kind,kinds) !== -1) { 22753 start = $rows.eq(i).find('td').eq(2).text(); 22754 end = $rows.eq(i).find('td').eq(3).text(); 22755 content = $rows.eq(i).find('td').eq(4)[0].innerText; 22756 if (start !== undefined && end !== undefined) { 22757 vtt[kind] += start + ' --> ' + end + "\n"; 22758 if (content !== 'undefined') { 22759 vtt[kind] += content; 22760 } 22761 vtt[kind] += "\n\n"; 22762 } 22763 } 22764 } 22765 } 22766 $output = $('<div>',{ 22767 'id': 'able-vts-output' 22768 }); 22769 $('#able-vts').append($output); 22770 for (i=0; i < kinds.length; i++) { 22771 kind = kinds[i]; 22772 if (vtt[kind].length > 8) { 22773 // some content has been added 22774 this.showWebVttOutput(kind,vtt[kind],lang); 22775 } 22776 } 22777 }; 22778 22779 AblePlayer.prototype.showWebVttOutput = function(kind,vttString,lang) { 22780 22781 var $heading, filename, $p, pText, $textarea; 22782 22783 $heading = $('<h3>').text( this.capitalizeFirstLetter( kind ) ); 22784 filename = this.getFilenameFromTracks(kind,lang); 22785 pText = 'If you made changes, copy/paste the following content '; 22786 if (filename) { 22787 pText += 'to replace the original content of your ' + this.getLanguageName(lang) + ' '; 22788 pText += '<em>' + kind + '</em> WebVTT file (<strong>' + filename + '</strong>).'; 22789 } else { 22790 pText += 'into a new ' + this.getLanguageName(lang) + ' <em>' + kind + '</em> WebVTT file.'; 22791 } 22792 $p = $('<p>',{ 22793 'class': 'able-vts-output-instructions' 22794 }).html(pText); 22795 $textarea = $('<textarea>').text(vttString); 22796 $('#able-vts-output').append($heading,$p,$textarea); 22797 }; 22798 22799 } 22800 22801 function addWebvttFunctions(AblePlayer) { 22802 // See section 4.1 of dev.w3.org/html5/webvtt for format details. 22803 AblePlayer.prototype.parseWebVTT = function(data) { 22804 22805 let srcFile = data.src; 22806 let text = data.text; 22807 // Normalize line ends to \n. 22808 text = text.replace(/(\r\n|\n|\r)/g,'\n'); 22809 22810 var parserState = { 22811 src: srcFile, 22812 text: text, 22813 error: null, 22814 metadata: {}, 22815 cues: [], 22816 line: 1, 22817 column: 1 22818 }; 22819 22820 try { 22821 act(parserState, parseFileBody); 22822 } 22823 catch (err) { 22824 var errString = 'Invalid WebVTT file: ' + parserState.src + '\n'; 22825 errString += 'Line: ' + parserState.line + ', '; 22826 errString += 'Column: ' + parserState.column + '\n'; 22827 errString += err; 22828 if (console.warn) { 22829 console.warn(errString); 22830 } else if (console.log) { 22831 console.log(errString); 22832 } 22833 } 22834 return parserState; 22835 }; 22836 22837 function actList(state, list) { 22838 var results = []; 22839 for (var ii = 0; ii < list.length; ii++) { 22840 results.push(act(state, list[ii])); 22841 } 22842 return results; 22843 } 22844 22845 // Applies the action and checks for errors. 22846 function act(state, action) { 22847 var val = action(state); 22848 if (state.error !== null) { 22849 throw state.error; 22850 } 22851 return val; 22852 } 22853 22854 function updatePosition(state, cutText) { 22855 for (var ii = 0; ii < cutText.length; ii++) { 22856 if (cutText[ii] === '\n') { 22857 state.column = 1; 22858 state.line += 1; 22859 } else { 22860 state.column += 1; 22861 } 22862 } 22863 } 22864 22865 function cut(state, length) { 22866 var returnText = state.text.substring(0, length); 22867 updatePosition(state, returnText); 22868 state.text = state.text.substring(length); 22869 return returnText; 22870 } 22871 22872 function cutLine(state) { 22873 var nextEOL = state.text.indexOf('\n'); 22874 var returnText; 22875 if (nextEOL === -1) { 22876 returnText = state.text; 22877 updatePosition(state, returnText); 22878 state.text = ''; 22879 } else { 22880 returnText = state.text.substring(0, nextEOL); 22881 updatePosition(state, returnText + '\n'); 22882 state.text = state.text.substring(nextEOL + 1); 22883 } 22884 return returnText; 22885 } 22886 22887 function peekLine(state) { 22888 var nextEOL = state.text.indexOf('\n'); 22889 return (nextEOL === -1) ? state.text : state.text.substring(0, nextEOL); 22890 } 22891 22892 function parseFileBody(state) { 22893 actList(state, [ 22894 eatOptionalBOM, 22895 eatSignature]); 22896 var c = state.text[0]; 22897 if (c === ' ' || c === '\t' || c === '\n') { 22898 actList(state, [ 22899 eatUntilEOLInclusive, 22900 parseMetadataHeaders, 22901 eatAtLeast1EmptyLines, 22902 parseCuesAndComments]); 22903 } else { 22904 state.error = "WEBVTT signature not followed by whitespace."; 22905 } 22906 } 22907 22908 // Parses all metadata headers until a cue is discovered. 22909 function parseMetadataHeaders(state) { 22910 while (true) { 22911 var nextLine = peekLine(state); 22912 if (nextLine.indexOf('-->') !== -1) { 22913 return; 22914 } else if (nextLine.length === 0) { 22915 return; 22916 } else { 22917 var keyValue = act(state, getMetadataKeyValue); 22918 state.metadata[keyValue[0]] = keyValue[1]; 22919 act(state, eatUntilEOLInclusive); 22920 } 22921 } 22922 } 22923 22924 function nextSpaceOrNewline(s) { 22925 var possible = []; 22926 var spaceIndex = s.indexOf(' '); 22927 if (spaceIndex >= 0) { 22928 possible.push(spaceIndex); 22929 } 22930 var tabIndex = s.indexOf('\t'); 22931 if (tabIndex >= 0) { 22932 possible.push(tabIndex); 22933 } 22934 var lineIndex = s.indexOf('\n'); 22935 if (lineIndex >= 0) { 22936 possible.push(lineIndex); 22937 } 22938 22939 return Math.min.apply(null, possible); 22940 } 22941 22942 function getMetadataKeyValue(state) { 22943 var next = state.text.indexOf('\n'); 22944 var pair = cut(state, next); 22945 var colon = pair.indexOf(':'); 22946 if (colon === -1) { 22947 state.error = 'Missing colon.'; 22948 return; 22949 } else { 22950 var pairName = pair.substring(0, colon); 22951 var pairValue = pair.substring(colon + 1); 22952 return [pairName, pairValue]; 22953 } 22954 } 22955 22956 function getSettingsKeyValue(state) { 22957 var next = nextSpaceOrNewline(state.text); 22958 var pair = cut(state, next); 22959 var colon = pair.indexOf(':'); 22960 if (colon === -1) { 22961 state.error = 'Missing colon.'; 22962 return; 22963 } else { 22964 var pairName = pair.substring(0, colon); 22965 var pairValue = pair.substring(colon + 1); 22966 return [pairName, pairValue]; 22967 } 22968 } 22969 22970 function parseCuesAndComments(state) { 22971 while (true) { 22972 var nextLine = peekLine(state); 22973 // If NOTE is not on a line all its own, it must be followed by a space or tab. 22974 if (nextLine.indexOf('NOTE') === 0 && ((nextLine.length === 4) || (nextLine[4] === ' ') || (nextLine[4] === '\t'))) { 22975 actList(state, [eatComment, eatEmptyLines]); 22976 } else if (nextLine.trim().length === 0 && state.text.length > 0) { 22977 act(state, eatEmptyLines); 22978 } else if (nextLine.trim().length > 0) { 22979 act(state, parseCue); 22980 } else { 22981 // Everythings parsed! 22982 return; 22983 } 22984 } 22985 } 22986 22987 function parseCue(state) { 22988 22989 var nextLine = peekLine(state); 22990 var cueId; 22991 var errString; 22992 22993 if(nextLine.indexOf('-->') === -1) { 22994 cueId = cutLine(state); 22995 nextLine = peekLine(state); 22996 if(nextLine.indexOf('-->') === -1) { 22997 errString = 'Invalid WebVTT file: ' + state.src + '\n'; 22998 errString += 'Line: ' + state.line + ', '; 22999 errString += 'Column: ' + state.column + '\n'; 23000 errString += 'Expected cue timing for cueId \''+cueId+'\' but found: ' + nextLine + '\n'; 23001 if (console.warn) { 23002 console.warn(errString); 23003 } else if (console.log) { 23004 console.log(errString); 23005 } 23006 return; // Return leaving line for parseCuesAndComments to handle 23007 } 23008 } 23009 23010 var cueTimings = actList(state, [getTiming, 23011 eatAtLeast1SpacesOrTabs, 23012 eatArrow, 23013 eatAtLeast1SpacesOrTabs, 23014 getTiming]); 23015 23016 var startTime = cueTimings[0]; 23017 var endTime = cueTimings[4]; 23018 if (startTime >= endTime) { 23019 state.error = 'Start time is not sooner than end time.'; 23020 return; 23021 } 23022 23023 act(state, eatSpacesOrTabs); 23024 var cueSettings = act(state, getCueSettings); 23025 // Cut the newline. 23026 cut(state, 1); 23027 var components = act(state, getCuePayload); 23028 23029 if (typeof cueId === 'undefined') { 23030 cueId = state.cues.length + 1; 23031 } 23032 state.cues.push({ 23033 id: cueId, 23034 start: startTime, 23035 end: endTime, 23036 settings: cueSettings, 23037 components: components 23038 }); 23039 } 23040 23041 function getCueSettings(state) { 23042 var cueSettings = {}; 23043 while (state.text.length > 0 && state.text[0] !== '\n') { 23044 var keyValue = act(state, getSettingsKeyValue); 23045 cueSettings[keyValue[0]] = keyValue[1]; 23046 act(state, eatSpacesOrTabs); 23047 } 23048 return cueSettings; 23049 } 23050 23051 function getCuePayload(state) { 23052 // Parser based on instructions in draft. 23053 var result = {type: 'internal', tagName: '', value: '', classes: [], annotation: '', parent: null, children: [], language: ''}; 23054 var current = result; 23055 var languageStack = []; 23056 while (state.text.length > 0) { 23057 var nextLine = peekLine(state); 23058 if (nextLine.indexOf('-->') !== -1 || /^\s+$/.test(nextLine)) { 23059 break; // Handle empty cues 23060 } 23061 // Have to separately detect double-lines ending cue due to our non-standard parsing. 23062 // TODO: Redo outer algorithm to conform to W3 spec? 23063 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 23064 cut(state, 2); 23065 break; 23066 } 23067 23068 var token = getCueToken(state); 23069 // We'll use the tokens themselves as objects where possible. 23070 if (token.type === 'string') { 23071 current.children.push(token); 23072 } else if (token.type === 'startTag') { 23073 token.type = token.tagName; 23074 // Define token.parent; added by Terrill to fix bug end 'endTag' loop 23075 token.parent = current; 23076 if ($.inArray(token.tagName, ['i', 'b', 'u', 'ruby']) !== -1) { 23077 if (languageStack.length > 0) { 23078 current.language = languageStack[languageStack.length - 1]; 23079 } 23080 current.children.push(token); 23081 current = token; 23082 } else if (token.tagName === 'rt' && current.tagName === 'ruby') { 23083 if (languageStack.length > 0) { 23084 current.language = languageStack[languageStack.length - 1]; 23085 } 23086 current.children.push(token); 23087 current = token; 23088 } else if (token.tagName === 'c') { 23089 token.value = token.annotation; 23090 if (languageStack.length > 0) { 23091 current.language = languageStack[languageStack.length - 1]; 23092 } 23093 current.children.push(token); 23094 current = token; 23095 } else if (token.tagName === 'v') { 23096 token.value = token.annotation; 23097 if (languageStack.length > 0) { 23098 current.language = languageStack[languageStack.length - 1]; 23099 } 23100 current.children.push(token); 23101 current = token; 23102 } else if (token.tagName === 'lang') { 23103 languageStack.push(token.annotation); 23104 if (languageStack.length > 0) { 23105 current.language = languageStack[languageStack.length - 1]; 23106 } 23107 current.children.push(token); 23108 current = token; 23109 } 23110 } else if (token.type === 'endTag') { 23111 if (token.tagName === current.type && $.inArray(token.tagName, ['c', 'i', 'b', 'u', 'ruby', 'rt', 'v']) !== -1) { 23112 // NOTE from Terrill: This was resulting in an error because current.parent was undefined 23113 // Fixed (I think) by assigning current token to token.parent in 'startTag' loop 23114 current = current.parent; 23115 } else if (token.tagName === 'lang' && current.type === 'lang') { 23116 current = current.parent; 23117 languageStack.pop(); 23118 } else if (token.tagName === 'ruby' && current.type === 'rt') { 23119 current = current.parent.parent; 23120 } 23121 } else if (token.type === 'timestampTag') { 23122 var tempState = { 23123 text: token.value, 23124 error: null, 23125 metadata: {}, 23126 cues: [], 23127 line: 1, 23128 column: 1 23129 }; 23130 try { 23131 var timing = act(tempState, getTiming); 23132 if (tempState.text.length === 0) { 23133 token.value = timing; 23134 current.push(token); 23135 } 23136 } 23137 catch (e) { 23138 // Errors are suppressed here, not sure why - VP 2026-03-02 23139 } 23140 } 23141 } 23142 return result; 23143 } 23144 23145 // Gets a single cue token; uses the method in the w3 specification. 23146 function getCueToken(state) { 23147 var tokenState = 'data'; 23148 var result = []; 23149 var buffer = ''; 23150 var token = {type: '', tagName: '', value: '', classes: [], annotation: '', children: []}; 23151 23152 while (true) { 23153 var c; 23154 // Double newlines indicate end of token. 23155 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 23156 c = '\u0004'; 23157 } else if (state.text.length > 0) { 23158 c = state.text[0]; 23159 } else { 23160 // End of file. 23161 c = '\u0004'; 23162 } 23163 if (tokenState === 'data') { 23164 if (c === '&') { 23165 buffer = '&'; 23166 tokenState = 'escape'; 23167 } else if (c === '<') { 23168 if (result.length === 0) { 23169 tokenState = 'tag'; 23170 } else { 23171 token.type = 'string'; 23172 token.value = result.join(''); 23173 return token; 23174 } 23175 } else if (c === '\u0004') { 23176 return {type: 'string', value: result.join('')}; 23177 } else { 23178 result.push(c); 23179 } 23180 } else if (tokenState === 'escape') { 23181 if (c === '&') { 23182 result.push(buffer); 23183 buffer = '&'; 23184 } else if (c.match(/[0-9a-z]/)) { 23185 buffer += c; 23186 } else if (c === ';') { 23187 if (buffer === '&') { 23188 result.push('&'); 23189 } else if (buffer === '<') { 23190 result.push('<'); 23191 } else if (buffer === '>') { 23192 result.push('>'); 23193 } else if (buffer === '&lrm') { 23194 result.push('\u200e'); 23195 } else if (buffer === '&rlm') { 23196 result.push('\u200f'); 23197 } else if (buffer === ' ') { 23198 result.push('\u00a0'); 23199 } else { 23200 result.push(buffer); 23201 result.push(';'); 23202 } 23203 tokenState = 'data'; 23204 } else if (c === '<' || c === '\u0004') { 23205 result.push(buffer); 23206 token.type = 'string'; 23207 token.value = result.join(''); 23208 return token; 23209 } else if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { // Handle unescaped & chars as strings 23210 result.push(buffer); 23211 token.type = 'string'; 23212 token.value = result.join(''); 23213 return token; 23214 } else { 23215 result.push(buffer); 23216 tokenState = 'data'; 23217 } 23218 } else if (tokenState === 'tag') { 23219 if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { 23220 tokenState = 'startTagAnnotation'; 23221 } else if (c === '.') { 23222 tokenState = 'startTagClass'; 23223 } else if (c === '/') { 23224 tokenState = 'endTag'; 23225 } else if (c.match('[0-9]')) { 23226 tokenState = 'timestampTag'; 23227 result.push(c); 23228 } else if (c === '>') { 23229 cut(state, 1); 23230 break; 23231 } else if (c === '\u0004') { 23232 token.tagName = ''; 23233 token.type = 'startTag'; 23234 return token; 23235 } else { 23236 result.push(c); 23237 tokenState = 'startTag'; 23238 } 23239 } else if (tokenState === 'startTag') { 23240 if (c === '\t' || c === '\u000c' || c === ' ') { 23241 tokenState = 'startTagAnnotation'; 23242 } else if (c === '\n') { 23243 buffer = c; 23244 tokenState = 'startTagAnnotation'; 23245 } else if (c === '.') { 23246 tokenState = 'startTagClass'; 23247 } else if (c === '>') { 23248 cut(state, 1); 23249 token.tagName = result.join(''); 23250 token.type = 'startTag'; 23251 return token; 23252 } else if (c === '\u0004') { 23253 token.tagName = result.join(''); 23254 token.type = 'startTag'; 23255 return token; 23256 } else { 23257 result.push(c); 23258 } 23259 } else if (tokenState === 'startTagClass') { 23260 if (c === '\t' || c === '\u000c' || c === ' ') { 23261 token.classes.push(buffer); 23262 buffer = ''; 23263 tokenState = 'startTagAnnotation'; 23264 } else if (c === '\n') { 23265 token.classes.push(buffer); 23266 buffer = c; 23267 tokenState = 'startTagAnnotation'; 23268 } else if (c === '.') { 23269 token.classes.push(buffer); 23270 buffer = ""; 23271 } else if (c === '>') { 23272 cut(state, 1); 23273 token.classes.push(buffer); 23274 token.type = 'startTag'; 23275 token.tagName = result.join(''); 23276 return token; 23277 } else if (c === '\u0004') { 23278 token.classes.push(buffer); 23279 token.type = 'startTag'; 23280 token.tagName = result.join(''); 23281 return token; 23282 } else { 23283 buffer += 'c'; 23284 } 23285 } else if (tokenState === 'startTagAnnotation') { 23286 if (c === '>') { 23287 cut(state, 1); 23288 buffer = buffer.trim().replace(/ +/, ' '); 23289 token.type = 'startTag'; 23290 token.tagName = result.join(''); 23291 token.annotation = buffer; 23292 return token; 23293 } else if (c === '\u0004') { 23294 buffer = buffer.trim().replace(/ +/, ' '); 23295 token.type = 'startTag'; 23296 token.tagName = result.join(''); 23297 token.annotation = buffer; 23298 return token; 23299 } else { 23300 buffer += c; 23301 } 23302 } else if (tokenState === 'endTag') { 23303 if (c === '>') { 23304 cut(state, 1); 23305 token.type = 'endTag'; 23306 token.tagName = result.join(''); 23307 return token; 23308 } else if (c === '\u0004') { 23309 token.type = 'endTag'; 23310 token.tagName = result.join(''); 23311 return token; 23312 } else { 23313 result.push(c); 23314 } 23315 } else if (tokenState === 'timestampTag') { 23316 if (c === '>') { 23317 cut(state, 1); 23318 token.type = 'timestampTag'; 23319 token.name = result.join(''); 23320 return token; 23321 } else if (c === '\u0004') { 23322 token.type = 'timestampTag'; 23323 token.name = result.join(''); 23324 return token; 23325 } else { 23326 result.push(c); 23327 } 23328 } else { 23329 throw 'Unknown tokenState ' + tokenState; 23330 } 23331 23332 cut(state, 1); 23333 } 23334 } 23335 23336 function eatComment(state) { 23337 // Cut the NOTE line. 23338 var noteLine = cutLine(state); 23339 if (noteLine.indexOf('-->') !== -1) { 23340 state.error = 'Invalid syntax: --> in NOTE line.'; 23341 return; 23342 } 23343 while (true) { 23344 var nextLine = peekLine(state); 23345 if ( nextLine.trim().length === 0) { 23346 // End of comment. 23347 return; 23348 } else if (nextLine.indexOf('-->') !== -1) { 23349 state.error = 'Invalid syntax: --> in comment.'; 23350 return; 23351 } else { 23352 cutLine(state); 23353 } 23354 } 23355 } 23356 23357 // Initial byte order mark. 23358 function eatOptionalBOM(state) { 23359 if (state.text[0] === '\ufeff') { 23360 cut(state, 1); 23361 } 23362 23363 } 23364 23365 // "WEBVTT" string. 23366 function eatSignature(state) { 23367 if (state.text.substring(0,6) === 'WEBVTT') { 23368 cut(state, 6); 23369 } else { 23370 state.error = 'Invalid signature.'; 23371 } 23372 } 23373 23374 function eatArrow(state) { 23375 if (state.text.length < 3 || state.text.substring(0,3) !== '-->') { 23376 state.error = 'Missing -->'; 23377 } else { 23378 cut(state, 3); 23379 } 23380 } 23381 23382 function eatSpacesOrTabs(state) { 23383 while (state.text[0] === '\t' || state.text[0] === ' ') { 23384 cut(state, 1); 23385 } 23386 } 23387 23388 function eatAtLeast1SpacesOrTabs(state) { 23389 var numEaten = 0; 23390 while (state.text[0] === '\t' || state.text[0] === ' ') { 23391 cut(state, 1); 23392 numEaten += 1; 23393 } 23394 if (numEaten === 0) { 23395 state.error = 'Missing space.'; 23396 } 23397 } 23398 23399 function eatUntilEOLInclusive(state) { 23400 var nextEOL = state.text.indexOf('\n'); 23401 if (nextEOL === -1) { 23402 state.error = 'Missing EOL.'; 23403 } else { 23404 cut(state, nextEOL + 1); 23405 } 23406 } 23407 23408 function eatEmptyLines(state) { 23409 while (state.text.length > 0) { 23410 var nextLine = peekLine(state); 23411 if ( nextLine.trim().length === 0) { 23412 cutLine(state); 23413 } else { 23414 break; 23415 } 23416 } 23417 } 23418 23419 // Eats empty lines, but throws an error if there's not at least one. 23420 function eatAtLeast1EmptyLines(state) { 23421 var linesEaten = 0; 23422 while (state.text.length > 0) { 23423 var nextLine = peekLine(state); 23424 if ( nextLine.trim().length === 0) { 23425 cutLine(state); 23426 linesEaten += 1; 23427 } else { 23428 break; 23429 } 23430 } 23431 if (linesEaten === 0) { 23432 state.error = 'Missing empty line.'; 23433 } 23434 } 23435 23436 function getTiming(state) { 23437 var nextSpace = nextSpaceOrNewline(state.text); 23438 if (nextSpace === -1) { 23439 state.error('Missing timing.'); 23440 return; 23441 } 23442 var timestamp = cut(state, nextSpace); 23443 23444 // The spec requires exactly 2 characters for minutes and seconds, and 2+ for hours, 23445 // but some VTT generation creates 1 digit hour times (e.g. "1:02:24.000 --> 1:04:48.000") and it seems harmless to allow that here 23446 var results = /((\d+):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp); 23447 23448 if (!results) { 23449 state.error = 'Unable to parse timestamp'; 23450 return; 23451 } 23452 var time = 0; 23453 var hours = results[2]; 23454 var minutes = results[4]; 23455 23456 if (minutes) { 23457 if (parseInt(minutes, 10) > 59) { 23458 state.error = 'Invalid minute range'; 23459 return; 23460 } 23461 if (hours) { 23462 time += 3600 * parseInt(hours, 10); 23463 } 23464 time += 60 * parseInt(minutes, 10); 23465 var seconds = results[5]; 23466 if (parseInt(seconds, 10) > 59) { 23467 state.error = 'Invalid second range'; 23468 return; 23469 } 23470 23471 time += parseInt(seconds, 10); 23472 time += parseInt(results[6], 10) / 1000; 23473 } else { 23474 time += parseInt(results[7], 10); 23475 time += parseInt(results[8], 10) / 1000; 23476 } 23477 23478 return time; 23479 } 23480 } 23481 23482 /* global YT */ 23483 23484 function addYoutubeFunctions(AblePlayer) { 23485 23486 AblePlayer.prototype.initYouTubePlayer = function () { 23487 23488 var thisObj, deferred, promise, youTubeId; 23489 thisObj = this; 23490 deferred = new this.defer(); 23491 promise = deferred.promise(); 23492 23493 this.youTubePlayerReady = false; 23494 23495 // if a described version is available && user prefers desription 23496 // init player using the described version 23497 youTubeId = (this.youTubeDescId && this.prefDesc) ? this.youTubeDescId : this.youTubeId; 23498 23499 this.activeYouTubeId = youTubeId; 23500 if (AblePlayer.youTubeIframeAPIReady) { 23501 // Script already loaded and ready. 23502 thisObj.finalizeYoutubeInit().then(function() { 23503 deferred.resolve(); 23504 }); 23505 } else { 23506 // Has another player already started loading the script? If so, abort... 23507 if (!AblePlayer.loadingYouTubeIframeAPI) { 23508 thisObj.getScript('https://www.youtube.com/iframe_api', function () { 23509 console.log( 'YouTube API loaded' ); 23510 }); 23511 } 23512 23513 // Otherwise, keeping waiting for script load event... 23514 $('body').on('youTubeIframeAPIReady', function () { 23515 thisObj.finalizeYoutubeInit().then(function() { 23516 deferred.resolve(); 23517 }); 23518 }); 23519 } 23520 return promise; 23521 }; 23522 23523 AblePlayer.prototype.finalizeYoutubeInit = function () { 23524 23525 // This is called once we're sure the Youtube iFrame API is loaded -- see above 23526 var deferred, promise, thisObj, containerId, ccLoadPolicy, autoplay; 23527 23528 deferred = new this.defer(); 23529 promise = deferred.promise(); 23530 thisObj = this; 23531 containerId = this.mediaId + '_youtube'; 23532 23533 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 23534 23535 // cc_load_policy: 23536 // 0 - show captions depending on user's preference on YouTube 23537 // 1 - show captions by default, even if the user has turned them off 23538 // IMPORTANT: This *must* be set to 1 or some browsers 23539 // fail to load any text tracks (observed in Chrome, not in Firefox) 23540 ccLoadPolicy = 1; 23541 autoplay = (this.okToPlay) ? 1 : 0; 23542 23543 // Documentation https://developers.google.com/youtube/player_parameters 23544 23545 if (typeof this.captionLang == 'undefined') { 23546 // init using the default player lang 23547 this.captionLang = this.lang; 23548 } 23549 this.youTubePlayer = new YT.Player(containerId, { 23550 videoId: this.activeYouTubeId, 23551 host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com', 23552 playerVars: { 23553 autoplay: autoplay, 23554 cc_lang_pref: this.captionLang, // set the caption language 23555 cc_load_policy: ccLoadPolicy, 23556 controls: 0, // no controls, using our own 23557 disableKb: 1, // disable keyboard shortcuts, using our own 23558 enablejsapi: 1, 23559 hl: this.lang, // set the UI language to match Able Player 23560 iv_load_policy: 3, // do not show video annotations 23561 origin: window.location.origin, 23562 playsinline: this.playsInline, 23563 rel: 0, // when video ends, show only related videos from same channel (1 shows any) 23564 start: this.startTime 23565 }, 23566 events: { 23567 onReady: function () { 23568 thisObj.youTubePlayerReady = true; 23569 if (!thisObj.playerWidth || !thisObj.playerHeight) { 23570 thisObj.getYouTubeDimensions(); 23571 } 23572 if (thisObj.playerWidth && thisObj.playerHeight) { 23573 thisObj.youTubePlayer.setSize(thisObj.playerWidth,thisObj.playerHeight); 23574 } 23575 if (thisObj.swappingSrc) { 23576 // swap is now complete 23577 thisObj.swappingSrc = false; 23578 thisObj.restoreFocus(); 23579 thisObj.cueingPlaylistItem = false; 23580 if (thisObj.playing || thisObj.okToPlay) { 23581 // resume playing 23582 thisObj.playMedia(); 23583 } 23584 } 23585 if (thisObj.userClickedPlaylist) { 23586 thisObj.userClickedPlaylist = false; // reset 23587 } 23588 if (thisObj.recreatingPlayer) { 23589 thisObj.recreatingPlayer = false; // reset 23590 } 23591 deferred.resolve(); 23592 }, 23593 onError: function (e) { 23594 deferred.reject(); 23595 }, 23596 onStateChange: function (e) { 23597 thisObj.getPlayerState().then(function(playerState) { 23598 // values of playerState: 'playing','paused','buffering','ended' 23599 if (playerState === 'playing') { 23600 if (thisObj.hasSignLanguage && thisObj.signVideo) { 23601 thisObj.signVideo.play(true); 23602 } 23603 thisObj.playing = true; 23604 thisObj.startedPlaying = true; 23605 thisObj.paused = false; 23606 } else if (playerState == 'ended') { 23607 thisObj.onMediaComplete(); 23608 } else { 23609 thisObj.playing = false; 23610 thisObj.paused = true; 23611 } 23612 if (thisObj.stoppingYouTube && playerState === 'paused') { 23613 if (thisObj.hasSignLanguage && thisObj.signVideo) { 23614 thisObj.signVideo.pause(true); 23615 } 23616 if (typeof thisObj.$posterImg !== 'undefined') { 23617 thisObj.$posterImg.show(); 23618 } 23619 thisObj.stoppingYouTube = false; 23620 thisObj.seeking = false; 23621 thisObj.playing = false; 23622 thisObj.paused = true; 23623 } 23624 }); 23625 // If caption tracks are hosted locally, but are also available on YouTube, 23626 // we need to turn them off on YouTube or there will be redundant captions 23627 // This is the most reliable event on which to unload the caption module 23628 if (thisObj.player === 'youtube' && !thisObj.usingYouTubeCaptions) { 23629 if (thisObj.youTubePlayer.getOptions('captions')) { 23630 thisObj.youTubePlayer.unloadModule('captions'); 23631 } 23632 } 23633 }, 23634 onApiChange: function() { 23635 // getDuration() can be fetched during API change event. 23636 thisObj.duration = thisObj.youTubePlayer.getDuration(); 23637 }, 23638 onPlaybackQualityChange: function () { 23639 // do something 23640 }, 23641 } 23642 }); 23643 if (!this.hasPlaylist) { 23644 // remove the media element, since YouTube replaces that with its own element in an iframe 23645 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 23646 this.$media.remove(); 23647 } 23648 return promise; 23649 }; 23650 23651 AblePlayer.prototype.getYouTubeDimensions = function () { 23652 23653 // The YouTube iframe API does not have a getSize() of equivalent method 23654 // so, need to get dimensions from YouTube's iframe 23655 var $iframe, width, height; 23656 23657 $iframe = this.$ableWrapper.find('iframe'); 23658 if (typeof $iframe !== 'undefined') { 23659 if ($iframe.prop('width')) { 23660 width = $iframe.prop('width'); 23661 if ($iframe.prop('height')) { 23662 height = $iframe.prop('height'); 23663 this.resizePlayer(width,height); 23664 } 23665 } 23666 } 23667 }; 23668 23669 /** 23670 * Get data from the YouTube iFrame API. Pushes data into `this.tracks` and `this.captions`. 23671 * Initiates play to trigger loading the captions module, then stops and collects data. 23672 * 23673 * @returns {Promise} promise 23674 */ 23675 AblePlayer.prototype.getYouTubeCaptionTracks = function () { 23676 23677 var deferred = new this.defer(); 23678 var promise = deferred.promise(); 23679 var thisObj, ytTracks, i, trackLang, trackLabel, isDefaultTrack, apiTriggered = false; 23680 23681 thisObj = this; 23682 if (!this.youTubePlayer.getOption('captions','tracklist') ) { 23683 // no tracks were found, probably because the captions module hasn't loaded 23684 // play video briefly (required to load the captions module) 23685 // and after the apiChange event is triggered, try again to retrieve tracks 23686 this.youTubePlayer.addEventListener('onApiChange',function() { 23687 apiTriggered = true; 23688 // getDuration() also requires video to play briefly 23689 // so, let's set that while we're here 23690 thisObj.duration = thisObj.youTubePlayer.getDuration(); 23691 23692 if (thisObj.loadingYouTubeCaptions) { 23693 // loadingYouTubeCaptions is a stopgap in case onApiChange is called more than once 23694 ytTracks = thisObj.youTubePlayer.getOption('captions','tracklist'); 23695 if ( ! thisObj.okToPlay ) { 23696 // Don't stopVideo() - that cancels loading, just pause. 23697 // No need to seekTo(0) - the time passed isn't noticeable to the user 23698 thisObj.youTubePlayer.pauseVideo(); 23699 } 23700 if (ytTracks && ytTracks.length) { 23701 // Step through ytTracks and add them to global tracks array 23702 // Note: Unlike YouTube Data API, the IFrame Player API only returns 23703 // tracks that are published, and does NOT include ASR captions 23704 // So, no additional filtering is required 23705 for (i=0; i < ytTracks.length; i++) { 23706 trackLang = ytTracks[i].languageCode; 23707 trackLabel = ytTracks[i].languageName; // displayName and languageName seem to always have the same value 23708 isDefaultTrack = false; 23709 if (typeof thisObj.captionLang !== 'undefined' && (trackLang === thisObj.captionLang) ) { 23710 isDefaultTrack = true; 23711 } else if (typeof thisObj.lang !== 'undefined') { 23712 if (trackLang === thisObj.lang) { 23713 isDefaultTrack = true; 23714 } 23715 } 23716 thisObj.tracks.push({ 23717 'kind': 'captions', 23718 'language': trackLang, 23719 'label': trackLabel, 23720 'def': isDefaultTrack 23721 }); 23722 thisObj.captions.push({ 23723 'language': trackLang, 23724 'label': trackLabel, 23725 'def': isDefaultTrack, 23726 'cues': null 23727 }); 23728 } 23729 thisObj.hasCaptions = true; 23730 // setupPopups again with new captions array, replacing original 23731 thisObj.setupPopups('captions'); 23732 } else { 23733 // there are no YouTube captions 23734 thisObj.usingYouTubeCaptions = false; 23735 thisObj.hasCaptions = false; 23736 } 23737 thisObj.loadingYouTubeCaptions = false; 23738 if (thisObj.okToPlay) { 23739 thisObj.youTubePlayer.playVideo(); 23740 } 23741 } 23742 if (thisObj.captionLangPending) { 23743 // user selected a new caption language prior to playback starting 23744 // set it now 23745 thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLangPending}); 23746 thisObj.captionLangPending = null; 23747 } 23748 if (typeof thisObj.prefCaptionsSize !== 'undefined') { 23749 // set the default caption size 23750 // this doesn't work until the captions module is loaded 23751 thisObj.youTubePlayer.setOption('captions','fontSize',thisObj.translatePrefs('size',thisObj.prefCaptionsSize,'youtube')); 23752 } 23753 deferred.resolve(); 23754 }); 23755 // Trigger the above event listener by briefly playing the video 23756 this.loadingYouTubeCaptions = true; 23757 this.youTubePlayer.playVideo(); 23758 // If onApiChange has not been triggered, the captions module is not loading. 23759 setTimeout(() => { 23760 if ( ! apiTriggered ) { 23761 setTimeout(() => { 23762 // If a second passes without loading captions, assume there are none. 23763 thisObj.youTubePlayer.pauseVideo(); 23764 deferred.resolve(); 23765 }, 500); 23766 } 23767 },500); 23768 } 23769 return promise; 23770 }; 23771 23772 AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) { 23773 23774 // return a URL for retrieving a YouTube poster image 23775 // supported values of width: 120, 320, 480, 640, 1280, 1920. 23776 var url = 'https://img.youtube.com/vi/' + youTubeId; 23777 if (width == '120') { 23778 // default (small) thumbnail, 120 x 90 23779 return url + '/default.jpg'; 23780 } else if (width == '320') { 23781 // medium quality thumbnail, 320 x 180 23782 return url + '/mqdefault.jpg'; 23783 } else if (width == '480') { 23784 // high quality thumbnail, 480 x 360 23785 return url + '/hqdefault.jpg'; 23786 } else if (width == '640') { 23787 // standard definition poster image, 640 x 480 23788 return url + '/sddefault.jpg'; 23789 } else if (width == '1280') { 23790 // standard definition poster image, 640 x 480 23791 return url + '/hq720.jpg'; 23792 } else if ( width == '1920' ) { 23793 // standard definition poster image, 640 x 480 23794 return url + '/maxresdefault.jpg'; 23795 } 23796 return false; 23797 }; 23798 23799 AblePlayer.prototype.getYouTubeId = function (url) { 23800 23801 // return a YouTube ID, extracted from a full YouTube URL 23802 // Supported URL patterns: 23803 // http|s://youtu.be/xxx 23804 // http|s://www.youtube.com/watch?v=xxx 23805 // http|s://www.youtube.com/embed/xxx 23806 23807 // in all supported patterns, the id is the last 11 characters 23808 var idStartPos, id; 23809 23810 if (url.indexOf('youtu') !== -1) { 23811 // this is a full Youtube URL 23812 url = url.trim(); 23813 idStartPos = url.length - 11; 23814 id = url.substring(idStartPos); 23815 return id; 23816 } else { 23817 return url; 23818 } 23819 }; 23820 23821 } 23822 23823 // Order copied over from 2026-02-27 Gruntfile.js, in case it matters 23824 addInitializeFunctions(AblePlayer); 23825 addPreferenceFunctions(AblePlayer); 23826 addWebvttFunctions(AblePlayer); 23827 addBuildplayerFunctions(AblePlayer); 23828 addTrackFunctions(AblePlayer); 23829 addYoutubeFunctions(AblePlayer); 23830 addVolumeFunctions(AblePlayer); 23831 addMiscFunctions(AblePlayer); 23832 addDescriptionFunctions(AblePlayer); 23833 addBrowserFunctions(AblePlayer); 23834 addControlFunctions(AblePlayer); 23835 addCaptionFunctions(AblePlayer); 23836 addChaptersFunctions(AblePlayer); 23837 addMetadataFunctions(AblePlayer); 23838 addTranscriptFunctions(AblePlayer); 23839 addSearchFunctions(AblePlayer); 23840 addEventFunctions(AblePlayer); 23841 addDragdropFunctions(AblePlayer); 23842 addSignFunctions(AblePlayer); 23843 addLangsFunctions(AblePlayer); 23844 addTranslationFunctions(AblePlayer); 23845 addVtsFunctions(AblePlayer); 23846 addVimeoFunctions(AblePlayer); 23847 23848 AblePlayer.ablePlayerSetupWindow(); 23849 23850 return AblePlayer; 23851 23852})); 23853//# sourceMappingURL=ableplayer.js.map
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.