vendor: 915,182 bytes, lines 1-23772
1/*! ableplayer V5.0.0 - with DOMPurify included. Console logs disabled, but not minified, for demos. */ 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 return; 1420 } 1421 $(function () { 1422 if (typeof purify === 'undefined') { 1423 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.'); 1424 } 1425 1426 $('video, audio').each(function (index, element) { 1427 if ($(element).data('able-player') !== undefined) { 1428 new AblePlayer($(this),$(element)); 1429 } 1430 }); 1431 }); 1432 1433 // YouTube player support; pass ready event to jQuery so we can catch in player. 1434 window.onYouTubeIframeAPIReady = function() { 1435 AblePlayer.youTubeIframeAPIReady = true; 1436 $('body').trigger('youTubeIframeAPIReady', []); 1437 }; 1438 1439 // If there is only one player on the page, dispatch global keydown events to it 1440 // Otherwise, keydowwn events are handled locally (see event.js > handleEventListeners()) 1441 $(window).on('keydown',function(e) { 1442 if (AblePlayer.hasSingleInstance()) { 1443 const singleInstance = AblePlayer.getSingleInstance(); 1444 singleInstance.onPlayerKeyPress(e); 1445 } 1446 }); 1447 } 1448 1449 // Outdented for a simpler diff during module conversion 1450 /** 1451 * Construct the AblePlayer object. 1452 * 1453 * Able Player needs `window` to instantiate, so, skip the constructor if 1454 * you are running outside the browser (for example, SSR). 1455 * 1456 * @param object media jQuery selector or element identifying the media. 1457 */ 1458 function AblePlayer(media) { 1459 1460 if (typeof window === 'undefined') { 1461 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)."); 1462 return; 1463 } 1464 1465 var thisObj = this; 1466 1467 this.media = media; 1468 1469 if ($(media).length === 0) { 1470 this.provideFallback(); 1471 return; 1472 } 1473 1474 // Default variables assignment 1475 // The following variables CAN be overridden with HTML attributes 1476 1477 // autoplay (Boolean; if present always resolves to true, regardless of value) 1478 if ($(media).attr('autoplay') !== undefined) { 1479 this.autoplay = true; // this value remains constant 1480 this.okToPlay = true; // this value can change dynamically 1481 } else { 1482 this.autoplay = false; 1483 this.okToPlay = false; 1484 } 1485 1486 // loop (Boolean; if present always resolves to true, regardless of value) 1487 this.loop = ($(media).attr('loop') !== undefined) ? true : false; 1488 1489 // playsinline (Boolean; if present always resolves to true, regardless of value) 1490 this.playsInline = ($(media).attr('playsinline') !== undefined) ? '1' : '0'; 1491 1492 // poster (Boolean, indicating whether media element has a poster attribute) 1493 this.hasPoster = ( $(media).attr('poster') || $(media).data('poster') ) ? true : false; 1494 1495 this.audioPoster = $(media).data('poster'); 1496 this.audioPosterAlt = $(media).data('poster-alt' ); 1497 1498 // get height and width attributes, if present 1499 // and add them to variables 1500 // Not currently used, but might be useful for resizing player 1501 this.width = $(media).attr('width') ?? 0; 1502 this.height = $(media).attr('height') ?? 0; 1503 1504 // start-time 1505 var startTime = $(media).data('start-time'); 1506 var isNumeric = ( typeof startTime === 'number' || ( typeof startTime === 'string' && startTime.trim() !== '' && ! isNaN(startTime) && isFinite( Number(startTime) ) ) ) ? true : false; 1507 this.startTime = ( startTime !== undefined && isNumeric ) ? startTime : 0; 1508 1509 // debug 1510 this.debug = ($(media).data('debug') !== undefined && $(media).data('debug') !== false) ? true : false; 1511 1512 // Volume 1513 // Range is 0 to 10. Best not to crank it to avoid overpowering screen readers 1514 this.defaultVolume = 7; 1515 if ($(media).data('volume') !== undefined && $(media).data('volume') !== "") { 1516 var volume = $(media).data('volume'); 1517 if (volume >= 0 && volume <= 10) { 1518 this.defaultVolume = volume; 1519 } 1520 } 1521 this.volume = this.defaultVolume; 1522 1523 // Optional Buttons 1524 // Buttons are added to the player controller if relevant media is present 1525 // However, in some applications it might be undesirable to show buttons 1526 // (e.g., if chapters or transcripts are provided in an external container) 1527 1528 if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) { 1529 this.useChaptersButton = false; 1530 } else { 1531 this.useChaptersButton = true; 1532 } 1533 1534 // Control whether text descriptions are read aloud 1535 // set to "false" if the sole purpose of the WebVTT descriptions file 1536 // is to integrate text description into the transcript 1537 // set to "true" to write description text to a div 1538 // This variable does *not* control the method by which description is read. 1539 // For that, see below (this.descMethod) 1540 if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) { 1541 this.readDescriptionsAloud = false; 1542 } else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) { 1543 // support both singular and plural spelling of attribute 1544 this.readDescriptionsAloud = false; 1545 } else { 1546 this.readDescriptionsAloud = true; 1547 } 1548 1549 // setting initial this.descVoices to an empty array 1550 // to be populated later by getBrowserVoices 1551 this.descVoices = []; 1552 1553 // Method by which text descriptions are read 1554 // valid values of data-desc-reader are: 1555 // 'brower' (default) - text-based audio description is handled by the browser, if supported 1556 // 'screenreader' - text-based audio description is always handled by screen readers 1557 // The latter may be preferable by owners of websites in languages that are not well supported 1558 // by the Web Speech API 1559 this.descReader = ($(media).data('desc-reader') == 'screenreader') ? 'screenreader' : 'browser'; 1560 1561 // Default state of captions and descriptions 1562 // This setting is overridden by user preferences, if they exist 1563 // values for data-state-captions and data-state-descriptions are 'on' or 'off' 1564 this.defaultStateCaptions = ($(media).data('state-captions') == 'off') ? 0 : 1; 1565 this.defaultStateDescriptions = ($(media).data('state-descriptions') == 'on') ? 1 : 0; 1566 1567 // Default setting for prefDescPause 1568 // Extended description (i.e., pausing during description) is on by default 1569 // but this settings give website owners control over that 1570 // since they know the nature of their videos, and whether pausing is necessary 1571 // This setting is overridden by user preferences, if they exist 1572 this.defaultDescPause = ($(media).data('desc-pause-default') == 'off') ? 0 : 1; 1573 1574 // Headings 1575 // By default, an off-screen heading is automatically added to the top of the media player 1576 // It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel() 1577 // Authors can override this behavior by manually assigning a heading level using data-heading-level 1578 // Accepted values are 1-6, or 0 which indicates "no heading" 1579 // (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this) 1580 if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") { 1581 var headingLevel = $(media).data('heading-level'); 1582 if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0 1583 this.playerHeadingLevel = headingLevel; 1584 } 1585 } 1586 1587 // Transcripts 1588 // There are three types of interactive transcripts. 1589 // In descending of order of precedence (in case there are conflicting tags), they are: 1590 // 1. "manual" - A manually coded external transcript (requires data-transcript-src) 1591 // 2. "external" - Automatically generated, written to an external div (requires data-transcript-div & a valid target element) 1592 // 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button 1593 // If data-include-transcript="false", there is no "popup" transcript 1594 var transcriptDivLocation = $(media).data('transcript-div'); 1595 if ( transcriptDivLocation !== undefined && transcriptDivLocation !== "" && null !== document.getElementById( transcriptDivLocation ) ) { 1596 this.transcriptDivLocation = transcriptDivLocation; 1597 } else { 1598 this.transcriptDivLocation = null; 1599 } 1600 var includeTranscript = $(media).data('include-transcript'); 1601 this.hideTranscriptButton = ( includeTranscript !== undefined && includeTranscript === false) ? true : false; 1602 1603 this.transcriptType = null; 1604 if ($(media).data('transcript-src') !== undefined) { 1605 this.transcriptSrc = $(media).data('transcript-src'); 1606 if (this.transcriptSrcHasRequiredParts()) { 1607 this.transcriptType = 'manual'; 1608 } 1609 } else if ($(media).find('track[kind="captions"],track[kind="subtitles"],track:not([kind])').length > 0) { 1610 // required tracks are present. COULD automatically generate a transcript 1611 this.transcriptType = (this.transcriptDivLocation) ? 'external' : 'popup'; 1612 } 1613 1614 // In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript 1615 // If false (default), line breaks are are removed from transcripts for a more seamless reading experience 1616 // If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem 1617 this.lyricsMode = ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) ? true : false; 1618 1619 // Set Transcript Title if defined explicitly. See transcript.js. 1620 if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") { 1621 this.transcriptTitle = $(media).data('transcript-title'); 1622 } 1623 1624 // Sign Language 1625 // sign language can be a modal (default) or assigned to a div on the page. 1626 var signDivLocation = $(media).data('sign-div'); 1627 if ( signDivLocation !== undefined && signDivLocation !== "" && null !== document.getElementById( signDivLocation ) ) { 1628 this.$signDivLocation = $( '#' + signDivLocation ); 1629 } else { 1630 this.$signDivLocation = null; 1631 } 1632 1633 // Captions 1634 // data-captions-position can be used to set the default captions position 1635 // this is only the default, and can be overridden by user preferences 1636 // valid values of data-captions-position are 'below' and 'overlay' 1637 this.defaultCaptionsPosition = ($(media).data('captions-position') === 'overlay') ? 'overlay' : 'below'; 1638 1639 // Chapters 1640 var chaptersDiv = $(media).data('chapters-div'); 1641 if ( chaptersDiv !== undefined && chaptersDiv !== "") { 1642 this.chaptersDivLocation = chaptersDiv; 1643 } 1644 1645 if ($(media).data('chapters-title') !== undefined) { 1646 // NOTE: empty string is valid; results in no title being displayed 1647 this.chaptersTitle = $(media).data('chapters-title'); 1648 } 1649 1650 var defaultChapter = $(media).data('chapters-default'); 1651 this.defaultChapter = ( defaultChapter !== undefined && defaultChapter !== "") ? defaultChapter : null; 1652 1653 // Slower/Faster buttons 1654 // valid values of data-speed-icons are 'animals' (default) and 'arrows' 1655 // 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows 1656 this.speedIcons = ($(media).data('speed-icons') === 'arrows') ? 'arrows' : 'animals'; 1657 1658 // Seekbar 1659 // valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters' 1660 var seekbarScope = $(media).data('seekbar-scope'); 1661 this.seekbarScope = ( seekbarScope === 'chapter' || seekbarScope === 'chapters') ? 'chapter' : 'video'; 1662 1663 // YouTube 1664 var youTubeId = $(media).data('youtube-id'); 1665 if ( youTubeId !== undefined && youTubeId !== "") { 1666 this.youTubeId = this.getYouTubeId(youTubeId); 1667 if ( ! this.hasPoster ) { 1668 let poster = this.getYouTubePosterUrl(this.youTubeId,'640'); 1669 $(media).attr( 'poster', poster ); 1670 } 1671 } 1672 1673 var youTubeDescId = $(media).data('youtube-desc-id'); 1674 if ( youTubeDescId !== undefined && youTubeDescId !== "") { 1675 this.youTubeDescId = this.getYouTubeId(youTubeDescId); 1676 } 1677 1678 var youTubeSignId = $(media).data('youtube-sign-src'); 1679 if ( youTubeSignId !== undefined && youTubeSignId !== "") { 1680 this.youTubeSignId = this.getYouTubeId(youTubeSignId); 1681 } 1682 1683 var youTubeNoCookie = $(media).data('youtube-nocookie'); 1684 this.youTubeNoCookie = (youTubeNoCookie !== undefined && youTubeNoCookie) ? true : false; 1685 1686 // Vimeo 1687 var vimeoId = $(media).data('vimeo-id'); 1688 if ( vimeoId !== undefined && vimeoId !== "") { 1689 this.vimeoId = this.getVimeoId(vimeoId); 1690 if ( ! this.hasPoster ) { 1691 let poster = thisObj.getVimeoPosterUrl(this.vimeoId,'1200'); 1692 $(media).attr( 'poster', poster ); 1693 } 1694 } 1695 var vimeoDescId = $(media).data('vimeo-desc-id'); 1696 if ( vimeoDescId !== undefined && vimeoDescId !== "") { 1697 this.vimeoDescId = this.getVimeoId(vimeoDescId); 1698 } 1699 1700 // Skin 1701 // valid values of data-skin are: 1702 // '2020' (default as of 4.6), all buttons in one row beneath a full-width seekbar 1703 // 'legacy', two rows of controls; seekbar positioned in available space within top row 1704 this.skin = ($(media).data('skin') == 'legacy') ? 'legacy' : '2020'; 1705 1706 // Size 1707 // width of Able Player is determined using the following order of precedence: 1708 // 1. data-width attribute 1709 // 2. width attribute (for video or audio, although it is not valid HTML for audio) 1710 // 3. Intrinsic size from video (video only, determined later) 1711 if ($(media).data('width') !== undefined) { 1712 this.playerWidth = parseInt($(media).data('width')); 1713 } else if ($(media)[0].getAttribute('width')) { 1714 // NOTE: jQuery attr() returns null for all invalid HTML attributes 1715 // (e.g., width on <audio>) 1716 // but it can be acessed via JavaScript getAttribute() 1717 this.playerWidth = parseInt($(media)[0].getAttribute('width')); 1718 } else { 1719 this.playerWidth = null; 1720 } 1721 1722 var allowFullScreen = $(media).data('allow-fullscreen'); 1723 this.allowFullscreen = (allowFullScreen !== undefined && allowFullScreen === false) ? false : true; 1724 1725 // Define other variables that are used in fullscreen program flow 1726 this.clickedFullscreenButton = false; 1727 this.restoringAfterFullscreen = false; 1728 1729 // Seek interval 1730 // Number of seconds to seek forward or back with Rewind & Forward buttons 1731 // Unless specified with data-seek-interval, the default value is re-calculated in initialize.js > setSeekInterval(); 1732 // Calculation attempts to intelligently assign a reasonable interval based on media length 1733 this.defaultSeekInterval = 10; 1734 this.useFixedSeekInterval = false; // will change to true if media has valid data-seek-interval attribute 1735 if ($(media).data('seek-interval') !== undefined && $(media).data('seek-interval') !== "") { 1736 var seekInterval = $(media).data('seek-interval'); 1737 if (/^[1-9][0-9]*$/.test(seekInterval)) { // must be a whole number greater than 0 1738 this.seekInterval = seekInterval; 1739 this.useFixedSeekInterval = true; // do not override with calculuation 1740 } 1741 } 1742 1743 // Now Playing 1744 // Shows "Now Playing:" plus the title of the current track above player 1745 // Only used if there is a playlist 1746 var showNowPlaying = $(media).data('show-now-playing'); 1747 this.showNowPlaying = (showNowPlaying !== undefined && showNowPlaying === false) ? false : true; 1748 1749 // Fallback 1750 // The data-test-fallback attribute can be used to test the fallback solution in any browser 1751 var testFallback = $(media).data('test-fallback'); 1752 if ( testFallback !== undefined && testFallback !== false) { 1753 // 1: build error; 2: browser doesn't support media. 1754 this.testFallback = ( testFallback == '2' ) ? 2 : 1; 1755 } else { 1756 this.testFallback = false; 1757 } 1758 1759 // Language 1760 // Player language is determined given the following precedence: 1761 // 1. The value of data-lang on the media element, if provided and a matching translation file is available 1762 // 2. Lang attribute on <html> or <body>, if a matching translation file is available 1763 // 3. English 1764 // Final calculation occurs in translation.js > getTranslationText() 1765 var lang = $(media).data('lang'); 1766 this.lang = ( lang !== undefined && lang !== "") ? lang.toLowerCase() : null; 1767 1768 // Metadata Tracks 1769 var metaType = $(media).data('meta-type'); 1770 if ( metaType !== undefined && metaType !== "") { 1771 this.metaType = metaType; 1772 } 1773 var metaDiv = $(media).data('meta-div'); 1774 if ( metaDiv !== undefined && metaDiv !== "") { 1775 this.metaDiv = metaDiv; 1776 } 1777 1778 // Search 1779 // conducting a search requires an external div in which to write the results 1780 var searchDiv = $(media).data('search-div'); 1781 if ( searchDiv !== undefined && searchDiv !== "") { 1782 1783 this.searchDiv = searchDiv; 1784 1785 // Search term (optional; could be assigned later in a JavaScript application) 1786 var searchString = $(media).data('search'); 1787 if ( searchString !== undefined && searchString !== "") { 1788 this.searchString = searchString; 1789 } 1790 1791 // Search Language 1792 var searchLang = $(media).data('search-lang'); 1793 this.searchLang = ( searchLang !== undefined && searchLang !== "") ? searchLang : null; 1794 1795 // Search option: Ignore capitalization in search terms 1796 var searchIgnoreCaps = $(media).data('search-ignore-caps'); 1797 this.searchIgnoreCaps = ( searchIgnoreCaps !== undefined && searchIgnoreCaps !== false) ? true : false; 1798 } 1799 1800 // Hide controls when video starts playing 1801 // They will reappear again when user presses a key or moves the mouse 1802 // As of v4.0, controls are hidden automatically on playback in fullscreen mode 1803 if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) { 1804 this.hideControls = true; 1805 this.hideControlsOriginal = true; // a copy of hideControls, since the former may change if user enters full screen mode 1806 } else { 1807 this.hideControls = false; 1808 this.hideControlsOriginal = false; 1809 } 1810 1811 // Steno mode 1812 // Enable support for Able Player keyboard shortcuts in textaarea fields 1813 // so users can control the player while transcribing 1814 if ($(media).data('steno-mode') !== undefined && $(media).data('steno-mode') !== false) { 1815 this.stenoMode = true; 1816 // Add support for stenography in an iframe via data-steno-iframe-id 1817 if ($(media).data('steno-iframe-id') !== undefined && $(media).data('steno-iframe-id') !== "") { 1818 this.stenoFrameId = $(media).data('steno-iframe-id'); 1819 this.$stenoFrame = $('#' + this.stenoFrameId); 1820 if (!(this.$stenoFrame.length)) { 1821 // iframe not found 1822 this.stenoFrameId = null; 1823 this.$stenoFrame = null; 1824 } 1825 } else { 1826 this.stenoFrameId = null; 1827 this.$stenoFrame = null; 1828 } 1829 } else { 1830 this.stenoMode = false; 1831 this.stenoFrameId = null; 1832 this.$stenoFrame = null; 1833 } 1834 1835 // Define built-in variables that CANNOT be overridden with HTML attributes 1836 this.setDefaults(); 1837 1838 //////////////////////////////////////// 1839 // End assignment of default variables 1840 //////////////////////////////////////// 1841 1842 this.ableIndex = AblePlayer.nextIndex; 1843 AblePlayer.nextIndex += 1; 1844 1845 this.title = $(media).attr('title'); 1846 1847 // populate translation object with localized versions of all labels and prompts 1848 this.tt = {}; 1849 try { 1850 this.getTranslationText(); 1851 this.setup(); 1852 } catch (e) { 1853 console.warn('Error setting up translations:', e); 1854 this.provideFallback(); 1855 } 1856 1857 ablePlayerInstances.add(this); 1858 } 1859 // Index to increment every time new player is created. 1860 // 5.0.0: this is now only used to generate unique IDs. Otherwise use hasSingleInstance. 1861 AblePlayer.nextIndex = 0; 1862 1863 AblePlayer.prototype.setup = function() { 1864 1865 var thisObj = this; 1866 this.initializing = true; // will remain true until entire sequence of function calls is complete 1867 1868 this.reinitialize().then(function () { 1869 if (!thisObj.player) { 1870 // No player for this media, show last-line fallback. 1871 thisObj.provideFallback(); 1872 } else { 1873 thisObj.setupInstance().then(function () { 1874 thisObj.setupInstancePlaylist(); 1875 if (thisObj.hasPlaylist) ; else { 1876 thisObj.recreatePlayer().then(function() { 1877 thisObj.initializing = false; 1878 thisObj.playerCreated = true; // remains true until browser is refreshed 1879 }); 1880 } 1881 }); 1882 } 1883 }); 1884 }; 1885 1886 /** 1887 * Removes this player from the global instance list. 1888 * 1889 * You probably want to call this during/after removing a player from the 1890 * DOM. This avoids memory leaks, and allows the event handling to have the 1891 * correct count of how many players are actually on the page. 1892 */ 1893 AblePlayer.prototype.dispose = function () { 1894 AblePlayer.ablePlayerInstances.delete(this); 1895 1896 // Look for various dialogs tied to this instance. Elements associated 1897 // with these are appended to the body, and they need to be 1898 // `.remove()`d here. 1899 const dialogs = [ 1900 this.captionPrefsDialog, 1901 this.descPrefsDialog, 1902 this.keyboardPrefsDialog, 1903 this.transcriptPrefsDialog, 1904 this.transcriptResizeDialog, 1905 this.signResizeDialog, 1906 ]; 1907 1908 for (const dialog of dialogs) { 1909 if (!dialog) { 1910 continue; 1911 } 1912 if (dialog.modal) { 1913 dialog.modal.remove(); 1914 } 1915 if (dialog.overlay) { 1916 dialog.overlay.remove(); 1917 } 1918 } 1919 }; 1920 1921 AblePlayer.getActiveDOMElement = function () { 1922 var activeElement = document.activeElement; 1923 1924 // For shadow DOMs we need to keep digging down through the DOMs 1925 while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) { 1926 activeElement = activeElement.shadowRoot.activeElement; 1927 } 1928 1929 return activeElement; 1930 }; 1931 1932 AblePlayer.localGetElementById = function(element, id) { 1933 if (element.getRootNode) { 1934 // Use getRootNode() and querySelector() where supported (for shadow DOM support) 1935 return $(element.getRootNode().querySelector('#' + id)); 1936 } else { 1937 // If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support) 1938 return $(document.getElementById(id)); 1939 } 1940 }; 1941 1942 AblePlayer.ablePlayerSetupWindow = ablePlayerSetupWindow; 1943 1944 AblePlayer.youTubeIframeAPIReady = false; 1945 AblePlayer.loadingYouTubeIframeAPI = false; 1946 1947 AblePlayer.ablePlayerInstances = ablePlayerInstances; 1948 1949 AblePlayer.hasSingleInstance = () => AblePlayer.ablePlayerInstances.size === 1; 1950 1951 AblePlayer.getSingleInstance = () => { 1952 // If there are actually more instances, this returns the first one 1953 for (const instance of AblePlayer.ablePlayerInstances) { 1954 return instance; 1955 } 1956 }; 1957 1958 function addBrowserFunctions(AblePlayer) { 1959 1960 AblePlayer.prototype.isIOS = function(version) { 1961 1962 // return true if this is iOS 1963 // if version is provided check for a particular version 1964 1965 var userAgent, iOS; 1966 1967 userAgent = navigator.userAgent.toLowerCase(); 1968 iOS = /ipad|iphone|ipod/.exec(userAgent); 1969 if (iOS) { 1970 if (typeof version !== 'undefined') { 1971 if (userAgent.indexOf('os ' + version) !== -1) { 1972 // this is the target version of iOS 1973 return true; 1974 } else { 1975 return false; 1976 } 1977 } else { 1978 // no version was specified 1979 return true; 1980 } 1981 } else { 1982 // this is not iOS 1983 return false; 1984 } 1985 }; 1986 1987 AblePlayer.prototype.browserSupportsVolume = function() { 1988 1989 // To test whether the browser supports changing the volume, 1990 // create a new audio element and try setting the volume to something other than 1. 1991 // Then, retrieve the current setting to see if it preserved it. 1992 // This doesn't work in iOS by design: https://developer.apple.com/documentation/avfoundation/avplayer/volume 1993 1994 var audio, testVolume; 1995 1996 if (this.isIOS()) { 1997 return false; 1998 } 1999 2000 testVolume = 0.9; // any value between 0.1 and 0.9 2001 audio = new Audio(); 2002 audio.volume = testVolume; 2003 2004 return ( audio.volume === testVolume ); 2005 }; 2006 2007 AblePlayer.prototype.nativeFullscreenSupported = function () { 2008 2009 return document.fullscreenEnabled || document.webkitFullscreenEnabled; 2010 }; 2011 2012 } 2013 2014 /** 2015 * @file validate.js 2016 * @description This file contains the code to validate the VTT data. 2017 */ 2018 2019 /** PRE-SANITIZED FUNCTIONS 2020 * Some of the VTT attributes need to be transformed before being sanitized by DOMPurify. 2021 * @namespace preProcessing 2022 */ 2023 var preProcessing = { 2024 /** 2025 * Transforms tags with class names separated by dots into tags with a class attribute containing space-separated class names. 2026 * @memberof preProcessing 2027 * @param {string} vttContent - The content of the VTT. 2028 * @returns {string} - The VTT content with processed tags. 2029 */ 2030 transformCSSClasses: function (vttContent) { 2031 // This function should only be passed one cue at a time. 2032 // Throw an error if the string checked is more than 1000 characters. 2033 if ( vttContent.length > 1000 ) { 2034 throw new Error( "Input too long" ); 2035 } 2036 return vttContent.replace( 2037 /<(v|c|b|i|u|lang|ruby)\.([\w.]+)([^>]*)>/g, 2038 function (_, tag, cssClasses, otherAttrs) { 2039 var classAttr = cssClasses.replace(/\./g, " "); 2040 return `<${tag} class="${classAttr}"${otherAttrs}>`; 2041 } 2042 ); 2043 }, 2044 2045 /** 2046 * Transforms <lang> tags by adding a lang attribute with the language code. 2047 * @memberof preProcessing 2048 * @param {string} content - The content with processed CSS classes. 2049 * @returns {string} - The content with <lang> tags transformed. 2050 */ 2051 transformLangTags: function (content) { 2052 return content.replace( 2053 /<lang\s+([\w-]+)([^>]*)>/g, 2054 function (_, langCode, otherAttrs) { 2055 return '<lang lang="' + langCode + '"' + otherAttrs + ">"; 2056 } 2057 ); 2058 }, 2059 2060 /** 2061 * Transforms <v> tags by extracting any non-attribute text as a `title` attribute, 2062 * retains existing attributes (except class), and preserves the class attribute if present. 2063 * Example: <v John class="foo" data-x="y"> becomes <v title="John" data-x="y" class="foo"> 2064 * 2065 * @function 2066 * @memberof preProcessing 2067 * @param {string} content - The string content containing <v> tags to process. 2068 * @returns {string} The content with <v> tags transformed to include a title attribute and preserved attributes. 2069 */ 2070 transformVTags: function (content) { 2071 return content.replace(/<v\s+([^>]*?)>/g, function (_, tagAttributes) { 2072 var classMatch = tagAttributes.match(/class="([^"]*)"/); 2073 var titleMatch = tagAttributes.match(/title="([^"]*)"/); 2074 var classAttr = classMatch ? classMatch[0] : ""; 2075 var titleAttr = titleMatch ? titleMatch[0] : ""; 2076 var otherAttributes = tagAttributes 2077 .replace(/class="[^"]*"/, "") 2078 .replace(/title="[^"]*"/, "") 2079 .trim() 2080 .split(/\s+/); 2081 2082 var attributes = []; 2083 var titleParts = []; 2084 2085 // Iterate over each token of the tag content 2086 otherAttributes.forEach(function (token) { 2087 if (token.indexOf("=") !== -1) { 2088 attributes.push(token); 2089 } else { 2090 titleParts.push(token); 2091 } 2092 }); 2093 2094 var title = ( titleParts ) ? titleParts.join(" ") : false; 2095 var newTag = "<v"; 2096 2097 if ( title && ! titleAttr ) { 2098 newTag += ' title="' + title + '"'; 2099 } 2100 2101 if ( titleAttr && ! title ) { 2102 newTag += " " + titleAttr; 2103 } 2104 2105 if (attributes.length > 0) { 2106 newTag += " " + attributes.join(" "); 2107 } 2108 2109 if (classAttr) { 2110 newTag += " " + classAttr; 2111 } 2112 2113 newTag += ">"; 2114 2115 return newTag; 2116 }); 2117 }, 2118 }; 2119 2120 /** POST-SANITIZED FUNCTIONS 2121 * After sanitizing the VTT data, some tags need to be transformed back to their original form. 2122 * @namespace postProcessing 2123 */ 2124 var postProcessing = { 2125 /** 2126 * Post-processes <c> tags by converting class attributes to dot-separated class names. 2127 * @memberof postProcessing 2128 * @param {string} vttContent - The VTT content to be processed. 2129 * @returns {string} - The VTT content with processed <c> tags. 2130 */ 2131 postprocessCTag: function (vttContent) { 2132 return vttContent.replace( 2133 /<c class="([\w\s]+)">/g, 2134 function (_, classNames) { 2135 var classes = classNames.replace(/ /g, "."); 2136 return "<c." + classes + ">"; 2137 } 2138 ); 2139 }, 2140 2141 /** 2142 * * Post-processes <v> tags by converting class attributes, no matter where found in the attribute order, to dot-separated class names. 2143 * For example, <v class="foo bar" title="John"> becomes <v.foo.bar title="John">. 2144 * Removes the class attribute and appends other attributes after the class names. 2145 * 2146 * @function 2147 * @memberof postProcessing 2148 * @param {string} vttContent - The VTT content to be processed. 2149 * @returns {string} - The VTT content with processed <v> tags. 2150 */ 2151 postprocessVTag: function (vttContent) { 2152 return vttContent.replace( 2153 /<v([^>]*)class="([\w\s]+)"([^>]*)>/g, 2154 function (_, beforeClass, classNames, afterClass) { 2155 var classes = classNames.trim().split(/\s+/).join("."); 2156 // Rebuild the tag: <v.{classes}{other attributes}> 2157 // Remove class="..." from attributes 2158 var attrs = (beforeClass + afterClass) 2159 .replace(/\s*class="[\w\s]+"/, "") 2160 .trim(); 2161 return "<v." + classes + (attrs ? " " + attrs : "") + ">"; 2162 } 2163 ); 2164 }, 2165 2166 /** 2167 * Post-processes <lang> tags by removing the lang attribute and placing the language code directly in the tag. 2168 * @memberof postProcessing 2169 * @param {string} vttContent - The VTT content to be processed. 2170 * @returns {string} - The VTT content with processed <lang> tags. 2171 */ 2172 postprocessLangTag: function (vttContent) { 2173 return vttContent.replace( 2174 /<lang lang="([\w-]+)"([^>]*)>/g, 2175 function (_, langCode, otherAttrs) { 2176 return "<lang " + langCode + otherAttrs + ">"; 2177 } 2178 ); 2179 }, 2180 }; 2181 2182 /** 2183 * Preprocesses, sanitizes and post-processes VTT content as well as other utility functions. 2184 * @namespace validate 2185 */ 2186 var validate = { 2187 /** 2188 * Sets up the VTT content before sanitizing by transforming tags. 2189 * This way DOM purify will process the tags correctly. 2190 * @memberof validate 2191 * @param {string} vttContent - The original content of the VTT. 2192 * @returns {string} - The VTT content for the next and final step of preprocessing. 2193 */ 2194 preProcessVttContent: function (vttContent) { 2195 var processedCSS = preProcessing.transformCSSClasses(vttContent); 2196 var processedLang = preProcessing.transformLangTags(processedCSS); 2197 var processedVTags = preProcessing.transformVTags(processedLang); 2198 return processedVTags; 2199 }, 2200 2201 /** 2202 * Post-processes the sanitized VTT data by converting class attributes to dot-separated class names and other transformations. 2203 * @memberof validate 2204 * @param {string} sanitizedVttContent - The sanitized VTT content to be post-processed. 2205 * @param {string} originalVttContent - The original VTT content before sanitization. 2206 * @returns {string} - The post-processed VTT content. 2207 */ 2208 postProcessVttContent: function (sanitizedVttContent, originalVttContent) { 2209 var processedCTags = postProcessing.postprocessCTag(sanitizedVttContent); 2210 var processedVTags = postProcessing.postprocessVTag(processedCTags); 2211 var processedLangTags = postProcessing.postprocessLangTag(processedVTags); 2212 2213 var arrowReplaced = processedLangTags.replace(/-->/g, "-->"); 2214 var timestampTagReplaced = arrowReplaced.replace(/<([\d:.]+)>/g, '<$1>'); 2215 2216 var finalContent = timestampTagReplaced.replace( 2217 /<\/v>/g, 2218 function (match, offset) { 2219 return originalVttContent.indexOf(match, offset) !== -1 ? match : ""; 2220 } 2221 ); 2222 2223 return finalContent; 2224 }, 2225 2226 /** 2227 * Sanitizes the VTT data by removing unwanted tags and attributes, and then post-processes it. 2228 * @memberof validate 2229 * @param {string} vttContent - The VTT content to be sanitized and post-processed. 2230 * @returns {string} - The fully processed VTT content. 2231 */ 2232 sanitizeVttContent: function (vttContent) { 2233 if (vttContent === null || vttContent === undefined) { 2234 return ""; 2235 } 2236 var preSanitizedVttContent = validate.preProcessVttContent(vttContent); 2237 2238 var config = { 2239 ALLOWED_TAGS: ["b", "i", "u", "v", "c", "lang", "ruby", "rt", "rp"], 2240 ALLOWED_ATTR: ["title", "class", "lang"], 2241 KEEP_CONTENT: true, 2242 }; 2243 2244 var sanitizedVttContent = purify.sanitize( 2245 preSanitizedVttContent, 2246 config 2247 ); 2248 2249 return validate.postProcessVttContent(sanitizedVttContent, vttContent); 2250 }, 2251 // Utility validation functions 2252 isProtocolSafe: function (url) { 2253 //creates a new URL object for analysis to check if the protocol is http or https 2254 //returns true if there is a match false otherwise 2255 try { 2256 const parsedUrl = new URL(url, window.location.origin); // Resolve relative URLs 2257 return ["http:", "https:"].includes(parsedUrl.protocol); // Allow only HTTP and HTTPS 2258 } catch (e) { 2259 return false; // Invalid URL 2260 } 2261 }, 2262 }; 2263 2264 // Events: 2265 // - startTracking(event, position) 2266 // - tracking(event, position) 2267 // - stopTracking(event, position) 2268 2269 function AccessibleSlider(div, max, bigInterval, label) { 2270 2271 // div is the host element around which the slider will be built 2272 // max is the high end of the slider scale 2273 // bigInterval is the number of steps supported by page up/page down (set to 0 if not supported) 2274 // (smallInterval, defined as nextStep below, is always set to 1) - this is the interval supported by arrow keys 2275 // label is used within an aria-label attribute to identify the slider to screen reader users 2276 2277 var thisObj, coords; 2278 2279 thisObj = this; 2280 2281 // Initialize some variables. 2282 this.position = 0; // Note: position does not change while tracking. 2283 this.tracking = false; 2284 this.trackDevice = null; // 'mouse' or 'keyboard' 2285 this.keyTrackPosition = 0; 2286 this.lastTrackPosition = 0; 2287 this.nextStep = 1; 2288 this.inertiaCount = 0; 2289 2290 this.seekbarDiv = $(div); 2291 2292 // Add divs for tracking amount of media loaded and played 2293 this.loadedDiv = $('<div></div>'); 2294 this.playedDiv = $('<div></div>'); 2295 2296 // Add a seekhead 2297 this.seekHead = $('<div>',{ 2298 'aria-orientation': 'horizontal', 2299 'class': 'able-seekbar-head' 2300 }); 2301 2302 this.seekHead.attr('tabindex', '0'); 2303 2304 // Since head is focusable, it gets the aria roles/titles. 2305 this.seekHead.attr({ 2306 'role': 'slider', 2307 'aria-label': label, 2308 'aria-valuemin': 0, 2309 'aria-valuemax': max 2310 }); 2311 2312 this.timeTooltipTimeoutId = null; 2313 this.overTooltip = false; 2314 this.timeTooltip = $('<div>'); 2315 this.seekbarDiv.append(this.timeTooltip); 2316 2317 this.timeTooltip.attr('role', 'tooltip'); 2318 this.timeTooltip.addClass('able-tooltip'); 2319 this.timeTooltip.on('mouseenter focus', function(){ 2320 thisObj.overTooltip = true; 2321 clearInterval(thisObj.timeTooltipTimeoutId); 2322 }); 2323 this.timeTooltip.on('mouseleave blur', function(){ 2324 thisObj.overTooltip = false; 2325 $(this).hide(); 2326 }); 2327 this.timeTooltip.hide(); 2328 2329 this.seekbarDiv.append(this.loadedDiv); 2330 this.seekbarDiv.append(this.playedDiv); 2331 this.seekbarDiv.append(this.seekHead); 2332 this.seekbarDiv.wrap('<div></div>'); 2333 this.wrapperDiv = this.seekbarDiv.parent(); 2334 2335 if (this.skin === 'legacy') { 2336 this.wrapperDiv.width( 100 ); 2337 this.loadedDiv.width(0); 2338 } 2339 this.wrapperDiv.addClass('able-seekbar-wrapper'); 2340 this.loadedDiv.addClass('able-seekbar-loaded'); 2341 this.playedDiv.width(0); 2342 this.playedDiv.addClass('able-seekbar-played'); 2343 2344 // Set a default duration. User can call this dynamically if duration changes. 2345 this.setDuration(max); 2346 2347 // handle seekHead events 2348 this.seekHead.on('mouseenter mouseleave mousemove mousedown mouseup focus blur touchstart touchmove touchend', function (e) { 2349 2350 coords = thisObj.pointerEventToXY(e); 2351 2352 if (e.type === 'mouseenter' || e.type === 'focus') { 2353 thisObj.overHead = true; 2354 } else if (e.type === 'mouseleave' || e.type === 'blur') { 2355 thisObj.overHead = false; 2356 if (!thisObj.overBody && thisObj.tracking && thisObj.trackDevice === 'mouse') { 2357 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2358 } 2359 } else if (e.type === 'mousemove' || e.type === 'touchmove') { 2360 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2361 thisObj.trackHeadAtPageX(coords.x); 2362 } 2363 } else if (e.type === 'mousedown' || e.type === 'touchstart') { 2364 thisObj.startTracking('mouse', thisObj.pageXToPosition(thisObj.seekHead.offset() + (thisObj.seekHead.width() / 2))); 2365 if (!thisObj.seekbarDiv.is(':focus')) { 2366 thisObj.seekbarDiv.focus(); 2367 } 2368 e.preventDefault(); 2369 } else if (e.type === 'mouseup' || e.type === 'touchend') { 2370 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2371 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2372 } 2373 } 2374 if (e.type !== 'mousemove' && e.type !== 'mousedown' && e.type !== 'mouseup' && e.type !== 'touchstart' && e.type !== 'touchend') { 2375 thisObj.refreshTooltip(); 2376 } 2377 }); 2378 2379 // handle seekbarDiv events 2380 this.seekbarDiv.on( 2381 'mouseenter mouseleave mousemove mousedown mouseup keydown keyup touchstart touchmove touchend', function (e) { 2382 2383 // Don't trigger move on right click. 2384 if ( e.button == 2 && e.type == 'mousedown' ) { 2385 return; 2386 } 2387 coords = thisObj.pointerEventToXY(e); 2388 let keyPressed = e.key; 2389 2390 if (e.type === 'mouseenter') { 2391 thisObj.overBody = true; 2392 thisObj.overBodyMousePos = { 2393 x: coords.x, 2394 y: coords.y 2395 }; 2396 } else if (e.type === 'mouseleave') { 2397 thisObj.overBody = false; 2398 thisObj.overBodyMousePos = null; 2399 if (!thisObj.overHead && thisObj.tracking && thisObj.trackDevice === 'mouse') { 2400 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2401 } 2402 } else if (e.type === 'mousemove' || e.type === 'touchmove') { 2403 thisObj.overBodyMousePos = { 2404 x: coords.x, 2405 y: coords.y 2406 }; 2407 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2408 thisObj.trackHeadAtPageX(coords.x); 2409 } 2410 } else if (e.type === 'mousedown' || e.type === 'touchstart') { 2411 thisObj.startTracking('mouse', thisObj.pageXToPosition(coords.x)); 2412 thisObj.trackHeadAtPageX(coords.x); 2413 if (!thisObj.seekHead.is(':focus')) { 2414 thisObj.seekHead.focus(); 2415 } 2416 e.preventDefault(); 2417 } else if (e.type === 'mouseup' || e.type === 'touchend') { 2418 if (thisObj.tracking && thisObj.trackDevice === 'mouse') { 2419 thisObj.stopTracking(thisObj.pageXToPosition(coords.x)); 2420 } 2421 } else if (e.type === 'keydown') { 2422 if (e.key === 'Home') { 2423 thisObj.trackImmediatelyTo(0); 2424 } else if (e.key === 'End') { 2425 thisObj.trackImmediatelyTo(thisObj.duration); 2426 } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') { 2427 thisObj.arrowKeyDown(-1); 2428 } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') { 2429 thisObj.arrowKeyDown(1); 2430 } else if (e.key === 'PageUp' && bigInterval > 0) { 2431 thisObj.arrowKeyDown(bigInterval); 2432 } else if (e.key === 'PageDown' && bigInterval > 0) { 2433 thisObj.arrowKeyDown(-bigInterval); 2434 } else { 2435 return; 2436 } 2437 e.preventDefault(); 2438 } else if (e.type === 'keyup') { 2439 if ( keyPressed === e.key ) { 2440 if (thisObj.tracking && thisObj.trackDevice === 'keyboard') { 2441 thisObj.stopTracking(thisObj.keyTrackPosition); 2442 } 2443 e.preventDefault(); 2444 } 2445 } 2446 if (!thisObj.overTooltip && e.type !== 'mouseup' && e.type !== 'keydown' && e.type !== 'keydown') { 2447 thisObj.refreshTooltip(); 2448 } 2449 }); 2450 } 2451 2452 AccessibleSlider.prototype.arrowKeyDown = function (multiplier) { 2453 if (this.tracking && this.trackDevice === 'keyboard') { 2454 this.keyTrackPosition = this.boundPos(this.keyTrackPosition + (this.nextStep * multiplier)); 2455 this.inertiaCount += 1; 2456 if (this.inertiaCount === 20) { 2457 this.inertiaCount = 0; 2458 this.nextStep *= 2; 2459 } 2460 this.trackHeadAtPosition(this.keyTrackPosition); 2461 } else { 2462 this.nextStep = 1; 2463 this.inertiaCount = 0; 2464 this.keyTrackPosition = this.boundPos(this.position + (this.nextStep * multiplier)); 2465 this.startTracking('keyboard', this.keyTrackPosition); 2466 this.trackHeadAtPosition(this.keyTrackPosition); 2467 } 2468 }; 2469 2470 AccessibleSlider.prototype.pageXToPosition = function (pageX) { 2471 var offset = pageX - this.seekbarDiv.offset().left; 2472 var position = this.duration * (offset / this.seekbarDiv.width()); 2473 return this.boundPos(position); 2474 }; 2475 2476 AccessibleSlider.prototype.boundPos = function (position) { 2477 return Math.max(0, Math.min(position, this.duration)); 2478 }; 2479 2480 AccessibleSlider.prototype.setDuration = function (duration) { 2481 if (duration !== this.duration) { 2482 this.duration = duration; 2483 this.resetHeadLocation(); 2484 this.seekHead.attr('aria-valuemax', duration); 2485 } 2486 }; 2487 2488 // Set width of the legacy seekbar. 2489 AccessibleSlider.prototype.setWidth = function (width) { 2490 this.wrapperDiv.width(width); 2491 this.resizeDivs(); 2492 this.resetHeadLocation(); 2493 }; 2494 2495 AccessibleSlider.prototype.getWidth = function () { 2496 return this.wrapperDiv.width(); 2497 }; 2498 2499 AccessibleSlider.prototype.resizeDivs = function () { 2500 this.playedDiv.width( 100 * (this.position / this.duration) + '%' ); 2501 this.loadedDiv.width( 100 * this.buffered + '%' ); 2502 }; 2503 2504 // Stops tracking, sets the head location to the current position. 2505 AccessibleSlider.prototype.resetHeadLocation = function () { 2506 var ratio = this.position / this.duration; 2507 var center = this.seekbarDiv.width() * ratio; 2508 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 2509 2510 if (this.tracking) { 2511 this.stopTracking(this.position); 2512 } 2513 }; 2514 2515 AccessibleSlider.prototype.setPosition = function (position, updateLive) { 2516 this.position = position; 2517 this.resetHeadLocation(); 2518 if (this.overHead) { 2519 this.refreshTooltip(); 2520 } 2521 this.resizeDivs(); 2522 this.updateAriaValues(position, updateLive); 2523 }; 2524 2525 // TODO: Native HTML5 can have several buffered segments, and this actually happens quite often. Change this to display them all. 2526 AccessibleSlider.prototype.setBuffered = function (ratio) { 2527 if (!isNaN(ratio)) { 2528 this.buffered = ratio; 2529 this.redrawDivs; 2530 } 2531 }; 2532 2533 AccessibleSlider.prototype.startTracking = function (device, position) { 2534 if (!this.tracking) { 2535 this.trackDevice = device; 2536 this.tracking = true; 2537 this.seekbarDiv.trigger('startTracking', [position]); 2538 } 2539 }; 2540 2541 AccessibleSlider.prototype.stopTracking = function (position) { 2542 this.trackDevice = null; 2543 this.tracking = false; 2544 this.seekbarDiv.trigger('stopTracking', [position]); 2545 this.setPosition(position, true); 2546 }; 2547 2548 AccessibleSlider.prototype.trackHeadAtPageX = function (pageX) { 2549 var position = this.pageXToPosition(pageX); 2550 var newLeft = pageX - this.seekbarDiv.offset().left - (this.seekHead.width() / 2); 2551 newLeft = Math.max(0, Math.min(newLeft, this.seekbarDiv.width() - this.seekHead.width())); 2552 this.lastTrackPosition = position; 2553 this.seekHead.css('left', newLeft); 2554 this.reportTrackAtPosition(position); 2555 }; 2556 2557 AccessibleSlider.prototype.trackHeadAtPosition = function (position) { 2558 var ratio = position / this.duration; 2559 var center = this.seekbarDiv.width() * ratio; 2560 this.lastTrackPosition = position; 2561 this.seekHead.css('left', center - (this.seekHead.width() / 2)); 2562 this.reportTrackAtPosition(position); 2563 }; 2564 2565 AccessibleSlider.prototype.reportTrackAtPosition = function (position) { 2566 this.seekbarDiv.trigger('tracking', [position]); 2567 this.updateAriaValues(position, true); 2568 }; 2569 2570 AccessibleSlider.prototype.updateAriaValues = function (position, updateLive) { 2571 // TODO: Localize, move to another function. 2572 var pHours = Math.floor(position / 3600); 2573 var pMinutes = Math.floor((position % 3600) / 60); 2574 var pSeconds = Math.floor(position % 60); 2575 2576 var pHourWord = pHours === 1 ? 'hour' : 'hours'; 2577 var pMinuteWord = pMinutes === 1 ? 'minute' : 'minutes'; 2578 var pSecondWord = pSeconds === 1 ? 'second' : 'seconds'; 2579 2580 var descriptionText; 2581 if (pHours > 0) { 2582 descriptionText = pHours + 2583 ' ' + pHourWord + 2584 ', ' + pMinutes + 2585 ' ' + pMinuteWord + 2586 ', ' + pSeconds + 2587 ' ' + pSecondWord; 2588 } else if (pMinutes > 0) { 2589 descriptionText = pMinutes + 2590 ' ' + pMinuteWord + 2591 ', ' + pSeconds + 2592 ' ' + pSecondWord; 2593 } else { 2594 descriptionText = pSeconds + ' ' + pSecondWord; 2595 } 2596 2597 /* Comment to stop live region from generating or being used. */ 2598 if (!this.liveAriaRegion) { 2599 this.liveAriaRegion = $('<span>', { 2600 'class': 'able-offscreen', 2601 'aria-live': 'polite' 2602 }); 2603 this.wrapperDiv.append(this.liveAriaRegion); 2604 } 2605 if (updateLive && (this.liveAriaRegion.text() !== descriptionText)) { 2606 this.liveAriaRegion.text(descriptionText); 2607 } 2608 2609 // Uncomment the following lines to use aria values instead of separate live region. 2610 this.seekHead.attr('aria-valuetext', descriptionText); 2611 this.seekHead.attr('aria-valuenow', Math.floor(position).toString()); 2612 }; 2613 2614 AccessibleSlider.prototype.trackImmediatelyTo = function (position) { 2615 this.startTracking('keyboard', position); 2616 this.trackHeadAtPosition(position); 2617 this.keyTrackPosition = position; 2618 }; 2619 2620 AccessibleSlider.prototype.refreshTooltip = function () { 2621 if (this.overHead) { 2622 this.timeTooltip.show(); 2623 if (this.tracking) { 2624 this.timeTooltip.text(this.positionToStr(this.lastTrackPosition)); 2625 } else { 2626 this.timeTooltip.text(this.positionToStr(this.position)); 2627 } 2628 this.setTooltipPosition(this.seekHead.position().left + (this.seekHead.width() / 2)); 2629 } else if (this.overBody && this.overBodyMousePos) { 2630 this.timeTooltip.show(); 2631 this.timeTooltip.text(this.positionToStr(this.pageXToPosition(this.overBodyMousePos.x))); 2632 this.setTooltipPosition(this.overBodyMousePos.x - this.seekbarDiv.offset().left); 2633 } else { 2634 2635 clearTimeout(this.timeTooltipTimeoutId); 2636 var _this = this; 2637 this.timeTooltipTimeoutId = setTimeout(function() { 2638 // give user a half second move cursor over tooltip 2639 _this.timeTooltip.hide(); 2640 }, 500); 2641 } 2642 }; 2643 2644 AccessibleSlider.prototype.hideSliderTooltips = function () { 2645 this.overHead = false; 2646 this.overBody = false; 2647 this.timeTooltip.hide(); 2648 }; 2649 2650 AccessibleSlider.prototype.setTooltipPosition = function (x) { 2651 this.timeTooltip.css({ 2652 left: x - (this.timeTooltip.width() / 2) - 10, 2653 bottom: this.seekHead.height() 2654 }); 2655 }; 2656 2657 AccessibleSlider.prototype.positionToStr = function (seconds) { 2658 2659 // same logic as misc.js > formatSecondsAsColonTime() 2660 var dHours = Math.floor(seconds / 3600); 2661 var dMinutes = Math.floor(seconds / 60) % 60; 2662 var dSeconds = Math.floor(seconds % 60); 2663 if (dSeconds < 10) { 2664 dSeconds = '0' + dSeconds; 2665 } 2666 if (dHours > 0) { 2667 if (dMinutes < 10) { 2668 dMinutes = '0' + dMinutes; 2669 } 2670 return dHours + ':' + dMinutes + ':' + dSeconds; 2671 } else { 2672 return dMinutes + ':' + dSeconds; 2673 } 2674 }; 2675 2676 AccessibleSlider.prototype.pointerEventToXY = function(e) { 2677 2678 // returns array of coordinates x and y in response to both mouse and touch events 2679 // for mouse events, this comes from e.pageX and e.pageY 2680 // for touch events, it's a bit more complicated 2681 var out = {x:0, y:0}; 2682 if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') { 2683 var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]; 2684 out.x = touch.pageX; 2685 out.y = touch.pageY; 2686 } else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover'|| e.type=='mouseout' || e.type=='mouseenter' || e.type=='mouseleave') { 2687 out.x = e.pageX; 2688 out.y = e.pageY; 2689 } 2690 return out; 2691 }; 2692 2693 function addBuildplayerFunctions(AblePlayer) { 2694 AblePlayer.prototype.injectPlayerCode = function() { 2695 2696 // create and inject surrounding HTML structure 2697 // If iOS & video: 2698 // iOS does not support any of the player's functionality - everything plays in its own player 2699 // Therefore, AblePlayer is not loaded & all functionality is disabled 2700 // (this all determined. If this is iOS && video, this function is never called) 2701 2702 var captionsContainer; 2703 // Wrappers, from inner to outer: 2704 // $mediaContainer - contains the original media element 2705 // $ableDiv - contains the media player and all its objects (e.g., captions, controls, descriptions) 2706 // $ableWrapper - contains additional widgets (e.g., transcript window, sign window) 2707 this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent(); 2708 this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent(); 2709 this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent(); 2710 this.$ableWrapper.addClass('able-skin-' + this.skin); 2711 2712 if (this.mediaType === 'video') { 2713 // youtube adds its own big play button 2714 // don't show ours *unless* video has a poster attribute 2715 // (which obstructs the YouTube poster & big play button) 2716 if (this.player !== 'youtube' || this.hasPoster) { 2717 this.injectBigPlayButton(); 2718 } 2719 } 2720 2721 // add container that captions or description will be appended to 2722 // Note: new Jquery object must be assigned _after_ wrap, hence the temp captionsContainer variable 2723 captionsContainer = $('<div>'); 2724 if (this.mediaType === 'video') { 2725 captionsContainer.addClass('able-vidcap-container'); 2726 } else if (this.mediaType === 'audio') { 2727 captionsContainer.addClass('able-audcap-container'); 2728 // hide this by default. It will be shown if captions are available 2729 captionsContainer.addClass('captions-off'); 2730 } 2731 2732 this.injectPlayerControlArea(); // this may need to be injected after captions??? 2733 this.$captionsContainer = this.$mediaContainer.wrap(captionsContainer).parent(); 2734 this.injectAlert(this.$ableDiv); 2735 this.injectPlaylist(); 2736 this.injectAudioPoster(); 2737 // Do this last, as it should be prepended to the top of this.$ableDiv 2738 // after everything else has prepended 2739 this.injectOffscreenHeading(); 2740 }; 2741 2742 AblePlayer.prototype.injectAudioPoster = function() { 2743 if ( this.mediaType === 'audio' && this.hasPoster ) { 2744 const audioPoster = purify.sanitize(this.audioPoster); 2745 const audioPosterAlt = purify.sanitize(this.audioPosterAlt); 2746 let audioPosterImg = document.createElement( 'img' ); 2747 audioPosterImg.setAttribute( 'src', audioPoster ); 2748 audioPosterImg.setAttribute( 'alt', audioPosterAlt ); 2749 this.$audioWrapper = this.$playerDiv.wrap( '<div class="able-audio-wrapper">' ).parent(); 2750 this.$audioWrapper.prepend( audioPosterImg ); 2751 } 2752 }; 2753 2754 AblePlayer.prototype.injectOffscreenHeading = function () { 2755 2756 // Inject an offscreen heading to the media container. 2757 // If heading hasn't already been manually defined via data-heading-level, 2758 // automatically assign a level that is one level deeper than the closest parent heading 2759 // as determined by getNextHeadingLevel() 2760 var headingType; 2761 if (this.playerHeadingLevel == '0') ; else { 2762 if (typeof this.playerHeadingLevel === 'undefined') { 2763 this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6 2764 } 2765 headingType = 'h' + this.playerHeadingLevel.toString(); 2766 this.$headingDiv = $('<' + headingType + '>'); 2767 this.$ableDiv.prepend(this.$headingDiv); 2768 this.$headingDiv.addClass('able-offscreen'); 2769 this.$headingDiv.text( this.translate( 'playerHeading', 'Media player' ) ); 2770 } 2771 }; 2772 2773 AblePlayer.prototype.injectBigPlayButton = function () { 2774 2775 var thisObj = this; 2776 2777 this.$bigPlayButton = $('<button>', { 2778 'class': 'able-big-play-button', 2779 'aria-hidden': false, 2780 'aria-label': this.translate( 'play', 'Play' ), 2781 'type': 'button', 2782 'tabindex': 0 2783 }); 2784 2785 this.getIcon( this.$bigPlayButton, 'play' ); 2786 2787 this.$bigPlayButton.on( 'click', function () { 2788 thisObj.handlePlay(); 2789 }); 2790 2791 this.$mediaContainer.append(this.$bigPlayButton); 2792 }; 2793 2794 AblePlayer.prototype.injectPlayerControlArea = function () { 2795 2796 this.$playerDiv = $('<div>', { 2797 'class' : 'able-player', 2798 'role' : 'region', 2799 'aria-label' : ( 'audio' === this.mediaType ) ? this.translate( 'audioPlayer', 'audio player' ) : this.translate( 'videoPlayer', 'video player' ) 2800 }); 2801 this.$playerDiv.addClass('able-' + this.mediaType); 2802 if (this.hasPlaylist && this.showNowPlaying) { 2803 this.$nowPlayingDiv = $('<div>',{ 2804 'class' : 'able-now-playing', 2805 'aria-live' : 'assertive', 2806 'aria-atomic': 'true' 2807 }); 2808 } 2809 this.$controllerDiv = $('<div>',{ 2810 'class' : 'able-controller' 2811 }); 2812 this.$controllerDiv.addClass('able-' + this.iconColor + '-controls'); 2813 2814 this.$statusBarDiv = $('<div>',{ 2815 'class' : 'able-status-bar' 2816 }); 2817 this.$timer = $('<span>',{ 2818 'class' : 'able-timer' 2819 }); 2820 this.$elapsedTimeContainer = $('<span>',{ 2821 'class': 'able-elapsedTime', 2822 text: '0:00' 2823 }); 2824 this.$durationContainer = $('<span>',{ 2825 'class': 'able-duration' 2826 }); 2827 this.$durationSeparator = $('<span>',{ 2828 'class': 'able-timer-separator', 2829 'text': ' / ' 2830 }); 2831 this.$timer.append(this.$elapsedTimeContainer).append(this.$durationSeparator).append(this.$durationContainer); 2832 2833 this.$speed = $('<span>',{ 2834 'class' : 'able-speed', 2835 'aria-live' : 'assertive', 2836 'aria-atomic' : 'true' 2837 }).text(this.translate( 'speed', 'Speed' ) + ': 1x'); 2838 2839 this.$status = $('<span>',{ 2840 'class' : 'able-status', 2841 'aria-live' : 'polite' 2842 }); 2843 2844 // Put everything together. 2845 this.$statusBarDiv.append(this.$timer, this.$speed, this.$status); 2846 if (this.showNowPlaying) { 2847 this.$playerDiv.append(this.$nowPlayingDiv, this.$controllerDiv, this.$statusBarDiv); 2848 } else { 2849 this.$playerDiv.append(this.$controllerDiv, this.$statusBarDiv); 2850 } 2851 2852 if (this.mediaType === 'video') { 2853 // the player controls go after the media & captions 2854 this.$ableDiv.append(this.$playerDiv); 2855 } else { 2856 // the player controls go before the media & captions 2857 this.$ableDiv.prepend(this.$playerDiv); 2858 } 2859 }; 2860 2861 AblePlayer.prototype.injectTextDescriptionArea = function () { 2862 2863 // create a div for writing description text 2864 this.$descDiv = $('<div>',{ 2865 'class': 'able-descriptions' 2866 }); 2867 // Add ARIA so description will be announced by screen readers 2868 // Later (in description.js > showDescription()), 2869 // if browser supports Web Speech API and this.descMethod === 'browser' 2870 // these attributes will be removed 2871 this.$descDiv.attr({ 2872 'aria-live': 'assertive', 2873 'aria-atomic': 'true' 2874 }); 2875 // Start off with description hidden. 2876 // It will be exposed conditionally within description.js > initDescription() 2877 this.$descDiv.hide(); 2878 this.$ableDiv.append(this.$descDiv); 2879 }; 2880 2881 AblePlayer.prototype.getDefaultWidth = function(which) { 2882 let viewportMaxwidth = window.innerWidth; 2883 // return default width of resizable elements 2884 // these values are somewhat arbitrary, but seem to result in good usability 2885 // if users disagree, they can resize (and resposition) them 2886 if (which === 'transcript') { 2887 return ( viewportMaxwidth <= 450 ) ? viewportMaxwidth : 450; 2888 } else if (which === 'sign') { 2889 return ( viewportMaxwidth <= 400 ) ? viewportMaxwidth : 400; 2890 } 2891 }; 2892 2893 /** 2894 * Reposition draggable windows when switched into fullscreen. 2895 * 2896 * @param {string} which 'transcript' or 'sign'. 2897 */ 2898 AblePlayer.prototype.rePositionDraggableWindow = function (which) { 2899 2900 let preferences, $window; 2901 preferences = this.getPref(); 2902 $window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow; 2903 if ( which === 'transcript' && $window ) { 2904 if (typeof preferences.transcript !== 'undefined') { 2905 this.prevTranscriptPosition = preferences.transcript; 2906 } 2907 $window.css({ 2908 'top': 0, 2909 'left': 0 2910 }); 2911 } else if ( 'sign' === which && $window ) { 2912 if (typeof preferences.sign !== 'undefined') { 2913 this.prevSignPosition = preferences.sign; 2914 } 2915 $window.css({ 2916 'top': 0, 2917 'right': 0, 2918 'left': 'auto' 2919 }); 2920 } 2921 }; 2922 2923 AblePlayer.prototype.positionDraggableWindow = function (which, width) { 2924 2925 // which is either 'transcript' or 'sign' 2926 var preferences, preferencePos, $window, windowPos, viewportWidth, windowWidth; 2927 2928 preferences = this.getPref(); 2929 $window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow; 2930 if ( ! $window ) { 2931 return; 2932 } 2933 if (which === 'transcript') { 2934 if (typeof preferences.transcript !== 'undefined') { 2935 preferencePos = preferences.transcript; 2936 } 2937 if ( this.prevTranscriptPosition ) { 2938 preferencePos = this.prevTranscriptPosition; 2939 this.prevTranscriptPosition = false; 2940 } 2941 } else if (which === 'sign') { 2942 if (typeof preferences.sign !== 'undefined') { 2943 preferencePos = preferences.sign; 2944 } 2945 if ( this.prevSignPosition ) { 2946 preferencePos = this.prevSignPosition; 2947 this.prevSignPosition = false; 2948 } 2949 } 2950 if (typeof preferencePos !== 'undefined' && !($.isEmptyObject(preferencePos))) { 2951 // position window using stored values from preferences 2952 $window.css({ 2953 'position': preferencePos['position'], 2954 'width': preferencePos['width'], 2955 'z-index': preferencePos['zindex'] 2956 }); 2957 if (preferencePos['position'] === 'absolute') { 2958 $window.css({ 2959 'top': preferencePos['top'], 2960 'left': preferencePos['left'] 2961 }); 2962 // Check whether the window is above the top of the viewport. 2963 let topPosition = $window.offset().top; 2964 let leftPosition = $window.offset().left; 2965 viewportWidth = window.innerWidth; 2966 windowWidth = $window.width(); 2967 if ( topPosition < 0 ) { 2968 $window.css({ 2969 'top': preferencePos['top'] - topPosition 2970 }); 2971 } 2972 // If draggable window is off screen to the left. 2973 if ( leftPosition < 0 && ! this.restoringAfterFullscreen ) { 2974 $window.css({ 2975 'left': preferencePos['left'] - leftPosition 2976 }); 2977 } 2978 // If draggable window is off screen to the right. 2979 if ( viewportWidth - leftPosition < 30 ) { 2980 $window.css({ 2981 'left': viewportWidth - windowWidth 2982 }); 2983 } 2984 } 2985 // since preferences are not page-specific, z-index needs may vary across different pages 2986 this.updateZIndex(which); 2987 } else { 2988 // position window using default values 2989 windowPos = this.getOptimumPosition(which, width); 2990 if (typeof width === 'undefined') { 2991 width = this.getDefaultWidth(which); 2992 } 2993 $window.css({ 2994 'position': windowPos[0], 2995 'width': width, 2996 'z-index': windowPos[3] 2997 }); 2998 if (windowPos[0] === 'absolute') { 2999 $window.css({ 3000 'top': windowPos[1] + 'px', 3001 'left': windowPos[2] + 'px', 3002 }); 3003 } 3004 } 3005 }; 3006 3007 AblePlayer.prototype.getOptimumPosition = function (targetWindow, targetWidth) { 3008 3009 // returns optimum position for targetWindow, as an array with the following structure: 3010 // 0 - CSS position ('absolute' or 'relative') 3011 // 1 - top 3012 // 2 - left 3013 // 3 - zindex (if not default) 3014 // targetWindow is either 'transcript' or 'sign' 3015 // if there is room to the right of the player, position element there 3016 // else if there is room the left of the player, position element there 3017 // else position element beneath player 3018 3019 var gap, position, ableWidth, ableOffset, ableLeft, windowWidth, otherWindowWidth; 3020 3021 if (typeof targetWidth === 'undefined') { 3022 targetWidth = this.getDefaultWidth(targetWindow); 3023 } 3024 3025 gap = 5; // number of pixels to preserve between Able Player objects 3026 position = []; // position, top, left 3027 3028 ableWidth = this.$ableDiv.width(); 3029 ableOffset = this.$ableDiv.offset(); 3030 ableLeft = ableOffset.left; 3031 windowWidth = $(window).width(); 3032 otherWindowWidth = 0; // width of other visiable draggable windows will be added to this 3033 3034 if (targetWindow === 'transcript') { 3035 // If placing the transcript window, check position of sign window first. 3036 if (typeof this.$signWindow !== 'undefined' && (this.$signWindow.is(':visible'))) { 3037 otherWindowWidth = this.$signWindow.width() + gap; 3038 } 3039 } else if (targetWindow === 'sign') { 3040 // If placing the sign window, check position of transcript window first. 3041 if (typeof this.$transcriptArea !== 'undefined' && (this.$transcriptArea.is(':visible'))) { 3042 otherWindowWidth = this.$transcriptArea.width() + gap; 3043 } 3044 } 3045 if (targetWidth < (windowWidth - (ableLeft + ableWidth + gap + otherWindowWidth))) { 3046 // there's room to the left of $ableDiv 3047 position[0] = 'absolute'; 3048 position[1] = 0; 3049 position[2] = ableWidth + otherWindowWidth + gap; 3050 } else if (targetWidth + gap < ableLeft) { 3051 // there's room to the right of $ableDiv 3052 position[0] = 'absolute'; 3053 position[1] = 0; 3054 position[2] = ableLeft - targetWidth - gap; 3055 } else { 3056 // position element below $ableDiv 3057 position[0] = 'relative'; 3058 // no need to define top, left, or z-index 3059 } 3060 return position; 3061 }; 3062 3063 AblePlayer.prototype.injectAlert = function ($container) { 3064 // inject two alerts, one visible for all users and one for screen reader users only 3065 this.$alertBox = $('<div role="alert"></div>'); 3066 this.$alertBox.addClass('able-alert'); 3067 this.$alertBox.hide(); 3068 3069 var $alertText = $( '<span></span>' ); 3070 $alertText.appendTo(this.$alertBox); 3071 3072 var $alertDismiss = $('<button type="button"></button>' ); 3073 $alertDismiss.attr( 'aria-label', this.translate( 'dismissButton', 'Dismiss' ) ); 3074 $alertDismiss.text( 'Ã' ); 3075 $alertDismiss.appendTo(this.$alertBox); 3076 3077 $alertDismiss.on( 'click', function(e) { 3078 $(this).parent('div').hide(); 3079 }); 3080 3081 this.$alertBox.appendTo($container); 3082 3083 if ( ! this.$srAlertBox ) { 3084 this.$srAlertBox = $('<div role="alert"></div>'); 3085 this.$srAlertBox.addClass('able-screenreader-alert'); 3086 this.$srAlertBox.appendTo($container); 3087 } 3088 }; 3089 3090 AblePlayer.prototype.injectPlaylist = function () { 3091 3092 if (this.playlistEmbed === true) { 3093 // move playlist into player, immediately before statusBarDiv 3094 var playlistClone = this.$playlistDom.clone(); 3095 playlistClone.insertBefore(this.$statusBarDiv); 3096 // Update to the new playlist copy. 3097 this.$playlist = playlistClone.find('li'); 3098 } 3099 }; 3100 3101 AblePlayer.prototype.createPopup = function (which, tracks) { 3102 3103 // Create popup menu and append to player 3104 // 'which' parameter is either 'captions', 'chapters', 'prefs', 'transcript-window' or 'sign-window' 3105 // 'tracks', if provided, is a list of tracks to be used as menu items 3106 3107 var thisObj, $menu, includeMenuItem, i, $menuItem, prefCat, whichPref, hasDefault, track, 3108 windowOptions, $thisItem, $prevItem, $nextItem, hasDescription, hasTranscript; 3109 3110 thisObj = this; 3111 3112 $menu = $('<ul>',{ 3113 'id': this.mediaId + '-' + which + '-menu', 3114 'class': 'able-popup', 3115 'role': 'menu' 3116 }).hide(); 3117 3118 if (which === 'captions') { 3119 $menu.addClass('able-popup-captions'); 3120 } 3121 3122 // Populate menu with menu items 3123 if (which === 'prefs') { 3124 if (this.prefCats.length > 1) { 3125 for (i = 0; i < this.prefCats.length; i++) { 3126 prefCat = this.prefCats[i]; 3127 hasDescription = ( thisObj.hasDescTracks || thisObj.hasOpenDesc || thisObj.hasClosedDesc ) ? true : false; 3128 hasTranscript = ( thisObj.transcriptType === null ) ? false : true; 3129 3130 // If this player does not have descriptions or transcripts, do not output that option preferences. 3131 if ( prefCat === 'descriptions' && ! hasDescription || prefCat === 'transcript' && ! hasTranscript ) { 3132 continue; 3133 } 3134 $menuItem = $('<li></li>',{ 3135 'role': 'menuitem', 3136 'tabindex': '-1' 3137 }); 3138 if (prefCat === 'captions') { 3139 $menuItem.text( this.translate( 'prefMenuCaptions', 'Captions' ) ); 3140 } else if (prefCat === 'descriptions') { 3141 $menuItem.text( this.translate( 'prefMenuDescriptions', 'Descriptions' ) ); 3142 } else if (prefCat === 'keyboard') { 3143 $menuItem.text( this.translate( 'prefMenuKeyboard', 'Keyboard' ) ); 3144 } else if (prefCat === 'transcript') { 3145 $menuItem.text( this.translate( 'prefMenuTranscript', 'Transcript' ) ); 3146 } 3147 $menuItem.on('click',function() { 3148 whichPref = $(this).text(); 3149 thisObj.showingPrefsDialog = true; 3150 thisObj.setFullscreen(false); 3151 if (whichPref === thisObj.translate( 'prefMenuCaptions', 'Captions' ) ) { 3152 thisObj.captionPrefsDialog.show(); 3153 } else if (whichPref === thisObj.translate( 'prefMenuDescriptions', 'Descriptions' ) ) { 3154 thisObj.descPrefsDialog.show(); 3155 } else if (whichPref === thisObj.translate( 'prefMenuKeyboard', 'Keyboard' ) ) { 3156 thisObj.keyboardPrefsDialog.show(); 3157 } else if (whichPref === thisObj.translate( 'prefMenuTranscript', 'Transcript' ) ) { 3158 thisObj.transcriptPrefsDialog.show(); 3159 } 3160 thisObj.closePopups(); 3161 thisObj.showingPrefsDialog = false; 3162 }); 3163 $menu.append($menuItem); 3164 } 3165 this.$prefsButton.attr('data-prefs-popup','menu'); 3166 } else if (this.prefCats.length == 1) { 3167 // only 1 category, so don't create a popup menu. 3168 // Instead, open dialog directly when user clicks Prefs button 3169 this.$prefsButton.attr('data-prefs-popup',this.prefCats[0]); 3170 } 3171 } else if (which === 'captions' || which === 'chapters') { 3172 hasDefault = false; 3173 for (i = 0; i < tracks.length; i++) { 3174 track = tracks[i]; 3175 if (which === 'captions' && this.player === 'html5' && typeof track.cues === 'undefined') { 3176 includeMenuItem = false; 3177 } else { 3178 includeMenuItem = true; 3179 } 3180 if (includeMenuItem) { 3181 $menuItem = $('<li></li>',{ 3182 'role': 'menuitemradio', 3183 'tabindex': '-1', 3184 'lang': track.language 3185 }); 3186 if (track.def && this.prefCaptions == 1) { 3187 $menuItem.attr('aria-checked','true'); 3188 hasDefault = true; 3189 } else { 3190 $menuItem.attr('aria-checked','false'); 3191 } 3192 // Get a label using track data 3193 if (which == 'captions') { 3194 $menuItem.text(track.label); 3195 $menuItem.on('click',this.getCaptionClickFunction(track)); 3196 } else if (which == 'chapters') { 3197 $menuItem.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start)); 3198 $menuItem.on('click',this.getChapterClickFunction(track.start)); 3199 } 3200 $menu.append($menuItem); 3201 } 3202 } 3203 if (which === 'captions') { 3204 // add a 'captions off' menu item 3205 $menuItem = $('<li></li>',{ 3206 'role': 'menuitemradio', 3207 'tabindex': '-1', 3208 }).text( this.translate( 'captionsOff', 'Captions off' ) ); 3209 if (this.prefCaptions === 0) { 3210 $menuItem.attr('aria-checked','true'); 3211 hasDefault = true; 3212 } else { 3213 $menuItem.attr('aria-checked','false'); 3214 } 3215 $menuItem.on('click',this.getCaptionOffFunction()); 3216 $menu.append($menuItem); 3217 } 3218 } else if (which === 'transcript-window' || which === 'sign-window') { 3219 windowOptions = []; 3220 windowOptions.push({ 3221 'name': 'move', 3222 'label': this.translate( 'windowMove', 'Move' ) 3223 }); 3224 windowOptions.push({ 3225 'name': 'resize', 3226 'label': this.translate( 'windowResize', 'Resize' ) 3227 }); 3228 windowOptions.push({ 3229 'name': 'close', 3230 'label': this.translate( 'closeButtonLabel', 'Close' ) 3231 }); 3232 for (i = 0; i < windowOptions.length; i++) { 3233 $menuItem = $('<li></li>',{ 3234 'role': 'menuitem', 3235 'tabindex': '-1', 3236 'data-choice': windowOptions[i].name 3237 }); 3238 $menuItem.text(windowOptions[i].label); 3239 $menuItem.on('click',function(e) { 3240 e.stopPropagation(); 3241 if (typeof e.button !== 'undefined' && e.button !== 0) { 3242 // this was a mouse click (if click is triggered by keyboard, e.button is undefined) 3243 // and the button was not a left click (left click = 0) 3244 // therefore, ignore this click 3245 return false; 3246 } 3247 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 3248 thisObj.windowMenuClickRegistered = true; 3249 thisObj.handleMenuChoice(which.substring(0, which.indexOf('-')), $(this).attr('data-choice'), e); 3250 } 3251 }); 3252 $menu.append($menuItem); 3253 } 3254 } 3255 // assign default item, if there isn't one already 3256 if (which === 'captions' && !hasDefault) { 3257 // check the menu item associated with the default language 3258 // as determined in control.js > syncTrackLanguages() 3259 if ($menu.find('li[lang=' + this.captionLang + ']')) { 3260 // a track exists for the default language. Check that item in the menu 3261 $menu.find('li[lang=' + this.captionLang + ']').attr('aria-checked','true'); 3262 } else { 3263 // check the last item (captions off) 3264 $menu.find('li').last().attr('aria-checked','true'); 3265 } 3266 } else if (which === 'chapters') { 3267 if ($menu.find('li:contains("' + this.defaultChapter + '")')) { 3268 $menu.find('li:contains("' + this.defaultChapter + '")').attr('aria-checked','true').addClass('able-focus'); 3269 } else { 3270 $menu.find('li').first().attr('aria-checked','true').addClass('able-focus'); 3271 } 3272 } 3273 // add keyboard handlers for navigating within popups 3274 $menu.on('keydown',function (e) { 3275 3276 $thisItem = $(this).find('li:focus'); 3277 if ($thisItem.is(':first-child')) { 3278 // this is the first item in the menu 3279 $prevItem = $(this).find('li').last(); // wrap to bottom 3280 $nextItem = $thisItem.next(); 3281 } else if ($thisItem.is(':last-child')) { 3282 // this is the last Item 3283 $prevItem = $thisItem.prev(); 3284 $nextItem = $(this).find('li').first(); // wrap to top 3285 } else { 3286 $prevItem = $thisItem.prev(); 3287 $nextItem = $thisItem.next(); 3288 } 3289 if (e.key === 'Tab') { 3290 if (e.shiftKey) { 3291 $thisItem.removeClass('able-focus'); 3292 $prevItem.trigger('focus').addClass('able-focus'); 3293 } else { 3294 $thisItem.removeClass('able-focus'); 3295 $nextItem.trigger('focus').addClass('able-focus'); 3296 } 3297 } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { 3298 $thisItem.removeClass('able-focus'); 3299 $nextItem.trigger('focus').addClass('able-focus'); 3300 } else if (e.key == 'ArrowUp' || e.key === 'ArrowLeft') { 3301 $thisItem.removeClass('able-focus'); 3302 $prevItem.trigger('focus').addClass('able-focus'); 3303 } else if (e.key === ' ' || e.key === 'Enter') { 3304 $thisItem.trigger( 'click' ); 3305 } else if (e.key === 'Escape') { 3306 $thisItem.removeClass('able-focus'); 3307 thisObj.closePopups(); 3308 e.stopPropagation; 3309 } 3310 e.preventDefault(); 3311 }); 3312 this.$controllerDiv.append($menu); 3313 return $menu; 3314 }; 3315 3316 AblePlayer.prototype.closePopups = function () { 3317 3318 var thisObj = this; 3319 3320 if (this.chaptersPopup && this.chaptersPopup.is(':visible')) { 3321 this.chaptersPopup.hide(); 3322 this.$chaptersButton.attr('aria-expanded','false').trigger('focus'); 3323 } 3324 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 3325 this.captionsPopup.hide(); 3326 this.$ccButton.attr('aria-expanded', 'false'); 3327 this.waitThenFocus(this.$ccButton); 3328 } 3329 if (this.prefsPopup && this.prefsPopup.is(':visible') && !this.hidingPopup) { 3330 this.hidingPopup = true; // stopgap to prevent popup from re-opening again on keypress 3331 this.prefsPopup.hide(); 3332 // restore menu items to their original state 3333 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3334 this.$prefsButton.attr('aria-expanded', 'false'); 3335 if (!this.showingPrefsDialog) { 3336 this.waitThenFocus(thisObj.$prefsButton); 3337 } 3338 // wait briefly, then reset hidingPopup 3339 setTimeout(function() { 3340 thisObj.hidingPopup = false; 3341 },100); 3342 } 3343 if (this.$volumeSlider && this.$volumeSlider.is(':visible')) { 3344 this.$volumeSlider.hide().attr('aria-hidden','true'); 3345 this.$volumeButton.attr('aria-expanded', 'false').trigger('focus'); 3346 } 3347 if (this.$transcriptPopup && this.$transcriptPopup.is(':visible')) { 3348 this.hidingPopup = true; 3349 this.$transcriptPopup.hide(); 3350 // restore menu items to their original state 3351 this.$transcriptPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3352 this.$transcriptPopupButton.attr('aria-expanded','false').trigger('focus'); 3353 // wait briefly, then reset hidingPopup 3354 setTimeout(function() { 3355 thisObj.hidingPopup = false; 3356 },100); 3357 } 3358 if (this.$signPopup && this.$signPopup.is(':visible')) { 3359 this.$signPopup.hide(); 3360 // restore menu items to their original state 3361 this.$signPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 3362 this.$signPopupButton.attr('aria-expanded','false').trigger('focus'); 3363 } 3364 }; 3365 3366 AblePlayer.prototype.setupPopups = function (which) { 3367 3368 // Create and fill in the popup menu forms for various controls. 3369 // parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters') 3370 // If which is undefined, automatically setup 'captions', 'chapters', and 'prefs' popups 3371 // However, only setup 'transcript-window' and 'sign-window' popups if passed as value of which 3372 var popups, i, tracks; 3373 3374 popups = []; 3375 if (typeof which === 'undefined') { 3376 popups.push('prefs'); 3377 } 3378 3379 if (which === 'captions' || (typeof which === 'undefined')) { 3380 if (this.captions.length > 0) { 3381 popups.push('captions'); 3382 } 3383 } 3384 if (which === 'chapters' || (typeof which === 'undefined')) { 3385 if (this.chapters.length > 0 && this.useChaptersButton) { 3386 popups.push('chapters'); 3387 } 3388 } 3389 if (which === 'transcript-window' && this.transcriptType === 'popup') { 3390 popups.push('transcript-window'); 3391 } 3392 if (which === 'sign-window' && this.hasSignLanguage) { 3393 popups.push('sign-window'); 3394 } 3395 if (popups.length > 0) { 3396 for (i=0; i<popups.length; i++) { 3397 var popup = popups[i]; 3398 if (popup == 'prefs') { 3399 this.prefsPopup = this.createPopup('prefs'); 3400 } else if (popup == 'captions') { 3401 if (typeof this.captionsPopup === 'undefined' || !this.captionsPopup) { 3402 this.captionsPopup = this.createPopup('captions',this.captions); 3403 } 3404 } else if (popup == 'chapters') { 3405 if (this.selectedChapters) { 3406 tracks = this.selectedChapters.cues; 3407 } else if (this.chapters.length >= 1) { 3408 tracks = this.chapters[0].cues; 3409 } else { 3410 tracks = []; 3411 } 3412 if (typeof this.chaptersPopup === 'undefined' || !this.chaptersPopup) { 3413 this.chaptersPopup = this.createPopup('chapters',tracks); 3414 } 3415 } else if (popup == 'transcript-window') { 3416 return this.createPopup('transcript-window'); 3417 } else if (popup == 'sign-window') { 3418 return this.createPopup('sign-window'); 3419 } 3420 } 3421 } 3422 }; 3423 3424 AblePlayer.prototype.provideFallback = function() { 3425 3426 // provide fallback in case of a critical error building the player 3427 // to test, set data-test-fallback to either of the following values: 3428 // 1 = emulate failure to build Able Player 3429 // 2 = emulate browser that doesn't support HTML5 media 3430 3431 var i, $fallback; 3432 3433 if (this.usingFallback) { 3434 // fallback has already been implemented. 3435 // stopgap to prevent this function from executing twice on the same media element 3436 return; 3437 } else { 3438 this.usingFallback = true; 3439 } 3440 3441 if (!this.testFallback) { 3442 // this is not a test. 3443 // an actual error has resulted in this function being called. 3444 // use scenario 1 3445 this.testFallback = 1; 3446 } 3447 3448 if (typeof this.$media === 'undefined') { 3449 // this function has been called prior to initialize.js > reinitialize() 3450 // before doing anything, need to create the jQuery media object 3451 this.$media = $(this.media); 3452 } 3453 3454 // get/assign an id for the media element 3455 if (this.$media.attr('id')) { 3456 this.mediaId = this.$media.attr('id'); 3457 } else { 3458 this.mediaId = 'media' + Math.floor(Math.random() * 1000000000).toString(); 3459 } 3460 3461 // check whether element has nested fallback content 3462 this.hasFallback = false; 3463 if (this.$media.children().length) { 3464 i = 0; 3465 while (i < this.$media.children().length && !this.hasFallback) { 3466 if (!(this.$media.children()[i].tagName === 'SOURCE' || 3467 this.$media.children()[i].tagName === 'TRACK')) { 3468 // this element is something other than <source> or <track> 3469 this.hasFallback = true; 3470 } 3471 i++; 3472 } 3473 } 3474 if (!this.hasFallback) { 3475 // the HTML code does not include any nested fallback content 3476 // inject our own 3477 // NOTE: this message is not translated, since fallback may be needed 3478 // due to an error loading the translation file 3479 // This will only be needed on very rare occasions, so English is ok. 3480 $fallback = $('<p>').text('Media player unavailable.'); 3481 this.$media.append($fallback); 3482 } 3483 3484 // get height and width attributes, if present 3485 // and add them to a style attribute 3486 if (this.$media.attr('width')) { 3487 this.$media.css('width',this.$media.attr('width') + 'px'); 3488 } 3489 if (this.$media.attr('height')) { 3490 this.$media.css('height',this.$media.attr('height') + 'px'); 3491 } 3492 // Remove data-able-player attribute 3493 this.$media.removeAttr('data-able-player'); 3494 3495 // Add controls attribute (so browser will add its own controls) 3496 this.$media.prop('controls',true); 3497 3498 if (this.testFallback == 2) { 3499 3500 // emulate browser failure to support HTML5 media by changing the media tag name 3501 // browsers should display the supported content that's nested inside 3502 $(this.$media).replaceWith($('<foobar id="foobar-' + this.mediaId + '">')); 3503 this.$newFallbackElement = $('#foobar-' + this.mediaId); 3504 3505 // append all children from the original media 3506 if (this.$media.children().length) { 3507 i = this.$media.children().length - 1; 3508 while (i >= 0) { 3509 this.$newFallbackElement.prepend($(this.$media.children()[i])); 3510 i--; 3511 } 3512 } 3513 if (!this.hasFallback) { 3514 // inject our own fallback content, defined above 3515 this.$newFallbackElement.append($fallback); 3516 } 3517 } else { 3518 console.warn("Able Player encountered a problem, falling back to browser's HTML5 player."); 3519 } 3520 return; 3521 }; 3522 3523 AblePlayer.prototype.calculateControlLayout = function () { 3524 3525 // Calculates the layout for controls based on media and options. 3526 // Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin) 3527 // Keys are the following order: 3528 // 0 = Top left 3529 // 1 = Top right 3530 // 2 = Bottom left (legacy skin only) 3531 // 3 = Bottom right (legacy skin only) 3532 // Each key contains an array of control names to put in that section. 3533 3534 var controlLayout, playbackSupported, numA11yButtons; 3535 3536 controlLayout = []; 3537 controlLayout[0] = []; 3538 controlLayout[1] = []; 3539 if (this.skin === 'legacy') { 3540 controlLayout[2] = []; 3541 controlLayout[3] = []; 3542 } 3543 3544 controlLayout[0].push('play'); 3545 controlLayout[0].push('restart'); 3546 controlLayout[0].push('rewind'); 3547 controlLayout[0].push('forward'); 3548 3549 if (this.skin === 'legacy') { 3550 controlLayout[1].push('seek'); 3551 } 3552 3553 if (this.hasPlaylist) { 3554 if (this.skin === 'legacy') { 3555 controlLayout[0].push('previous'); 3556 controlLayout[0].push('next'); 3557 } else { 3558 controlLayout[0].push('previous'); 3559 controlLayout[0].push('next'); 3560 } 3561 } 3562 3563 if (this.isPlaybackRateSupported()) { 3564 playbackSupported = true; 3565 if (this.skin === 'legacy') { 3566 controlLayout[2].push('slower'); 3567 controlLayout[2].push('faster'); 3568 } 3569 } else { 3570 playbackSupported = false; 3571 } 3572 3573 numA11yButtons = 0; 3574 if (this.hasCaptions) { 3575 numA11yButtons++; 3576 if (this.skin === 'legacy') { 3577 controlLayout[2].push('captions'); 3578 } else { 3579 controlLayout[1].push('captions'); 3580 } 3581 } 3582 if (this.hasSignLanguage) { 3583 numA11yButtons++; 3584 if (this.skin === 'legacy') { 3585 controlLayout[2].push('sign'); 3586 } else { 3587 controlLayout[1].push('sign'); 3588 } 3589 } 3590 if (this.mediaType === 'video') { 3591 if (this.hasOpenDesc || this.hasClosedDesc) { 3592 numA11yButtons++; 3593 if (this.skin === 'legacy') { 3594 controlLayout[2].push('descriptions'); 3595 } else { 3596 controlLayout[1].push('descriptions'); 3597 } 3598 } 3599 } 3600 if (this.transcriptType !== null && !(this.hideTranscriptButton)) { 3601 numA11yButtons++; 3602 if (this.skin === 'legacy') { 3603 controlLayout[2].push('transcript'); 3604 } else { 3605 controlLayout[1].push('transcript'); 3606 } 3607 } 3608 if (this.hasChapters && this.useChaptersButton) { 3609 numA11yButtons++; 3610 if (this.skin === 'legacy') { 3611 controlLayout[2].push('chapters'); 3612 } else { 3613 controlLayout[1].push('chapters'); 3614 } 3615 } 3616 3617 if (this.skin == '2020' && numA11yButtons > 0) { 3618 controlLayout[1].push('pipe'); 3619 } 3620 3621 if (playbackSupported && this.skin === '2020') { 3622 controlLayout[1].push('faster'); 3623 controlLayout[1].push('slower'); 3624 controlLayout[1].push('pipe'); 3625 } 3626 3627 if (this.skin === 'legacy') { 3628 controlLayout[3].push('preferences'); 3629 } else { 3630 controlLayout[1].push('preferences'); 3631 } 3632 3633 if (this.mediaType === 'video' && this.allowFullscreen && this.nativeFullscreenSupported() ) { 3634 if (this.skin === 'legacy') { 3635 controlLayout[3].push('fullscreen'); 3636 } else { 3637 controlLayout[1].push('fullscreen'); 3638 } 3639 } 3640 3641 if (this.browserSupportsVolume()) { 3642 this.volumeButton = 'volume-' + this.getVolumeName(this.volume); 3643 if (this.skin === 'legacy') { 3644 controlLayout[1].push('volume'); 3645 } else { 3646 controlLayout[1].push('volume'); 3647 } 3648 } else { 3649 this.volume = false; 3650 } 3651 return controlLayout; 3652 }; 3653 3654 AblePlayer.prototype.addControls = function() { 3655 3656 // determine which controls to show based on several factors: 3657 // mediaType (audio vs video) 3658 // availability of tracks (e.g., for closed captions & audio description) 3659 // browser support (e.g., for sliders and speedButtons) 3660 // user preferences (???) 3661 // some controls are aligned on the left, and others on the right 3662 3663 var thisObj, controlLayout, numSections, 3664 i, j, controls, $controllerSpan, $sliderDiv, sliderLabel, $pipe, control, 3665 buttonTitle, $newButton, buttonText, position, buttonHeight, 3666 buttonWidth, buttonSide, controllerWidth, tooltipId, tooltipY, tooltipX, 3667 tooltipWidth, tooltipStyle, tooltip, tooltipTimerId, captionLabel, popupMenuId; 3668 3669 thisObj = this; 3670 3671 // Initialize the layout into the this.controlLayout variable. 3672 controlLayout = this.calculateControlLayout(); 3673 numSections = controlLayout.length; 3674 3675 // add an empty div to serve as a tooltip 3676 tooltipId = this.mediaId + '-tooltip'; 3677 this.$tooltipDiv = $('<div>',{ 3678 'id': tooltipId, 3679 'class': 'able-tooltip' 3680 }).hide(); 3681 this.$controllerDiv.append(this.$tooltipDiv); 3682 3683 if (this.skin == '2020') { 3684 // add a full-width seek bar 3685 $sliderDiv = $('<div class="able-seekbar"></div>'); 3686 sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' ); 3687 this.$controllerDiv.append($sliderDiv); 3688 this.seekBar = new AccessibleSlider($sliderDiv, this.duration, this.seekInterval, sliderLabel ); 3689 } 3690 3691 // add a full-width seek bar 3692 let $controlRow = $('<div class="able-control-row"></div>'); 3693 this.$controllerDiv.append($controlRow); 3694 3695 for (i = 0; i < numSections; i++) { 3696 controls = controlLayout[i]; 3697 if ((i % 2) === 0) { // even keys on the left 3698 $controllerSpan = $('<div>',{ 3699 'class': 'able-left-controls' 3700 }); 3701 } else { // odd keys on the right 3702 $controllerSpan = $('<div>',{ 3703 'class': 'able-right-controls' 3704 }); 3705 } 3706 $controlRow.append($controllerSpan); 3707 3708 for (j=0; j<controls.length; j++) { 3709 control = controls[j]; 3710 if (control === 'seek') { 3711 $sliderDiv = $('<div class="able-seekbar"></div>'); 3712 sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' ); 3713 $controllerSpan.append($sliderDiv); 3714 if (typeof this.duration === 'undefined' || this.duration === 0) { 3715 // set arbitrary starting duration, and change it when duration is known 3716 this.duration = 60; 3717 // also set elapsed to 0 3718 this.elapsed = 0; 3719 } 3720 this.seekBar = new AccessibleSlider( $sliderDiv, this.duration, this.seekInterval, sliderLabel ); 3721 } else if (control === 'pipe') { 3722 $pipe = $('<span>', { 3723 'aria-hidden': 'true', 3724 'class': 'able-pipe', 3725 }); 3726 $pipe.append('|'); 3727 $controllerSpan.append($pipe); 3728 } else { 3729 // this control is a button 3730 buttonTitle = this.getButtonTitle(control); 3731 3732 // Buttons consist of a <div role="button"> with an <svg> inside. 3733 // We add aria-label to the button (but not title) 3734 // This has been thoroughly tested and works well in all screen reader/browser combinations 3735 // See https://github.com/ableplayer/ableplayer/issues/81 3736 3737 // NOTE: Changed from <button> to <div role="button" as of 4.2.18 3738 // because <button> elements are rendered poorly in high contrast mode 3739 // in some OS/browser/plugin combinations 3740 3741 // In 5.0.0, icons are always SVG, so the font & image icon edge cases are removed. 3742 $newButton = $('<div>',{ 3743 'role': 'button', 3744 'tabindex': '0', 3745 'class': 'able-button-handler-' + control 3746 }); 3747 3748 if (control === 'volume' || control === 'preferences' || control === 'captions') { 3749 if (control == 'preferences') { 3750 this.prefCats = this.getPreferencesGroups(); 3751 if (this.prefCats.length > 1) { 3752 // Prefs button will trigger a menu 3753 popupMenuId = this.mediaId + '-prefs-menu'; 3754 $newButton.attr({ 3755 'aria-controls': popupMenuId, 3756 'aria-haspopup': 'menu', 3757 'aria-expanded': 'false' 3758 }); 3759 } else if (this.prefCats.length === 1) { 3760 // Prefs button will trigger a dialog 3761 $newButton.attr({ 3762 'aria-haspopup': 'dialog' 3763 }); 3764 } 3765 } else if (control === 'volume') { 3766 popupMenuId = this.mediaId + '-volume-slider'; 3767 // volume slider popup is not a menu or a dialog 3768 // therefore, using aria-expanded rather than aria-haspopup to communicate properties/state 3769 $newButton.attr({ 3770 'aria-controls': popupMenuId, 3771 'aria-expanded': 'false' 3772 }); 3773 } else if (control === 'captions' && this.captions) { 3774 if (this.captions.length > 1) { 3775 $newButton.attr('aria-expanded', 'false'); 3776 } else { 3777 $newButton.attr('aria-pressed', 'false'); 3778 } 3779 } 3780 } 3781 var getControl = control; 3782 if ( control === 'faster' && this.speedIcons === 'animals' ) { 3783 getControl = 'rabbit'; 3784 } 3785 if ( control === 'slower' && this.speedIcons === 'animals' ) { 3786 getControl = 'turtle'; 3787 } 3788 if ( control === 'volume' ) { 3789 this.getIcon( $newButton, this.volumeButton ); 3790 } else { 3791 if ( 'fullscreen' === getControl ) { 3792 getControl = ( this.fullscreen ) ? 'fullscreen-collapse' : 'fullscreen-expand'; 3793 } 3794 this.getIcon( $newButton, getControl ); 3795 } 3796 3797 this.setText($newButton,buttonTitle); 3798 // add an event listener that displays a tooltip on mouseenter or focus 3799 $newButton.on('mouseenter focus',function(e) { 3800 3801 // when entering a new tooltip, we can forget about hiding the previous tooltip. 3802 // since the same tooltip div is used, it's location just changes. 3803 clearTimeout(tooltipTimerId); 3804 3805 buttonText = $(this).attr('aria-label'); 3806 // get position of this button 3807 position = $(this).position(); 3808 buttonHeight = $(this).height(); 3809 buttonWidth = $(this).width(); 3810 // position() is expressed using top and left (of button); 3811 // add right (of button) too, for convenience 3812 controllerWidth = thisObj.$controllerDiv.width(); 3813 position.right = controllerWidth - position.left - buttonWidth; 3814 3815 // The following formula positions tooltip below the button 3816 // which allows the tooltip to be hoverable as per WCAG 2.x SC 1.4.13 3817 // without obstructing the seekbar 3818 tooltipY = position.top + buttonHeight + 5; 3819 3820 if ($(this).parent().hasClass('able-right-controls')) { 3821 // this control is on the right side 3822 buttonSide = 'right'; 3823 } else { 3824 // this control is on the left side 3825 buttonSide = 'left'; 3826 } 3827 // populate tooltip, then calculate its width before showing it 3828 tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).width(); 3829 // center the tooltip horizontally over the button 3830 if (buttonSide == 'left') { 3831 tooltipX = position.left - tooltipWidth/2; 3832 if (tooltipX < 0) { 3833 // tooltip would exceed the bounds of the player. Adjust. 3834 tooltipX = 2; 3835 } 3836 tooltipStyle = { 3837 left: tooltipX + 'px', 3838 right: '', 3839 top: tooltipY + 'px' 3840 }; 3841 } else { 3842 tooltipX = position.right - tooltipWidth/2; 3843 if (tooltipX < 0) { 3844 // tooltip would exceed the bounds of the player. Adjust. 3845 tooltipX = 2; 3846 } 3847 tooltipStyle = { 3848 left: '', 3849 right: tooltipX + 'px', 3850 top: tooltipY + 'px' 3851 }; 3852 } 3853 tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).css(tooltipStyle); 3854 thisObj.showTooltip(tooltip); 3855 $(this).on('mouseleave blur',function() { 3856 3857 // (keep the tooltip visible if user hovers over it) 3858 // This causes unwanted side effects if tooltips are positioned above the buttons 3859 // as the persistent tooltip obstructs the seekbar, 3860 // blocking users from being able to move a pointer from a button to the seekbar 3861 // This limitation was addressed in 4.4.49 by moving the tooltip below the buttons 3862 3863 // clear existing timeout before reassigning variable 3864 clearTimeout(tooltipTimerId); 3865 tooltipTimerId = setTimeout(function() { 3866 // give the user a half second to move cursor to tooltip before removing 3867 // see https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus#hoverable 3868 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 3869 }, 500); 3870 3871 thisObj.$tooltipDiv.on('mouseenter focus', function() { 3872 clearTimeout(tooltipTimerId); 3873 }); 3874 3875 thisObj.$tooltipDiv.on('mouseleave blur', function() { 3876 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 3877 }); 3878 3879 }); 3880 }); 3881 3882 if (control === 'captions') { 3883 if (!this.prefCaptions || this.prefCaptions !== 1) { 3884 // captions are available, but user has them turned off 3885 if (this.captions.length > 1) { 3886 captionLabel = this.translate( 'captions', 'Captions' ); 3887 } else { 3888 captionLabel = this.translate( 'showCaptions', 'Show captions' ); 3889 } 3890 $newButton.addClass('buttonOff').attr('title',captionLabel); 3891 $newButton.attr('aria-pressed', 'false'); 3892 } 3893 } else if (control === 'descriptions') { 3894 if (!this.prefDesc || this.prefDesc !== 1) { 3895 // user prefer non-audio described version 3896 // Therefore, load media without description 3897 // Description can be toggled on later with this button 3898 $newButton.addClass('buttonOff').attr( 'title', this.translate( 'turnOnDescriptions', 'Turn on descriptions' ) ); 3899 } 3900 } 3901 3902 $controllerSpan.append($newButton); 3903 3904 // create variables of buttons that are referenced throughout the AblePlayer object 3905 if (control === 'play') { 3906 this.$playpauseButton = $newButton; 3907 } else if (control == 'previous') { 3908 this.$prevButton = $newButton; 3909 // if player is being rebuilt because user clicked the Prev button 3910 // return focus to that (newly built) button 3911 if (this.buttonWithFocus == 'previous') { 3912 this.$prevButton.trigger('focus'); 3913 this.buttonWithFocus = null; 3914 } 3915 } else if (control == 'next') { 3916 this.$nextButton = $newButton; 3917 // if player is being rebuilt because user clicked the Next button 3918 // return focus to that (newly built) button 3919 if (this.buttonWithFocus == 'next') { 3920 this.$nextButton.trigger('focus'); 3921 this.buttonWithFocus = null; 3922 } 3923 } else if (control === 'captions') { 3924 this.$ccButton = $newButton; 3925 } else if (control === 'sign') { 3926 this.$signButton = $newButton; 3927 // gray out sign button if sign language window is not active 3928 if (!(this.$signWindow.is(':visible'))) { 3929 this.$signButton.addClass('buttonOff'); 3930 } 3931 } else if (control === 'descriptions') { 3932 this.$descButton = $newButton; 3933 // button will be enabled or disabled in description.js > initDescription() 3934 } else if (control === 'mute') { 3935 this.$muteButton = $newButton; 3936 } else if (control === 'transcript') { 3937 this.$transcriptButton = $newButton; 3938 // gray out transcript button if transcript is not active 3939 if (!(this.$transcriptDiv.is(':visible'))) { 3940 this.$transcriptButton.addClass('buttonOff').attr( 'title', this.translate( 'showTranscript', 'Show transcript' ) ); 3941 } 3942 } else if (control === 'fullscreen') { 3943 this.$fullscreenButton = $newButton; 3944 } else if (control === 'chapters') { 3945 this.$chaptersButton = $newButton; 3946 } else if (control === 'preferences') { 3947 this.$prefsButton = $newButton; 3948 } else if (control === 'volume') { 3949 this.$volumeButton = $newButton; 3950 } 3951 } 3952 if (control === 'volume') { 3953 // in addition to the volume button, add a hidden slider 3954 this.addVolumeSlider($controllerSpan); 3955 } 3956 } 3957 if ((i % 2) == 1) { 3958 this.$controllerDiv.append('<div class="ableplayer-clear"></div>'); 3959 } 3960 } 3961 3962 if (typeof this.$captionsDiv !== 'undefined') { 3963 // stylize captions based on user prefs 3964 this.stylizeCaptions(this.$captionsDiv); 3965 } 3966 if (typeof this.$descDiv !== 'undefined') { 3967 // stylize descriptions based on user's caption prefs 3968 this.stylizeCaptions(this.$descDiv); 3969 } 3970 3971 // combine left and right controls arrays for future reference 3972 this.controls = []; 3973 for (var sec in controlLayout) if (Object.hasOwn(controlLayout, sec)) { 3974 this.controls = this.controls.concat(controlLayout[sec]); 3975 } 3976 3977 // Update state-based display of controls. 3978 this.refreshControls(); 3979 }; 3980 3981 AblePlayer.prototype.cuePlaylistItem = function(sourceIndex) { 3982 3983 // Move to a new item in a playlist. 3984 // NOTE: Swapping source for audio description is handled elsewhere; 3985 // see description.js > swapDescription() 3986 3987 var $newItem, prevPlayer, newPlayer, itemTitle, itemLang, nowPlayingSpan; 3988 3989 var thisObj = this; 3990 3991 prevPlayer = this.player; 3992 3993 if (this.initializing) ; else { 3994 if (this.playerCreated) { 3995 // remove the old 3996 this.deletePlayer('playlist'); 3997 } 3998 } 3999 4000 // set swappingSrc; needs to be true within recreatePlayer(), called below 4001 this.swappingSrc = true; 4002 4003 // if a new playlist item is being requested, and playback has already started, 4004 // it should be ok to play automatically, regardless of how it was requested 4005 if (this.startedPlaying) { 4006 this.okToPlay = true; 4007 } else { 4008 this.okToPlay = false; 4009 } 4010 4011 // We are no longer loading the previous media source 4012 // Only now, as a new source is requested, is it safe to reset this var 4013 // It will be reset to true when media.load() is called 4014 this.loadingMedia = false; 4015 4016 // Determine appropriate player to play this media 4017 $newItem = this.$playlist.eq(sourceIndex); 4018 this.playlistIndex = sourceIndex; 4019 if (this.hasAttr($newItem,'data-youtube-id')) { 4020 this.youTubeId = this.getYouTubeId($newItem.attr('data-youtube-id')); 4021 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 4022 this.youTubeDescId = this.getYouTubeId($newItem.attr('data-youtube-desc-id')); 4023 } 4024 newPlayer = 'youtube'; 4025 } else if (this.hasAttr($newItem,'data-vimeo-id')) { 4026 this.vimeoId = this.getVimeoId($newItem.attr('data-vimeo-id')); 4027 if (this.hasAttr($newItem,'data-vimeo-desc-id')) { 4028 this.vimeoDescId = this.getVimeoId($newItem.attr('data-vimeo-desc-id')); 4029 } 4030 newPlayer = 'vimeo'; 4031 } else { 4032 newPlayer = 'html5'; 4033 } 4034 if (newPlayer === 'youtube') { 4035 if (prevPlayer === 'html5') { 4036 // pause and hide the previous media 4037 if (this.playing) { 4038 this.pauseMedia(); 4039 } 4040 this.$media.hide(); 4041 } 4042 } else { 4043 // the new player is not youtube 4044 this.youTubeId = false; 4045 if (prevPlayer === 'youtube') { 4046 // unhide the media element 4047 this.$media.show(); 4048 } 4049 } 4050 this.player = newPlayer; 4051 4052 // remove source and track elements from previous playlist item 4053 this.$media.empty(); 4054 4055 // transfer media attributes from playlist to media element 4056 if (this.hasAttr($newItem,'data-poster')) { 4057 this.$media.attr('poster',$newItem.attr('data-poster')); 4058 } 4059 if (this.hasAttr($newItem,'data-youtube-desc-id')) { 4060 this.$media.attr('data-youtube-desc-id',$newItem.attr('data-youtube-desc-id')); 4061 } 4062 if (this.youTubeId) { 4063 this.$media.attr('data-youtube-id',$newItem.attr('data-youtube-id')); 4064 } 4065 4066 // add new <source> elements from playlist data 4067 var $sourceSpans = $newItem.children('span.able-source'); 4068 if ($sourceSpans.length) { 4069 $sourceSpans.each(function() { 4070 const $this = $(this); 4071 4072 // Check if the required data-src attribute exists 4073 if (thisObj.hasAttr($this, "data-src")) { 4074 const sanitizedSrc = purify.sanitize($this.attr("data-src")); 4075 4076 // Validate the protocol of the sanitized URL 4077 if (validate.isProtocolSafe(sanitizedSrc)) { 4078 // Create a new <source> element with the sanitized src 4079 const $newSource = $("<source>", { src: sanitizedSrc }); 4080 4081 // List of optional attributes to sanitize and add 4082 const optionalAttributes = [ 4083 "data-type", 4084 "data-desc-src", 4085 "data-sign-src", 4086 ]; 4087 4088 // Process optional attributes 4089 optionalAttributes.forEach((attr) => { 4090 if (thisObj.hasAttr($this, attr)) { 4091 const attrValue = $this.attr(attr); // Get the attribute value 4092 const sanitizedValue = purify.sanitize(attrValue); // Sanitize the value 4093 4094 // If the attribute ends with "-src", validate the protocol 4095 if (attr.endsWith("-src") && validate.isProtocolSafe(sanitizedValue)) { 4096 $newSource.attr(attr, sanitizedValue); // Add the sanitized and validated attribute 4097 } else if (!attr.endsWith("-src")) { 4098 $newSource.attr(attr, sanitizedValue); // Add sanitized value for non-src attributes 4099 } 4100 } 4101 }); 4102 4103 // Append the new <source> element to the media object 4104 thisObj.$media.append($newSource); 4105 } 4106 } 4107 }); 4108 } 4109 4110 // add new <track> elements from playlist data 4111 var $trackSpans = $newItem.children('span.able-track'); 4112 if ($trackSpans.length) { 4113 // for each element in $trackSpans, create a new <track> element 4114 $trackSpans.each(function() { 4115 const $this = $(this); 4116 if (thisObj.hasAttr($this, "data-src") && thisObj.hasAttr($this, "data-kind") && thisObj.hasAttr($this, "data-srclang")) { 4117 // all required attributes are present 4118 const sanitizedSrc = purify.sanitize($this.attr("data-src")); 4119 // Validate the protocol of the sanitized URL 4120 if (validate.isProtocolSafe(sanitizedSrc)) { 4121 // Create a new <track> element with the sanitized src 4122 const $newTrack = $("<track>", { 4123 src: sanitizedSrc, 4124 kind: $this.attr("data-kind"), 4125 srclang: $this.attr("data-srclang"), 4126 }); 4127 // List of optional attributes to sanitize and add 4128 const optionalAttributes = [ 4129 "data-label", 4130 "data-desc", 4131 "data-default", 4132 ]; 4133 optionalAttributes.forEach((attr) => { 4134 if (thisObj.hasAttr($this, attr)) { 4135 $newTrack.attr(attr, purify.sanitize($this.attr(attr))); 4136 } 4137 }); 4138 // Append the new <track> element to the media object 4139 thisObj.$media.append($newTrack); 4140 } 4141 } 4142 }); 4143 } 4144 4145 itemTitle = purify.sanitize( $newItem.text() ); 4146 if (this.hasAttr($newItem,'lang')) { 4147 itemLang = $newItem.attr('lang'); 4148 } 4149 // Update relevant arrays 4150 this.$sources = this.$media.find('source'); 4151 4152 // recreate player, informed by new attributes and track elements 4153 if (this.recreatingPlayer) { 4154 // stopgap to prevent multiple firings of recreatePlayer() 4155 return; 4156 } 4157 this.recreatePlayer().then(function() { 4158 4159 // update playlist to indicate which item is playing 4160 thisObj.$playlist.removeClass('able-current') 4161 .children('button').removeAttr('aria-current'); 4162 thisObj.$playlist.eq(sourceIndex).addClass('able-current') 4163 .children('button').attr('aria-current','true'); 4164 4165 // update Now Playing div 4166 if (thisObj.showNowPlaying === true) { 4167 if (typeof thisObj.$nowPlayingDiv !== 'undefined') { 4168 nowPlayingSpan = $('<span>'); 4169 if (typeof itemLang !== 'undefined') { 4170 nowPlayingSpan.attr('lang',itemLang); 4171 } 4172 nowPlayingSpan.html('<span>' + thisObj.translate( 'selectedTrack', 'Selected Track' ) + ':</span>' + itemTitle); 4173 thisObj.$nowPlayingDiv.html(nowPlayingSpan); 4174 } 4175 } 4176 4177 // if thisObj.swappingSrc is true, media will autoplay when ready 4178 if (thisObj.initializing) { // this is the first track - user hasn't pressed play yet 4179 thisObj.swappingSrc = false; 4180 } else { 4181 if (thisObj.player === 'html5') { 4182 if (!thisObj.loadingMedia) { 4183 thisObj.media.load(); 4184 thisObj.loadingMedia = true; 4185 } 4186 } else if (thisObj.player === 'youtube') { 4187 thisObj.okToPlay = true; 4188 } 4189 } 4190 thisObj.initializing = false; 4191 thisObj.playerCreated = true; // remains true until browser is refreshed 4192 }); 4193 }; 4194 4195 AblePlayer.prototype.deletePlayer = function(context) { 4196 4197 // remove player components that need to be rebuilt 4198 // after swapping media sources that have different durations 4199 // or explicitly declared data-desc attributes 4200 4201 // Context is one of the following: 4202 // playlist - called from cuePlaylistItem() 4203 // swap-desc-html - called from swapDescription with this.player == 'html' 4204 // swap-desc-youtube - called from swapDescription with this.player == 'youtube' 4205 // swap-desc-vimeo - called from swapDescription with this.player == 'vimeo' 4206 4207 if (this.player === 'youtube' && this.youTubePlayer) { 4208 this.youTubePlayer.destroy(); 4209 } 4210 4211 if (this.player === 'vimeo' && this.vimeoPlayer) { 4212 this.vimeoPlayer.destroy(); 4213 } 4214 4215 // Empty elements that will be rebuilt 4216 this.$controllerDiv.empty(); 4217 // this.$statusBarDiv.empty(); 4218 // this.$timer.empty(); 4219 this.$elapsedTimeContainer.empty().text('0:00'); // span.able-elapsedTime 4220 this.$durationContainer.empty(); // span.able-duration 4221 4222 // Remove popup windows and modal dialogs; these too will be rebuilt 4223 if (this.$signWindow) { 4224 this.$signWindow.remove(); 4225 } 4226 if (this.$transcriptArea) { 4227 this.$transcriptArea.remove(); 4228 } 4229 $('.able-modal-dialog').remove(); 4230 4231 // Remove caption and description wrappers 4232 if (this.$captionsWrapper) { 4233 this.$captionsWrapper.remove(); 4234 } 4235 if (this.$descDiv) { 4236 this.$descDiv.remove(); 4237 } 4238 4239 // reset key variables 4240 this.hasCaptions = false; 4241 this.hasChapters = false; 4242 this.hasDescTracks = false; 4243 this.hasOpenDesc = false; 4244 this.hasClosedDesc = false; 4245 4246 this.captionsPopup = null; 4247 this.chaptersPopup = null; 4248 this.transcriptType = null; 4249 4250 this.playerDeleted = true; // will reset to false in recreatePlayer() 4251 }; 4252 4253 AblePlayer.prototype.getButtonTitle = function(control) { 4254 4255 if (control === 'playpause') { 4256 return this.translate( 'play', 'Play' ); 4257 } else if (control === 'play') { 4258 return this.translate( 'play', 'Play' ); 4259 } else if (control === 'pause') { 4260 return this.translate( 'pause', 'Pause' ); 4261 } else if (control === 'restart') { 4262 return this.translate( 'restart', 'Restart' ); 4263 } else if (control === 'previous') { 4264 return this.translate( 'prevTrack', 'Previous track' ); 4265 } else if (control === 'next') { 4266 return this.translate( 'nextTrack', 'Next track' ); 4267 } else if (control === 'rewind') { 4268 return this.translate( 'rewind', 'Rewind' ); 4269 } else if (control === 'forward') { 4270 return this.translate( 'forward', 'Forward' ); 4271 } else if (control === 'captions') { 4272 if (this.captions.length > 1) { 4273 return this.translate( 'captions', 'Captions' ); 4274 } else { 4275 return (this.captionsOn) ? this.translate( 'hideCaptions', 'Hide captions' ) : this.translate( 'showCaptions', 'Show captions' ); 4276 } 4277 } else if (control === 'descriptions') { 4278 return (this.descOn) ? this.translate( 'turnOffDescriptions', 'Turn off descriptions' ) : this.translate( 'turnOnDescriptions', 'Turn on descriptions' ); 4279 } else if (control === 'transcript') { 4280 return (this.$transcriptDiv.is(':visible')) ? this.translate( 'hideTranscript', 'Hide transcript' ) : this.translate( 'showTranscript', 'Show transcript' ); 4281 } else if (control === 'chapters') { 4282 return this.translate( 'chapters', 'Chapters' ); 4283 } else if (control === 'sign') { 4284 return this.translate( 'sign', 'Sign language' ); 4285 } else if (control === 'volume') { 4286 return this.translate( 'volume', 'Volume' ); 4287 } else if (control === 'faster') { 4288 return this.translate( 'faster', 'Faster' ); 4289 } else if (control === 'slower') { 4290 return this.translate( 'slower', 'Slower' ); 4291 } else if (control === 'preferences') { 4292 return this.translate( 'preferences', 'Preferences' ); 4293 } else if (control === 'fullscreen') { 4294 return ( !this.fullscreen ) ? this.translate( 'enterFullScreen', 'Enter full screen' ) : this.translate( 'exitFullScreen', 'Exit full screen' ); 4295 } else { 4296 // there should be no other controls, but just in case: 4297 // return the name of the control with first letter in upper case 4298 // ultimately will need to get a translated label from this.tt 4299 if (this.debug) ; 4300 return this.capitalizeFirstLetter( control ); 4301 } 4302 }; 4303 } 4304 4305 function addCaptionFunctions(AblePlayer) { 4306 AblePlayer.prototype.updateCaption = function (time) { 4307 if ( 4308 !this.usingYouTubeCaptions && 4309 !this.usingVimeoCaptions && 4310 typeof this.$captionsWrapper !== "undefined" 4311 ) { 4312 if (this.captionsOn) { 4313 this.$captionsWrapper.show(); 4314 if (typeof time !== "undefined") { 4315 this.showCaptions(time); 4316 } 4317 } else if (this.$captionsWrapper) { 4318 this.$captionsWrapper.hide(); 4319 this.prefCaptions = 0; 4320 } 4321 } 4322 }; 4323 4324 AblePlayer.prototype.updateCaptionsMenu = function (lang) { 4325 // uncheck all previous menu items 4326 this.captionsPopup.find("li").attr("aria-checked", "false"); 4327 if (typeof lang === "undefined") { 4328 // check the last menu item (captions off) 4329 this.captionsPopup.find("li").last().attr("aria-checked", "true"); 4330 } else { 4331 // check the newly selected lang 4332 this.captionsPopup 4333 .find("li[lang=" + lang + "]") 4334 .attr("aria-checked", "true"); 4335 } 4336 }; 4337 4338 AblePlayer.prototype.getCaptionClickFunction = function (track) { 4339 // Returns the function used when a caption is clicked in the captions menu. 4340 // Not called if user clicks "Captions off". Instead, that triggers getCaptionOffFunction() 4341 4342 var thisObj = this; 4343 return function () { 4344 thisObj.selectedCaptions = track; 4345 thisObj.captionLang = track.language; 4346 thisObj.currentCaption = -1; 4347 if (thisObj.usingYouTubeCaptions) { 4348 if (thisObj.captionsOn) { 4349 // Two things must be true in order for setOption() to work: 4350 // The YouTube caption module must be loaded 4351 // and the video must have started playing 4352 if ( 4353 thisObj.youTubePlayer.getOptions("captions") && 4354 thisObj.startedPlaying 4355 ) { 4356 thisObj.youTubePlayer.setOption("captions", "track", { 4357 languageCode: thisObj.captionLang, 4358 }); 4359 } else { 4360 // the two conditions were not met 4361 // try again to set the language after onApiChange event is triggered 4362 // meanwhile, the following variable will hold the value 4363 thisObj.captionLangPending = thisObj.captionLang; 4364 } 4365 } else { 4366 if (thisObj.youTubePlayer.getOptions("captions")) { 4367 thisObj.youTubePlayer.setOption("captions", "track", { 4368 languageCode: thisObj.captionLang, 4369 }); 4370 } else { 4371 thisObj.youTubePlayer.loadModule("captions"); 4372 thisObj.captionLangPending = thisObj.captionLang; 4373 } 4374 } 4375 } else if (thisObj.usingVimeoCaptions) { 4376 thisObj.vimeoPlayer 4377 .enableTextTrack(thisObj.captionLang) 4378 .catch(function (error) { 4379 switch (error.name) { 4380 } 4381 }); 4382 } else { 4383 // using local track elements for captions/subtitles 4384 thisObj.syncTrackLanguages("captions", thisObj.captionLang); 4385 if (!thisObj.swappingSrc) { 4386 thisObj.updateCaption(thisObj.elapsed); 4387 thisObj.showDescription(thisObj.elapsed); 4388 } 4389 } 4390 thisObj.captionsOn = true; 4391 // stopgap to prevent spacebar in Firefox from reopening popup 4392 // immediately after closing it (used in handleCaptionToggle()) 4393 thisObj.hidingPopup = true; 4394 thisObj.captionsPopup.hide(); 4395 thisObj.$ccButton.attr("aria-expanded", "false"); 4396 if (thisObj.mediaType === "audio") { 4397 thisObj.$captionsContainer.removeClass("captions-off"); 4398 } 4399 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 4400 // e.g., if user triggered button with Enter or mouse click, not spacebar 4401 setTimeout(function () { 4402 thisObj.hidingPopup = false; 4403 }, 100); 4404 thisObj.updateCaptionsMenu(thisObj.captionLang); 4405 thisObj.waitThenFocus(thisObj.$ccButton); 4406 4407 // save preference to cookie 4408 thisObj.prefCaptions = 1; 4409 thisObj.updatePreferences("prefCaptions"); 4410 thisObj.refreshControls("captions"); 4411 }; 4412 }; 4413 4414 // Returns the function used when the "Captions Off" button is clicked in the captions tooltip. 4415 AblePlayer.prototype.getCaptionOffFunction = function () { 4416 var thisObj = this; 4417 return function () { 4418 if (thisObj.player == "youtube") { 4419 thisObj.youTubePlayer.unloadModule("captions"); 4420 } else if (thisObj.usingVimeoCaptions) { 4421 thisObj.vimeoPlayer.disableTextTrack(); 4422 } 4423 thisObj.captionsOn = false; 4424 thisObj.currentCaption = -1; 4425 4426 if (thisObj.mediaType === "audio") { 4427 thisObj.$captionsContainer.addClass("captions-off"); 4428 } 4429 4430 // stopgap to prevent spacebar in Firefox from reopening popup 4431 // immediately after closing it (used in handleCaptionToggle()) 4432 thisObj.hidingPopup = true; 4433 thisObj.captionsPopup.hide(); 4434 thisObj.$ccButton.attr("aria-expanded", "false"); 4435 // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called 4436 // e.g., if user triggered button with Enter or mouse click, not spacebar 4437 setTimeout(function () { 4438 thisObj.hidingPopup = false; 4439 }, 100); 4440 thisObj.updateCaptionsMenu(); 4441 thisObj.waitThenFocus(thisObj.$ccButton); 4442 4443 // save preference to cookie 4444 thisObj.prefCaptions = 0; 4445 thisObj.updatePreferences("prefCaptions"); 4446 if (!this.swappingSrc) { 4447 thisObj.refreshControls("captions"); 4448 thisObj.updateCaption(); 4449 } 4450 }; 4451 }; 4452 4453 AblePlayer.prototype.showCaptions = function (now) { 4454 var c, thisCaption, nextCaption, captionText, announceText, announcement, availableTime, rate, cueLength, estimatedTime; 4455 var cues; 4456 if (null !== this.selectedCaptions.cues && this.selectedCaptions.cues.length) { 4457 cues = this.selectedCaptions.cues; 4458 } else if (this.captions.length >= 1) { 4459 cues = this.captions[0].cues; 4460 } else { 4461 cues = []; 4462 } 4463 for (c = 0; c < cues.length; c++) { 4464 if (cues[c].start <= now && cues[c].end > now) { 4465 thisCaption = c; 4466 nextCaption = cues[ c + 1 ]; 4467 break; 4468 } 4469 } 4470 4471 if (typeof thisCaption !== "undefined") { 4472 if (this.currentCaption !== thisCaption) { 4473 // it's time to load the new caption into the container div 4474 captionText = this.flattenCueForCaption(cues[thisCaption]).replace( /\n/g, "<br>" ); 4475 // If preference enabled to voice captions, send to synthesizer. 4476 if ( this.speechEnabled && this.prefCaptionsSpeak == 1 ) { 4477 announceText = new DOMParser().parseFromString( captionText, 'text/html' ); 4478 announcement = announceText.body.textContent || ''; 4479 availableTime = ( thisCaption ) ? nextCaption.start - cues[thisCaption].start : 0; 4480 rate = false; 4481 if ( availableTime ) { 4482 cueLength = announcement.trim().split(/\W+/).length; 4483 estimatedTime = Math.round( ( ( cueLength ) / 135 ) * 60 ); 4484 rate = ( estimatedTime / availableTime ); 4485 } 4486 // use browser's built-in speech synthesis 4487 this.announceText( 'caption', announcement, rate ); 4488 } 4489 this.$captionsDiv.html(captionText); 4490 this.currentCaption = thisCaption; 4491 if (captionText.length === 0) { 4492 // hide captionsDiv; otherwise background-color is visible due to padding 4493 this.$captionsDiv.css("display", "none"); 4494 } else { 4495 this.$captionsDiv.css("display", "inline-block"); 4496 } 4497 } 4498 } else { 4499 this.$captionsDiv.html("").css("display", "none"); 4500 this.currentCaption = -1; 4501 } 4502 }; 4503 4504 AblePlayer.prototype.flattenCueForCaption = function (cue) { 4505 // Takes a cue and returns the caption text to display 4506 // Also used for chapters 4507 4508 // Support for 'i' and 'b' tags added in 2.3.66 4509 // TODO: Add support for 'c' (class) and 'ruby' 4510 4511 // c (class): <c.myClass1.myClass2>Some text</c> 4512 // Classes can be used to modify other tags too (e.g., <v.loud>) 4513 // If <c> tag, should be rendered as a <span> 4514 4515 // ruby: http://www.w3schools.com/tags/tag_ruby.asp 4516 4517 // WebVTT also supports 'u' (underline) 4518 // I see no reason to support that in Able Player. 4519 // If it's available authors are likely to use it incorrectly 4520 // where <i> or <b> should be used instead 4521 // Here are the rare use cases where an underline is appropriate on the web: 4522 // http://html5doctor.com/u-element/ 4523 4524 var result = []; 4525 4526 var flattenComponent = function (component) { 4527 var result = [], 4528 ii; 4529 if (component.type === "string") { 4530 result.push(component.value); 4531 } else if (component.type === "v") { 4532 result.push("(" + component.value + ")"); 4533 for (ii = 0; ii < component.children.length; ii++) { 4534 result.push(flattenComponent(component.children[ii])); 4535 } 4536 } else if (component.type === "i") { 4537 result.push("<em>"); 4538 for (ii = 0; ii < component.children.length; ii++) { 4539 result.push(flattenComponent(component.children[ii])); 4540 } 4541 result.push("</em>"); 4542 } else if (component.type === "b") { 4543 result.push("<strong>"); 4544 for (ii = 0; ii < component.children.length; ii++) { 4545 result.push(flattenComponent(component.children[ii])); 4546 } 4547 result.push("</strong>"); 4548 } else { 4549 for (ii = 0; ii < component.children.length; ii++) { 4550 result.push(flattenComponent(component.children[ii])); 4551 } 4552 } 4553 return result.join(""); 4554 }; 4555 4556 if (typeof cue.components !== "undefined") { 4557 for (var ii = 0; ii < cue.components.children.length; ii++) { 4558 result.push(flattenComponent(cue.components.children[ii])); 4559 } 4560 } 4561 return result.join(""); 4562 }; 4563 4564 AblePlayer.prototype.getCaptionsOptions = function (pref) { 4565 var options = []; 4566 4567 switch (pref) { 4568 case "prefCaptionsFont": 4569 options[0] = ["serif", this.translate( 'serif', 'serif' )]; 4570 options[1] = ["sans-serif", this.translate( 'sans', 'sans-serif' )]; 4571 options[2] = ["cursive", this.translate( 'cursive', 'cursive' )]; 4572 options[3] = ["fantasy", this.translate( 'fantasy', 'fantasy' )]; 4573 options[4] = ["monospace", this.translate( 'monospace', 'monospace' )]; 4574 break; 4575 4576 case "prefCaptionsColor": 4577 case "prefCaptionsBGColor": 4578 // HTML color values must be in English 4579 options[0] = ["white", this.translate( 'white', 'white' )]; 4580 options[1] = ["yellow", this.translate( 'yellow', 'yellow' )]; 4581 options[2] = ["green", this.translate( 'green', 'green' )]; 4582 options[3] = ["cyan", this.translate( 'cyan', 'cyan' )]; 4583 options[4] = ["blue", this.translate( 'blue', 'blue' )]; 4584 options[5] = ["magenta", this.translate( 'magenta', 'magenta' )]; 4585 options[6] = ["red", this.translate( 'red', 'red' )]; 4586 options[7] = ["black", this.translate( 'black', 'black' )]; 4587 break; 4588 4589 case "prefCaptionsSize": 4590 options[0] = "75%"; 4591 options[1] = "100%"; 4592 options[2] = "125%"; 4593 options[3] = "150%"; 4594 options[4] = "200%"; 4595 break; 4596 4597 case "prefCaptionsOpacity": 4598 options[0] = "0%"; 4599 options[1] = "25%"; 4600 options[2] = "50%"; 4601 options[3] = "75%"; 4602 options[4] = "100%"; 4603 break; 4604 4605 case "prefCaptionsStyle": 4606 options[0] = this.translate( 'captionsStylePopOn', 'Pop-on' ); 4607 options[1] = this.translate( 'captionsStyleRollUp', 'Roll-up' ); 4608 break; 4609 4610 case "prefCaptionsPosition": 4611 options[0] = "overlay"; 4612 options[1] = "below"; 4613 break; 4614 4615 case "prefCaptionsSpeak": 4616 options[0] = ["0", this.translate( 'off', 'Off' ) ]; 4617 options[1] = ["1", this.translate( 'on', 'On' ) ]; 4618 break; 4619 4620 case "prefCaptionsVoice": 4621 options[0] = null; // set later. 4622 break; 4623 4624 case "prefCaptionsPitch": 4625 options[0] = null; // set later. 4626 break; 4627 4628 case "prefCaptionsRate": 4629 options[0] = null; // set later. 4630 break; 4631 4632 case "prefCaptionsVolume": 4633 options[0] = null; // set later. 4634 break; 4635 } 4636 4637 return options; 4638 }; 4639 4640 AblePlayer.prototype.translatePrefs = function (pref, value, outputFormat) { 4641 // translate current value of pref to a value supported by outputformat 4642 if (outputFormat == "youtube") { 4643 if (pref === "size") { 4644 // YouTube font sizes are a range from -1 to 3 (0 = default) 4645 switch (value) { 4646 case "75%": 4647 return -1; 4648 case "100%": 4649 return 0; 4650 case "125%": 4651 return 1; 4652 case "150%": 4653 return 2; 4654 case "200%": 4655 return 3; 4656 } 4657 } 4658 } 4659 return false; 4660 }; 4661 4662 AblePlayer.prototype.stylizeCaptions = function ($element, pref) { 4663 // $element is the jQuery element containing the captions 4664 // this function handles stylizing of the sample caption text in the Prefs dialog 4665 // plus the actual production captions 4666 // TODO: consider applying the same user prefs to visible text-based description 4667 var property, newValue, opacity; 4668 4669 if (typeof $element !== "undefined") { 4670 if (pref == "prefCaptionsPosition") { 4671 this.positionCaptions(); 4672 } else if (typeof pref !== "undefined") { 4673 // just change the one property that user just changed 4674 if (pref === "prefCaptionsFont") { 4675 property = "font-family"; 4676 } else if (pref === "prefCaptionsSize") { 4677 property = "font-size"; 4678 } else if (pref === "prefCaptionsColor") { 4679 property = "color"; 4680 } else if (pref === "prefCaptionsBGColor") { 4681 property = "background-color"; 4682 } else if (pref === "prefCaptionsOpacity") { 4683 property = "opacity"; 4684 } 4685 if (pref === "prefCaptionsOpacity") { 4686 newValue = 4687 parseFloat($("#" + this.mediaId + "_" + pref).val()) / 100.0; 4688 } else { 4689 newValue = $("#" + this.mediaId + "_" + pref).val(); 4690 } 4691 $element.css(property, newValue); 4692 } else { 4693 // no property was specified, update all styles with current saved prefs 4694 opacity = parseFloat(this.prefCaptionsOpacity) / 100.0; 4695 $element.css({ 4696 "font-family": this.prefCaptionsFont, 4697 color: this.prefCaptionsColor, 4698 "background-color": this.prefCaptionsBGColor, 4699 opacity: opacity, 4700 }); 4701 if ($element === this.$captionsDiv) { 4702 if (typeof this.$captionsDiv !== "undefined") { 4703 this.$captionsDiv.css({ 4704 "font-size": this.prefCaptionsSize, 4705 }); 4706 } 4707 } 4708 if (this.prefCaptionsPosition === "below") { 4709 // also need to add the background color to the wrapper div 4710 if (typeof this.$captionsWrapper !== "undefined") { 4711 this.$captionsWrapper.css({ 4712 "background-color": this.prefCaptionsBGColor, 4713 opacity: "1", 4714 }); 4715 } 4716 } else if (this.prefCaptionsPosition === "overlay") { 4717 // no background color for overlay wrapper, captions are displayed in-line 4718 if (typeof this.$captionsWrapper !== "undefined") { 4719 this.$captionsWrapper.css({ 4720 "background-color": "transparent", 4721 opacity: "", 4722 }); 4723 } 4724 } 4725 this.positionCaptions(); 4726 } 4727 } 4728 }; 4729 AblePlayer.prototype.positionCaptions = function (position) { 4730 // set caption position to either 'overlay' or 'below' 4731 // if position parameter was passed to this function, use that 4732 // otherwise use user preference 4733 if (typeof position === "undefined") { 4734 position = this.prefCaptionsPosition; 4735 } 4736 if (typeof this.$captionsWrapper !== "undefined") { 4737 if (position == "below") { 4738 this.$captionsWrapper 4739 .removeClass("able-captions-overlay") 4740 .addClass("able-captions-below"); 4741 // also need to update in-line styles 4742 this.$captionsWrapper.css({ 4743 "background-color": this.prefCaptionsBGColor, 4744 opacity: "1", 4745 }); 4746 } else { 4747 this.$captionsWrapper 4748 .removeClass("able-captions-below") 4749 .addClass("able-captions-overlay"); 4750 this.$captionsWrapper.css({ 4751 "background-color": "transparent", 4752 opacity: "", 4753 }); 4754 } 4755 } 4756 }; 4757 } 4758 4759 function addChaptersFunctions(AblePlayer) { 4760 4761 AblePlayer.prototype.populateChaptersDiv = function() { 4762 4763 var headingLevel, headingType, headingId, $chaptersHeading; 4764 if ( ! this.chaptersDivLocation ) { 4765 return; 4766 } 4767 if ($('#' + this.chaptersDivLocation)) { 4768 4769 this.$chaptersDiv = $('#' + this.chaptersDivLocation); 4770 this.$chaptersDiv.addClass('able-chapters-div'); 4771 4772 // empty content from previous build before starting fresh 4773 this.$chaptersDiv.empty(); 4774 4775 // add optional header 4776 if (this.chaptersTitle) { 4777 headingLevel = this.getNextHeadingLevel(this.$chaptersDiv); 4778 headingType = 'h' + headingLevel.toString(); 4779 headingId = this.mediaId + '-chapters-heading'; 4780 $chaptersHeading = $('<' + headingType + '>', { 4781 'class': 'able-chapters-heading', 4782 'id': headingId 4783 }).text(this.chaptersTitle); 4784 this.$chaptersDiv.append($chaptersHeading); 4785 } 4786 4787 this.$chaptersNav = $('<nav>'); 4788 if (this.chaptersTitle) { 4789 this.$chaptersNav.attr( 'aria-labelledby', headingId ); 4790 } else { 4791 this.$chaptersNav.attr( 'aria-label', this.translate( 'chapters', 'Chapters' ) ); 4792 } 4793 this.$chaptersDiv.append(this.$chaptersNav); 4794 4795 // populate this.$chaptersNav with a list of chapters 4796 this.updateChaptersList(); 4797 } 4798 }; 4799 4800 AblePlayer.prototype.updateChaptersList = function() { 4801 4802 var thisObj, cues, $chaptersList, c, thisChapter, 4803 $chapterItem, $chapterButton, hasDefault, 4804 getClickFunction, $clickedItem; 4805 4806 thisObj = this; 4807 4808 // TODO: Update this so it can change the chapters popup menu 4809 // currently it only works if chapters are in an external container 4810 if (!this.$chaptersNav) { 4811 return false; 4812 } 4813 4814 if (typeof this.useChapterTimes === 'undefined') { 4815 this.useChapterTimes = (this.seekbarScope === 'chapter' && this.selectedChapters.cues.length) ? true : false; 4816 } 4817 if (this.useChapterTimes) { 4818 cues = this.selectedChapters.cues; 4819 } else if (this.chapters.length >= 1) { 4820 cues = this.chapters[0].cues; 4821 } else { 4822 cues = []; 4823 } 4824 if (cues.length > 0) { 4825 $chaptersList = $('<ul>'); 4826 for (c = 0; c < cues.length; c++) { 4827 thisChapter = c; 4828 $chapterItem = $('<li></li>'); 4829 $chapterButton = $('<button>',{ 4830 'type': 'button', 4831 'val': thisChapter 4832 }).text(this.flattenCueForCaption(cues[thisChapter])); 4833 4834 // add event listeners 4835 getClickFunction = function (time) { 4836 return function () { 4837 thisObj.seekTrigger = 'chapter'; 4838 $clickedItem = $(this).closest('li'); 4839 $chaptersList = $(this).closest('ul').find('li'); 4840 $chaptersList.removeClass('able-current-chapter') 4841 .children('button').removeAttr('aria-current'); 4842 $clickedItem.addClass('able-current-chapter') 4843 .children('button').attr('aria-current','true'); 4844 // Need to updateChapter before seeking to it 4845 // Otherwise seekBar is redrawn with wrong chapterDuration and/or chapterTime 4846 thisObj.updateChapter(time); 4847 thisObj.seekTo(time); 4848 } 4849 }; 4850 $chapterButton.on('click',getClickFunction(cues[thisChapter].start)); // works with Enter too 4851 $chapterButton.on('focus',function() { 4852 $(this).closest('ul').find('li').removeClass('able-focus'); 4853 $(this).closest('li').addClass('able-focus'); 4854 }); 4855 $chapterItem.on('hover',function() { 4856 $(this).closest('ul').find('li').removeClass('able-focus'); 4857 $(this).addClass('able-focus'); 4858 }); 4859 $chapterItem.on('mouseleave',function() { 4860 $(this).removeClass('able-focus'); 4861 }); 4862 $chapterButton.on('blur',function() { 4863 $(this).closest('li').removeClass('able-focus'); 4864 }); 4865 4866 // put it all together 4867 $chapterItem.append($chapterButton); 4868 $chaptersList.append($chapterItem); 4869 if (this.defaultChapter === cues[thisChapter].id) { 4870 $chapterButton.attr('aria-current','true').parent('li').addClass('able-current-chapter'); 4871 this.currentChapter = cues[thisChapter]; 4872 hasDefault = true; 4873 } 4874 } 4875 if (!hasDefault) { 4876 // select the first chapter 4877 this.currentChapter = cues[0]; 4878 $chaptersList.find('button').first().attr('aria-current','true') 4879 .parent('li').addClass('able-current-chapter'); 4880 } 4881 this.$chaptersNav.html($chaptersList); 4882 } 4883 return false; 4884 }; 4885 4886 AblePlayer.prototype.seekToChapter = function(chapterId) { 4887 4888 // step through chapters looking for matching ID 4889 var i=0; 4890 while (i < this.selectedChapters.cues.length) { 4891 if (this.selectedChapters.cues[i].id == chapterId) { 4892 // found the target chapter! Seek to it 4893 this.seekTo(this.selectedChapters.cues[i].start); 4894 this.updateChapter(this.selectedChapters.cues[i].start); 4895 break; 4896 } 4897 i++; 4898 } 4899 }; 4900 4901 AblePlayer.prototype.updateChapter = function (now) { 4902 4903 // as time-synced chapters change during playback, track changes in current chapter 4904 if (typeof this.selectedChapters === 'undefined') { 4905 return; 4906 } 4907 4908 var chapters, i, thisChapterIndex; 4909 4910 chapters = this.selectedChapters.cues; 4911 for (i = 0; i < chapters.length; i++) { 4912 if ((chapters[i].start <= now) && (chapters[i].end > now)) { 4913 thisChapterIndex = i; 4914 break; 4915 } 4916 } 4917 if (typeof thisChapterIndex !== 'undefined') { 4918 if (this.currentChapter !== chapters[thisChapterIndex]) { 4919 // this is a new chapter 4920 this.currentChapter = chapters[thisChapterIndex]; 4921 if (this.useChapterTimes) { 4922 this.chapterDuration = this.getChapterDuration(); 4923 this.seekIntervalCalculated = false; // will be recalculated in setSeekInterval() 4924 } 4925 if (typeof this.$chaptersDiv !== 'undefined') { 4926 // chapters are listed in an external container 4927 this.$chaptersDiv.find('ul').find('li') 4928 .removeClass('able-current-chapter') 4929 .children('button').removeAttr('aria-current'); 4930 this.$chaptersDiv.find('ul').find('li').eq(thisChapterIndex) 4931 .addClass('able-current-chapter') 4932 .children('button').attr('aria-current','true'); 4933 } 4934 } 4935 } 4936 }; 4937 4938 AblePlayer.prototype.getChapterDuration = function () { 4939 4940 // called if this.seekbarScope === 'chapter' 4941 // get duration of the current chapter 4942 4943 var lastChapterIndex, chapterEnd; 4944 4945 if (typeof this.currentChapter === 'undefined') { 4946 return 0; 4947 } 4948 if (typeof this.duration === 'undefined') { 4949 return 0; 4950 } 4951 lastChapterIndex = this.selectedChapters.cues.length-1; 4952 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 4953 // this is the last chapter 4954 if (this.currentChapter.end !== this.duration) { 4955 // chapter ends before or after video ends, adjust chapter end to match video end 4956 chapterEnd = this.duration; 4957 this.currentChapter.end = this.duration; 4958 } else { 4959 chapterEnd = this.currentChapter.end; 4960 } 4961 } else { // this is not the last chapter 4962 chapterEnd = this.currentChapter.end; 4963 } 4964 return chapterEnd - this.currentChapter.start; 4965 }; 4966 4967 AblePlayer.prototype.getChapterElapsed = function () { 4968 // called if this.seekbarScope === 'chapter' 4969 // get current elapsed time, relative to the current chapter duration 4970 4971 if (typeof this.currentChapter === 'undefined') { 4972 return 0; 4973 } 4974 4975 if (this.elapsed > this.currentChapter.start) { 4976 return this.elapsed - this.currentChapter.start; 4977 } else { 4978 return 0; 4979 } 4980 }; 4981 4982 AblePlayer.prototype.convertChapterTimeToVideoTime = function (chapterTime) { 4983 4984 // chapterTime is the time within the current chapter 4985 // return the same time, relative to the entire video 4986 if (typeof this.currentChapter !== 'undefined') { 4987 var newTime = this.currentChapter.start + chapterTime; 4988 if (newTime > this.currentChapter.end) { 4989 return this.currentChapter.end; 4990 } else { 4991 return newTime; 4992 } 4993 } else { 4994 return chapterTime; 4995 } 4996 }; 4997 4998 AblePlayer.prototype.getChapterClickFunction = function (time) { 4999 5000 // Returns the function used when a chapter is clicked in the chapters menu. 5001 var thisObj = this; 5002 return function () { 5003 thisObj.seekTrigger = 'chapter'; 5004 thisObj.seekTo(time); 5005 // stopgap to prevent spacebar in Firefox from reopening popup 5006 // immediately after closing it (used in handleChapters()) 5007 thisObj.hidingPopup = true; 5008 thisObj.chaptersPopup.hide(); 5009 // Ensure stopgap gets cancelled if handleChapters() isn't called 5010 // e.g., if user triggered button with Enter or mouse click, not spacebar 5011 setTimeout(function() { 5012 thisObj.hidingPopup = false; 5013 }, 100); 5014 thisObj.$chaptersButton.trigger('focus'); 5015 } 5016 }; 5017 5018 } 5019 5020 function addControlFunctions(AblePlayer) { 5021 5022 AblePlayer.prototype.seekTo = function (newTime) { 5023 5024 var thisObj = this; 5025 5026 // define variables to be used for analytics 5027 // e.g., to measure the extent to which users seek back and forward 5028 this.seekFromTime = this.media.currentTime; 5029 this.seekToTime = newTime; 5030 5031 this.seeking = true; 5032 this.liveUpdatePending = true; 5033 5034 if (this.speakingDescription) { 5035 this.synth.cancel(); 5036 } 5037 5038 this.syncSignVideo( {'time' : this.startTime } ); 5039 5040 if (this.player === 'html5') { 5041 var seekable; 5042 5043 this.startTime = newTime; 5044 // Check HTML5 media "seekable" property to be sure media is seekable to startTime 5045 seekable = this.media.seekable; 5046 if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) { 5047 // ok to seek to startTime 5048 // canplaythrough will be triggered when seeking is complete 5049 // this.seeking will be set to false at that point 5050 this.media.currentTime = this.startTime; 5051 this.seekStatus = 'complete'; 5052 this.syncSignVideo( { 'time' : this.startTime } ); 5053 } 5054 } else if (this.player === 'youtube') { 5055 this.youTubePlayer.seekTo(newTime,true); 5056 if (newTime > 0) { 5057 if (typeof this.$posterImg !== 'undefined') { 5058 this.$posterImg.hide(); 5059 } 5060 } 5061 this.syncSignVideo( {'time' : newTime } ); 5062 } else if (this.player === 'vimeo') { 5063 this.vimeoPlayer.setCurrentTime(newTime).then(function() { 5064 // seek finished. 5065 // successful completion also fires a 'seeked' event (see event.js) 5066 thisObj.elapsed = newTime; 5067 thisObj.refreshControls('timeline'); 5068 }); 5069 } 5070 this.refreshControls('timeline'); 5071 }; 5072 5073 AblePlayer.prototype.getMediaTimes = function (duration, elapsed) { 5074 5075 // Returns an array with keys 'duration' and 'elapsed' 5076 // Vars passed to this function come courtesy of select Vimeo events 5077 // Use those if they're available. 5078 // Otherwise, will need to call the relevant media API 5079 // This function should only be called from onMediaUpdateTime() 5080 // If duration and elapsed are needed other times, use this.duration and this.elapsed 5081 5082 // both values are expressed in seconds, and all player APIs are similar: 5083 // they return a value that is rounded to the nearest second before playback begins, 5084 // then to the nearest thousandth of a second after playback begins 5085 // With HTML5 media API, some browsers are more precise (e.g., Firefox rounds to 6 decimal points) 5086 // but inconsistent (values with 9 decimal points have been sporadically observed in Safari) 5087 // For standardization, values are rounded to 6 decimal points before they're returned 5088 5089 var deferred, promise, thisObj, mediaTimes; 5090 mediaTimes = {}; 5091 5092 deferred = new this.defer(); 5093 promise = deferred.promise(); 5094 thisObj = this; 5095 if (typeof duration !== 'undefined' && typeof elapsed !== 'undefined') { 5096 mediaTimes['duration'] = duration; 5097 mediaTimes['elapsed'] = elapsed; 5098 deferred.resolve(mediaTimes); 5099 } else { 5100 this.getDuration().then(function(duration) { 5101 mediaTimes['duration'] = thisObj.roundDown(duration,6); 5102 thisObj.getElapsed().then(function(elapsed) { 5103 mediaTimes['elapsed'] = thisObj.roundDown(elapsed,6); 5104 deferred.resolve(mediaTimes); 5105 }); 5106 }); 5107 } 5108 return promise; 5109 }; 5110 5111 AblePlayer.prototype.getDuration = function () { 5112 5113 // returns duration of the current media, expressed in seconds 5114 // function is called by getMediaTimes, and return value is sanitized there 5115 var deferred, promise; 5116 5117 deferred = new this.defer(); 5118 promise = deferred.promise(); 5119 5120 if (this.player === 'vimeo') { 5121 if (this.vimeoPlayer) { 5122 this.vimeoPlayer.getDuration().then(function(duration) { 5123 if (duration === undefined || isNaN(duration) || duration === -1) { 5124 deferred.resolve(0); 5125 } else { 5126 deferred.resolve(duration); 5127 } 5128 }); 5129 } else { // vimeoPlayer hasn't been initialized yet. 5130 deferred.resolve(0); 5131 } 5132 } else { 5133 var duration; 5134 if (this.player === 'html5') { 5135 duration = this.media.duration; 5136 } else if (this.player === 'youtube') { 5137 if (this.youTubePlayerReady) { 5138 if (this.duration > 0) { 5139 // duration was already retrieved while checking for captions 5140 duration = this.duration; 5141 } else { 5142 duration = this.youTubePlayer.getDuration(); 5143 } 5144 } else { // the YouTube player hasn't initialized yet 5145 duration = 0; 5146 } 5147 } 5148 if (duration === undefined || isNaN(duration) || duration === -1) { 5149 deferred.resolve(0); 5150 } else { 5151 deferred.resolve(duration); 5152 } 5153 } 5154 return promise; 5155 }; 5156 5157 AblePlayer.prototype.getElapsed = function () { 5158 5159 // returns elapsed time of the current media, expressed in seconds 5160 // function is called by getMediaTimes, and return value is sanitized there 5161 5162 var deferred, promise; 5163 5164 deferred = new this.defer(); 5165 promise = deferred.promise(); 5166 5167 if (this.player === 'vimeo') { 5168 if (this.vimeoPlayer) { 5169 this.vimeoPlayer.getCurrentTime().then(function(elapsed) { 5170 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 5171 deferred.resolve(0); 5172 } else { 5173 deferred.resolve(elapsed); 5174 } 5175 }); 5176 } else { // vimeoPlayer hasn't been initialized yet. 5177 deferred.resolve(0); 5178 } 5179 } else { 5180 var elapsed; 5181 if (this.player === 'html5') { 5182 elapsed = this.media.currentTime; 5183 } else if (this.player === 'youtube') { 5184 if (this.youTubePlayerReady) { 5185 elapsed = this.youTubePlayer.getCurrentTime(); 5186 } else { // the YouTube player hasn't initialized yet 5187 elapsed = 0; 5188 } 5189 } 5190 if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) { 5191 deferred.resolve(0); 5192 } else { 5193 deferred.resolve(elapsed); 5194 } 5195 } 5196 return promise; 5197 }; 5198 5199 AblePlayer.prototype.getPlayerState = function () { 5200 5201 // Returns one of the following states: 5202 // - 'stopped' - Not yet played for the first time, or otherwise reset to unplayed. 5203 // - 'ended' - Finished playing. 5204 // - 'paused' - Not playing, but not stopped or ended. 5205 // - 'buffering' - Momentarily paused to load, but will resume once data is loaded. 5206 // - 'playing' - Currently playing. 5207 5208 var deferred, promise, thisObj; 5209 deferred = new this.defer(); 5210 promise = deferred.promise(); 5211 thisObj = this; 5212 5213 if (this.player === 'html5') { 5214 if (this.media.ended) { 5215 deferred.resolve('ended'); 5216 } else if (this.media.paused) { 5217 deferred.resolve('paused'); 5218 } else if (this.media.readyState !== 4) { 5219 deferred.resolve('buffering'); 5220 } else { 5221 deferred.resolve('playing'); 5222 } 5223 } else if (this.player === 'youtube' && this.youTubePlayerReady) { 5224 var state = this.youTubePlayer.getPlayerState(); 5225 if (state === -1 || state === 5) { 5226 deferred.resolve('stopped'); 5227 } else if (state === 0) { 5228 deferred.resolve('ended'); 5229 } else if (state === 1) { 5230 deferred.resolve('playing'); 5231 } else if (state === 2) { 5232 deferred.resolve('paused'); 5233 } else if (state === 3) { 5234 deferred.resolve('buffering'); 5235 } 5236 } else if (this.player === 'vimeo' && this.vimeoPlayer) { 5237 // curiously, Vimeo's API has no getPlaying(), getBuffering(), or getState() methods 5238 // so maybe if it's neither paused nor ended, it must be playing??? 5239 this.vimeoPlayer.getPaused().then(function(paused) { 5240 if (paused) { 5241 deferred.resolve('paused'); 5242 } else { 5243 thisObj.vimeoPlayer.getEnded().then(function(ended) { 5244 if (ended) { 5245 deferred.resolve('ended'); 5246 } else { 5247 deferred.resolve('playing'); 5248 } 5249 }); 5250 } 5251 }); 5252 } 5253 return promise; 5254 }; 5255 5256 AblePlayer.prototype.isPlaybackRateSupported = function () { 5257 5258 if (this.player === 'html5') { 5259 return (this.media.playbackRate) ? true : false; 5260 } else if (this.player === 'youtube') { 5261 // Youtube supports varying playback rates per video. 5262 // Only expose controls if more than one playback rate is available. 5263 if (this.youTubePlayerReady) { 5264 return (this.youTubePlayer.getAvailablePlaybackRates().length > 1) ? true : false; 5265 } else { 5266 return false; 5267 } 5268 } else if (this.player === 'vimeo') { 5269 // since this takes longer to determine, it was set previously in initVimeoPlayer() 5270 return this.vimeoSupportsPlaybackRateChange; 5271 } 5272 }; 5273 5274 AblePlayer.prototype.setPlaybackRate = function (rate) { 5275 5276 rate = Math.max(0.5, rate); 5277 5278 if (this.hasClosedDesc && this.descMethod === 'text') { 5279 // keep speech rate in sync with playback rate even if descOn is false 5280 this.syncSpeechToPlaybackRate(rate); 5281 } 5282 5283 this.syncSignVideo( {'rate' : rate } ); 5284 5285 if (this.player === 'html5') { 5286 this.media.playbackRate = rate; 5287 } else if (this.player === 'youtube') { 5288 this.youTubePlayer.setPlaybackRate(rate); 5289 } else if (this.player === 'vimeo') { 5290 this.vimeoPlayer.setPlaybackRate(rate); 5291 } 5292 this.syncSignVideo( { 'rate' : rate } ); 5293 this.playbackRate = rate; 5294 this.$speed.text( this.translate( 'speed', 'Speed' ) + ': ' + rate.toFixed(2).toString() + 'x'); 5295 }; 5296 5297 AblePlayer.prototype.getPlaybackRate = function () { 5298 5299 if (this.player === 'html5') { 5300 return this.media.playbackRate; 5301 } else if (this.player === 'youtube' && (this.youTubePlayerReady)) { 5302 return this.youTubePlayer.getPlaybackRate(); 5303 } 5304 }; 5305 5306 AblePlayer.prototype.isPaused = function () { 5307 5308 // Note there are three player states that count as paused in this sense, 5309 // and one of them is named 'paused'. 5310 // A better name would be 'isCurrentlyNotPlayingOrBuffering' 5311 5312 if (this.player === 'vimeo') { 5313 // just rely on value of this.playing 5314 return (this.playing) ? false : true; 5315 } else { 5316 this.getPlayerState().then(function(state) { 5317 // if any of the following is true, consider the media 'paused' 5318 return state === 'paused' || state === 'stopped' || state === 'ended'; 5319 }); 5320 } 5321 }; 5322 5323 AblePlayer.prototype.syncSignVideo = function(options) { 5324 if (this.hasSignLanguage && ( this.signVideo || this.signYoutube ) ) { 5325 if (options && typeof options.time !== 'undefined') { 5326 if ( this.signVideo ) { 5327 this.signVideo.currentTime = options.time; 5328 } else { 5329 this.youTubeSignPlayer.seekTo(options.time,true); 5330 } 5331 } 5332 if (options && typeof options.rate !== 'undefined') { 5333 if ( this.signVideo ) { 5334 this.signVideo.playbackRate = options.rate; 5335 } else { 5336 this.youTubeSignPlayer.setPlaybackRate(options.rate); 5337 } 5338 } 5339 if (options && typeof options.pause !== 'undefined') { 5340 if ( this.signVideo ) { 5341 this.signVideo.pause(true); 5342 } else { 5343 this.youTubeSignPlayer.pauseVideo(); 5344 } 5345 } 5346 if (options && typeof options.play !== 'undefined') { 5347 if ( this.signVideo ) { 5348 this.signVideo.play(true); 5349 } else { 5350 this.youTubeSignPlayer.playVideo(); 5351 } 5352 } 5353 if (options && typeof options.volume !== 'undefined') { 5354 if ( this.signVideo ) { 5355 this.signVideo.volume = 0; 5356 } 5357 } 5358 } 5359 }; 5360 5361 AblePlayer.prototype.pauseMedia = function () { 5362 5363 this.syncSignVideo( { 'pause' : true } ); 5364 5365 if (this.player === 'html5') { 5366 this.media.pause(true); 5367 } else if (this.player === 'youtube') { 5368 this.youTubePlayer.pauseVideo(); 5369 } else if (this.player === 'vimeo') { 5370 this.vimeoPlayer.pause(); 5371 } 5372 }; 5373 5374 AblePlayer.prototype.playMedia = function () { 5375 5376 this.syncSignVideo( { 'play' : true } ); 5377 5378 if (this.player === 'html5') { 5379 this.media.play(true); 5380 } else if (this.player === 'youtube') { 5381 5382 this.youTubePlayer.playVideo(); 5383 if (typeof this.$posterImg !== 'undefined') { 5384 this.$posterImg.hide(); 5385 } 5386 this.stoppingYouTube = false; 5387 } else if (this.player === 'vimeo') { 5388 this.vimeoPlayer.play(); 5389 } 5390 this.startedPlaying = true; 5391 if (this.hideControls) { 5392 // wait briefly after playback begins, then hide controls 5393 this.hidingControls = true; 5394 this.invokeHideControlsTimeout(); 5395 } 5396 }; 5397 5398 AblePlayer.prototype.fadeControls = function(direction) { 5399 5400 // Visibly fade controls without hiding them from screen reader users 5401 // direction is either 'out' or 'in' 5402 5403 // After the player fades, it's replaced by an empty space 5404 // Would be better if the video and captions expanded to fill the void 5405 // replace JS animation with CSS animation in 12/2025. 5406 5407 if (direction == 'out') { 5408 // get the original height of two key components: 5409 this.$playerDiv.addClass( 'fade-out' ).removeClass( 'fade-in' ); 5410 } else if (direction == 'in') { 5411 this.$playerDiv.addClass( 'fade-in' ).removeClass( 'fade-out' ); 5412 } 5413 }; 5414 5415 AblePlayer.prototype.invokeHideControlsTimeout = function () { 5416 5417 // invoke timeout for waiting a few seconds after a mouse move or key down 5418 // before hiding controls again 5419 var thisObj = this; 5420 this.hideControlsTimeout = window.setTimeout(function() { 5421 if (typeof thisObj.playing !== 'undefined' && thisObj.playing === true && thisObj.hideControls) { 5422 thisObj.fadeControls('out'); 5423 thisObj.controlsHidden = true; 5424 } 5425 },5000); 5426 this.hideControlsTimeoutStatus = 'active'; 5427 }; 5428 5429 AblePlayer.prototype.refreshControls = function(context = 'init', duration, elapsed) { 5430 5431 // context is one of the following: 5432 // 'init' - initial build (or subsequent change that requires full rebuild) 5433 // 'timeline' - a change may effect time-related controls 5434 // 'captions' - a change may effect caption-related controls 5435 // 'descriptions' - a change may effect description-related controls 5436 // 'transcript' - a change may effect the transcript window or button 5437 // 'fullscreen' - a change has been triggered by full screen toggle 5438 // 'playpause' - a change triggered by either a 'play' or 'pause' event 5439 5440 // NOTE: context is not currently supported. 5441 // The steps in this function have too many complex interdependencies 5442 // The gains in efficiency are offset by the possibility of introducing bugs 5443 // For now, executing everything 5444 context = 'init'; 5445 5446 // duration and elapsed are passed from callback functions of Vimeo API events 5447 // duration is expressed as sss.xxx 5448 // elapsed is expressed as sss.xxx 5449 5450 var thisObj, textByState, timestamp, captionsCount, newTop, statusBarWidthBreakpoint; 5451 5452 thisObj = this; 5453 // wait until new source has loaded before refreshing controls 5454 // some critical events won't fire until playback of new media starts 5455 if ( this.swappingSrc && this.playing ) { 5456 return; 5457 } 5458 5459 if ( context === 'timeline' || context === 'init' ) { 5460 // Update timeline controls. 5461 var lastChapterIndex, displayElapsed, updateLive, widthUsed, 5462 leftControls, rightControls, seekbarWidth, buffered; 5463 // all timeline-related functionality requires duration 5464 if (typeof this.duration === 'undefined') { 5465 // wait until duration is known before proceeding with refresh 5466 return; 5467 } 5468 if (this.useChapterTimes) { 5469 this.chapterDuration = this.getChapterDuration(); 5470 this.chapterElapsed = this.getChapterElapsed(); 5471 } 5472 5473 if ( !this.useFixedSeekInterval && !this.seekIntervalCalculated && this.duration > 0) { 5474 // couldn't calculate seekInterval previously; try again. 5475 this.setSeekInterval(); 5476 } 5477 5478 if (this.seekBar) { 5479 if (this.useChapterTimes) { 5480 lastChapterIndex = this.selectedChapters.cues.length-1; 5481 if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) { 5482 // this is the last chapter 5483 if (this.currentChapter.end !== this.duration) { 5484 // chapter ends before or after video ends 5485 // need to adjust seekbar duration to match video end 5486 this.seekBar.setDuration(this.duration - this.currentChapter.start); 5487 } else { 5488 this.seekBar.setDuration(this.chapterDuration); 5489 } 5490 } else { 5491 // this is not the last chapter 5492 this.seekBar.setDuration(this.chapterDuration); 5493 } 5494 } else if ( !(this.duration === undefined || isNaN(this.duration) || this.duration === -1) ) { 5495 this.seekBar.setDuration(this.duration); 5496 } 5497 if (!(this.seekBar.tracking)) { 5498 // Only update the aria live region if we have an update pending 5499 // (from a seek button control) or if the seekBar has focus. 5500 // We use document.activeElement instead of $(':focus') due to a strange bug: 5501 // When the seekHead element is focused, .is(':focus') is failing and $(':focus') is returning an undefined element. 5502 updateLive = this.liveUpdatePending || this.seekBar.seekHead.is($(document.activeElement)); 5503 this.liveUpdatePending = false; 5504 if (this.useChapterTimes) { 5505 this.seekBar.setPosition(this.chapterElapsed, updateLive); 5506 } else { 5507 this.seekBar.setPosition(this.elapsed, updateLive); 5508 } 5509 } 5510 5511 // When seeking, display the seek bar time instead of the actual elapsed time. 5512 if (this.seekBar.tracking) { 5513 displayElapsed = this.seekBar.lastTrackPosition; 5514 } else { 5515 displayElapsed = ( this.useChapterTimes ) ? this.chapterElapsed : this.elapsed; 5516 } 5517 } 5518 // update elapsed & duration 5519 if (typeof this.$durationContainer !== 'undefined') { 5520 if (this.useChapterTimes) { 5521 this.$durationContainer.text( this.formatSecondsAsColonTime(this.chapterDuration)); 5522 } else { 5523 this.$durationContainer.text( this.formatSecondsAsColonTime(this.duration)); 5524 } 5525 } 5526 if (typeof this.$elapsedTimeContainer !== 'undefined') { 5527 this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed)); 5528 } 5529 5530 if (this.skin === 'legacy') { 5531 // Update seekbar width. 5532 // To do this, we need to calculate the width of all buttons surrounding it. 5533 if (this.seekBar) { 5534 let controlWrapper = this.seekBar.wrapperDiv.parent().parent(); 5535 leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls'); 5536 rightControls = leftControls.next('div.able-right-controls'); 5537 widthUsed = leftControls.outerWidth(true); 5538 rightControls.children().each(function () { 5539 if ($(this).attr('role')=='button') { 5540 widthUsed += $(this).outerWidth(true) + 5; 5541 } 5542 }); 5543 if (this.fullscreen) { 5544 seekbarWidth = $(window).width() - widthUsed; 5545 } else { 5546 // seekbar is wide enough to fill the remaining space 5547 // include a 10px buffer to account for minor browser differences or custom styles. 5548 seekbarWidth = controlWrapper.width() - widthUsed - 10; 5549 } 5550 // Sometimes some minor fluctuations based on browser weirdness, so set a threshold. 5551 if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) { 5552 this.seekBar.setWidth(seekbarWidth); 5553 } 5554 } 5555 } 5556 5557 // Update buffering progress. 5558 // TODO: Currently only using the first HTML5 buffered interval, 5559 // but this fails sometimes when buffering is split into two or more intervals. 5560 if (this.player === 'html5' && this.media.buffered.length > 0) { 5561 buffered = this.media.buffered.end(0); 5562 if (this.useChapterTimes) { 5563 if (buffered > this.chapterDuration) { 5564 buffered = this.chapterDuration; 5565 } 5566 if (this.seekBar) { 5567 this.seekBar.setBuffered(buffered / this.chapterDuration); 5568 } 5569 } else if ( this.seekBar && !isNaN(buffered) ) { 5570 this.seekBar.setBuffered(buffered / duration); 5571 } 5572 } else if (this.player === 'youtube' && this.seekBar && this.youTubePlayerReady ) { 5573 this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction()); 5574 } else if (this.player === 'vimeo') ; 5575 } 5576 5577 if (context === 'descriptions' || context == 'init') { 5578 if (this.$descButton) { 5579 this.toggleButtonState( 5580 this.$descButton, 5581 this.descOn, 5582 this.translate( 'turnOffDescriptions', 'Turn off descriptions' ), 5583 this.translate( 'turnOnDescriptions', 'Turn on descriptions' ), 5584 ); 5585 } 5586 } 5587 5588 if (context === 'captions' || context == 'init') { 5589 5590 if (this.$ccButton) { 5591 5592 captionsCount = this.captions.length; 5593 if (captionsCount > 1) { 5594 this.$ccButton.attr({ 5595 'aria-haspopup': 'true', 5596 'aria-controls': this.mediaId + '-captions-menu' 5597 }); 5598 } 5599 var ariaLabelOn = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' ); 5600 var ariaLabelOff = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' ); 5601 var ariaPressed = ( captionsCount > 1 ) ? true : false; 5602 5603 this.toggleButtonState( 5604 this.$ccButton, 5605 this.captionsOn, 5606 ariaLabelOff, 5607 ariaLabelOn, 5608 ariaPressed 5609 ); 5610 } 5611 } 5612 5613 if (context === 'fullscreen' || context == 'init'){ 5614 if (this.$fullscreenButton) { 5615 if (!this.fullscreen) { 5616 this.$fullscreenButton.attr( 'aria-label', this.translate( 'enterFullScreen', 'Enter full screen' ) ); 5617 this.getIcon( this.$fullscreenButton, 'fullscreen-expand' ); 5618 } else { 5619 this.$fullscreenButton.attr('aria-label', this.translate( 'exitFullScreen', 'Exit full screen' ) ); 5620 this.getIcon( this.$fullscreenButton, 'fullscreen-collapse' ); 5621 } 5622 } 5623 } 5624 if (context === 'playpause' || context == 'init'){ 5625 if (typeof this.$bigPlayButton !== 'undefined' && typeof this.seekBar !== 'undefined') { 5626 // Choose show/hide for big play button and adjust position. 5627 if (this.paused && !this.seekBar.tracking) { 5628 if (!this.hideBigPlayButton) { 5629 this.$bigPlayButton.show(); 5630 this.$bigPlayButton.attr('aria-hidden', 'false'); 5631 } 5632 } else { 5633 this.$bigPlayButton.hide(); 5634 this.$bigPlayButton.attr('aria-hidden', 'true'); 5635 } 5636 } 5637 } 5638 5639 if (context === 'transcript' || context == 'init'){ 5640 5641 if (this.transcriptType) { 5642 // Sync checkbox and autoScrollTranscript with user preference 5643 if (this.prefAutoScrollTranscript === 1) { 5644 this.autoScrollTranscript = true; 5645 this.$autoScrollTranscriptCheckbox.prop('checked',true); 5646 } else { 5647 this.autoScrollTranscript = false; 5648 this.$autoScrollTranscriptCheckbox.prop('checked',false); 5649 } 5650 5651 // If transcript locked, scroll transcript to current highlight location. 5652 if (this.autoScrollTranscript && this.currentHighlight) { 5653 newTop = Math.floor(this.$transcriptDiv.scrollTop() + 5654 $(this.currentHighlight).position().top - 5655 (this.$transcriptDiv.height() / 2) + 5656 ($(this.currentHighlight).height() / 2)); 5657 if (newTop !== Math.floor(this.$transcriptDiv.scrollTop())) { 5658 // Set a flag to ignore the coming scroll event. 5659 // there's no other way I know of to differentiate programmatic and user-initiated scroll events. 5660 this.scrollingTranscript = true; 5661 // only scroll once after moving a highlight 5662 if (this.movingHighlight) { 5663 this.$transcriptDiv.scrollTop(newTop); 5664 this.movingHighlight = false; 5665 } 5666 } 5667 } 5668 } 5669 } 5670 5671 if (context === 'init') { 5672 5673 if (this.$chaptersButton) { 5674 this.$chaptersButton.attr({ 5675 'aria-label': this.translate( 'chapters', 'Chapters' ), 5676 'aria-haspopup': 'true', 5677 'aria-controls': this.mediaId + '-chapters-menu' 5678 }); 5679 } 5680 } 5681 5682 if (context === 'timeline' || context === 'playpause' || context === 'init') { 5683 5684 // update status 5685 textByState = { 5686 'stopped': this.translate( 'statusStopped', 'Stopped' ), 5687 'paused': this.translate( 'statusPaused', 'Paused' ), 5688 'playing': this.translate( 'statusPlaying', 'Playing' ), 5689 'buffering': this.translate( 'statusBuffering', 'Buffering' ), 5690 'ended': this.translate( 'statusEnd', 'End of track' ) 5691 }; 5692 5693 if (this.stoppingYouTube) { 5694 // stoppingYouTube is true temporarily while video is paused and seeking to 0 5695 // See notes in handleRestart() 5696 // this.stoppingYouTube will be reset when seek to 0 is finished (in event.js > onMediaUpdateTime()) 5697 if (this.$status.text() !== this.translate( 'statusStopped', 'Stopped' ) ) { 5698 this.$status.text( this.translate( 'statusStopped', 'Stopped' ) ); 5699 } 5700 this.getIcon( this.$playpauseButton, 'play' ); 5701 } else if (typeof this.$status !== 'undefined' && typeof this.seekBar !== 'undefined') { 5702 // Update the text only if it's changed since it has role="alert"; 5703 // also don't update while tracking, since this may Pause/Play the player but we don't want to send a Pause/Play update. 5704 this.getPlayerState().then(function(currentState) { 5705 if (thisObj.$status.text() !== textByState[currentState] && !thisObj.seekBar.tracking) { 5706 // Debounce updates; only update after status has stayed steadily different for a while 5707 // "A while" is defined differently depending on context 5708 if (thisObj.swappingSrc) { 5709 // this is where most of the chatter occurs (e.g., playing, paused, buffering, playing), 5710 // so set a longer wait time before writing a status message 5711 if (!thisObj.debouncingStatus) { 5712 thisObj.statusMessageThreshold = 2000; // in ms (2 seconds) 5713 } 5714 } else if (!thisObj.debouncingStatus) { 5715 // for all other contexts (e.g., users clicks Play/Pause) 5716 // user should receive more rapid feedback 5717 thisObj.statusMessageThreshold = 250; // in ms 5718 } 5719 timestamp = (new Date()).getTime(); 5720 if (!thisObj.statusDebounceStart) { 5721 thisObj.statusDebounceStart = timestamp; 5722 // Call refreshControls() again after allotted time has passed 5723 thisObj.debouncingStatus = true; 5724 thisObj.statusTimeout = setTimeout(function () { 5725 thisObj.debouncingStatus = false; 5726 thisObj.refreshControls(context); 5727 }, thisObj.statusMessageThreshold); 5728 } else if ((timestamp - thisObj.statusDebounceStart) > thisObj.statusMessageThreshold) { 5729 thisObj.$status.text(textByState[currentState]); 5730 thisObj.statusDebounceStart = null; 5731 clearTimeout(thisObj.statusTimeout); 5732 thisObj.statusTimeout = null; 5733 } 5734 } else { 5735 thisObj.statusDebounceStart = null; 5736 thisObj.debouncingStatus = false; 5737 clearTimeout(thisObj.statusTimeout); 5738 thisObj.statusTimeout = null; 5739 } 5740 // Don't change play/pause button display while using the seek bar (or if YouTube stopped) 5741 if (!thisObj.seekBar.tracking && !thisObj.stoppingYouTube) { 5742 if (currentState === 'paused' || currentState === 'stopped' || currentState === 'ended') { 5743 thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'play', 'Play' ) ); 5744 thisObj.getIcon( thisObj.$playpauseButton, 'play' ); 5745 } else { 5746 thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'pause', 'Pause' ) ); 5747 thisObj.getIcon( thisObj.$playpauseButton, 'pause' ); 5748 } 5749 } 5750 }); 5751 } 5752 } 5753 5754 // Show/hide status bar content conditionally 5755 if (!this.fullscreen) { 5756 statusBarWidthBreakpoint = 300; 5757 if (this.$statusBarDiv.width() < statusBarWidthBreakpoint) { 5758 // Player is too small for a speed span 5759 this.$statusBarDiv.find('span.able-speed').hide(); 5760 this.hidingSpeed = true; 5761 } else { 5762 if (this.hidingSpeed) { 5763 this.$statusBarDiv.find('span.able-speed').show(); 5764 this.hidingSpeed = false; 5765 } 5766 } 5767 } 5768 5769 }; 5770 5771 AblePlayer.prototype.handlePlay = function(e) { 5772 if (this.paused) { 5773 // user clicked play 5774 this.okToPlay = true; 5775 this.playMedia(); 5776 if (this.synth.paused) { 5777 // media was paused while description was speaking 5778 // resume utterance 5779 this.synth.resume(); 5780 } 5781 } else { 5782 // user clicked pause 5783 this.okToPlay = false; 5784 this.pauseMedia(); 5785 if (this.speakingDescription) { 5786 // pause the current utterance 5787 // it will resume when the user presses play 5788 this.synth.pause(); 5789 } 5790 } 5791 if (this.speechEnabled === null) { 5792 this.initSpeech('play'); 5793 } 5794 }; 5795 5796 AblePlayer.prototype.handleRestart = function() { 5797 5798 if (this.speakingDescription) { 5799 // cancel audio description 5800 this.synth.cancel(); 5801 } 5802 this.seekTo(0); 5803 }; 5804 5805 AblePlayer.prototype.handlePrevTrack = function() { 5806 5807 // currently on the first track 5808 // wrap to bottom and play the last track 5809 let newIndex = (this.playlistIndex === 0) ? this.$playlist.length - 1 : this.playlistIndex - 1; 5810 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 5811 this.cuePlaylistItem(newIndex); 5812 }; 5813 5814 AblePlayer.prototype.handleNextTrack = function() { 5815 5816 // currently on the last track 5817 // wrap to top and play the first track 5818 let newIndex = (this.playlistIndex === this.$playlist.length - 1) ? 0 : this.playlistIndex + 1; 5819 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 5820 this.cuePlaylistItem(newIndex); 5821 }; 5822 5823 AblePlayer.prototype.handleRewind = function() { 5824 5825 var targetTime; 5826 5827 targetTime = this.elapsed - this.seekInterval; 5828 if (this.useChapterTimes && (targetTime < this.currentChapter.start)) { 5829 targetTime = this.currentChapter.start; 5830 } else if (targetTime < 0) { 5831 targetTime = 0; 5832 } 5833 this.seekTo(targetTime); 5834 }; 5835 5836 AblePlayer.prototype.handleFastForward = function() { 5837 5838 var targetTime, lastChapterIndex; 5839 5840 lastChapterIndex = this.chapters.length-1; 5841 targetTime = this.elapsed + this.seekInterval; 5842 if (this.useChapterTimes) { 5843 if (this.chapters[lastChapterIndex] == this.currentChapter) { 5844 // this is the last chapter 5845 if (targetTime > this.duration || targetTime > this.currentChapter.end) { 5846 // targetTime would exceed the end of the video (or chapter) 5847 // scrub to end of whichever is earliest 5848 targetTime = Math.min(this.duration, this.currentChapter.end); 5849 } else if (this.duration % targetTime < this.seekInterval) { 5850 // nothing left but pocket change after seeking to targetTime 5851 // go ahead and seek to end of video (or chapter), whichever is earliest 5852 targetTime = Math.min(this.duration, this.currentChapter.end); 5853 } 5854 } else { 5855 // this is not the last chapter 5856 if (targetTime > this.currentChapter.end) { 5857 // targetTime would exceed the end of the chapter 5858 // scrub exactly to end of chapter 5859 targetTime = this.currentChapter.end; 5860 } 5861 } 5862 } else { 5863 // not using chapter times 5864 if (targetTime > this.duration) { 5865 targetTime = this.duration; 5866 } 5867 } 5868 this.seekTo(targetTime); 5869 }; 5870 5871 AblePlayer.prototype.handleRateIncrease = function() { 5872 this.changeRate(1); 5873 }; 5874 5875 AblePlayer.prototype.handleRateDecrease = function() { 5876 this.changeRate(-1); 5877 }; 5878 5879 // Increases or decreases playback rate, where dir is 1 or -1 indication direction. 5880 AblePlayer.prototype.changeRate = function (dir) { 5881 5882 var rates, currentRate, index, newRate, vimeoMin, vimeoMax; 5883 5884 if (this.player === 'html5') { 5885 this.setPlaybackRate(this.getPlaybackRate() + (0.25 * dir)); 5886 } else if (this.player === 'youtube') { 5887 if (this.youTubePlayerReady) { 5888 rates = this.youTubePlayer.getAvailablePlaybackRates(); 5889 currentRate = this.getPlaybackRate(); 5890 index = rates.indexOf(currentRate); 5891 if (index === -1) ; else { 5892 index += dir; 5893 // Can only increase or decrease rate if there's another rate available. 5894 if (index < rates.length && index >= 0) { 5895 this.setPlaybackRate(rates[index]); 5896 } 5897 } 5898 } 5899 } else if (this.player === 'vimeo') { 5900 // range is 0.5 to 2 5901 // increase/decrease in inrements of 0.5 5902 vimeoMin = 0.5; 5903 vimeoMax = 2; 5904 if (dir === 1) { 5905 newRate = (this.vimeoPlaybackRate + 0.5 <= vimeoMax) ? this.vimeoPlaybackRate + 0.5 : vimeoMax; 5906 } else if (dir === -1) { 5907 newRate = (this.vimeoPlaybackRate - 0.5 >= vimeoMin) ? this.vimeoPlaybackRate - 0.5 : vimeoMin; 5908 } 5909 this.setPlaybackRate(newRate); 5910 } 5911 }; 5912 5913 AblePlayer.prototype.handleCaptionToggle = function() { 5914 5915 var thisObj = this; 5916 var captions, ariaPressed; 5917 if (this.hidingPopup) { 5918 // stopgap to prevent spacebar in Firefox from reopening popup 5919 // immediately after closing it 5920 this.hidingPopup = false; 5921 return false; 5922 } 5923 5924 captions = (this.captions.length) ? this.captions : []; 5925 if (captions.length === 1) { 5926 // When there's only one set of captions, just do an on/off toggle. 5927 if (this.captionsOn === true) { 5928 // turn them off 5929 this.captionsOn = false; 5930 this.prefCaptions = 0; 5931 ariaPressed = false; 5932 this.updatePreferences('prefCaptions'); 5933 if (this.usingYouTubeCaptions) { 5934 this.youTubePlayer.unloadModule('captions'); 5935 } else if (this.usingVimeoCaptions) { 5936 this.vimeoPlayer.disableTextTrack(); 5937 } else { 5938 this.$captionsWrapper.hide(); 5939 } 5940 } else { 5941 // captions are off. Turn them on. 5942 this.captionsOn = true; 5943 this.prefCaptions = 1; 5944 ariaPressed = true; 5945 this.updatePreferences('prefCaptions'); 5946 if (this.usingYouTubeCaptions) { 5947 this.youTubePlayer.loadModule('captions'); 5948 } else if (this.usingVimeoCaptions) { 5949 this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) { 5950 switch (error.name) { 5951 } 5952 }); 5953 } else { 5954 this.$captionsWrapper.show(); 5955 } 5956 for (var i=0; i<captions.length; i++) { 5957 if (captions[i].def === true) { // this is the default language 5958 this.selectedCaptions = captions[i]; 5959 } 5960 } 5961 this.selectedCaptions = this.captions[0]; 5962 if (this.descriptions.length >= 0) { 5963 this.selectedDescriptions = this.descriptions[0]; 5964 } 5965 } 5966 } else { 5967 // there is more than one caption track. 5968 // clicking on a track is handled via caption.js > getCaptionClickFunction() 5969 if (this.captionsPopup && this.captionsPopup.is(':visible')) { 5970 this.captionsPopup.hide(); 5971 this.hidingPopup = false; 5972 this.$ccButton.attr('aria-expanded', 'false'); 5973 this.waitThenFocus(this.$ccButton); 5974 } else { 5975 this.closePopups(); 5976 if (this.captionsPopup) { 5977 this.captionsPopup.show(); 5978 this.$ccButton.attr('aria-expanded','true'); 5979 5980 // Gives time to "register" expanded ccButton 5981 setTimeout(function() { 5982 thisObj.captionsPopup.css('top', thisObj.$ccButton.position().top - thisObj.captionsPopup.outerHeight()); 5983 thisObj.captionsPopup.css('left', thisObj.$ccButton.position().left); 5984 // Place focus on the first button (even if another button is checked) 5985 thisObj.captionsPopup.find('li').removeClass('able-focus'); 5986 thisObj.captionsPopup.find('li').first().trigger('focus').addClass('able-focus'); 5987 }, 50); 5988 } 5989 } 5990 } 5991 var ariaLabelOn = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' ); 5992 var ariaLabelOff = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' ); 5993 5994 this.toggleButtonState( 5995 this.$ccButton, 5996 this.captionsOn, 5997 ariaLabelOff, 5998 ariaLabelOn, 5999 ariaPressed 6000 ); 6001 }; 6002 6003 /** 6004 * Gives enough time for DOM changes to take effect before adjusting focus. 6005 * Helpful for allowing screen reading of elements whose state is intermittently changed. 6006 * 6007 * @param {*} $el element to focus on 6008 * @param {*} timeout optional wait time in milliseconds before focus 6009 */ 6010 AblePlayer.prototype.waitThenFocus = function($el, timeout) { 6011 6012 // Default wait time of 50 ms 6013 var _timeout = (timeout === undefined || timeout === null) ? 50 : timeout; 6014 6015 setTimeout(function() { 6016 $el.trigger('focus'); 6017 }, _timeout); 6018 }; 6019 6020 AblePlayer.prototype.handleChapters = function () { 6021 if (this.hidingPopup) { 6022 // stopgap to prevent spacebar in Firefox from reopening popup 6023 // immediately after closing it 6024 this.hidingPopup = false; 6025 return false; 6026 } 6027 if (this.chaptersPopup.is(':visible')) { 6028 this.chaptersPopup.hide(); 6029 this.hidingPopup = false; 6030 this.$chaptersButton.attr('aria-expanded','false').trigger('focus'); 6031 } else { 6032 this.closePopups(); 6033 this.chaptersPopup.show(); 6034 this.$chaptersButton.attr('aria-expanded','true'); 6035 this.chaptersPopup.css('top', this.$chaptersButton.position().top - this.chaptersPopup.outerHeight()); 6036 this.chaptersPopup.css('left', this.$chaptersButton.position().left); 6037 6038 // Highlight the current chapter, if any chapters are checked 6039 // Otherwise, place focus on the first chapter 6040 this.chaptersPopup.find('li').removeClass('able-focus'); 6041 if (this.chaptersPopup.find('li[aria-checked="true"]').length) { 6042 this.chaptersPopup.find('li[aria-checked="true"]').trigger('focus').addClass('able-focus'); 6043 } else { 6044 this.chaptersPopup.find('li').first().addClass('able-focus').attr('aria-checked','true').trigger('focus'); 6045 } 6046 } 6047 }; 6048 6049 AblePlayer.prototype.handleDescriptionToggle = function() { 6050 6051 this.descOn = !this.descOn; 6052 this.prefDesc = + this.descOn; // convert boolean to integer 6053 this.updatePreferences('prefDesc'); 6054 if (typeof this.$descDiv !== 'undefined') { 6055 if (!this.$descDiv.is(':hidden')) { 6056 this.$descDiv.hide(); 6057 } 6058 // NOTE: now showing $descDiv here if previously hidden 6059 // that's handled elsewhere, dependent on whether there's text to show 6060 } 6061 this.initDescription(); 6062 this.refreshControls('descriptions'); 6063 }; 6064 6065 AblePlayer.prototype.handlePrefsClick = function(pref) { 6066 6067 // NOTE: the prefs menu is positioned near the right edge of the player 6068 // This assumes the Prefs button is also positioned in that vicinity 6069 // (last or second-last button the right) 6070 6071 // NOTE: If previously unable to fully populate the Description dialog 6072 // because the Web Speech API failed to getVoices() 6073 // now is a good time to try again 6074 // so the Description dialog can be rebuilt before the user requests it 6075 6076 var thisObj, prefsButtonPosition, prefsMenuRight, prefsMenuLeft; 6077 6078 thisObj = this; 6079 6080 if (this.speechEnabled === null) { 6081 this.initSpeech('prefs'); 6082 } 6083 if (this.hidingPopup) { 6084 // stopgap to prevent spacebar in Firefox from reopening popup 6085 // immediately after closing it 6086 this.hidingPopup = false; 6087 return false; 6088 } 6089 if (this.prefsPopup.is(':visible')) { 6090 this.prefsPopup.hide(); 6091 this.$prefsButton.attr('aria-expanded','false'); 6092 // restore each menu item to original hidden state 6093 this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 6094 if (!this.showingPrefsDialog) { 6095 this.$prefsButton.trigger('focus'); 6096 } 6097 // wait briefly, then reset hidingPopup 6098 setTimeout(function() { 6099 thisObj.hidingPopup = false; 6100 },100); 6101 } else { 6102 this.closePopups(); 6103 this.prefsPopup.show(); 6104 this.$prefsButton.attr('aria-expanded','true'); 6105 this.$prefsButton.trigger('focus'); // focus first on prefs button to announce expanded state 6106 // give time for focus on button then adjust popup settings and focus 6107 setTimeout(function() { 6108 prefsButtonPosition = thisObj.$prefsButton.position(); 6109 prefsMenuRight = thisObj.$ableDiv.width() - 5; 6110 prefsMenuLeft = prefsMenuRight - thisObj.prefsPopup.width(); 6111 thisObj.prefsPopup.css('top', prefsButtonPosition.top - thisObj.prefsPopup.outerHeight()); 6112 thisObj.prefsPopup.css('left', prefsMenuLeft); 6113 // remove prior focus and set focus on first item; also change tabindex from -1 to 0 6114 thisObj.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','0'); 6115 thisObj.prefsPopup.find('li').first().trigger('focus').addClass('able-focus'); 6116 }, 50); 6117 } 6118 }; 6119 6120 AblePlayer.prototype.handleTranscriptToggle = function () { 6121 var thisObj = this; 6122 var visible = this.$transcriptDiv.is(':visible'); 6123 if ( visible ) { 6124 this.$transcriptArea.hide(); 6125 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6126 this.prefTranscript = 0; 6127 if ( this.transcriptType === 'popup' ) { 6128 this.$transcriptButton.trigger('focus').addClass('able-focus'); 6129 // wait briefly before resetting stopgap var 6130 // otherwise the keypress used to select 'Close' will trigger the transcript button 6131 // Benchmark tests: If this is gonna happen, it typically happens in around 3ms; max 12ms 6132 // Setting timeout to 100ms is a virtual guarantee of proper functionality 6133 setTimeout(function() { 6134 thisObj.closingTranscript = false; 6135 }, 100); 6136 } 6137 } else { 6138 if ( this.transcriptType === 'popup' ) { 6139 this.positionDraggableWindow('transcript'); 6140 this.$transcriptArea.show(); 6141 // showing transcriptArea has a cascading effect of showing all content *within* transcriptArea 6142 // need to re-hide the popup menu 6143 this.$transcriptPopup.hide(); 6144 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6145 this.prefTranscript = 1; 6146 // move focus to first focusable element (window options button) 6147 this.focusNotClick = true; 6148 this.$transcriptArea.find('button').first().trigger('focus'); 6149 // wait briefly before resetting stopgap var 6150 setTimeout(function() { 6151 thisObj.focusNotClick = false; 6152 }, 100); 6153 } else { 6154 this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) ); 6155 this.$transcriptArea.show(); 6156 } 6157 } 6158 this.updatePreferences('prefTranscript'); 6159 }; 6160 6161 AblePlayer.prototype.handleSignToggle = function () { 6162 6163 var thisObj = this; 6164 var visible = this.$signWindow.is(':visible'); 6165 if ( visible ) { 6166 this.$signWindow.hide(); 6167 this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) ); 6168 this.prefSign = 0; 6169 this.$signButton.trigger('focus').addClass('able-focus'); 6170 // wait briefly before resetting stopgap var 6171 // otherwise the keypress used to select 'Close' will trigger the transcript button 6172 setTimeout(function() { 6173 thisObj.closingSign = false; 6174 }, 100); 6175 } else { 6176 this.positionDraggableWindow('sign'); 6177 this.$signWindow.show(); 6178 // showing signWindow has a cascading effect of showing all content *within* signWindow 6179 // need to re-hide the popup menu 6180 this.$signPopup.hide(); 6181 this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) ); 6182 this.prefSign = 1; 6183 this.focusNotClick = true; 6184 this.$signWindow.find('button').first().trigger('focus'); 6185 // wait briefly before resetting stopgap var 6186 // otherwise the keypress used to select 'Close' will trigger the transcript button 6187 setTimeout(function() { 6188 thisObj.focusNotClick = false; 6189 }, 100); 6190 } 6191 this.updatePreferences('prefSign'); 6192 }; 6193 6194 AblePlayer.prototype.setFullscreen = function (fullscreen) { 6195 6196 if (this.fullscreen == fullscreen) { 6197 return; 6198 } 6199 var thisObj = this; 6200 var $el = this.$ableWrapper; 6201 var el = $el[0]; 6202 6203 if (this.nativeFullscreenSupported()) { 6204 // Note: many varying names for options for browser compatibility. 6205 if (fullscreen) { 6206 var scroll = { 6207 x: window.pageXOffset || 0, 6208 y: window.pageYOffset || 0 6209 }; 6210 if (this.prefTranscript === 1) { 6211 // transcript is on. Go ahead and reposition it 6212 this.rePositionDraggableWindow("transcript"); 6213 } 6214 if (this.prefSign === 1) { 6215 // sign is on. Go ahead and reposition it 6216 this.rePositionDraggableWindow("sign"); 6217 } 6218 this.scrollPosition = scroll; 6219 // Initialize fullscreen 6220 if (el.requestFullscreen) { 6221 el.requestFullscreen(); 6222 } else if (el.webkitRequestFullscreen) { 6223 el.webkitRequestFullscreen(); 6224 } 6225 this.fullscreen = true; 6226 } else { 6227 // Exit fullscreen 6228 this.restoringAfterFullscreen = true; 6229 if (document.exitFullscreen) { 6230 document.exitFullscreen(); 6231 } else if (document.webkitExitFullscreen) { 6232 document.webkitExitFullscreen(); 6233 } else if (document.webkitCancelFullscreen) { 6234 document.webkitCancelFullscreen(); 6235 } 6236 if (this.prefTranscript === 1) { 6237 // transcript is on. Go ahead and reposition it 6238 this.positionDraggableWindow("transcript"); 6239 } 6240 if (this.prefSign === 1) { 6241 // sign is on. Go ahead and reposition it 6242 this.positionDraggableWindow("sign"); 6243 } 6244 this.fullscreen = false; 6245 } 6246 } 6247 // add event handlers for changes in fullscreen mode. 6248 // Browsers natively trigger this event with the Escape key, 6249 // in addition to clicking the exit fullscreen button 6250 $(document).on('fullscreenchange webkitfullscreenchange', function(e) { 6251 // NOTE: e.type = the specific event that fired (in case needing to control for browser-specific idiosyncrasies) 6252 if (!thisObj.fullscreen) { 6253 // user has just exited full screen 6254 thisObj.restoringAfterFullscreen = true; 6255 } else if (!thisObj.clickedFullscreenButton) { 6256 // user triggered fullscreenchange without clicking fullscreen button 6257 thisObj.fullscreen = false; 6258 thisObj.restoringAfterFullscreen = true; 6259 } 6260 thisObj.resizePlayer(); 6261 thisObj.refreshControls('fullscreen'); 6262 // Reset scrollPosition after closing fullscreen. 6263 if ( thisObj.scrollPosition ) { 6264 scroll = thisObj.scrollPosition; 6265 window.scrollTo( scroll.x, scroll.y ); 6266 } 6267 // NOTE: The fullscreenchange (or browser-equivalent) event is triggered twice 6268 // when exiting fullscreen via the "Exit fullscreen" button (only once if using Escape) 6269 // Not sure why, but consequently we need to be sure thisObj.clickedFullscreenButton 6270 // continues to be true through both events 6271 // Could use a counter variable to control that (reset to false after the 2nd trigger) 6272 // However, since I don't know why it's happening, and whether it's 100% reliable 6273 // resetting clickedFullscreenButton after a timeout seems to be better approach 6274 setTimeout(function() { 6275 thisObj.clickedFullscreenButton = false; 6276 thisObj.restoringAfterFullscreen = false; 6277 },100); 6278 }); 6279 }; 6280 6281 AblePlayer.prototype.handleFullscreenToggle = function () { 6282 6283 var stillPaused = this.paused; 6284 this.setFullscreen(!this.fullscreen); 6285 if (stillPaused) { 6286 this.pauseMedia(); // when toggling fullscreen and media is just paused, keep media paused. 6287 } else if (!stillPaused) { 6288 this.playMedia(); // when toggling fullscreen and media is playing, continue playing. 6289 } 6290 // automatically hide controller in fullscreen mode 6291 // then reset back to original setting after exiting fullscreen mode 6292 if (this.fullscreen) { 6293 this.hideControls = true; 6294 if (this.playing) { 6295 // go ahead and hide the controls 6296 this.fadeControls('out'); 6297 this.controlsHidden = true; 6298 } 6299 } else { 6300 // exit fullscreen mode 6301 this.hideControls = this.hideControlsOriginal; 6302 if (!this.hideControls) { // do not hide controls 6303 if (this.controlsHidden) { 6304 this.fadeControls('in'); 6305 this.controlsHidden = false; 6306 } 6307 // if there's an active timeout to fade controls out again, clear it 6308 if (this.hideControlsTimeoutStatus === 'active') { 6309 window.clearTimeout(this.hideControlsTimeout); 6310 this.hideControlsTimeoutStatus = 'clear'; 6311 } 6312 } 6313 } 6314 // don't resizePlayer yet; that will be called in response to the window resize event 6315 }; 6316 6317 AblePlayer.prototype.handleTranscriptLockToggle = function (val) { 6318 6319 this.autoScrollTranscript = val; // val is boolean 6320 this.prefAutoScrollTranscript = +val; // convert boolean to numeric 1 or 0 for cookie 6321 this.updatePreferences('prefAutoScrollTranscript'); 6322 this.refreshControls('transcript'); 6323 }; 6324 6325 AblePlayer.prototype.getIcon = function( $button, id) { 6326 // Remove existing HTML before generating. 6327 // iconData: [0 = svg viewbox, 1 = svg path] 6328 // Font and image icon functionality was removed in 5.0.0 in favor of SVG. 6329 var iconData = this.getIconData( id ); 6330 6331 var existingIcon = $button.find( 'svg#ableplayer-' + id ); 6332 // Avoid repainting icon if there's no change. 6333 if ( existingIcon.length > 0 ) { 6334 return; 6335 } 6336 $button.find('svg').remove(); 6337 6338 // Outdented for simpler diff 6339 // Function to create SVG nodes. 6340 function getNode(n, v) { 6341 n = document.createElementNS("http://www.w3.org/2000/svg", n); 6342 for (var p in v) { 6343 n.setAttributeNS(null, p.replace(/[A-Z]/g, function(m) { 6344 return "-" + m.toLowerCase(); 6345 }), v[p]); 6346 } 6347 return n; 6348 } 6349 var icon = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); 6350 icon.setAttribute( 'focusable', 'false' ); 6351 icon.setAttribute( 'aria-hidden', 'true'); 6352 icon.setAttribute( 'viewBox', iconData[0] ); 6353 icon.setAttribute( 'id', 'ableplayer-' + id ); 6354 let path = getNode( 'path', { d: iconData[1] } ); 6355 icon.appendChild( path ); 6356 6357 $button.append( icon ); 6358 // Refresh the DOM. 6359 $button.html($button.html()); 6360 }; 6361 6362 AblePlayer.prototype.setText = function( $button, text ) { 6363 $button.attr( 'aria-label', text ); 6364 }; 6365 6366 AblePlayer.prototype.toggleButtonState = function($button, isOn, onLabel, offLabel, ariaPressed = false, ariaExpanded = false) { 6367 // isOn means "the feature is being turned on". 6368 let buttonOff = ( $button.hasClass( 'buttonOff' ) ) ? true : false; 6369 if ( buttonOff && ! isOn || ! buttonOff && isOn ) { 6370 // Only toggle state if button state does not match feature state. 6371 return; 6372 } 6373 if (! isOn) { 6374 $button.addClass('buttonOff').attr('aria-label', offLabel); 6375 if ( ariaPressed ) { 6376 $button.attr('aria-pressed', 'false'); 6377 } 6378 if ( ariaExpanded ) { 6379 $button.attr( 'aria-expanded', 'false' ); 6380 } 6381 } else { 6382 $button.removeClass('buttonOff').attr('aria-label', onLabel); 6383 if ( ariaPressed ) { 6384 $button.attr('aria-pressed', 'true'); 6385 } 6386 if ( ariaExpanded ) { 6387 $button.attr( 'aria-expanded', 'true' ); 6388 } 6389 } 6390 }; 6391 6392 AblePlayer.prototype.showTooltip = function($tooltip) { 6393 6394 $tooltip.show(); 6395 }; 6396 6397 AblePlayer.prototype.showAlert = function( msg, location = 'main' ) { 6398 6399 // location is either of the following: 6400 // 'main' (default) 6401 // 'screenreader (visibly hidden) 6402 // 'sign' (sign language window) 6403 // 'transcript' (transcript window) 6404 var thisObj, $alertBox, $parentWindow; 6405 6406 thisObj = this; 6407 $alertBox = thisObj.$alertBox; 6408 $parentWindow = thisObj.$ableDiv; 6409 if (location === 'transcript') { 6410 $parentWindow = thisObj.$transcriptArea; 6411 } else if (location === 'sign') { 6412 $parentWindow = thisObj.$signWindow; 6413 } else if (location === 'screenreader') { 6414 $alertBox = thisObj.$srAlertBox; 6415 } 6416 $alertBox.find('span').text(msg); 6417 $alertBox.appendTo($parentWindow); 6418 $alertBox.css( {'display': 'flex'} ); 6419 6420 if (location !== 'screenreader') { 6421 setTimeout( function () { 6422 $alertBox.hide(); 6423 }, 30000 ); 6424 } 6425 }; 6426 6427 AblePlayer.prototype.showedAlert = function (which) { 6428 6429 // returns true if the target alert has already been shown 6430 // useful for throttling alerts that only need to be shown once 6431 // e.g., move alerts with instructions for dragging a window 6432 if (which === 'transcript') { 6433 return this.showedTranscriptAlert ?? false; 6434 } else if (which === 'sign') { 6435 return this.showedSignAlert ?? false; 6436 } 6437 return false; 6438 }; 6439 6440 // Resizes all relevant player attributes. 6441 AblePlayer.prototype.resizePlayer = function (width, height) { 6442 6443 var captionSize, newWidth, newHeight, $iframe; 6444 6445 if (this.mediaType === 'audio') { 6446 return; 6447 } 6448 if (typeof width !== 'undefined' && typeof height !== 'undefined') { 6449 // this is being called the first time a player is initialized 6450 // width and height were collected from the HTML, YouTube, or Vimeo media API 6451 // so are reflective of the actual size of the media 6452 // use these values to calculate aspectRatio 6453 this.aspectRatio = height / width; 6454 if (this.playerWidth) { 6455 // default width is already defined via a width or data-width attribute. Use that. 6456 newWidth = this.playerWidth; 6457 if (this.playerHeight) { 6458 newHeight = this.playerHeight; 6459 } else { 6460 newHeight = Math.round(newWidth * this.aspectRatio); 6461 this.playerHeight = newHeight; 6462 } 6463 } else { 6464 // playerWidth was not defined via HTML attributes 6465 newWidth = (this.player === 'html5') ? $(window).width() : this.$ableWrapper.width(); 6466 newHeight = Math.round(newWidth * this.aspectRatio); 6467 } 6468 } else if (this.fullscreen) { 6469 this.$ableWrapper.addClass('fullscreen'); 6470 newWidth = $(window).width(); 6471 // the 5 pixel buffer is arbitrary, but results in a better fit for all browsers 6472 newHeight = $(window).height() - this.$playerDiv.outerHeight() - 5; 6473 this.positionCaptions('overlay'); 6474 } else { // not fullscreen, and not first time initializing player 6475 this.$ableWrapper.removeClass('fullscreen'); 6476 if (this.player === 'html5') { 6477 newWidth = (this.playerWidth) ? this.playerWidth : $(window).width(); 6478 } else { 6479 newWidth = this.$ableWrapper.width(); 6480 } 6481 newHeight = Math.round(newWidth * this.aspectRatio); 6482 this.positionCaptions(this.prefCaptionsPosition); 6483 } 6484 if (this.debug) ; 6485 // Now size the player with newWidth and newHeight 6486 if (this.player === 'youtube' || this.player === 'vimeo') { 6487 $iframe = this.$ableWrapper.find('iframe'); 6488 if (this.player === 'youtube' && this.youTubePlayer) { 6489 // alternatively, YouTube API offers a method for setting the video size 6490 // this adds width and height attributes to the iframe 6491 // but might have other effects, so best to do it this way 6492 this.youTubePlayer.setSize(newWidth,newHeight); 6493 } else { 6494 // Vimeo API does not have a method for changing size of player 6495 // Therefore, need to change iframe attributes directly 6496 $iframe.attr({ 6497 'width': newWidth, 6498 'height': newHeight 6499 }); 6500 } 6501 if (this.playerWidth && this.playerHeight) { 6502 if (this.fullscreen) { 6503 // remove constraints 6504 $iframe.css({ 6505 'max-width': '', 6506 'max-height': '' 6507 }); 6508 } else { 6509 // use CSS on iframe to enforce explicitly defined size constraints 6510 $iframe.css({ 6511 'max-width': this.playerWidth + 'px', 6512 'max-height': this.playerHeight + 'px' 6513 }); 6514 } 6515 } 6516 } else if (this.player === 'html5') { 6517 if (this.fullscreen) { 6518 this.$media.attr({ 6519 'width': newWidth, 6520 'height': newHeight 6521 }); 6522 this.$ableWrapper.css({ 6523 'width': newWidth, 6524 'height': newHeight 6525 }); 6526 } else { 6527 // No constraints. Let CSS handle the positioning. 6528 this.$media.removeAttr('width height'); 6529 this.$ableWrapper.removeAttr( 'style' ); 6530 } 6531 } 6532 // Resize captions 6533 if (typeof this.$captionsDiv !== 'undefined') { 6534 6535 // Font-size is too small in full screen view 6536 // use viewport units (vw) for large viewports 6537 // % units work fine if not fullscreen 6538 // prefCaptionSize is expressed as a percentage 6539 var isSmallScreen = false; 6540 var windowWidth = window.screen.width; 6541 if ( windowWidth < 1200 ) { 6542 isSmallScreen = true; 6543 } 6544 captionSize = parseInt(this.prefCaptionsSize,10); 6545 if (this.fullscreen && ! isSmallScreen ) { 6546 captionSize = (captionSize / 100) + 'vw'; 6547 } else if ( this.fullscreen && isSmallScreen ) { 6548 captionSize = '1.2rem'; 6549 } else { 6550 captionSize = captionSize + '%'; 6551 } 6552 this.$captionsDiv.css({ 6553 'font-size': captionSize 6554 }); 6555 } 6556 this.refreshControls(); 6557 }; 6558 6559 AblePlayer.prototype.retrieveOffscreenWindow = function( which, width, height ) { 6560 6561 // check to be sure popup windows ('transcript' or 'sign') are positioned on-screen 6562 // (they sometimes disappear off-screen when entering fullscreen mode) 6563 // if off-screen, recalculate so they are back on screen 6564 6565 var window, windowPos, windowTop, windowLeft, windowRight, windowWidth, windowBottom, windowHeight; 6566 6567 if (which == 'transcript') { 6568 window = this.$transcriptArea; 6569 } else if (which == 'sign') { 6570 window = this.$signWindow; 6571 } 6572 windowWidth = window.width(); 6573 windowHeight = window.height(); 6574 windowPos = window.position(); 6575 windowTop = windowPos.top; 6576 windowLeft = windowPos.left; 6577 windowRight = windowLeft + windowWidth; 6578 windowBottom = windowTop + windowHeight; 6579 6580 if (windowTop < 0) { // off-screen to the top 6581 windowTop = 10; 6582 window.css('top',windowTop); 6583 } 6584 if (windowLeft < 0) { // off-screen to the left 6585 windowLeft = 10; 6586 window.css('left',windowLeft); 6587 } 6588 if (windowRight > width) { // off-screen to the right 6589 windowLeft = (width - 20) - windowWidth; 6590 window.css('left',windowLeft); 6591 } 6592 if (windowBottom > height) { // off-screen to the bottom 6593 windowTop = (height - 10) - windowHeight; 6594 window.css('top',windowTop); 6595 } 6596 }; 6597 6598 AblePlayer.prototype.updateZIndex = function(which) { 6599 6600 // update z-index of 'transcript' or 'sign', relative to each other 6601 // direction is always 'up' (i.e., move window to top) 6602 // windows come to the top when the user clicks on them 6603 var defHighZ, defLowZ, transcriptZ, signZ, newHighZ, newLowZ; 6604 6605 // set the default z-indexes, as defined in ableplayer.css 6606 defHighZ = 8000; // by default, assigned to the sign window 6607 defLowZ = 7000; // by default, assigned to the transcript area 6608 6609 // Previously collected the highest z-index. Removed in 4.6. 6610 // If something on the page has a higher z-index than the transcript or sign window, do we care? 6611 // Excluding it here assumes "No". Our immediate concern is with the relationship between our own components. 6612 // If we elevate our z-indexes so our content is on top, we run the risk of starting a z-index war. 6613 6614 if (typeof this.$transcriptArea === 'undefined' || typeof this.$signWindow === 'undefined' ) { 6615 // at least one of the windows doesn't exist, so there's no conflict 6616 // since z-index may have been stored to a cookie on another page, need to restore default 6617 if (typeof this.$transcriptArea !== 'undefined') { 6618 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 6619 if (transcriptZ > defLowZ) { 6620 // restore to the default 6621 this.$transcriptArea.css('z-index',defLowZ); 6622 } 6623 } else if (typeof this.$signWindow !== 'undefined') { 6624 signZ = parseInt(this.$signWindow.css('z-index')); 6625 if (signZ > defHighZ) { 6626 // restore to the default 6627 this.$signWindow.css('z-index',defHighZ); 6628 } 6629 } 6630 return false; 6631 } 6632 6633 // both windows exist 6634 6635 // get current values 6636 transcriptZ = parseInt(this.$transcriptArea.css('z-index')); 6637 signZ = parseInt(this.$signWindow.css('z-index')); 6638 6639 if (transcriptZ === signZ) { 6640 // the two windows are equal; restore defaults (the target window will be on top) 6641 newHighZ = defHighZ; 6642 newLowZ = defLowZ; 6643 } else if (transcriptZ > signZ) { 6644 if (which === 'transcript') { 6645 // transcript is already on top; nothing to do 6646 return false; 6647 } else { 6648 // swap z's 6649 newHighZ = transcriptZ; 6650 newLowZ = signZ; 6651 } 6652 } else { // signZ is greater 6653 if (which === 'sign') { 6654 // sign is already on top; nothing to do 6655 return false; 6656 } else { 6657 newHighZ = signZ; 6658 newLowZ = transcriptZ; 6659 } 6660 } 6661 // now assign the new values 6662 if (which === 'transcript') { 6663 this.$transcriptArea.css('z-index',newHighZ); 6664 this.$signWindow.css('z-index',newLowZ); 6665 } else if (which === 'sign') { 6666 this.$signWindow.css('z-index',newHighZ); 6667 this.$transcriptArea.css('z-index',newLowZ); 6668 } 6669 }; 6670 6671 AblePlayer.prototype.syncTrackLanguages = function (source, language) { 6672 6673 // this function is called when the player is built (source == 'init') 6674 // and again when user changes the language of either 'captions' or 'transcript' 6675 // It syncs the languages of chapters, descriptions, and metadata tracks 6676 // NOTE: Caption and transcript languages are somewhat independent from one another 6677 // If a user changes the caption language, the transcript follows 6678 // However, if a user changes the transcript language, this only affects the transcript 6679 // This was a group decision based on the belief that users may want a transcript 6680 // that is in a different language than the captions 6681 6682 var i, captions, descriptions, chapters, meta; 6683 6684 // Captions 6685 for (i = 0; i < this.captions.length; i++) { 6686 if (this.captions[i].language === language) { 6687 captions = this.captions[i]; 6688 } 6689 } 6690 // Chapters 6691 for (i = 0; i < this.chapters.length; i++) { 6692 if (this.chapters[i].language === language) { 6693 chapters = this.chapters[i]; 6694 } 6695 } 6696 // Descriptions 6697 for (i = 0; i < this.descriptions.length; i++) { 6698 if (this.descriptions[i].language === language) { 6699 descriptions = this.descriptions[i]; 6700 } 6701 } 6702 // Metadata 6703 for (i = 0; i < this.meta.length; i++) { 6704 if (this.meta[i].language === language) { 6705 meta = this.meta[i]; 6706 } 6707 } 6708 // regardless of source... 6709 this.transcriptLang = language; 6710 if (source === 'init' || source === 'captions') { 6711 this.captionLang = language; 6712 this.selectedCaptions = captions; 6713 this.selectedChapters = chapters; 6714 this.selectedDescriptions = descriptions; 6715 this.selectedMeta = meta; 6716 this.transcriptCaptions = captions; 6717 this.transcriptChapters = chapters; 6718 this.transcriptDescriptions = descriptions; 6719 this.updateChaptersList(); 6720 // the following was commented out in Oct/Nov 2018. 6721 // chapters popup is setup automatically when setupPopups() is called later with no param 6722 // not sure why it was included here. 6723 // this.setupPopups('chapters'); 6724 } else if (source === 'transcript') { 6725 this.transcriptCaptions = captions; 6726 this.transcriptChapters = chapters; 6727 this.transcriptDescriptions = descriptions; 6728 } 6729 if (this.selectedDescriptions) { 6730 // updating description voice to match new description language 6731 this.setDescriptionVoice(); 6732 if (this.$sampleDescDiv) { 6733 if (this.sampleText) { 6734 for (i = 0; i < this.sampleText.length; i++) { 6735 if (this.sampleText[i].lang === this.selectedDescriptions.language) { 6736 this.currentSampleText = this.sampleText[i]['text']; 6737 this.$sampleDescDiv.html(this.currentSampleText); 6738 } 6739 } 6740 } 6741 } 6742 } 6743 this.updateTranscript(); 6744 }; 6745 6746 } 6747 6748 function addDescriptionFunctions(AblePlayer) { 6749 AblePlayer.prototype.initDescription = function() { 6750 6751 // set default mode for delivering description (open vs closed) 6752 // based on availability and user preference 6753 6754 // called when player is being built, or when a user 6755 // toggles the Description button or changes a description-related preference 6756 6757 // The following variables are applicable to delivery of description: 6758 // defaultStateDescriptions == 'on' or 'off', defined by website owner (overridden by prefDesc) 6759 // prefDesc == 1 if user wants description (i.e., Description button is on); else 0 6760 // prefDescPause == 1 to pause video when description starts; else 0 6761 // prefDescVisible == 1 to visibly show text-based description area; else 0 6762 // prefDescMethod == either 'video' or 'text' (as of v4.0.10, prefDescMethod is always 'video') 6763 // descMethod is the format actually used ('video' or 'text'), regardless of user preference 6764 // hasOpenDesc == true if a described version of video is available via data-desc-src attribute 6765 // hasClosedDesc == true if a description text track is available 6766 // descOn == true if description of either type is on 6767 // readDescriptionsAloud == true if text description is to be announced audibly; otherwise false 6768 // descReader == either 'browser' or 'screenreader' 6769 6770 var deferred, promise; 6771 6772 deferred = new this.defer(); 6773 promise = deferred.promise(); 6774 6775 if (this.mediaType === 'audio') { 6776 deferred.resolve(); 6777 } 6778 6779 // check to see if there's an open-described version of this video 6780 // checks only the first source since if a described version is provided, 6781 // it must be provided for all sources 6782 this.descFile = this.$sources.first().attr('data-desc-src'); 6783 if (typeof this.descFile !== 'undefined') { 6784 this.hasOpenDesc = true; 6785 } else { 6786 // there's no open-described version via data-desc-src, 6787 // but what about data-youtube-desc-src or data-vimeo-desc-src? 6788 // if these exist, they would have been defined earlier 6789 this.hasOpenDesc = (this.youTubeDescId || this.vimeoDescId) ? true : false; 6790 } 6791 6792 // Set this.descMethod based on media availability & user preferences 6793 // no description is available for this video 6794 this.descMethod = null; 6795 if (this.hasOpenDesc && this.hasClosedDesc) { 6796 // both formats are available. User gets their preference. 6797 this.descMethod = (this.prefDescMethod) ? this.prefDescMethod : 'video'; 6798 } else if (this.hasOpenDesc) { 6799 this.descMethod = 'video'; 6800 } else if (this.hasClosedDesc) { 6801 this.descMethod = 'text'; 6802 } 6803 6804 // Set the default state of descriptions 6805 this.descOn = false; 6806 if (this.descMethod) { 6807 if (this.prefDesc === 1) { 6808 this.descOn = true; 6809 } else if (this.prefDesc === 0) { 6810 this.descOn = false; 6811 } else { 6812 // user has no prefs. Use default state. 6813 this.descOn = (this.defaultStateDescriptions === 1) ? true : false; 6814 } 6815 } 6816 6817 // If a video has text audio descriptions, inject the description area. 6818 if (typeof this.$descDiv === 'undefined' && this.hasClosedDesc ) { 6819 this.injectTextDescriptionArea(); 6820 } 6821 6822 if (this.descOn) { 6823 if (this.descMethod === 'video' && !this.usingDescribedVersion() ) { 6824 // switched from non-described to described version 6825 this.swapDescription(); 6826 } 6827 if (this.hasClosedDesc) { 6828 if (this.prefDescVisible) { 6829 // make description text visible 6830 if (typeof this.$descDiv !== 'undefined') { 6831 this.$descDiv.show(); 6832 this.$descDiv.removeClass('able-offscreen'); 6833 } 6834 } else { 6835 // keep it visible to screen readers, but hide it visibly 6836 if (typeof this.$descDiv !== 'undefined') { 6837 this.$descDiv.addClass('able-offscreen'); 6838 } 6839 } 6840 } 6841 } else { // description is off. 6842 if (this.descMethod === 'video') { // user has turned off described version of video 6843 if (this.usingDescribedVersion()) { 6844 // user was using the described verion. Swap for non-described version 6845 this.swapDescription(); 6846 } 6847 } else if (this.descMethod === 'text') { // user has turned off text description 6848 // hide description div from everyone, including screen reader users 6849 if (typeof this.$descDiv !== 'undefined') { 6850 this.$descDiv.hide(); 6851 this.$descDiv.removeClass('able-offscreen'); 6852 } 6853 } 6854 } 6855 deferred.resolve(); 6856 return promise; 6857 }; 6858 6859 AblePlayer.prototype.usingDescribedVersion = function () { 6860 6861 // Returns true if currently using audio description, false otherwise. 6862 6863 if (this.player === 'youtube') { 6864 return (this.activeYouTubeId === this.youTubeDescId); 6865 } else if (this.player === 'vimeo') { 6866 return (this.activeVimeoId === this.vimeoDescId); 6867 } else { 6868 return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src')); 6869 } 6870 }; 6871 6872 /** 6873 * Initializes speech synthesis capabilities for the player. 6874 * This method addresses browser and OS limitations that require user interaction 6875 * before speech synthesis functions become available. It handles different contexts 6876 * like initialization, playing media, accessing preferences, or announcing descriptions. 6877 * @param {string} context - The context in which the function is called ('init', 'play', 'prefs', 'desc'). 6878 */ 6879 AblePlayer.prototype.initSpeech = function (context) { 6880 var thisObj = this; 6881 6882 // Function to attempt enabling speech synthesis 6883 function attemptEnableSpeech() { 6884 var greeting = new SpeechSynthesisUtterance("\x20"); 6885 greeting.onend = function () { 6886 thisObj.getBrowserVoices(); 6887 if ( 6888 (Array.isArray(thisObj.descVoices) && thisObj.descVoices.length) || 6889 context !== "init" 6890 ) { 6891 thisObj.speechEnabled = true; 6892 } 6893 }; 6894 thisObj.synth.speak(greeting); 6895 } 6896 6897 // Function to handle the initial click and enable speech synthesis 6898 function handleInitialClick() { 6899 attemptEnableSpeech(); 6900 // Once the utterance starts, remove this specific click event listener 6901 // Ensures the event handler only runs once and cleans up after itself 6902 $(document).off("click", handleInitialClick); 6903 } 6904 6905 if (this.speechEnabled === null) { 6906 if (window.speechSynthesis) { 6907 // Browser supports speech synthesis 6908 this.synth = window.speechSynthesis; 6909 this.synth.cancel(); // Cancel any ongoing speech synthesis 6910 6911 if (context === "init") { 6912 // Attempt to enable speech synthesis directly for browsers that don't require a click 6913 attemptEnableSpeech(); 6914 // For initial setup, require a user click to enable speech synthesis 6915 // Scoping to a particular handler to avoid conflicts with other click events 6916 $(document).on("click", handleInitialClick); 6917 } else { 6918 // For other contexts, attempt to enable speech synthesis directly 6919 attemptEnableSpeech(); 6920 } 6921 } else { 6922 // Browser does not support speech synthesis 6923 this.speechEnabled = false; 6924 } 6925 } 6926 }; 6927 6928 AblePlayer.prototype.getBrowserVoices = function () { 6929 6930 // define this.descVoices array 6931 // includes only languages that match the language of the captions or player 6932 6933 var voices, trackLangs, voiceLang, preferredLang; 6934 6935 preferredLang = (this.captionLang) ? this.captionLang.substring(0,2).toLowerCase() : this.lang.substring(0,2).toLowerCase(); 6936 6937 this.descVoices = []; 6938 voices = this.synth.getVoices(); 6939 trackLangs = this.getTrackLangs(); 6940 if (voices.length > 0) { 6941 this.descVoices = []; 6942 // available languages are identified with local suffixes (e.g., en-US) 6943 for (var i=0; i<voices.length; i++) { 6944 // match only the first 2 characters of the lang code 6945 voiceLang = voices[i].lang.substring(0,2).toLowerCase(); 6946 if (voiceLang === preferredLang && (trackLangs.indexOf(voiceLang) !== -1)) { 6947 // this voice matches preferredLang 6948 // AND there's a matching description track in this language 6949 // Add this voice to final array 6950 this.descVoices.push(voices[i]); 6951 } 6952 } 6953 if (!this.descVoices.length) { 6954 // no voices available in the default language(s) 6955 // just use all voices, regardless of language 6956 this.descVoices = voices; 6957 } 6958 } 6959 return false; 6960 }; 6961 6962 AblePlayer.prototype.getTrackLangs = function () { 6963 6964 // returns an array of languages (from srclang atttributes) 6965 // in which there are description tracks 6966 // use only first two characters of the lang code 6967 var descLangs = []; 6968 if (this.tracks) { 6969 for (var i=0; i < this.tracks.length; i++) { 6970 if (this.tracks[i].kind === 'descriptions' || this.tracks[i].kind === 'captions' ) { 6971 descLangs.push(this.tracks[i].language.substring(0,2).toLowerCase()); 6972 } 6973 } 6974 } 6975 return descLangs; 6976 }; 6977 6978 AblePlayer.prototype.setDescriptionVoice = function () { 6979 6980 // set description voice on player init, or when user changes caption language 6981 // Voice is determined in the following order of precedence: 6982 // 1. User's preferred voice for this language, saved in preferences 6983 // 2. The first available voice in the array of available voices for this browser in this language 6984 6985 var preferences, voices, prefDescVoice, descVoice, descLang, prefVoiceFound; 6986 preferences = this.getPref(); 6987 prefDescVoice = (typeof preferences.voices !== 'undefined') ? this.getPrefVoice() : null; 6988 6989 this.getBrowserVoices(); 6990 this.rebuildVoicePrefsForm( '_prefDescVoice' ); 6991 this.rebuildVoicePrefsForm( '_prefCaptionsVoice' ); 6992 6993 if (this.selectedDescriptions) { 6994 descLang = this.selectedDescriptions.language; 6995 } else if (this.captionLang) { 6996 descLang = this.captionLang; 6997 } else { 6998 descLang = this.lang; 6999 } 7000 7001 if (this.synth) { 7002 voices = this.synth.getVoices(); 7003 if (voices.length > 0) { 7004 if (prefDescVoice) { 7005 // select the language that matches prefDescVoice, if it's available 7006 prefVoiceFound = false; 7007 for (var i=0; i<voices.length; i++) { 7008 // first, be sure voice is the correct language 7009 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7010 if (voices[i].name === prefDescVoice) { 7011 descVoice = voices[i].name; 7012 prefVoiceFound = true; 7013 break; 7014 } 7015 } 7016 } 7017 } 7018 if (!prefVoiceFound) { 7019 // select the first language that matches the first 2 characters of the lang code 7020 for (i=0; i<voices.length; i++) { 7021 if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) { 7022 descVoice = voices[i].name; 7023 break; 7024 } 7025 } 7026 } 7027 // make this the user's current preferred voice 7028 this.prefDescVoice = descVoice; 7029 this.prefDescVoiceLang = descLang; 7030 // select this voice in the Description Prefs dialog 7031 if (this.$voiceSelectField) { 7032 this.$voiceSelectField.val(this.prefDescVoice); 7033 } 7034 this.updatePreferences('voice'); 7035 } 7036 } 7037 }; 7038 7039 AblePlayer.prototype.swapDescription = function() { 7040 7041 // swap described and non-described source media, depending on which is playing 7042 // this function is only called in two circumstances: 7043 // 1. Swapping to described version when initializing player (based on user prefs & availability) 7044 // (playerCreated == false) 7045 // 2. User is toggling description 7046 // (playerCreated == true) 7047 7048 var thisObj, i, origSrc, descSrc; 7049 7050 thisObj = this; 7051 7052 // We are no longer loading the previous media source 7053 // Only now, as a new source is requested, is it safe to reset this var 7054 // It will be reset to true when media.load() is called 7055 this.loadingMedia = false; 7056 7057 // get element that has focus at the time swap is initiated 7058 // after player is rebuilt, focus will return to that same element 7059 // (if it exists) 7060 this.$focusedElement = $(':focus'); 7061 this.activeMedia = this.mediaId; 7062 7063 // get current time of current source, and attempt to start new video at the same time 7064 // whether this is possible will be determined after the new media source has loaded 7065 // see onMediaNewSourceLoad() 7066 if (this.elapsed > 0) { 7067 this.swapTime = this.elapsed; 7068 } else { 7069 this.swapTime = 0; 7070 } 7071 if (this.duration > 0) { 7072 this.prevDuration = this.duration; 7073 } 7074 7075 // Capture current playback state, so media can resume after source is swapped 7076 if (!this.okToPlay) { 7077 this.okToPlay = this.playing; 7078 } 7079 7080 if (this.descOn) { 7081 this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) ); 7082 } else { 7083 this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) ); 7084 } 7085 7086 if (this.player === 'html5') { 7087 7088 this.swappingSrc = true; 7089 this.paused = true; 7090 7091 if (this.usingDescribedVersion()) { 7092 // the described version is currently playing. Swap to non-described 7093 for (i=0; i < this.$sources.length; i++) { 7094 // for all <source> elements, replace src with data-orig-src 7095 origSrc = purify.sanitize( this.$sources[i].getAttribute('data-orig-src') ); 7096 if (origSrc) { 7097 this.$sources[i].setAttribute('src',origSrc); 7098 } 7099 } 7100 } else { 7101 // the non-described version is currently playing. Swap to described. 7102 for (i=0; i < this.$sources.length; i++) { 7103 // for all <source> elements, replace src with data-desc-src (if one exists) 7104 // then store original source in a new data-orig-src attribute 7105 origSrc = purify.sanitize( this.$sources[i].getAttribute('src') ); 7106 descSrc = purify.sanitize( this.$sources[i].getAttribute('data-desc-src') ); 7107 if (descSrc) { 7108 this.$sources[i].setAttribute('src',descSrc); 7109 this.$sources[i].setAttribute('data-orig-src',origSrc); 7110 } 7111 } 7112 } 7113 7114 if (this.recreatingPlayer) { 7115 // stopgap to prevent multiple firings of recreatePlayer() 7116 return; 7117 } 7118 if (this.playerCreated) { 7119 // delete old player, then recreate it with new source & tracks 7120 this.deletePlayer('swap-desc-html'); 7121 this.recreatePlayer().then(function() { 7122 if (!thisObj.loadingMedia) { 7123 thisObj.media.load(); 7124 thisObj.loadingMedia = true; 7125 } 7126 }); 7127 } 7128 } else if (this.player === 'youtube') { 7129 7130 // if the described version is currently playing, swap to non-described 7131 this.activeYouTubeId = (this.usingDescribedVersion()) ? this.youTubeId : this.youTubeDescId; 7132 7133 if (typeof this.youTubePlayer !== 'undefined') { 7134 thisObj.swappingSrc = true; 7135 if (thisObj.playing) { 7136 // loadVideoById() loads and immediately plays the new video at swapTime 7137 thisObj.youTubePlayer.loadVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 7138 } else { 7139 // cueVideoById() loads the new video and seeks to swapTime, but does not play 7140 thisObj.youTubePlayer.cueVideoById(thisObj.activeYouTubeId,thisObj.swapTime); 7141 } 7142 } 7143 if (this.playerCreated) { 7144 this.deletePlayer('swap-desc-youtube'); 7145 } 7146 // player needs to be recreated with new source 7147 if (this.recreatingPlayer) { 7148 // stopgap to prevent multiple firings of recreatePlayer() 7149 return; 7150 } 7151 this.recreatePlayer().then(function() { 7152 // nothing to do here 7153 // next steps occur when youtube onReady event fires 7154 // see youtube.js > finalizeYoutubeInit() 7155 }); 7156 } else if (this.player === 'vimeo') { 7157 if (this.usingDescribedVersion()) { 7158 // the described version is currently playing. Swap to non-described 7159 this.activeVimeoId = this.vimeoId; 7160 this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) ); 7161 } else { 7162 // the non-described version is currently playing. Swap to described. 7163 this.activeVimeoId = this.vimeoDescId; 7164 this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) ); 7165 } 7166 if (this.playerCreated) { 7167 this.deletePlayer('swap-desc-vimeo'); 7168 } 7169 // player needs to be recreated with new source 7170 if (this.recreatingPlayer) { 7171 // stopgap to prevent multiple firings of recreatePlayer() 7172 return; 7173 } 7174 this.recreatePlayer().then(function() { 7175 // load the new video source 7176 thisObj.vimeoPlayer.loadVideo(thisObj.activeVimeoId).then(function() { 7177 if (thisObj.playing) { 7178 // video was playing when user requested an alternative version 7179 // seek to swapTime and continue playback (playback happens automatically) 7180 thisObj.vimeoPlayer.setCurrentTime(thisObj.swapTime); 7181 } else { 7182 // Vimeo autostarts immediately after video loads 7183 // The "Described" button should not trigger playback, so stop this before the user notices. 7184 thisObj.vimeoPlayer.pause(); 7185 } 7186 }); 7187 }); 7188 } 7189 }; 7190 7191 AblePlayer.prototype.showDescription = function(now) { 7192 if (!this.playing || !this.hasClosedDesc || this.swappingSrc || !this.descOn || ( this.descMethod === 'video' && !this.prefDescVisible ) ) { 7193 return; 7194 } 7195 7196 var cues, d, thisDescription, descText; 7197 7198 var flattenComponentForDescription = function (component) { 7199 var result = []; 7200 if (component.type === 'string') { 7201 result.push(component.value); 7202 } else { 7203 for (var i = 0; i < component.children.length; i++) { 7204 result.push(flattenComponentForDescription(component.children[i])); 7205 } 7206 } 7207 return result.join(''); 7208 }; 7209 cues = []; 7210 if (this.selectedDescriptions) { 7211 cues = this.selectedDescriptions.cues; 7212 } else if (this.descriptions.length >= 1) { 7213 cues = this.descriptions[0].cues; 7214 } 7215 for (d = 0; d < cues.length; d++) { 7216 if ((cues[d].start <= now) && (cues[d].end > now)) { 7217 thisDescription = d; 7218 break; 7219 } 7220 } 7221 if (typeof thisDescription !== 'undefined') { 7222 if (this.currentDescription !== thisDescription) { 7223 // temporarily remove aria-live from $status to prevent description from being interrupted 7224 this.$status.removeAttr('aria-live'); 7225 descText = flattenComponentForDescription(cues[thisDescription].components); 7226 if (this.descReader === 'screenreader') { 7227 // load the new description into the container div for screen readers to read 7228 this.$descDiv.html(descText); 7229 } else if (this.speechEnabled) { 7230 if ( 'video' !== this.descMethod ) { 7231 // use browser's built-in speech synthesis 7232 this.announceText('description',descText); 7233 } 7234 if (this.prefDescVisible) { 7235 // write description to the screen for sighted users 7236 // but remove ARIA attributes since it isn't intended to be read by screen readers 7237 this.$descDiv.html(descText).removeAttr('aria-live aria-atomic'); 7238 } 7239 } else { 7240 // browser does not support speech synthesis 7241 // load the new description into the container div for screen readers to read 7242 this.$descDiv.html(descText); 7243 } 7244 // Only pause video if not using a described video. 7245 if (this.prefDescPause && this.descMethod === 'text') { 7246 this.pauseMedia(); 7247 this.pausedForDescription = true; 7248 } 7249 this.currentDescription = thisDescription; 7250 } 7251 } else { 7252 this.$descDiv.html(''); 7253 this.currentDescription = -1; 7254 // restore aria-live to $status 7255 this.$status.attr('aria-live','polite'); 7256 } 7257 }; 7258 7259 AblePlayer.prototype.syncSpeechToPlaybackRate = function(rate) { 7260 7261 // called when user changed playback rate 7262 // adjust rate of audio description to match 7263 var speechRate; 7264 7265 if (rate === 0.5) { 7266 speechRate = 0.7; // option 1 in prefs menu 7267 } else if (rate === 0.75) { 7268 speechRate = 0.8; // option 2 in prefs menu 7269 } else if (rate === 1.0) { 7270 speechRate = 1; // option 4 in prefs menu (normal speech, default) 7271 } else if (rate === 1.25) { 7272 speechRate = 1.1; // option 5 in prefs menu 7273 } else if (rate === 1.5) { 7274 speechRate = 1.2; // option 6 in prefs menu 7275 } else if (rate === 1.75) { 7276 speechRate = 1.5; // option 7 in prefs menu 7277 } else if (rate === 2.0) { 7278 speechRate = 2; // option 8 in prefs menu (fast) 7279 } else if (rate === 2.25) { 7280 speechRate = 2.5; // option 9 in prefs menu (very fast) 7281 } else if (rate >= 2.5) { 7282 speechRate = 3; // option 10 in prefs menu (super fast) 7283 } 7284 this.prefDescRate = speechRate; 7285 }; 7286 7287 AblePlayer.prototype.announceText = function(context, text, rate) { 7288 7289 // this function announces text using speech synthesis 7290 // it's only called if already determined that browser supports speech synthesis 7291 // context is either: 7292 // 'description' - actual description text extracted from WebVTT file 7293 // 'sample' - called when user changes a setting in Description Prefs dialog 7294 // 'caption' - called when announcing a caption. 7295 7296 var thisObj, voiceName, i, voice, pitch, volume, utterance, 7297 timeElapsed; 7298 7299 thisObj = this; 7300 7301 if (!this.speechEnabled) { 7302 // voices array failed to load the first time. Try again 7303 this.initSpeech('desc'); 7304 } 7305 7306 if (context === 'sample') { 7307 // get settings from form 7308 voiceName = $('#' + this.mediaId + '_prefDescVoice').val(); 7309 pitch = $('#' + this.mediaId + '_prefDescPitch').val(); 7310 rate = $('#' + this.mediaId + '_prefDescRate').val(); 7311 volume = $('#' + this.mediaId + '_prefDescVolume').val(); 7312 } else if ( context === 'captionSample' ) { 7313 // get settings from form 7314 voiceName = $('#' + this.mediaId + '_prefCaptionsVoice').val(); 7315 pitch = $('#' + this.mediaId + '_prefCaptionsPitch').val(); 7316 rate = $('#' + this.mediaId + '_prefCaptionsRate').val(); 7317 volume = $('#' + this.mediaId + '_prefCaptionsVolume').val(); 7318 } else if ( context === 'description' ) { 7319 // get settings from global prefs 7320 voiceName = this.prefDescVoice; 7321 pitch = this.prefDescPitch; 7322 rate = this.prefDescRate; 7323 volume = this.prefDescVolume; 7324 } else { 7325 // get settings from global prefs 7326 voiceName = this.prefCaptionsVoice; 7327 pitch = this.prefCaptionsPitch; 7328 rate = ( rate < this.prefCaptionsRate ) ? this.prefCaptionsRate : rate; 7329 volume = this.prefCaptionsVolume; 7330 } 7331 7332 // get the voice associated with the user's chosen voice name 7333 if (this.descVoices) { 7334 if (this.descVoices.length > 0) { 7335 if (voiceName) { 7336 // get the voice that matches user's preferred voiceName 7337 for (i = 0; i < this.descVoices.length; i++) { 7338 if (this.descVoices[i].name == voiceName) { 7339 voice = this.descVoices[i]; 7340 break; 7341 } 7342 } 7343 } 7344 if (typeof voice === 'undefined') { 7345 // no matching voice was found 7346 // use the first voice in the array 7347 voice = this.descVoices[0]; 7348 } 7349 } 7350 } else { 7351 voice = null; 7352 } 7353 utterance = new SpeechSynthesisUtterance(); 7354 if (voice) { 7355 utterance.voice = voice; 7356 } 7357 utterance.voiceURI = 'native'; 7358 utterance.volume = volume; 7359 utterance.rate = rate; 7360 utterance.pitch = pitch; 7361 utterance.text = text; 7362 // TODO: Consider the best language for the utterance: 7363 // language of the web page? (this.lang) 7364 // language of the WebVTT description track? 7365 // language of the user's chosen voice? 7366 // If there's a mismatch between any of these, the description will likely be unintelligible 7367 utterance.lang = this.lang; 7368 utterance.onstart = function(e) { 7369 // utterance has started 7370 }; 7371 utterance.onpause = function(e) { 7372 // utterance has paused 7373 }; 7374 utterance.onend = function(e) { 7375 // utterance has ended 7376 if ( 'description' === context ) { 7377 this.speakingDescription = false; 7378 } else if ( 'caption' === context ) { 7379 this.speakingCaption = false; 7380 } 7381 timeElapsed = e.elapsedTime; 7382 // As of Firefox 95, e.elapsedTime is expressed in seconds 7383 // Other browsers (tested in Chrome & Edge) express this in milliseconds 7384 // Assume no utterance will require over 100 seconds to express... 7385 // If a large value, time is likely expressed in milliseconds. 7386 (timeElapsed > 100) ? (e.elapsedTime/1000).toFixed(2) : (e.elapsedTime).toFixed(2); 7387 7388 if (this.debug) ; 7389 if (context === 'description') { 7390 if (thisObj.prefDescPause) { 7391 if (thisObj.pausedForDescription) { 7392 thisObj.playMedia(); 7393 this.pausedForDescription = false; 7394 } 7395 } 7396 } 7397 }; 7398 utterance.onerror = function(e) { 7399 // handle error 7400 }; 7401 if (this.synth.paused) { 7402 this.synth.resume(); 7403 } 7404 this.synth.speak(utterance); 7405 if ( 'description' === context ) { 7406 this.speakingDescription = true; 7407 } else if ( 'caption' === context ) { 7408 this.speakingCaption = true; 7409 } 7410 }; 7411 7412 } 7413 7414 // Outdented for a simpler diff 7415 var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"; 7416 7417 // Based on the incredible accessible modal dialog. 7418 function AccessibleDialog( modalDiv, $returnElement, title, closeButtonLabel) { 7419 7420 this.title = title; 7421 this.closeButtonLabel = closeButtonLabel; 7422 this.focusedElementBeforeModal = $returnElement; 7423 this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString(); 7424 var thisObj = this; 7425 var modal = modalDiv; 7426 this.modal = modal; 7427 7428 modal.addClass('able-modal-dialog'); 7429 7430 var closeButton = $('<button>',{ 7431 'class': 'modalCloseButton', 7432 'title': thisObj.closeButtonLabel, 7433 'aria-label': thisObj.closeButtonLabel 7434 }).text('Ã'); 7435 closeButton.on( 'keydown', function (e) { 7436 if (e.key === ' ') { 7437 thisObj.hide(); 7438 } 7439 }).on( 'click', function () { 7440 thisObj.hide(); 7441 }); 7442 7443 var titleH1 = $('<h1></h1>'); 7444 titleH1.attr('id', 'modalTitle-' + this.baseId); 7445 titleH1.text(title); 7446 this.titleH1 = titleH1; 7447 7448 modal.attr({ 7449 'aria-labelledby': 'modalTitle-' + this.baseId, 7450 }); 7451 var modalHeader = $( '<div>', { 7452 'class': 'able-modal-header' 7453 }); 7454 modalHeader.prepend(titleH1); 7455 modalHeader.prepend(closeButton); 7456 modal.prepend(modalHeader); 7457 7458 modal.attr({ 7459 'aria-hidden': 'true', 7460 'role': 'dialog', 7461 'aria-modal': 'true' 7462 }); 7463 7464 modal.on( 'keydown', function (e) { 7465 if (e.key === 'Escape') { 7466 thisObj.hide(); 7467 e.preventDefault(); 7468 } else if (e.key === 'Tab') { 7469 // Manually loop tab navigation inside the modal. 7470 var parts = modal.find('*'); 7471 var focusable = parts.filter(focusableElementsSelector).filter(':visible'); 7472 7473 if (focusable.length === 0) { 7474 return; 7475 } 7476 7477 var focused = $(':focus'); 7478 var currentIndex = focusable.index(focused); 7479 if (e.shiftKey) { 7480 // If backwards from first element, go to last. 7481 if (currentIndex === 0) { 7482 focusable.get(focusable.length - 1).trigger('focus'); 7483 e.preventDefault(); 7484 } 7485 } else { 7486 if (currentIndex === focusable.length - 1) { 7487 focusable.get(0).trigger('focus'); 7488 e.preventDefault(); 7489 } 7490 } 7491 } 7492 e.stopPropagation(); 7493 }); 7494 7495 if ( $( 'body' ).hasClass( 'able-modal-active' ) ) { 7496 $( 'body > *') .not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert'); 7497 $( 'body' ).removeClass( 'able-modal-active' ); 7498 } 7499 } 7500 AccessibleDialog.prototype.show = function () { 7501 if (!this.overlay) { 7502 // Generate overlay. 7503 var overlay = $('<div></div>').attr({ 7504 'class': 'able-modal-overlay', 7505 'tabindex': '-1' 7506 }); 7507 this.overlay = overlay; 7508 $('body').append(overlay); 7509 7510 // Keep from moving focus out of dialog when clicking outside of it. 7511 overlay.on('mousedown.accessibleModal', function (e) { 7512 e.preventDefault(); 7513 thisObj.hide(); 7514 }); 7515 } 7516 7517 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('inert', true); 7518 $( 'body' ).addClass( 'able-modal-active' ); 7519 7520 this.overlay.css('display', 'block'); 7521 this.modal.css('display', 'block'); 7522 this.modal.attr({ 7523 'aria-hidden': 'false', 7524 'tabindex': '-1' 7525 }); 7526 7527 var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible'); 7528 if (focusable.length === 0) { 7529 this.focusedElementBeforeModal.blur(); 7530 } 7531 var thisObj = this; 7532 setTimeout(function () { 7533 // set focus on the first focusable element 7534 thisObj.modal.find('button.modalCloseButton').first().trigger('focus'); 7535 }, 300); 7536 }; 7537 7538 AccessibleDialog.prototype.hide = function () { 7539 if (this.overlay) { 7540 this.overlay.css('display', 'none'); 7541 } 7542 this.modal.css('display', 'none'); 7543 this.modal.attr('aria-hidden', 'true'); 7544 $('body > *').not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert'); 7545 $( 'body' ).removeClass( 'able-modal-active' ); 7546 7547 this.focusedElementBeforeModal.trigger('focus'); 7548 }; 7549 7550 AccessibleDialog.prototype.getInputs = function () { 7551 7552 // return an array of input elements within this dialog 7553 if (this.modal) { 7554 var inputs = this.modal.find('input'); 7555 return inputs; 7556 } 7557 return false; 7558 }; 7559 7560 function addDragdropFunctions(AblePlayer) { 7561 AblePlayer.prototype.initDragDrop = function ( which ) { 7562 7563 // supported values of which: 'sign', 'transcript' 7564 7565 // NOTE: "Drag and Drop" for Able Player is a metaphor only!!! 7566 // HTML5 Drag & Drop API enables moving elements to new locations in the DOM 7567 // Thats not our purpose; we're simply changing the visible position on-screen 7568 // Therefore, the drag & drop interface was overhauled in v2.3.41 to simply 7569 // use mouse (and keyboard) events to change CSS positioning properties 7570 7571 // There are nevertheless lessons to be learned from Drag & Drop about accessibility: 7572 // http://dev.opera.com/articles/accessible-drag-and-drop/ 7573 7574 var thisObj, $window, $toolbar, windowName, $dragHandle, $resizeHandle, $resizeSvg, 7575 i, x1, y1, x2, y2, $resizeLine, resizeZIndex; 7576 7577 thisObj = this; 7578 7579 if (which === 'transcript') { 7580 $window = this.$transcriptArea; 7581 windowName = 'transcript-window'; 7582 $toolbar = this.$transcriptToolbar; 7583 $toolbar.attr( 'aria-label', this.translate( 'transcriptControls', 'Transcript Window Controls' ) ); 7584 } else if (which === 'sign') { 7585 $window = this.$signWindow; 7586 windowName = 'sign-window'; 7587 $toolbar = this.$signToolbar; 7588 $toolbar.attr( 'aria-label', this.translate( 'signControls', 'Sign Language Window Controls' ) ); 7589 } 7590 7591 // add class to trigger change in cursor on hover 7592 $toolbar.addClass('able-draggable'); 7593 $toolbar.attr( 'role', 'application' ); 7594 7595 $dragHandle = $('<div>',{ 7596 'class': 'able-drag-handle' 7597 }); 7598 7599 $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>'); 7600 // add resize handle selector to bottom right corner 7601 $resizeHandle = $('<div>',{ 7602 'class': 'able-resizable' 7603 }); 7604 7605 // fill it with three parallel diagonal lines 7606 $resizeSvg = $('<svg>').attr({ 7607 'width': '100%', 7608 'height': '100%', 7609 'viewBox': '0 0 100 100', 7610 'preserveAspectRatio': 'none' 7611 }); 7612 for (i=1; i<=3; i++) { 7613 if (i === 1) { 7614 x1 = '100'; 7615 y1 = '0'; 7616 x2 = '0'; 7617 y2 = '100'; 7618 } else if (i === 2) { 7619 x1 = '33'; 7620 y1 = '100'; 7621 x2 = '100'; 7622 y2 = '33'; 7623 } else if (i === 3) { 7624 x1 = '67'; 7625 y1 = '100'; 7626 x2 = '100'; 7627 y2 = '67'; 7628 } 7629 $resizeLine = $('<line>').attr({ 7630 'x1': x1, 7631 'y1': y1, 7632 'x2': x2, 7633 'y2': y2, 7634 'vector-effect': 'non-scaling-stroke' 7635 }); 7636 $resizeSvg.append($resizeLine); 7637 } 7638 $resizeHandle.html($resizeSvg); 7639 7640 // assign z-index that's slightly higher than parent window 7641 resizeZIndex = parseInt($window.css('z-index')) + 100; 7642 $resizeHandle.css('z-index',resizeZIndex); 7643 $window.append($resizeHandle); 7644 $toolbar.append($dragHandle); 7645 7646 // Final step: Need to refresh the DOM in order for browser to process & display the SVG 7647 $resizeHandle.html($resizeHandle.html()); 7648 7649 // add event listener to toolbar to start and end drag 7650 // other event listeners will be added when drag starts 7651 $dragHandle.on('mousedown mouseup touchstart touchend', function(e) { 7652 e.stopPropagation(); 7653 if (e.type === 'mousedown' || e.type === 'touchstart' ) { 7654 if (!thisObj.windowMenuClickRegistered) { 7655 thisObj.windowMenuClickRegistered = true; 7656 thisObj.startMouseX = e.pageX; 7657 thisObj.startMouseY = e.pageY; 7658 thisObj.dragDevice = 'mouse'; // ok to use this even if device is a touchpad 7659 thisObj.startDrag(which, $window); 7660 } 7661 } else if (e.type === 'mouseup' || e.type === 'touchend') { 7662 if (thisObj.dragging && thisObj.dragDevice === 'mouse') { 7663 thisObj.endDrag(which); 7664 } 7665 } 7666 return false; 7667 }); 7668 7669 // add event listeners for resizing 7670 $resizeHandle.on('mousedown mouseup touchstart touchend', function(e) { 7671 e.stopPropagation(); 7672 if (e.type === 'mousedown' || e.type === 'touchstart') { 7673 if (!thisObj.windowMenuClickRegistered) { 7674 thisObj.windowMenuClickRegistered = true; 7675 thisObj.startMouseX = e.pageX; 7676 thisObj.startMouseY = e.pageY; 7677 thisObj.startResize(which, $window); 7678 } 7679 } else if (e.type === 'mouseup' || e.type === 'touchend') { 7680 if (thisObj.resizing) { 7681 thisObj.endResize(which); 7682 } 7683 } 7684 return false; 7685 }); 7686 7687 // whenever a window is clicked, bring it to the foreground 7688 $window.on('click', function() { 7689 7690 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 7691 thisObj.updateZIndex(which); 7692 } 7693 thisObj.finishingDrag = false; 7694 }); 7695 this.addWindowMenu(which,$window,windowName); 7696 }; 7697 7698 AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) { 7699 7700 var thisObj, menuId, $newButton, tooltipId, $tooltip, $popup; 7701 7702 thisObj = this; 7703 7704 // Add a Boolean that will be set to true temporarily if window button or a menu item is clicked 7705 // This will prevent the click event from also triggering a mousedown event on the toolbar 7706 // (which would unexpectedly send the window into drag mode) 7707 this.windowMenuClickRegistered = false; 7708 7709 // Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag 7710 // this will prevent the click event from being triggered 7711 this.finishingDrag = false; 7712 7713 // add button to draggable window which triggers a popup menu 7714 menuId = this.mediaId + '-' + windowName + '-menu'; 7715 $newButton = $('<button>',{ 7716 'type': 'button', 7717 'tabindex': '0', 7718 'aria-haspopup': 'true', 7719 'aria-controls': menuId, 7720 'aria-expanded': 'false', 7721 'class': 'able-button-handler-preferences' 7722 }); 7723 this.getIcon( $newButton, 'preferences' ); 7724 this.setText( $newButton, this.translate( 'windowButtonLabel', 'Window options' ) ); 7725 7726 // add a tooltip that displays aria-label on mouseenter or focus 7727 tooltipId = this.mediaId + '-' + windowName + '-tooltip'; 7728 $tooltip = $('<div>',{ 7729 'class' : 'able-tooltip', 7730 'id' : tooltipId 7731 }).hide(); 7732 7733 $newButton.on('mouseenter focus',function(e) { 7734 var label = $(this).attr('aria-label'); 7735 var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label); 7736 // get height of the tooltip 7737 var tooltipHeight = tooltip.height(); 7738 var tooltipY = ( tooltipHeight + 2 ) * -1; 7739 var tooltipX = 0; 7740 var tooltipStyle = { 7741 right: '', 7742 left: tooltipX + 'px', 7743 top: tooltipY + 'px' 7744 }; 7745 tooltip.css(tooltipStyle); 7746 thisObj.showTooltip(tooltip); 7747 $(this).on('mouseleave blur',function() { 7748 AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide(); 7749 }); 7750 }); 7751 7752 // setup popup menu 7753 $popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window' 7754 // define vars and assemble all the parts 7755 if (which === 'transcript') { 7756 this.$transcriptPopupButton = $newButton; 7757 this.$transcriptPopup = $popup; 7758 this.$transcriptToolbar.prepend($newButton,$tooltip,$popup); 7759 } else if (which === 'sign') { 7760 this.$signPopupButton = $newButton; 7761 this.$signPopup = $popup; 7762 this.$signToolbar.append($newButton,$tooltip,$popup); 7763 } 7764 7765 // handle button click 7766 $newButton.on('click keydown',function(e) { 7767 7768 if (thisObj.focusNotClick) { 7769 return false; 7770 } 7771 if (thisObj.dragging) { 7772 thisObj.dragKeys(which, e); 7773 return false; 7774 } 7775 e.stopPropagation(); 7776 if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) { 7777 // don't set windowMenuClickRegistered yet; that happens in handler function 7778 thisObj.handleWindowButtonClick(which, e); 7779 } 7780 thisObj.finishingDrag = false; 7781 }); 7782 7783 this.addResizeDialog(which, $window); 7784 }; 7785 7786 AblePlayer.prototype.addResizeDialog = function (which) { 7787 7788 var thisObj, $windowPopup, $windowButton, widthId, heightId, 7789 $resizeForm, $resizeWrapper, $resizeControls, $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel, 7790 $resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel, $saveButton, $cancelButton, 7791 newWidth, newHeight, resizeDialog; 7792 7793 thisObj = this; 7794 7795 if (which === 'transcript') { 7796 $windowPopup = this.$transcriptPopup; 7797 $windowButton = this.$transcriptPopupButton; 7798 } else if (which === 'sign') { 7799 $windowPopup = this.$signPopup; 7800 $windowButton = this.$signPopupButton; 7801 } 7802 7803 widthId = this.mediaId + '-resize-' + which + '-width'; 7804 heightId = this.mediaId + '-resize-' + which + '-height'; 7805 7806 $resizeForm = $('<div></div>',{ 7807 'class' : 'able-resize-form' 7808 }); 7809 7810 // inner container for all content, will be assigned to modal div's aria-describedby 7811 $resizeWrapper = $('<div></div>'); 7812 $resizeControls = $( '<div class="able-prefs-buttons"></div>' ); 7813 7814 // width field 7815 $resizeWidthDiv = $('<div></div>'); 7816 $resizeWidthInput = $('<input>',{ 7817 'type': 'number', 7818 'id': widthId, 7819 'min': 0, 7820 'value': '', 7821 }); 7822 $resizeWidthLabel = $('<label>',{ 7823 'for': widthId 7824 }).text( this.translate( 'width', 'Width' ) ); 7825 7826 // height field 7827 $resizeHeightDiv = $('<div></div>'); 7828 $resizeHeightInput = $('<input>',{ 7829 'type': 'number', 7830 'id': heightId, 7831 'min': 0, 7832 'value': '', 7833 }); 7834 $resizeHeightLabel = $('<label>',{ 7835 'for': heightId 7836 }).text( this.translate( 'height', 'Height' ) ); 7837 7838 // Add save and cancel buttons. 7839 $saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>'); 7840 $cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>'); 7841 $saveButton.on('click',function () { 7842 newWidth = $('#' + widthId).val(); 7843 newHeight = $('#' + heightId).val(); 7844 thisObj.resizeObject(which,newWidth,newHeight); 7845 thisObj.updatePreferences(which); 7846 7847 resizeDialog.hide(); 7848 $windowPopup.hide(); 7849 $windowButton.trigger('focus'); 7850 }); 7851 $cancelButton.on('click',function () { 7852 resizeDialog.hide(); 7853 $windowPopup.hide(); 7854 $windowButton.trigger('focus'); 7855 }); 7856 7857 // Now assemble all the parts 7858 $resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput); 7859 $resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput); 7860 $resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv); 7861 $resizeControls.append($saveButton,$cancelButton); 7862 $resizeForm.append($resizeWrapper,$resizeControls); 7863 7864 // must be appended to the BODY! 7865 // otherwise when aria-hidden="true" is applied to all background content 7866 // that will include an ancestor of the dialog, 7867 // which will render the dialog unreadable by screen readers 7868 $('body').append($resizeForm); 7869 resizeDialog = new AccessibleDialog( 7870 $resizeForm, 7871 $windowButton, 7872 this.translate( 'windowResizeHeading', 'Resize Window' ), 7873 this.translate( 'closeButtonLabel', 'Close' ), 7874 ); 7875 if (which === 'transcript') { 7876 this.transcriptResizeDialog = resizeDialog; 7877 } else if (which === 'sign') { 7878 this.signResizeDialog = resizeDialog; 7879 } 7880 }; 7881 7882 AblePlayer.prototype.handleWindowButtonClick = function (which, e) { 7883 7884 var thisObj, $windowPopup, $windowButton, $toolbar, popupTop; 7885 7886 thisObj = this; 7887 if (this.focusNotClick) { 7888 // transcript or sign window has just opened, 7889 // and focus moved to the window button 7890 // ignore the keystroke that triggered the popup 7891 return false; 7892 } 7893 7894 if (which === 'transcript') { 7895 $windowPopup = this.$transcriptPopup; 7896 $windowButton = this.$transcriptPopupButton; 7897 $toolbar = this.$transcriptToolbar; 7898 } else if (which === 'sign') { 7899 $windowPopup = this.$signPopup; 7900 $windowButton = this.$signPopupButton; 7901 $toolbar = this.$signToolbar; 7902 } 7903 if (e.type === 'keydown') { 7904 // user pressed a key 7905 if (e.key === ' ' || e.key === 'Enter') { 7906 this.windowMenuClickRegistered = true; 7907 } else if (e.key === 'Escape') { 7908 if ($windowPopup.is(':visible')) { 7909 // close the popup menu 7910 $windowPopup.hide(); 7911 // also reset the Boolean 7912 thisObj.windowMenuClickRegistered = false; 7913 // also restore menu items to their original state 7914 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 7915 // also return focus to window options button 7916 $windowButton.trigger('focus'); 7917 } else { 7918 // popup isn't open. Close the window 7919 if (which === 'sign') { 7920 this.handleSignToggle(); 7921 } else if (which === 'transcript') { 7922 this.handleTranscriptToggle(); 7923 } 7924 } 7925 } else { 7926 return false; 7927 } 7928 } else { 7929 // this was a mouse event 7930 this.windowMenuClickRegistered = true; 7931 } 7932 7933 if ( $windowPopup.is(':visible') ) { 7934 $windowPopup.hide(); 7935 thisObj.windowMenuClickRegistered = false; // reset 7936 $windowPopup.find('li').removeClass('able-focus'); 7937 $windowButton.attr('aria-expanded','false').trigger('focus'); 7938 } else { 7939 // first, be sure window is on top 7940 this.updateZIndex(which); 7941 popupTop = $toolbar.outerHeight() - 1; 7942 $windowPopup.css('top', popupTop); 7943 $windowPopup.show(); 7944 $windowButton.attr('aria-expanded','true'); 7945 $(this).find('li').first().trigger('focus').addClass('able-focus'); 7946 thisObj.windowMenuClickRegistered = false; // reset 7947 } 7948 }; 7949 7950 AblePlayer.prototype.handleMenuChoice = function (which, choice, e) { 7951 7952 var thisObj, $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight, 7953 aspectRatio, tempWidth, tempHeight; 7954 7955 thisObj = this; 7956 if (which === 'transcript') { 7957 $window = this.$transcriptArea; 7958 $windowPopup = this.$transcriptPopup; 7959 $windowButton = this.$transcriptPopupButton; 7960 resizeDialog = this.transcriptResizeDialog; 7961 } else if (which === 'sign') { 7962 $window = this.$signWindow; 7963 $windowPopup = this.$signPopup; 7964 $windowButton = this.$signPopupButton; 7965 resizeDialog = this.signResizeDialog; 7966 7967 startingWidth = $window.outerWidth(); 7968 startingHeight = $window.outerHeight(); 7969 aspectRatio = startingWidth / startingHeight; 7970 // make height a read-only field 7971 // and calculate its value based on width to preserve aspect ratio 7972 let widthId = this.mediaId + '-resize-' + which + '-width'; 7973 let heightId = this.mediaId + '-resize-' + which + '-height'; 7974 $( '#' + heightId ).prop('readonly',true); 7975 $( '#' + widthId ).on('input',function() { 7976 tempWidth = $(this).val(); 7977 tempHeight = Math.round(tempWidth/aspectRatio); 7978 $( '#' + heightId ).val(tempHeight); 7979 }); 7980 } 7981 this.$activeWindow = $window; 7982 7983 if (e.type === 'keydown') { 7984 if (e.key === 'Escape') { // escape 7985 // hide the popup menu 7986 $windowPopup.hide(); 7987 // also reset the Boolean 7988 thisObj.windowMenuClickRegistered = false; 7989 // also restore menu items to their original state 7990 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 7991 $windowButton.attr('aria-expanded','false'); 7992 // also return focus to window options button 7993 $windowButton.trigger('focus'); 7994 7995 return false; 7996 } else { 7997 // all other keys will be handled by upstream functions 7998 if (choice !== 'close') { 7999 this.$activeWindow = $window; 8000 } 8001 return false; 8002 } 8003 } 8004 8005 // hide the popup menu 8006 $windowPopup.hide(); 8007 // also reset the boolean 8008 thisObj.windowMenuClickRegistered = false; 8009 // also restore menu items to their original state 8010 $windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1'); 8011 $windowButton.attr('aria-expanded','false'); 8012 8013 if (choice !== 'close') { 8014 $windowButton.trigger('focus'); 8015 } 8016 if (choice === 'move') { 8017 // temporarily add role="application" to activeWindow 8018 // otherwise, screen readers incercept arrow keys and moving window will not work 8019 this.$activeWindow.attr('role','application'); 8020 8021 if (!this.showedAlert(which)) { 8022 this.showAlert( this.translate( 'windowMoveAlert', 'Drag or use arrow keys to move the window; Enter to stop' ),which); 8023 if (which === 'transcript') { 8024 this.showedTranscriptAlert = true; 8025 } else if (which === 'sign') { 8026 this.showedSignAlert = true; 8027 } 8028 } 8029 this.dragDevice = (e.type === 'keydown') ? 'keyboard' : 'mouse'; 8030 this.startDrag(which, $window); 8031 $windowPopup.hide().parent().attr( 'tabindex', '-1' ).trigger('focus'); 8032 } else if (choice == 'resize') { 8033 // resize through the menu uses a form, not drag 8034 var resizeFields = resizeDialog.getInputs(); 8035 if (resizeFields) { 8036 // reset width and height values in form 8037 resizeFields[0].value = Math.round( $window.outerWidth() ); 8038 resizeFields[1].value = Math.round( $window.outerHeight() ); 8039 } 8040 resizeDialog.show(); 8041 } else if (choice == 'close') { 8042 // close window, place focus on corresponding button on controller bar 8043 if (which === 'transcript') { 8044 this.closingTranscript = true; // stopgap to prevent double-firing of keypress 8045 this.handleTranscriptToggle(); 8046 } else if (which === 'sign') { 8047 this.closingSign = true; // stopgap to prevent double-firing of keypress 8048 this.handleSignToggle(); 8049 } 8050 } 8051 }; 8052 8053 AblePlayer.prototype.startDrag = function(which, $element) { 8054 8055 var thisObj, $windowPopup, startPos, newX, newY; 8056 8057 thisObj = this; 8058 8059 if (!this.$activeWindow) { 8060 this.$activeWindow = $element; 8061 } 8062 this.dragging = true; 8063 8064 if (which === 'transcript') { 8065 $windowPopup = this.$transcriptPopup; 8066 } else if (which === 'sign') { 8067 $windowPopup = this.$signPopup; 8068 } 8069 8070 // if window's popup menu is open, close it 8071 if ($windowPopup.is(':visible')) { 8072 $windowPopup.hide(); 8073 } 8074 8075 // be sure this window is on top 8076 this.updateZIndex(which); 8077 8078 // get starting position of element 8079 startPos = this.$activeWindow.position(); 8080 this.dragStartX = startPos.left; 8081 this.dragStartY = startPos.top; 8082 8083 if (typeof this.startMouseX === 'undefined') { 8084 this.dragDevice = 'keyboard'; 8085 this.dragKeyX = this.dragStartX; 8086 this.dragKeyY = this.dragStartY; 8087 // add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd() 8088 this.startingDrag = true; 8089 } else { 8090 this.dragDevice = 'mouse'; 8091 // get offset between mouse position and top left corner of draggable element 8092 this.dragOffsetX = this.startMouseX - this.dragStartX; 8093 this.dragOffsetY = this.startMouseY - this.dragStartY; 8094 } 8095 8096 // prepare element for dragging 8097 this.$activeWindow.addClass('able-drag').css({ 8098 'position': 'absolute', 8099 'top': this.dragStartY + 'px', 8100 'left': this.dragStartX + 'px' 8101 }).trigger('focus'); 8102 8103 // add device-specific event listeners 8104 if (this.dragDevice === 'mouse') { // might also be a touchpad 8105 $(document).on('mousemove touchmove',function(e) { 8106 if (thisObj.dragging) { 8107 // calculate new top left based on current mouse position - offset 8108 newX = e.pageX - thisObj.dragOffsetX; 8109 newY = e.pageY - thisObj.dragOffsetY; 8110 thisObj.resetDraggedObject( newX, newY ); 8111 } 8112 }); 8113 } else if (this.dragDevice === 'keyboard') { 8114 this.$activeWindow.on('keydown',function(e) { 8115 if (thisObj.dragging) { 8116 thisObj.dragKeys(which, e); 8117 } 8118 }); 8119 } 8120 return false; 8121 }; 8122 8123 /** 8124 * Handle moving the transcript or sign window from the keyboard. 8125 * 8126 * @param {string} which 'transcript' or 'sign' window. 8127 * @param {Event} e Triggered event. 8128 */ 8129 AblePlayer.prototype.dragKeys = function(which, e) { 8130 8131 var key, keySpeed; 8132 8133 // stopgap to prevent firing on initial Enter or space 8134 // that selected "Move" from menu 8135 if (this.startingDrag) { 8136 this.startingDrag = false; 8137 return false; 8138 } 8139 key = e.key; 8140 keySpeed = 10; // pixels per keypress event 8141 8142 switch (key) { 8143 case 'ArrowLeft': // left 8144 this.dragKeyX -= keySpeed; 8145 this.$srAlertBox.text( this.translate( 'windowMoveLeft', 'Window moved left' ) ); 8146 break; 8147 case 'ArrowUp': // up 8148 this.dragKeyY -= keySpeed; 8149 this.$srAlertBox.text( this.translate( 'windowMoveUp', 'Window moved up' ) ); 8150 break; 8151 case 'ArrowRight': // right 8152 this.dragKeyX += keySpeed; 8153 this.$srAlertBox.text( this.translate( 'windowMoveRight', 'Window moved right' ) ); 8154 break; 8155 case 'ArrowDown': // down 8156 this.dragKeyY += keySpeed; 8157 this.$srAlertBox.text( this.translate( 'windowMoveDown', 'Window moved down' ) ); 8158 break; 8159 case 'Enter': // enter 8160 case 'Escape': // escape 8161 this.$srAlertBox.text( this.translate( 'windowMoveStopped', 'Window move stopped' ) ); 8162 this.endDrag(which); 8163 return false; 8164 default: 8165 return false; 8166 } 8167 this.resetDraggedObject(this.dragKeyX,this.dragKeyY); 8168 if (e.preventDefault) { 8169 e.preventDefault(); 8170 } 8171 return false; 8172 }; 8173 8174 AblePlayer.prototype.resetDraggedObject = function ( x, y) { 8175 setTimeout( () => { 8176 this.$srAlertBox.text( '' ); 8177 }, 2000 ); 8178 8179 this.$activeWindow.css({ 8180 'left': x + 'px', 8181 'top': y + 'px' 8182 }); 8183 }, 8184 8185 AblePlayer.prototype.resizeObject = function ( which, width, height ) { 8186 8187 var innerHeight; 8188 8189 // which is either 'transcript' or 'sign' 8190 this.$activeWindow.css({ 8191 'width': width + 'px', 8192 'height': height + 'px' 8193 }); 8194 8195 if (which === 'transcript') { 8196 // $activeWindow is the outer $transcriptArea 8197 // but the inner able-transcript also needs to be resized proportionally 8198 // (it's 50px less than its outer container) 8199 innerHeight = height - 50; 8200 this.$transcriptDiv.css('height', innerHeight + 'px'); 8201 } 8202 }; 8203 8204 AblePlayer.prototype.endDrag = function(which) { 8205 8206 var thisObj, $windowButton; 8207 thisObj = this; 8208 8209 if (which === 'transcript') { 8210 $windowButton = this.$transcriptPopupButton; 8211 } else if (which === 'sign') { 8212 $windowButton = this.$signPopupButton; 8213 } 8214 8215 $(document).off('mousemove mouseup touchmove touchup'); 8216 this.$activeWindow.off('keydown').removeClass('able-drag'); 8217 // restore activeWindow role from 'application' to 'dialog' 8218 this.$activeWindow.attr('role','dialog'); 8219 this.$activeWindow = null; 8220 8221 if (this.dragDevice === 'keyboard') { 8222 $windowButton.trigger('focus'); 8223 } 8224 this.dragging = false; 8225 8226 // save final position of dragged element 8227 this.updatePreferences(which); 8228 8229 // reset starting mouse positions 8230 this.startMouseX = undefined; 8231 this.startMouseY = undefined; 8232 8233 // Boolean to stop stray events from firing 8234 this.windowMenuClickRegistered = false; 8235 this.finishingDrag = true; // will be reset after window click event 8236 // finishingDrag should be reset after window click event, 8237 // which is triggered automatically after mouseup 8238 // However, in case that's not reliable in some browsers 8239 // need to ensure this gets cancelled 8240 setTimeout(function() { 8241 thisObj.finishingDrag = false; 8242 }, 100); 8243 }; 8244 8245 AblePlayer.prototype.startResize = function(which, $element) { 8246 8247 var thisObj, $windowPopup, newWidth, newHeight; 8248 8249 thisObj = this; 8250 this.$activeWindow = $element; 8251 this.resizing = true; 8252 8253 $windowPopup = (which === 'transcript') ? this.$transcriptPopup : this.$signPopup; 8254 8255 // if window's popup menu is open, close it & place focus on button (???) 8256 if ($windowPopup.is(':visible')) { 8257 $windowPopup.hide().parent().trigger('focus'); 8258 } 8259 8260 // get starting width and height 8261 this.dragKeyX = this.dragStartX; 8262 this.dragKeyY = this.dragStartY; 8263 this.dragStartWidth = this.$activeWindow.width(); 8264 this.dragStartHeight = this.$activeWindow.outerHeight(); 8265 8266 // add event listeners 8267 $(document).on('mousemove touchmove',function(e) { 8268 if (thisObj.resizing) { 8269 // calculate new width and height based on changes to mouse position 8270 newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX); 8271 newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY); 8272 thisObj.resizeObject( which, newWidth, newHeight ); 8273 } 8274 }); 8275 8276 return false; 8277 }; 8278 8279 AblePlayer.prototype.endResize = function(which) { 8280 8281 var $windowButton; 8282 8283 if (which === 'transcript') { 8284 $windowButton = this.$transcriptPopupButton; 8285 } else if (which === 'sign') { 8286 $windowButton = this.$signPopupButton; 8287 } 8288 8289 $(document).off('mousemove mouseup touchmove touchup'); 8290 this.$activeWindow.off('keydown'); 8291 $windowButton.show().trigger('focus'); 8292 this.resizing = false; 8293 this.$activeWindow.removeClass('able-resize'); 8294 8295 // save final width and height of dragged element 8296 this.updatePreferences(which); 8297 8298 // Booleans for preventing stray events 8299 this.windowMenuClickRegistered = false; 8300 this.finishingDrag = true; 8301 8302 // finishingDrag should e reset after window click event, 8303 // which is triggered automatically after mouseup 8304 // However, in case that's not reliable in some browsers 8305 // need to ensure this gets cancelled 8306 setTimeout(function() { 8307 this.finishingDrag = false; 8308 }, 100); 8309 }; 8310 } 8311 8312 function addEventFunctions(AblePlayer) { 8313 // Media events 8314 AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) { 8315 8316 // duration and elapsed are passed from callback functions of Vimeo API events 8317 // duration is expressed as sss.xxx 8318 // elapsed is expressed as sss.xxx 8319 var thisObj = this; 8320 this.getMediaTimes(duration,elapsed).then(function(mediaTimes) { 8321 thisObj.duration = mediaTimes['duration']; 8322 thisObj.elapsed = mediaTimes['elapsed']; 8323 if (thisObj.duration > 0) { 8324 // do all the usual time-sync stuff during playback 8325 if (thisObj.prefHighlight === 1) { 8326 thisObj.highlightTranscript(thisObj.elapsed); 8327 } 8328 thisObj.updateCaption(thisObj.elapsed); 8329 thisObj.showDescription(thisObj.elapsed); 8330 thisObj.updateChapter(thisObj.elapsed); 8331 thisObj.updateMeta(thisObj.elapsed); 8332 thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed); 8333 } 8334 }); 8335 }; 8336 8337 AblePlayer.prototype.onMediaPause = function () { 8338 8339 if (this.controlsHidden) { 8340 this.fadeControls('in'); 8341 this.controlsHidden = false; 8342 } 8343 if (this.hideControlsTimeoutStatus === 'active') { 8344 window.clearTimeout(this.hideControlsTimeout); 8345 this.hideControlsTimeoutStatus = 'clear'; 8346 8347 } 8348 this.refreshControls('playpause'); 8349 }; 8350 8351 AblePlayer.prototype.onMediaComplete = function () { 8352 // if there's a playlist, advance to next item and start playing 8353 if (this.hasPlaylist && !this.cueingPlaylistItem) { 8354 if (this.playlistIndex === (this.$playlist.length - 1)) { 8355 // this is the last track in the playlist 8356 if (this.loop) { 8357 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 8358 this.cuePlaylistItem(0); 8359 } else { 8360 this.playing = false; 8361 this.paused = true; 8362 } 8363 } else { 8364 // this is not the last track. Play the next one. 8365 this.playlistIndex++; 8366 this.cueingPlaylistItem = true; // stopgap to prevent multiple firings 8367 this.cuePlaylistItem(this.playlistIndex); 8368 } 8369 } 8370 this.refreshControls(); 8371 }; 8372 8373 AblePlayer.prototype.onMediaNewSourceLoad = function () { 8374 8375 var loadIsComplete = false; 8376 8377 if (this.cueingPlaylistItem) { 8378 // this variable was set to address bugs caused by multiple firings of media 'end' event 8379 // safe to reset now 8380 this.cueingPlaylistItem = false; 8381 } 8382 if (this.recreatingPlayer) { 8383 // same as above; different bugs 8384 this.recreatingPlayer = false; 8385 } 8386 if (this.playbackRate) { 8387 // user has set playbackRate on a previous src or track 8388 // use that setting on the new src or track too 8389 this.setPlaybackRate(this.playbackRate); 8390 } 8391 if (this.userClickedPlaylist) { 8392 if (!this.startedPlaying || this.okToPlay) { 8393 // start playing; no further user action is required 8394 this.playMedia(); 8395 loadIsComplete = true; 8396 } 8397 } else if (this.seekTrigger == 'restart' || 8398 this.seekTrigger == 'chapter' || 8399 this.seekTrigger == 'transcript' || 8400 this.seekTrigger == 'search' 8401 ) { 8402 // by clicking on any of these elements, user is likely intending to play 8403 // Not included: elements where user might click multiple times in succession 8404 // (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play 8405 this.playMedia(); 8406 loadIsComplete = true; 8407 } else if (this.swappingSrc) { 8408 // new source file has just been loaded 8409 if (this.hasPlaylist) { 8410 // a new source file from the playlist has just been loaded 8411 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 8412 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 8413 this.playMedia(); 8414 loadIsComplete = true; 8415 } 8416 } else if (this.swapTime > 0) { 8417 if (this.seekStatus === 'complete') { 8418 if (this.okToPlay) { 8419 // should be able to resume playback 8420 this.playMedia(); 8421 } 8422 loadIsComplete = true; 8423 } else if (this.seekStatus === 'seeking') ; else { 8424 if (this.swapTime === this.elapsed) { 8425 // seek is finished! 8426 this.seekStatus = 'complete'; 8427 if (this.okToPlay) { 8428 // should be able to resume playback 8429 this.playMedia(); 8430 } 8431 loadIsComplete = true; 8432 } else { 8433 // seeking hasn't started yet 8434 // first, determine whether it's possible 8435 if (this.hasDescTracks) { 8436 // do nothing. Unable to seek ahead if there are descTracks 8437 loadIsComplete = true; 8438 } else if (this.durationsAreCloseEnough(this.duration,this.prevDuration)) { 8439 // durations of two sources are close enough to making seek ahead in new source ok 8440 this.seekStatus = 'seeking'; 8441 this.seekTo(this.swapTime); 8442 } else { 8443 // durations of two sources are too dissimilar to support seeking ahead to swapTime. 8444 loadIsComplete = true; 8445 } 8446 } 8447 } 8448 } else { 8449 // swapTime is 0. No seeking required. 8450 if (this.playing) { 8451 this.playMedia(); 8452 // swap is complete. Reset vars. 8453 loadIsComplete = true; 8454 } 8455 } 8456 } else if (!this.startedPlaying) { 8457 if (this.startTime > 0) { 8458 if (this.seeking) { 8459 // a seek has already been initiated 8460 // since canplaythrough has been triggered, the seek is complete 8461 this.seeking = false; 8462 if (this.okToPlay) { 8463 this.playMedia(); 8464 } 8465 loadIsComplete = true; 8466 } else { 8467 // haven't started seeking yet 8468 this.seekTo(this.startTime); 8469 } 8470 } else if (this.defaultChapter && typeof this.selectedChapters !== 'undefined') { 8471 this.seekToChapter(this.defaultChapter); 8472 } else { 8473 // there is no startTime, therefore no seeking required 8474 if (this.okToPlay) { 8475 this.playMedia(); 8476 } 8477 loadIsComplete = true; 8478 } 8479 } else if (this.hasPlaylist) { 8480 // new source media is part of a playlist, but user didn't click on it 8481 // (and somehow, swappingSrc is false) 8482 // this may happen when the previous track ends and next track loads 8483 // this same code is called above when swappingSrc is true 8484 if ((this.playlistIndex !== this.$playlist.length) || this.loop) { 8485 // this is not the last track in the playlist (OR playlist is looping so it doesn't matter) 8486 this.playMedia(); 8487 loadIsComplete = true; 8488 } 8489 } else { 8490 // None of the above. 8491 // User is likely seeking to a new time, but not loading a new media source 8492 // need to reset vars 8493 loadIsComplete = true; 8494 } 8495 if (loadIsComplete) { 8496 // reset vars 8497 this.swappingSrc = false; 8498 this.seekStatus = null; 8499 this.swapTime = 0; 8500 this.seekTrigger = null; 8501 this.seekingFromTranscript = false; 8502 this.userClickedPlaylist = false; 8503 this.okToPlay = false; 8504 } 8505 this.refreshControls(); 8506 if (this.$focusedElement) { 8507 this.restoreFocus(); 8508 this.$focusedElement = null; 8509 this.activeMedia = null; 8510 } 8511 }; 8512 8513 AblePlayer.prototype.durationsAreCloseEnough = function(d1,d2) { 8514 8515 // Compare the durations of two media sources to determine whether it's ok to seek ahead after swapping src 8516 // The durations may not be exact, but they might be "close enough" 8517 // returns true if "close enough", otherwise false 8518 8519 var tolerance, diff; 8520 8521 tolerance = 1; // number of seconds between rounded durations that is considered "close enough" 8522 diff = Math.abs(Math.round(d1) - Math.round(d2)); 8523 8524 return (diff <= tolerance) ? true : false; 8525 }; 8526 8527 AblePlayer.prototype.restoreFocus = function() { 8528 8529 // function called after player has been rebuilt (during media swap) 8530 // the original focusedElement no longer exists, 8531 // but this function finds a match in the new player 8532 // and places focus there 8533 8534 var classList, $mediaParent; 8535 8536 if ( this.$focusedElement && null !== this.activeMedia ) { 8537 $mediaParent = $( '#' + this.activeMedia ).closest( '.able' ); 8538 if ( (this.$focusedElement).attr('role') === 'button' ) { 8539 classList = this.$focusedElement.attr("class").split(/\s+/); 8540 $.each(classList, function(index, item) { 8541 if (item.substring(0,20) === 'able-button-handler-') { 8542 $mediaParent.find('div.able-controller div.' + item).trigger('focus'); 8543 } 8544 }); 8545 } 8546 } 8547 8548 }; 8549 8550 AblePlayer.prototype.addSeekbarListeners = function () { 8551 8552 var thisObj = this; 8553 8554 // Handle seek bar events. 8555 this.seekBar.seekbarDiv.on('startTracking', function (e) { 8556 thisObj.pausedBeforeTracking = thisObj.paused; 8557 thisObj.pauseMedia(); 8558 }).on('tracking', function (e, position) { 8559 // Scrub transcript, captions, and metadata. 8560 thisObj.highlightTranscript(position); 8561 thisObj.updateCaption(position); 8562 thisObj.showDescription(position); 8563 thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position)); 8564 thisObj.updateMeta(position); 8565 thisObj.refreshControls(); 8566 }).on('stopTracking', function (e, position) { 8567 if (thisObj.useChapterTimes) { 8568 thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position)); 8569 } else { 8570 thisObj.seekTo(position); 8571 } 8572 if (!thisObj.pausedBeforeTracking) { 8573 setTimeout(function () { 8574 thisObj.playMedia(); 8575 }, 200); 8576 } 8577 }); 8578 }; 8579 8580 AblePlayer.prototype.onClickPlayerButton = function (el) { 8581 var whichButton, prefsPopup; 8582 whichButton = this.getButtonNameFromClass($(el).attr('class')); 8583 switch ( whichButton ) { 8584 case 'play': 8585 this.clickedPlay = true; 8586 this.handlePlay(); 8587 break; 8588 case 'restart': 8589 this.seekTrigger = 'restart'; 8590 this.handleRestart(); 8591 break; 8592 case 'previous': 8593 this.userClickedPlaylist = true; 8594 this.okToPlay = true; 8595 this.seekTrigger = 'previous'; 8596 this.buttonWithFocus = 'previous'; 8597 this.handlePrevTrack(); 8598 break; 8599 case 'next': 8600 this.userClickedPlaylist = true; 8601 this.okToPlay = true; 8602 this.seekTrigger = 'next'; 8603 this.buttonWithFocus = 'next'; 8604 this.handleNextTrack(); 8605 break; 8606 case 'rewind': 8607 this.seekTrigger = 'rewind'; 8608 this.handleRewind(); 8609 break; 8610 case 'forward': 8611 this.seekTrigger = 'forward'; 8612 this.handleFastForward(); 8613 break; 8614 case 'mute': 8615 this.handleMute(); 8616 break; 8617 case 'volume': 8618 this.handleVolumeButtonClick(); 8619 break; 8620 case 'faster': 8621 this.handleRateIncrease(); 8622 break; 8623 case 'slower': 8624 this.handleRateDecrease(); 8625 break; 8626 case 'captions': 8627 this.handleCaptionToggle(); 8628 break; 8629 case 'chapters': 8630 this.handleChapters(); 8631 break; 8632 case 'descriptions': 8633 this.handleDescriptionToggle(); 8634 break; 8635 case 'sign': 8636 if ( ! this.closingSign ) { 8637 this.handleSignToggle(); 8638 } 8639 break; 8640 case 'preferences': 8641 if ($(el).attr('data-prefs-popup') === 'menu') { 8642 this.handlePrefsClick(); 8643 } else { 8644 this.showingPrefsDialog = true; // stopgap 8645 this.closePopups(); 8646 prefsPopup = $(el).attr('data-prefs-popup'); 8647 if (prefsPopup === 'keyboard') { 8648 this.keyboardPrefsDialog.show(); 8649 } else if (prefsPopup === 'captions') { 8650 this.captionPrefsDialog.show(); 8651 } else if (prefsPopup === 'descriptions') { 8652 this.descPrefsDialog.show(); 8653 } else if (prefsPopup === 'transcript') { 8654 this.transcriptPrefsDialog.show(); 8655 } 8656 this.showingPrefsDialog = false; 8657 } 8658 break; 8659 case 'transcript': 8660 if ( !this.closingTranscript ) { 8661 this.handleTranscriptToggle(); 8662 } 8663 break; 8664 case 'fullscreen': 8665 this.clickedFullscreenButton = true; 8666 this.handleFullscreenToggle(); 8667 break; 8668 } 8669 }; 8670 8671 AblePlayer.prototype.getButtonNameFromClass = function (classString) { 8672 // player control buttons all have class="able-button-handler-x" where x is the identifier 8673 // buttons might also have other classes assigned though 8674 var classes, i; 8675 8676 classes = classString.split(' '); 8677 for (i = 0; i < classes.length; i++) { 8678 if (classes[i].substring(0,20) === 'able-button-handler-') { 8679 return classes[i].substring(20); 8680 } 8681 } 8682 return classString; 8683 }; 8684 8685 AblePlayer.prototype.okToHandleKeyPress = function () { 8686 let defaultReturn = true; 8687 if ( this.prefNoKeyShortcuts === 1 ) { 8688 defaultReturn = false; 8689 } 8690 // returns true unless user's focus is on a UI element or user has disabled keyboard shortcuts. 8691 // that is likely to need supported keystrokes, including space 8692 var activeElement = AblePlayer.getActiveDOMElement(); 8693 8694 return ($(activeElement).prop('tagName') === 'INPUT') ? false : defaultReturn; 8695 }; 8696 8697 AblePlayer.prototype.onPlayerKeyPress = function (e) { 8698 8699 // handle keystrokes (using DHTML Style Guide recommended key combinations) 8700 // https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer 8701 // Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences 8702 // - Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down 8703 // The rest are reasonable best choices 8704 // - If there are multiple players on a single page, keystroke handlers 8705 // are only bound to the FIRST player 8706 // - The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions 8707 // including removal of the "media player" design pattern. There's an issue about that: 8708 // https://github.com/w3c/aria-practices/issues/27 8709 8710 var key, $thisElement; 8711 8712 // Convert to lower case. 8713 key = e.key; 8714 $thisElement = $(document.activeElement); 8715 8716 if (key === 'Escape') { 8717 if (this.$transcriptArea && $.contains(this.$transcriptArea[0],$thisElement[0]) && !this.hidingPopup) { 8718 // This element is part of transcript area. 8719 this.handleTranscriptToggle(); 8720 return false; 8721 } 8722 } 8723 if (!this.okToHandleKeyPress()) { 8724 return false; 8725 } 8726 8727 // Only use keypress to control player if focus is NOT on a form field or contenteditable element 8728 // (or a textarea element with player in stenoMode) 8729 if (!( 8730 $(':focus').is('[contenteditable]') || 8731 $(':focus').is('input') || 8732 ($(':focus').is('textarea') && !this.stenoMode) || 8733 $(':focus').is('select') || 8734 e.target.hasAttribute('contenteditable') || 8735 e.target.tagName === 'INPUT' || 8736 (e.target.tagName === 'TEXTAREA' && !this.stenoMode) || 8737 e.target.tagName === 'SELECT' 8738 )){ 8739 if (key === 'Escape') { 8740 this.closePopups(); 8741 this.$tooltipDiv.hide(); 8742 this.seekBar.hideSliderTooltips(); 8743 } else if (key === ' ') { 8744 // disable spacebar support for play/pause toggle as of 4.2.10 8745 // spacebar should not be handled everywhere on the page, since users use that to scroll the page 8746 // when the player has focus, most controls are buttons so spacebar should be used to trigger the buttons 8747 if ($thisElement.attr('role') === 'button') { 8748 // register a click on this element 8749 e.preventDefault(); 8750 $thisElement.trigger( 'click' ); 8751 } 8752 } else if ( key === 'p' ) { 8753 if (this.usingModifierKeys(e)) { 8754 e.preventDefault(); 8755 this.handlePlay(); 8756 } 8757 } else if (key === 's') { 8758 if (this.usingModifierKeys(e)) { 8759 e.preventDefault(); 8760 this.handleRestart(); 8761 } 8762 } else if (key === 'm') { 8763 if (this.usingModifierKeys(e)) { 8764 e.preventDefault(); 8765 this.handleMute(); 8766 } 8767 } else if (key === 'v') { 8768 if (this.usingModifierKeys(e)) { 8769 e.preventDefault(); 8770 this.handleVolumeButtonClick(); 8771 } 8772 } else if (key >= 0 && key <= 9) { 8773 if (this.usingModifierKeys(e)) { 8774 e.preventDefault(); 8775 this.handleVolumeKeystroke(key); 8776 } 8777 } else if (key === 'c') { 8778 if (this.usingModifierKeys(e)) { 8779 e.preventDefault(); 8780 this.handleCaptionToggle(); 8781 } 8782 } else if (key === 'd') { 8783 if (this.usingModifierKeys(e)) { 8784 e.preventDefault(); 8785 this.handleDescriptionToggle(); 8786 } 8787 } else if (key === 'f') { 8788 if (this.usingModifierKeys(e)) { 8789 e.preventDefault(); 8790 this.handleFastForward(); 8791 } 8792 } else if (key === 'r') { 8793 if (this.usingModifierKeys(e)) { 8794 e.preventDefault(); 8795 this.handleRewind(); 8796 } 8797 } else if (key === 'b') { 8798 if (this.usingModifierKeys(e)) { 8799 e.preventDefault(); 8800 this.handlePrevTrack(); 8801 } 8802 } else if (key === 'n') { 8803 if (this.usingModifierKeys(e)) { 8804 e.preventDefault(); 8805 this.handleNextTrack(); 8806 } 8807 } else if (key === 'e') { 8808 if (this.usingModifierKeys(e)) { 8809 e.preventDefault(); 8810 this.handlePrefsClick(); 8811 } 8812 } else if (key === 'Enter') { 8813 if ($thisElement.attr('role') === 'button' || $thisElement.prop('tagName') === 'SPAN') { 8814 // register a click on this element 8815 // if it's a transcript span the transcript span click handler will take over 8816 $thisElement.trigger( 'click' ); 8817 } else if ($thisElement.prop('tagName') === 'LI') { 8818 $thisElement.trigger( 'click' ); 8819 } 8820 } 8821 } 8822 }; 8823 8824 AblePlayer.prototype.addHtml5MediaListeners = function () { 8825 8826 var thisObj = this; 8827 8828 // NOTE: iOS and some browsers do not support autoplay 8829 // and no events are triggered until media begins to play 8830 // Able Player gets around this by automatically loading media in some circumstances 8831 // (see initialize.js > initPlayer() for details) 8832 8833 this.$media 8834 .on('emptied',function() { 8835 // do something 8836 }) 8837 .on('loadedmetadata',function() { 8838 // should be able to get duration now 8839 thisObj.duration = thisObj.media.duration; 8840 }) 8841 .on('canplay',function() { 8842 // previously handled seeking to startTime here 8843 // but it's probably safer to wait for canplaythrough 8844 // so we know player can seek ahead to anything 8845 }) 8846 .on('canplaythrough',function() { 8847 // previously onMediaNewSourceLoad() was called on 'loadedmetadata' 8848 // but that proved to be too soon for some of this functionality. 8849 // TODO: Monitor this. If moving it here causes performance issues, 8850 // consider moving some or all of this functionality to 'canplay' 8851 thisObj.onMediaNewSourceLoad(); 8852 }) 8853 .on('play',function() { 8854 // 'play' indicates that the play method has been called. 8855 // Don't do anything until playback has actually started. 8856 }) 8857 .on('playing',function() { 8858 // 'playing' indicates that the video is playing. 8859 thisObj.playing = true; 8860 thisObj.paused = false; 8861 thisObj.swappingSrc = false; 8862 thisObj.refreshControls('playpause'); 8863 }) 8864 .on('ended',function() { 8865 thisObj.playing = false; 8866 thisObj.paused = true; 8867 thisObj.onMediaComplete(); 8868 }) 8869 .on('progress', function() { 8870 thisObj.refreshControls('timeline'); 8871 }) 8872 .on('waiting',function() { 8873 // could fire a notification about loss of data. 8874 }) 8875 .on('durationchange',function() { 8876 // Display new duration. 8877 thisObj.refreshControls('timeline'); 8878 }) 8879 .on('timeupdate',function() { 8880 thisObj.onMediaUpdateTime(); // includes a call to refreshControls() 8881 }) 8882 .on('pause',function() { 8883 if (!thisObj.clickedPlay) { 8884 // 'pause' was triggered automatically, not initiated by user 8885 // this happens in some browsers when swapping source 8886 // (e.g., between tracks in a playlist or swapping description) 8887 if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else { 8888 thisObj.playing = false; 8889 thisObj.paused = true; 8890 } 8891 } else { 8892 thisObj.playing = false; 8893 thisObj.paused = true; 8894 } 8895 thisObj.clickedPlay = false; // done with this variable 8896 thisObj.onMediaPause(); // includes a call to refreshControls() 8897 }) 8898 .on('ratechange',function() { 8899 // do something 8900 }) 8901 .on('volumechange',function() { 8902 thisObj.volume = thisObj.getVolume(); 8903 }) 8904 .on('error',function() { 8905 if (thisObj.debug) { 8906 switch (thisObj.media.error.code) { 8907 } 8908 } 8909 }); 8910 }; 8911 8912 AblePlayer.prototype.addVimeoListeners = function () { 8913 8914 var thisObj = this; 8915 8916 // Vimeo doesn't seem to support chaining of on() functions 8917 // so each event listener must be attached separately 8918 this.vimeoPlayer.on('loaded', function(vimeoId) { 8919 // Triggered when a new video is loaded in the player 8920 thisObj.onMediaNewSourceLoad(); 8921 }); 8922 this.vimeoPlayer.on('play', function(e) { 8923 // Triggered when the video plays 8924 thisObj.playing = true; 8925 thisObj.startedPlaying = true; 8926 thisObj.paused = false; 8927 thisObj.refreshControls('playpause'); 8928 }); 8929 this.vimeoPlayer.on('ended', function(e) { 8930 // Triggered any time the video playback reaches the end. 8931 // Note: when loop is turned on, the ended event will not fire. 8932 thisObj.playing = false; 8933 thisObj.paused = true; 8934 thisObj.onMediaComplete(); 8935 }); 8936 this.vimeoPlayer.on('bufferstart', function() { 8937 // Triggered when buffering starts in the player. 8938 // This is also triggered during preload and while seeking. 8939 // There is no associated data with this event. 8940 }); 8941 this.vimeoPlayer.on('bufferend', function() { 8942 // Triggered when buffering ends in the player. 8943 // This is also triggered at the end of preload and seeking. 8944 // There is no associated data with this event. 8945 }); 8946 this.vimeoPlayer.on('progress', function(e) { 8947 // Triggered as the video is loaded. 8948 // Reports back the amount of the video that has been buffered (NOT the amount played) 8949 // Data has keys duration, percent, and seconds 8950 }); 8951 this.vimeoPlayer.on('seeking', function(e) { 8952 // Triggered when the player starts seeking to a specific time. 8953 // A timeupdate event will also be fired at the same time. 8954 }); 8955 this.vimeoPlayer.on('seeked', function(e) { 8956 // Triggered when the player seeks to a specific time. 8957 // A timeupdate event will also be fired at the same time. 8958 }); 8959 this.vimeoPlayer.on('timeupdate',function(e) { 8960 // Triggered as the currentTime of the video updates. 8961 // It generally fires every 250ms, but it may vary depending on the browser. 8962 thisObj.onMediaUpdateTime(e['duration'], e['seconds']); 8963 }); 8964 this.vimeoPlayer.on('pause',function(e) { 8965 // Triggered when the video pauses 8966 if (!thisObj.clickedPlay) { 8967 // 'pause' was triggered automatically, not initiated by user 8968 // this happens in some browsers (not Chrome, as of 70.x) 8969 // when swapping source (e.g., between tracks in a playlist, or swapping description) 8970 if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else { 8971 thisObj.playing = false; 8972 thisObj.paused = true; 8973 } 8974 } else { 8975 thisObj.playing = false; 8976 thisObj.paused = true; 8977 } 8978 thisObj.clickedPlay = false; // done with this variable 8979 thisObj.onMediaPause(); 8980 thisObj.refreshControls('playpause'); 8981 }); 8982 this.vimeoPlayer.on('playbackratechange',function(e) { 8983 // Triggered when the playback rate of the video in the player changes. 8984 // The ability to change rate can be disabled by the creator 8985 // and the event will not fire for those videos. 8986 // data contains one key: 'playbackRate' 8987 thisObj.vimeoPlaybackRate = e['playbackRate']; 8988 }); 8989 this.vimeoPlayer.on('texttrackchange', function(e) { 8990 // Triggered when the active text track (captions/subtitles) changes. 8991 // The values will be null if text tracks are turned off. 8992 // data contains three keys: kind, label, language 8993 }); 8994 this.vimeoPlayer.on('volumechange',function(e) { 8995 // Triggered when the volume in the player changes. 8996 // Some devices do not support setting the volume of the video 8997 // independently from the system volume, 8998 // so this event will never fire on those devices. 8999 thisObj.volume = e['volume'] * 10; 9000 }); 9001 this.vimeoPlayer.on('error',function(e) { 9002 // do something with the available data 9003 // data contains three keys: message, method, name 9004 // message: A user-friendly error message 9005 // method: The Vimeo API method call that triggered the error 9006 // name: Name of the error (not necesssarily user-friendly) 9007 }); 9008 }; 9009 9010 AblePlayer.prototype.addEventListeners = function () { 9011 9012 var thisObj = this; 9013 9014 // Appropriately resize media player for full screen. 9015 $(window).on('resize',function () { 9016 thisObj.resizePlayer(); 9017 }); 9018 9019 // Refresh player if it changes from hidden to visible 9020 // There is no event triggered by a change in visibility 9021 // but MutationObserver works in most browsers (but NOT in IE 10 or earlier) 9022 // http://caniuse.com/#feat=mutationobserver 9023 if (window.MutationObserver) { 9024 var target = this.$ableDiv[0]; 9025 var observer = new MutationObserver(function(mutations) { 9026 mutations.forEach(function(mutation) { 9027 if (mutation.type === 'attributes' && mutation.attributeName === 'style') { 9028 // the player's style attribute has changed. Check to see if it's visible 9029 if (thisObj.$ableDiv.is(':visible')) { 9030 thisObj.refreshControls(); 9031 } 9032 } 9033 }); 9034 }); 9035 var config = { attributes: true, childList: true, characterData: true }; 9036 observer.observe(target, config); 9037 } 9038 if (typeof this.seekBar !== 'undefined') { 9039 this.addSeekbarListeners(); 9040 } else { 9041 // wait a bit and try again 9042 // TODO: Should set this up to keep trying repeatedly. 9043 // Seekbar listeners are critical. 9044 setTimeout(function() { 9045 if (typeof thisObj.seekBar !== 'undefined') { 9046 thisObj.addSeekbarListeners(); 9047 } 9048 },2000); 9049 } 9050 9051 // handle clicks on player buttons 9052 this.$controllerDiv.find('div[role="button"]').on('click',function(e){ 9053 e.stopPropagation(); 9054 thisObj.onClickPlayerButton(this); 9055 }); 9056 9057 // handle clicks (left only) anywhere on the page. If any popups are open, close them. 9058 $('body').on('click', function(e) { 9059 9060 if (e.button !== 0) { // not a left click 9061 return false; 9062 } 9063 if ($('.able-popup:visible').length || $('.able-volume-slider:visible').length ) { 9064 // at least one popup is visible 9065 thisObj.closePopups(); 9066 } 9067 if (e.target.tagName === 'VIDEO') { 9068 // user clicked the video (not an element that sits on top of the video) 9069 // handle this as a play/pause toggle click 9070 thisObj.clickedPlay = true; 9071 } 9072 }); 9073 9074 // handle mouse movement over player; make controls visible again if hidden 9075 this.$ableDiv.on('mousemove',function() { 9076 if (thisObj.controlsHidden) { 9077 thisObj.fadeControls('in'); 9078 thisObj.controlsHidden = false; 9079 // if there's already an active timeout, clear it and start timer again 9080 if (thisObj.hideControlsTimeoutStatus === 'active') { 9081 window.clearTimeout(thisObj.hideControlsTimeout); 9082 thisObj.hideControlsTimeoutStatus = 'clear'; 9083 } 9084 if (thisObj.hideControls) { 9085 // after showing controls, hide them again after a brief timeout 9086 thisObj.invokeHideControlsTimeout(); 9087 } 9088 } else { 9089 // if there's already an active timeout, clear it and start timer again 9090 if (thisObj.hideControlsTimeoutStatus === 'active') { 9091 window.clearTimeout(thisObj.hideControlsTimeout); 9092 thisObj.hideControlsTimeoutStatus = 'clear'; 9093 if (thisObj.hideControls) { 9094 thisObj.invokeHideControlsTimeout(); 9095 } 9096 } 9097 } 9098 }); 9099 9100 // if user presses a key from anywhere on the page, show player controls 9101 $(document).on( 'keydown', function(e) { 9102 if (thisObj.controlsHidden) { 9103 thisObj.fadeControls('in'); 9104 thisObj.controlsHidden = false; 9105 if (thisObj.hideControlsTimeoutStatus === 'active') { 9106 window.clearTimeout(thisObj.hideControlsTimeout); 9107 thisObj.hideControlsTimeoutStatus = 'clear'; 9108 } 9109 if (thisObj.hideControls) { 9110 // after showing controls, hide them again after a brief timeout 9111 thisObj.invokeHideControlsTimeout(); 9112 } 9113 } else { 9114 // controls are visible 9115 // if there's already an active timeout, clear it and start timer again 9116 if (thisObj.hideControlsTimeoutStatus === 'active') { 9117 window.clearTimeout(thisObj.hideControlsTimeout); 9118 thisObj.hideControlsTimeoutStatus = 'clear'; 9119 9120 if (thisObj.hideControls) { 9121 thisObj.invokeHideControlsTimeout(); 9122 } 9123 } 9124 } 9125 }); 9126 9127 // handle local keydown events if this isn't the only player on the page; 9128 // otherwise these are dispatched by global handler (see ableplayer-base.js) 9129 this.$ableDiv.on( 'keydown', function (e) { 9130 if (!AblePlayer.hasSingleInstance()) { 9131 thisObj.onPlayerKeyPress(e); 9132 } 9133 }); 9134 9135 // If stenoMode is enabled in an iframe, handle keydown events from the iframe 9136 if (this.stenoMode && (typeof this.stenoFrameContents !== 'undefined')) { 9137 this.stenoFrameContents.on('keydown',function(e) { 9138 thisObj.onPlayerKeyPress(e); 9139 }); 9140 } 9141 // transcript is not a child of this.$ableDiv 9142 // therefore, must be added separately 9143 if (this.$transcriptArea) { 9144 this.$transcriptArea.on('keydown',function (e) { 9145 if (!AblePlayer.hasSingleInstance()) { 9146 thisObj.onPlayerKeyPress(e); 9147 } 9148 }); 9149 } 9150 9151 // handle clicks on playlist items 9152 if (this.$playlist) { 9153 this.$playlist.on( 'click', function(e) { 9154 if (!thisObj.userClickedPlaylist) { 9155 // stopgap in case multiple clicks are fired on the same playlist item 9156 thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered 9157 thisObj.playlistIndex = $(this).index(); 9158 thisObj.cuePlaylistItem(thisObj.playlistIndex); 9159 } 9160 }); 9161 } 9162 9163 // Also play/pause when clicking on the media. 9164 this.$media.on( 'click', function () { 9165 thisObj.handlePlay(); 9166 }); 9167 9168 // add listeners for media events 9169 if (this.player === 'html5') { 9170 this.addHtml5MediaListeners(); 9171 } else if (this.player === 'vimeo') { 9172 this.addVimeoListeners(); 9173 } else if (this.player === 'youtube') { 9174 // Youtube doesn't give us time update events, so we just periodically generate them ourselves 9175 setInterval(function () { 9176 thisObj.onMediaUpdateTime(); 9177 }, 300); 9178 } 9179 }; 9180 } 9181 9182 function addInitializeFunctions(AblePlayer) { 9183 // Set default variable values. 9184 AblePlayer.prototype.setDefaults = function () { 9185 9186 this.playerCreated = false; // will set to true after recreatePlayer() is complete the first time 9187 this.playing = false; // will change to true after 'playing' event is triggered 9188 this.paused = true; // will always be the opposite of this.playing (available for convenience) 9189 this.clickedPlay = false; // will change to true temporarily if user clicks 'play' (or pause) 9190 this.fullscreen = false; // will change to true if player is in full screen mode 9191 this.swappingSrc = false; // will change to true temporarily while media source is being swapped 9192 this.initializing = false; // will change to true temporarily while initPlayer() is processing 9193 this.cueingPlaylistItems = false; // will change to true temporarily while cueing next playlist item 9194 this.buttonWithFocus = null; // will change to 'previous' or 'next' if user clicks either of those buttons 9195 this.speechEnabled = null; // will change either to 'true' in initSpeech(), or false if not supported 9196 9197 this.setIconColor(); 9198 }; 9199 9200 AblePlayer.prototype.setIconColor = function() { 9201 9202 // determine the best color choice (white or black) for icons, 9203 // given the background-color of their container elements 9204 // Source for relative luminance formula: 9205 // https://en.wikipedia.org/wiki/Relative_luminance 9206 9207 // We need to know the color *before* creating the element 9208 // so the element doesn't exist yet when this function is called 9209 // therefore, need to create a temporary element then remove it after color is determined 9210 // Temp element must be added to the DOM or WebKit can't retrieve its CSS properties 9211 9212 var $elements, i, $el, bgColor, rgb, red, green, blue, luminance, iconColor; 9213 9214 $elements = ['controller', 'toolbar']; 9215 for (i=0; i<$elements.length; i++) { 9216 if ($elements[i] == 'controller') { 9217 $el = $('<div>', { 9218 'class': 'able-controller' 9219 }).hide(); 9220 } else if ($elements[i] === 'toolbar') { 9221 $el = $('<div>', { 9222 'class': 'able-window-toolbar' 9223 }).hide(); 9224 } 9225 $('body').append($el); 9226 bgColor = $el.css('background-color'); 9227 // bgColor is a string in the form 'rgb(R, G, B)', perhaps with a 4th item for alpha; 9228 // split the 3 or 4 channels into an array 9229 rgb = bgColor.replace(/[^\d,]/g, '').split(','); 9230 red = rgb[0]; 9231 green = rgb[1]; 9232 blue = rgb[2]; 9233 luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue); 9234 // range is 1 - 255; therefore 125 is the tipping point 9235 iconColor = (luminance < 125) ? 'white' : 'black'; 9236 9237 if ($elements[i] === 'controller') { 9238 this.iconColor = iconColor; 9239 } else if ($elements[i] === 'toolbar') { 9240 this.toolbarIconColor = iconColor; 9241 } 9242 $el.remove(); 9243 } 9244 }; 9245 9246 AblePlayer.prototype.getIconData = function(button) { 9247 9248 // returns array of values for creating <svg> tag for specified button 9249 // 0 = <svg> viewBox attribute 9250 // 1 = <path> d (description) attribute 9251 // 2 = icon class for font icons 9252 // 3 = img URL for images. 9253 var svg = Array(); 9254 9255 switch (button) { 9256 9257 case 'play': 9258 svg[0] = '0 0 16 20'; 9259 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'; 9260 break; 9261 9262 case 'pause': 9263 svg[0] = '0 0 20 20'; 9264 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'; 9265 break; 9266 9267 case 'restart': 9268 svg[0] = '0 0 20 20'; 9269 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'; 9270 break; 9271 9272 case 'rewind': 9273 svg[0] = '0 0 20 20'; 9274 svg[1] = 'M11.25 3.125v6.25l6.25-6.25v13.75l-6.25-6.25v6.25l-6.875-6.875z'; 9275 break; 9276 9277 case 'forward': 9278 svg[0] = '0 0 20 20'; 9279 svg[1] = 'M10 16.875v-6.25l-6.25 6.25v-13.75l6.25 6.25v-6.25l6.875 6.875z'; 9280 break; 9281 9282 case 'previous': 9283 svg[0] = '0 0 20 20'; 9284 svg[1] = 'M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z'; 9285 break; 9286 9287 case 'next': 9288 svg[0] = '0 0 20 20'; 9289 svg[1] = 'M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z'; 9290 break; 9291 9292 case 'slower': 9293 svg[0] = '0 0 11 20'; 9294 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'; 9295 break; 9296 9297 case 'faster': 9298 svg[0] = '0 0 11 20'; 9299 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'; 9300 break; 9301 9302 case 'turtle': 9303 svg[0] = '0 0 20 20'; 9304 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'; 9305 break; 9306 9307 case 'rabbit': 9308 svg[0] = '0 0 20 20'; 9309 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'; 9310 break; 9311 9312 case 'ellipsis': 9313 svg[0] = '0 0 20 20'; 9314 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'; 9315 break; 9316 9317 case 'pipe': 9318 svg[0] = '0 0 20 20'; 9319 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'; 9320 break; 9321 9322 case 'captions': 9323 svg[0] = '0 0 20 20'; 9324 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'; 9325 break; 9326 9327 case 'descriptions': 9328 svg[0] = '0 0 20 20'; 9329 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'; 9330 break; 9331 9332 case 'sign': 9333 svg[0] = '0 0 20 20'; 9334 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'; 9335 break; 9336 9337 case 'mute': 9338 case 'volume-mute': 9339 svg[0] = '0 0 20 20'; 9340 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'; 9341 break; 9342 9343 case 'volume-soft': 9344 svg[0] = '0 0 20 20'; 9345 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'; 9346 break; 9347 9348 case 'volume-medium': 9349 svg[0] = '0 0 20 20'; 9350 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'; 9351 break; 9352 9353 case 'volume-loud': 9354 svg[0] = '0 0 21 20'; 9355 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'; 9356 break; 9357 9358 case 'chapters': 9359 svg[0] = '0 0 20 20'; 9360 svg[1] = 'M5 2.5v17.5l6.25-6.25 6.25 6.25v-17.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z'; 9361 break; 9362 9363 case 'transcript': 9364 svg[0] = '0 0 20 20'; 9365 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'; 9366 break; 9367 9368 case 'preferences': 9369 svg[0] = '0 0 20 20'; 9370 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'; 9371 break; 9372 9373 case 'close': 9374 svg[0] = '0 0 16 20'; 9375 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'; 9376 break; 9377 9378 case 'fullscreen-expand': 9379 svg[0] = '0 0 20 20'; 9380 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'; 9381 break; 9382 9383 case 'fullscreen-collapse': 9384 svg[0] = '0 0 20 20'; 9385 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'; 9386 break; 9387 } 9388 9389 return svg; 9390 }; 9391 9392 // Initialize player based on data on page. 9393 // This sets some variables, but does not modify anything. Safe to call multiple times. 9394 // Can call again after updating this.media so long as new media element has the same ID. 9395 AblePlayer.prototype.reinitialize = function () { 9396 9397 var deferred, promise; 9398 9399 deferred = new this.defer(); 9400 promise = deferred.promise(); 9401 9402 this.startedPlaying = false; 9403 // TODO: Move this setting to preferences. 9404 this.autoScrollTranscript = true; 9405 //this.autoScrollTranscript = this.getPref(autoScrollTranscript); // (doesn't work) 9406 9407 // Bootstrap from this.media possibly being an ID or other selector. 9408 this.$media = $(this.media).first(); 9409 this.media = this.$media[0]; 9410 9411 // Set media type to 'audio' or 'video'; this determines some of the behavior of player creation. 9412 if (this.$media.is('audio')) { 9413 this.mediaType = 'audio'; 9414 } else if (this.$media.is('video')) { 9415 this.mediaType = 'video'; 9416 } else { 9417 // Able Player was initialized with some element other than <video> or <audio> 9418 this.provideFallback(); 9419 deferred.reject(); 9420 return promise; 9421 } 9422 9423 this.$sources = this.$media.find('source'); 9424 9425 this.player = this.getPlayer(); 9426 if (!this.player) { 9427 // an error was generated in getPlayer() 9428 this.provideFallback(); 9429 } 9430 9431 deferred.resolve(); 9432 return promise; 9433 }; 9434 9435 AblePlayer.prototype.setPlayerSize = function(width, height) { 9436 9437 // Called again after width and height are known 9438 if (this.mediaType !== 'audio' && width > 0 && height > 0) { 9439 this.playerWidth = width; 9440 this.playerHeight = height; 9441 this.aspectRatio = height / width; 9442 } 9443 }; 9444 9445 // Perform one-time setup for this instance of player; called after player is first initialized. 9446 AblePlayer.prototype.setupInstance = function () { 9447 9448 var deferred = new this.defer(); 9449 var promise = deferred.promise(); 9450 9451 if (this.$media.attr('id')) { 9452 this.mediaId = this.$media.attr('id'); 9453 } else { 9454 // Ensure the base media element always has an ID. 9455 this.mediaId = "ableMediaId_" + this.ableIndex; 9456 this.$media.attr('id', this.mediaId); 9457 } 9458 deferred.resolve(); 9459 return promise; 9460 }; 9461 9462 AblePlayer.prototype.setupInstancePlaylist = function() { 9463 9464 // find a matching playlist and set this.hasPlaylist 9465 // if there is one, also set this.$playlist, this.playlistIndex, & this.playlistEmbed 9466 var thisObj = this; 9467 9468 this.hasPlaylist = false; // will change to true if a matching playlist is found 9469 9470 $('.able-playlist').each(function() { 9471 if ($(this).data('player') === thisObj.mediaId) { 9472 // this is the playlist for the current player 9473 thisObj.hasPlaylist = true; 9474 // If using an embedded player, we'll replace $playlist with the clone later. 9475 thisObj.$playlist = $(this).find('li'); 9476 9477 // check to see if list item has YouTube as its source 9478 // if it does, inject a thumbnail from YouTube 9479 var $youTubeVideos = $(this).find('li[data-youtube-id]'); 9480 $youTubeVideos.each(function() { 9481 var youTubeId = purify.sanitize( $(this).attr('data-youtube-id') ); 9482 var youTubePoster = thisObj.getYouTubePosterUrl(youTubeId,'120'); 9483 var $youTubeImg = $('<img>',{ 9484 'src': youTubePoster, 9485 'alt': '' 9486 }); 9487 $(this).find('button').prepend($youTubeImg); 9488 }); 9489 9490 // check to see if list item has Vimeo as its source 9491 // if it does, inject a thumbnail from Vimeo 9492 var $vimeoVideos = $(this).find('li[data-vimeo-id]'); 9493 $vimeoVideos.each(function() { 9494 var vimeoId = $(this).attr('data-vimeo-id'); 9495 var vimeoPoster = thisObj.getVimeoPosterUrl(vimeoId,'120'); 9496 var $vimeoImg = $('<img>',{ 9497 'src': vimeoPoster, 9498 'alt': '' 9499 }); 9500 $(this).find('button').prepend($vimeoImg); 9501 }); 9502 9503 // add accessibility to the list markup 9504 $(this).find('li span').attr('aria-hidden','true'); 9505 thisObj.playlistIndex = 0; 9506 var dataEmbedded = $(this).data('embedded'); 9507 // is playlist embedded within player? 9508 thisObj.playlistEmbed = (typeof dataEmbedded !== 'undefined' && dataEmbedded !== false) ? true : false; 9509 } 9510 }); 9511 9512 if (this.hasPlaylist && this.loop) { 9513 // browser will loop the current track in the playlist, rather than the playlist 9514 // therefore, need to remove loop attribute from media element 9515 // but keep this.loop as true and handle the playlist looping ourselves 9516 this.media.removeAttribute('loop'); 9517 } 9518 if (this.hasPlaylist && this.playlistEmbed) { 9519 // Copy the playlist out of the dom, so we can reinject when we build the player. 9520 var parent = this.$playlist.parent(); 9521 this.$playlistDom = parent.clone(); 9522 parent.remove(); 9523 } 9524 if (this.hasPlaylist && this.$sources.length === 0) { 9525 // no source elements were provided. Construct them from the first playlist item 9526 this.cuePlaylistItem(0); 9527 // redefine this.$sources now that media contains one or more <source> elements 9528 this.$sources = this.$media.find('source'); 9529 } 9530 }; 9531 9532 AblePlayer.prototype.recreatePlayer = function () { 9533 9534 // Creates the appropriate player for the current source. 9535 // This function is called each time a new media instance is loaded 9536 // e.g., 9537 // User clicks on an item in a playlist 9538 // User swaps to/from described version of video 9539 // Blocks of code that only need to be executed once are controlled 9540 // by this.playerCreated 9541 9542 // TODO: Ensure when recreating player that we carry over the mediaId 9543 if (!this.player) { 9544 return; 9545 } 9546 9547 var deferred, promise, thisObj, prefsGroups, i; 9548 9549 deferred = new this.defer(); 9550 promise = deferred.promise(); 9551 thisObj = this; 9552 9553 this.playerDeleted = false; // reset after deletePlayer() 9554 9555 // set temp stopgap to prevent this function from executing again before finished 9556 this.recreatingPlayer = true; 9557 9558 if (!this.playerCreated) { 9559 // only call these functions once 9560 this.loadCurrentPreferences(); 9561 this.injectPlayerCode(); 9562 this.resizePlayer(this.media.videoWidth,this.media.videoHeight); 9563 } 9564 9565 this.getSampleDescriptionText(); 9566 9567 this.initSignLanguage(); 9568 9569 this.initPlayer().then(function() { 9570 9571 thisObj.getTracks().then(function() { 9572 9573 thisObj.initDescription().then(function() { 9574 9575 thisObj.setupTracks().then(function() { 9576 if (thisObj.hasClosedDesc) { 9577 if (!thisObj.$descDiv || (thisObj.$descDiv && !($.contains(thisObj.$ableDiv[0], thisObj.$descDiv[0])))) { 9578 // descDiv either doesn't exist, or exists in an orphaned state 9579 // Either way, it needs to be rebuilt... 9580 thisObj.injectTextDescriptionArea(); 9581 } 9582 } 9583 thisObj.initSpeech('init'); 9584 9585 thisObj.setupTranscript().then(function() { 9586 9587 thisObj.initStenoFrame().then(function() { 9588 9589 if (thisObj.stenoMode && thisObj.$stenoFrame) { 9590 thisObj.stenoFrameContents = thisObj.$stenoFrame.contents(); 9591 } 9592 thisObj.getMediaTimes().then(function(mediaTimes) { 9593 9594 thisObj.duration = mediaTimes['duration']; 9595 thisObj.elapsed = mediaTimes['elapsed']; 9596 9597 if (typeof thisObj.volume === 'undefined') { 9598 thisObj.volume = thisObj.defaultVolume; 9599 } 9600 if (thisObj.volume) { 9601 thisObj.setVolume(thisObj.volume); 9602 } 9603 if (thisObj.transcriptType) { 9604 thisObj.addTranscriptAreaEvents(); 9605 thisObj.updateTranscript(); 9606 } 9607 if (thisObj.captions.length) { 9608 thisObj.initDefaultCaption(); 9609 } 9610 9611 // setMediaAttributes() sets textTrack.mode to 'disabled' for all tracks 9612 // This tells browsers to ignore the text tracks so Able Player can handle them 9613 // However, timing is critical as browsers - especially Safari - tend to ignore this request 9614 // unless it's sent late in the intialization process. 9615 // If browsers ignore the request, the result is redundant captions 9616 thisObj.setMediaAttributes(); 9617 thisObj.addControls(); 9618 thisObj.addEventListeners(); 9619 9620 // inject each of the hidden forms that will be accessed from the Preferences popup menu 9621 prefsGroups = thisObj.getPreferencesGroups(); 9622 for (i = 0; i < prefsGroups.length; i++) { 9623 thisObj.injectPrefsForm(prefsGroups[i]); 9624 } 9625 thisObj.setupPopups(); 9626 thisObj.updateCaption(); 9627 thisObj.injectVTS(); 9628 thisObj.populateChaptersDiv(); 9629 thisObj.showSearchResults(); 9630 9631 // Go ahead and load media, without user requesting it 9632 // Ideally, we would wait until user clicks play, rather than unnecessarily consume their bandwidth 9633 // However, the media needs to load for us to get the media's duration 9634 if (thisObj.player === 'html5') { 9635 if (!thisObj.loadingMedia) { 9636 thisObj.$media[0].load(); 9637 thisObj.loadingMedia = true; 9638 } 9639 } 9640 // refreshControls is called twice building/initializing the player 9641 // this is the second. Best to pause a bit before executing, to be sure all prior steps are complete 9642 setTimeout(function() { 9643 thisObj.refreshControls(); 9644 deferred.resolve(); 9645 },100); 9646 }); 9647 }); 9648 }); 9649 }); 9650 }); 9651 }); 9652 }, 9653 function() { // initPlayer fail 9654 thisObj.provideFallback(); 9655 }); 9656 return promise; 9657 }; 9658 9659 AblePlayer.prototype.initPlayer = function () { 9660 9661 var thisObj = this; 9662 var playerPromise; 9663 // First run player specific initialization. 9664 if (this.player === 'html5') { 9665 playerPromise = this.initHtml5Player(); 9666 } else if (this.player === 'youtube') { 9667 playerPromise = this.initYouTubePlayer(); 9668 } else if (this.player === 'vimeo') { 9669 playerPromise = this.initVimeoPlayer(); 9670 } 9671 // After player specific initialization is done, run remaining general initialization. 9672 var deferred = new this.defer(); 9673 var promise = deferred.promise(); 9674 playerPromise.then( 9675 function () { // done/resolved 9676 if (thisObj.useFixedSeekInterval) { 9677 // if fixed seekInterval was not already assigned (using value of data-seek-interval) 9678 if (!thisObj.seekInterval) { 9679 thisObj.seekInterval = thisObj.defaultSeekInterval; 9680 } 9681 thisObj.seekIntervalCalculated = true; 9682 } else { 9683 thisObj.setSeekInterval(); 9684 } 9685 deferred.resolve(); 9686 } 9687 ).finally(function () { // failed 9688 deferred.reject(); 9689 } 9690 ); 9691 9692 return promise; 9693 }; 9694 9695 AblePlayer.prototype.initStenoFrame = function() { 9696 9697 var deferred, promise; 9698 deferred = new this.defer(); 9699 promise = deferred.promise(); 9700 9701 if (this.stenoMode && this.$stenoFrame) { 9702 9703 if (this.$stenoFrame[0].contentWindow,document.readyState == 'complete') { 9704 // iframe has already loaded 9705 deferred.resolve(); 9706 } else { 9707 // iframe has not loaded. Wait for it. 9708 this.$stenoFrame.on('load',function() { 9709 deferred.resolve(); 9710 }); 9711 } 9712 } else { 9713 // there is no stenoFrame to initialize 9714 deferred.resolve(); 9715 } 9716 return promise; 9717 }; 9718 9719 AblePlayer.prototype.setSeekInterval = function () { 9720 9721 // this function is only called if this.useFixedSeekInterval is false 9722 // if this.useChapterTimes, this is called as each new chapter is loaded 9723 // otherwise, it's called once, as the player is initialized 9724 var duration; 9725 this.seekInterval = this.defaultSeekInterval; 9726 duration = (this.useChapterTimes) ? this.chapterDuration : this.duration; 9727 9728 if (typeof duration === 'undefined' || duration < 1) { 9729 // no duration; just use default for now but keep trying until duration is available 9730 this.seekIntervalCalculated = false; 9731 return; 9732 } else { 9733 if (duration <= 20) { 9734 this.seekInterval = 5; // 4 steps max 9735 } else if (duration <= 30) { 9736 this.seekInterval = 6; // 5 steps max 9737 } else if (duration <= 40) { 9738 this.seekInterval = 8; // 5 steps max 9739 } else if (duration <= 100) { 9740 this.seekInterval = 10; // 10 steps max 9741 } else { 9742 // never more than 10 steps from start to end 9743 this.seekInterval = Math.round(duration / 10, 0); 9744 } 9745 this.seekIntervalCalculated = true; 9746 } 9747 }; 9748 9749 AblePlayer.prototype.initDefaultCaption = function () { 9750 9751 var captions, i; 9752 9753 captions = this.captions; 9754 if (captions.length > 0) { 9755 for (i=0; i<captions.length; i++) { 9756 if (captions[i].def === true) { 9757 this.captionLang = captions[i].language; 9758 this.selectedCaptions = captions[i]; 9759 } 9760 } 9761 if (typeof this.captionLang === 'undefined') { 9762 // No caption track was flagged as default 9763 // find and use a caption language that matches the player language 9764 for (i=0; i<captions.length; i++) { 9765 if (captions[i].language === this.lang) { 9766 this.captionLang = captions[i].language; 9767 this.selectedCaptions = captions[i]; 9768 } 9769 } 9770 } 9771 if (typeof this.captionLang === 'undefined') { 9772 // Still no matching caption track 9773 // just use the first track 9774 this.captionLang = captions[0].language; 9775 this.selectedCaptions = captions[0]; 9776 } 9777 if (typeof this.captionLang !== 'undefined') { 9778 // reset transcript selected <option> to this.captionLang 9779 if (this.$transcriptLanguageSelect) { 9780 this.$transcriptLanguageSelect.find('option[lang=' + this.captionLang + ']').prop('selected',true); 9781 } 9782 // sync all other tracks to this same languge 9783 this.syncTrackLanguages('init',this.captionLang); 9784 } 9785 if (this.player === 'vimeo') { 9786 if (this.usingVimeoCaptions && this.prefCaptions == 1) { 9787 // initialize Vimeo captions to the default language 9788 this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) { 9789 switch (error.name) { 9790 } 9791 }); 9792 } else { 9793 // disable Vimeo captions. 9794 this.vimeoPlayer.disableTextTrack().then(function() { 9795 // Vimeo captions disabled 9796 }).catch(function(error) { 9797 }); 9798 } 9799 } 9800 } 9801 }; 9802 9803 AblePlayer.prototype.initHtml5Player = function () { 9804 // Nothing special to do! 9805 var deferred = new this.defer(); 9806 var promise = deferred.promise(); 9807 deferred.resolve(); 9808 return promise; 9809 }; 9810 9811 // Sets media/track/source attributes; is called whenever player is recreated since $media may have changed. 9812 AblePlayer.prototype.setMediaAttributes = function () { 9813 // Firefox puts videos in tab order; remove. 9814 this.$media.attr('tabindex', -1); 9815 9816 // Keep native player from displaying captions/subtitles by setting textTrack.mode='disabled' 9817 // https://dev.w3.org/html5/spec-author-view/video.html#text-track-mode 9818 // This *should* work but historically hasn't been supported in all browsers 9819 // As of July 2025, 96% supported per https://caniuse.com/?search=text-track-mode. 9820 // Workaround for non-supporting browsers is to remove default attribute 9821 // We're doing that too in track.js > setupCaptions() 9822 var textTracks = this.$media.get(0).textTracks; 9823 if (textTracks) { 9824 var i = 0; 9825 while (i < textTracks.length) { 9826 textTracks[i].mode = 'disabled'; 9827 i += 1; 9828 } 9829 } 9830 }; 9831 9832 AblePlayer.prototype.getPlayer = function() { 9833 9834 // Determine which player to use, if any 9835 // return 'html5', 'youtube', 'vimeo', or null 9836 if (this.testFallback) { 9837 return null; 9838 } else if (this.youTubeId) { 9839 // null if attempting to play a YouTube video using an element other than <video> 9840 return (this.mediaType !== 'video') ? null : 'youtube'; 9841 } else if (this.vimeoId) { 9842 // null if attempting to play a Vimeo video using an element other than <video> 9843 return (this.mediaType !== 'video') ? null : 'vimeo'; 9844 } else if (this.media.canPlayType) { 9845 return 'html5'; 9846 } else { 9847 // Browser does not support the available media file 9848 return null; 9849 } 9850 }; 9851 } 9852 9853 function addLangsFunctions(AblePlayer) { 9854 // Look up ISO 639-1 language codes to be used as subtitle labels 9855 // In some instances "name" has been trunctation for readability 9856 // Sources: 9857 // http://stackoverflow.com/questions/3217492/list-of-language-codes-in-yaml-or-json/4900304#4900304 9858 // https://www.venea.net/web/culture_code 9859 9860 var isoLangs = { 9861 "ab":{ 9862 "name":"Abkhaz", 9863 "nativeName":"аҧÑÑа" 9864 }, 9865 "aa":{ 9866 "name":"Afar", 9867 "nativeName":"Afaraf" 9868 }, 9869 "af":{ 9870 "name":"Afrikaans", 9871 "nativeName":"Afrikaans" 9872 }, 9873 "ak":{ 9874 "name":"Akan", 9875 "nativeName":"Akan" 9876 }, 9877 "sq":{ 9878 "name":"Albanian", 9879 "nativeName":"Shqip" 9880 }, 9881 "am":{ 9882 "name":"Amharic", 9883 "nativeName":"á ááá" 9884 }, 9885 "ar":{ 9886 "name":"Arabic", 9887 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ©" 9888 }, 9889 "an":{ 9890 "name":"Aragonese", 9891 "nativeName":"Aragonés" 9892 }, 9893 "hy":{ 9894 "name":"Armenian", 9895 "nativeName":"ÕÕ¡ÕµÕ¥ÖÕ¥Õ¶" 9896 }, 9897 "as":{ 9898 "name":"Assamese", 9899 "nativeName":"ঠসমà§à¦¯à¦¼à¦¾" 9900 }, 9901 "av":{ 9902 "name":"Avaric", 9903 "nativeName":"Ð°Ð²Ð°Ñ Ð¼Ð°ÑÓ, магÓаÑÑл маÑÓ" 9904 }, 9905 "ae":{ 9906 "name":"Avestan", 9907 "nativeName":"avesta" 9908 }, 9909 "ay":{ 9910 "name":"Aymara", 9911 "nativeName":"aymar aru" 9912 }, 9913 "az":{ 9914 "name":"Azerbaijani", 9915 "nativeName":"azÉrbaycan dili" 9916 }, 9917 "bm":{ 9918 "name":"Bambara", 9919 "nativeName":"bamanankan" 9920 }, 9921 "ba":{ 9922 "name":"Bashkir", 9923 "nativeName":"баÑҡоÑÑ Ñеле" 9924 }, 9925 "eu":{ 9926 "name":"Basque", 9927 "nativeName":"euskara, euskera" 9928 }, 9929 "be":{ 9930 "name":"Belarusian", 9931 "nativeName":"ÐелаÑÑÑкаÑ" 9932 }, 9933 "bn":{ 9934 "name":"Bengali", 9935 "nativeName":"বাà¦à¦²à¦¾" 9936 }, 9937 "bh":{ 9938 "name":"Bihari", 9939 "nativeName":"à¤à¥à¤à¤ªà¥à¤°à¥" 9940 }, 9941 "bi":{ 9942 "name":"Bislama", 9943 "nativeName":"Bislama" 9944 }, 9945 "bs":{ 9946 "name":"Bosnian", 9947 "nativeName":"bosanski jezik" 9948 }, 9949 "br":{ 9950 "name":"Breton", 9951 "nativeName":"brezhoneg" 9952 }, 9953 "bg":{ 9954 "name":"Bulgarian", 9955 "nativeName":"бÑлгаÑÑки език" 9956 }, 9957 "my":{ 9958 "name":"Burmese", 9959 "nativeName":"ááá¬á á¬" 9960 }, 9961 "ca":{ 9962 "name":"Catalan", 9963 "nativeName":"Català " 9964 }, 9965 "ch":{ 9966 "name":"Chamorro", 9967 "nativeName":"Chamoru" 9968 }, 9969 "ce":{ 9970 "name":"Chechen", 9971 "nativeName":"Ð½Ð¾Ñ Ñийн моÑÑ" 9972 }, 9973 "ny":{ 9974 "name":"Chichewa", 9975 "nativeName":"chiCheŵa, chinyanja" 9976 }, 9977 "zh":{ 9978 "name":"Chinese", 9979 "nativeName":"䏿 (ZhÅngwén), æ±è¯, æ¼¢èª" 9980 }, 9981 "cv":{ 9982 "name":"Chuvash", 9983 "nativeName":"ÑÓÐ²Ð°Ñ ÑÓÐ»Ñ Ð¸" 9984 }, 9985 "kw":{ 9986 "name":"Cornish", 9987 "nativeName":"Kernewek" 9988 }, 9989 "co":{ 9990 "name":"Corsican", 9991 "nativeName":"corsu, lingua corsa" 9992 }, 9993 "cr":{ 9994 "name":"Cree", 9995 "nativeName":"áá¦ááááá£" 9996 }, 9997 "hr":{ 9998 "name":"Croatian", 9999 "nativeName":"hrvatski" 10000 }, 10001 "cs":{ 10002 "name":"Czech", 10003 "nativeName":"Äesky, ÄeÅ¡tina" 10004 }, 10005 "da":{ 10006 "name":"Danish", 10007 "nativeName":"dansk" 10008 }, 10009 "dv":{ 10010 "name":"Divehi", 10011 "nativeName":"ÞÞ¨ÞÞ¬ÞÞ¨" 10012 }, 10013 "nl":{ 10014 "name":"Dutch", 10015 "nativeName":"Nederlands, Vlaams" 10016 }, 10017 "en":{ 10018 "name":"English", 10019 "nativeName":"English" 10020 }, 10021 "eo":{ 10022 "name":"Esperanto", 10023 "nativeName":"Esperanto" 10024 }, 10025 "et":{ 10026 "name":"Estonian", 10027 "nativeName":"eesti, eesti keel" 10028 }, 10029 "ee":{ 10030 "name":"Ewe", 10031 "nativeName":"EÊegbe" 10032 }, 10033 "fo":{ 10034 "name":"Faroese", 10035 "nativeName":"føroyskt" 10036 }, 10037 "fj":{ 10038 "name":"Fijian", 10039 "nativeName":"vosa Vakaviti" 10040 }, 10041 "fi":{ 10042 "name":"Finnish", 10043 "nativeName":"suomi, suomen kieli" 10044 }, 10045 "fr":{ 10046 "name":"French", 10047 "nativeName":"français, langue française" 10048 }, 10049 "ff":{ 10050 "name":"Fula", 10051 "nativeName":"Fulfulde, Pulaar, Pular" 10052 }, 10053 "gl":{ 10054 "name":"Galician", 10055 "nativeName":"Galego" 10056 }, 10057 "ka":{ 10058 "name":"Georgian", 10059 "nativeName":"á¥áá áá£áá" 10060 }, 10061 "de":{ 10062 "name":"German", 10063 "nativeName":"Deutsch" 10064 }, 10065 "el":{ 10066 "name":"Greek", 10067 "nativeName":"Îλληνικά" 10068 }, 10069 "gn":{ 10070 "name":"GuaranÃ", 10071 "nativeName":"Avañeẽ" 10072 }, 10073 "gu":{ 10074 "name":"Gujarati", 10075 "nativeName":"àªà«àªàª°àª¾àª¤à«" 10076 }, 10077 "ht":{ 10078 "name":"Haitian", 10079 "nativeName":"Kreyòl ayisyen" 10080 }, 10081 "ha":{ 10082 "name":"Hausa", 10083 "nativeName":"Hausa, ÙÙÙÙØ³Ù" 10084 }, 10085 "he":{ 10086 "name":"Hebrew", 10087 "nativeName":"×¢×ר×ת" 10088 }, 10089 "hz":{ 10090 "name":"Herero", 10091 "nativeName":"Otjiherero" 10092 }, 10093 "hi":{ 10094 "name":"Hindi", 10095 "nativeName":"हिनà¥à¤¦à¥, हिà¤à¤¦à¥" 10096 }, 10097 "ho":{ 10098 "name":"Hiri Motu", 10099 "nativeName":"Hiri Motu" 10100 }, 10101 "hu":{ 10102 "name":"Hungarian", 10103 "nativeName":"Magyar" 10104 }, 10105 "ia":{ 10106 "name":"Interlingua", 10107 "nativeName":"Interlingua" 10108 }, 10109 "id":{ 10110 "name":"Indonesian", 10111 "nativeName":"Bahasa Indonesia" 10112 }, 10113 "ie":{ 10114 "name":"Interlingue", 10115 "nativeName":"Originally called Occidental; then Interlingue after WWII" 10116 }, 10117 "ga":{ 10118 "name":"Irish", 10119 "nativeName":"Gaeilge" 10120 }, 10121 "ig":{ 10122 "name":"Igbo", 10123 "nativeName":"Asụsụ Igbo" 10124 }, 10125 "ik":{ 10126 "name":"Inupiaq", 10127 "nativeName":"Iñupiaq, Iñupiatun" 10128 }, 10129 "io":{ 10130 "name":"Ido", 10131 "nativeName":"Ido" 10132 }, 10133 "is":{ 10134 "name":"Icelandic", 10135 "nativeName":"Ãslenska" 10136 }, 10137 "it":{ 10138 "name":"Italian", 10139 "nativeName":"Italiano" 10140 }, 10141 "iu":{ 10142 "name":"Inuktitut", 10143 "nativeName":"áááááá¦" 10144 }, 10145 "ja":{ 10146 "name":"Japanese", 10147 "nativeName":"æ¥æ¬èª (ã«ã»ããï¼ã«ã£ã½ãã)" 10148 }, 10149 "jv":{ 10150 "name":"Javanese", 10151 "nativeName":"basa Jawa" 10152 }, 10153 "kl":{ 10154 "name":"Kalaallisut", 10155 "nativeName":"kalaallisut, kalaallit oqaasii" 10156 }, 10157 "kn":{ 10158 "name":"Kannada", 10159 "nativeName":"à²à²¨à³à²¨à²¡" 10160 }, 10161 "kr":{ 10162 "name":"Kanuri", 10163 "nativeName":"Kanuri" 10164 }, 10165 "ks":{ 10166 "name":"Kashmiri", 10167 "nativeName":"à¤à¤¶à¥à¤®à¥à¤°à¥, ÙØ´Ù ÙØ±Ùâ" 10168 }, 10169 "kk":{ 10170 "name":"Kazakh", 10171 "nativeName":"ÒÐ°Ð·Ð°Ò ÑÑлÑ" 10172 }, 10173 "km":{ 10174 "name":"Khmer", 10175 "nativeName":"áá¶áá¶ááááá" 10176 }, 10177 "ki":{ 10178 "name":"Kikuyu", 10179 "nativeName":"GÄ©kÅ©yÅ©" 10180 }, 10181 "rw":{ 10182 "name":"Kinyarwanda", 10183 "nativeName":"Ikinyarwanda" 10184 }, 10185 "ky":{ 10186 "name":"Kyrgyz", 10187 "nativeName":"кÑÑгÑз Ñили" 10188 }, 10189 "kv":{ 10190 "name":"Komi", 10191 "nativeName":"коми кÑв" 10192 }, 10193 "kg":{ 10194 "name":"Kongo", 10195 "nativeName":"KiKongo" 10196 }, 10197 "ko":{ 10198 "name":"Korean", 10199 "nativeName":"íêµì´ (éåèª), ì¡°ì ë§ (æé®®èª)" 10200 }, 10201 "ku":{ 10202 "name":"Kurdish", 10203 "nativeName":"Kurdî, ÙÙØ±Ø¯Ûâ" 10204 }, 10205 "kj":{ 10206 "name":"Kuanyama", 10207 "nativeName":"Kuanyama" 10208 }, 10209 "la":{ 10210 "name":"Latin", 10211 "nativeName":"latine, lingua latina" 10212 }, 10213 "lb":{ 10214 "name":"Luxembourgish", 10215 "nativeName":"Lëtzebuergesch" 10216 }, 10217 "lg":{ 10218 "name":"Luganda", 10219 "nativeName":"Luganda" 10220 }, 10221 "li":{ 10222 "name":"Limburgish", 10223 "nativeName":"Limburgs" 10224 }, 10225 "ln":{ 10226 "name":"Lingala", 10227 "nativeName":"Lingála" 10228 }, 10229 "lo":{ 10230 "name":"Lao", 10231 "nativeName":"àºàº²àºªàº²àº¥àº²àº§" 10232 }, 10233 "lt":{ 10234 "name":"Lithuanian", 10235 "nativeName":"lietuvių kalba" 10236 }, 10237 "lu":{ 10238 "name":"Luba-Katanga", 10239 "nativeName":"" 10240 }, 10241 "lv":{ 10242 "name":"Latvian", 10243 "nativeName":"latvieÅ¡u valoda" 10244 }, 10245 "gv":{ 10246 "name":"Manx", 10247 "nativeName":"Gaelg, Gailck" 10248 }, 10249 "mk":{ 10250 "name":"Macedonian", 10251 "nativeName":"македонÑки Ñазик" 10252 }, 10253 "mg":{ 10254 "name":"Malagasy", 10255 "nativeName":"Malagasy fiteny" 10256 }, 10257 "ms":{ 10258 "name":"Malay", 10259 "nativeName":"bahasa Melayu, Ø¨ÙØ§Ø³ Ù ÙØ§ÙÙâ" 10260 }, 10261 "ml":{ 10262 "name":"Malayalam", 10263 "nativeName":"മലയാളà´" 10264 }, 10265 "mt":{ 10266 "name":"Maltese", 10267 "nativeName":"Malti" 10268 }, 10269 "mi":{ 10270 "name":"MÄori", 10271 "nativeName":"te reo MÄori" 10272 }, 10273 "mr":{ 10274 "name":"Marathi", 10275 "nativeName":"मराठà¥" 10276 }, 10277 "mh":{ 10278 "name":"Marshallese", 10279 "nativeName":"Kajin M̧ajeļ" 10280 }, 10281 "mn":{ 10282 "name":"Mongolian", 10283 "nativeName":"монгол" 10284 }, 10285 "na":{ 10286 "name":"Nauru", 10287 "nativeName":"EkakairÅ© Naoero" 10288 }, 10289 "nv":{ 10290 "name":"Navajo", 10291 "nativeName":"Diné bizaad, DinékʼehǰÃ" 10292 }, 10293 "nb":{ 10294 "name":"Norwegian BokmÃ¥l", 10295 "nativeName":"Norsk bokmÃ¥l" 10296 }, 10297 "nd":{ 10298 "name":"North Ndebele", 10299 "nativeName":"isiNdebele" 10300 }, 10301 "ne":{ 10302 "name":"Nepali", 10303 "nativeName":"नà¥à¤ªà¤¾à¤²à¥" 10304 }, 10305 "ng":{ 10306 "name":"Ndonga", 10307 "nativeName":"Owambo" 10308 }, 10309 "nn":{ 10310 "name":"Norwegian Nynorsk", 10311 "nativeName":"Norsk nynorsk" 10312 }, 10313 "no":{ 10314 "name":"Norwegian", 10315 "nativeName":"Norsk" 10316 }, 10317 "ii":{ 10318 "name":"Nuosu", 10319 "nativeName":"êê ê¿ Nuosuhxop" 10320 }, 10321 "nr":{ 10322 "name":"South Ndebele", 10323 "nativeName":"isiNdebele" 10324 }, 10325 "oc":{ 10326 "name":"Occitan", 10327 "nativeName":"Occitan" 10328 }, 10329 "oj":{ 10330 "name":"Ojibwe", 10331 "nativeName":"ááááá¯á§áá" 10332 }, 10333 "cu":{ 10334 "name":"Church Slavonic", 10335 "nativeName":"ѩзÑÐºÑ ÑловѣнÑÑкÑ" 10336 }, 10337 "om":{ 10338 "name":"Oromo", 10339 "nativeName":"Afaan Oromoo" 10340 }, 10341 "or":{ 10342 "name":"Oriya", 10343 "nativeName":"à¬à¬¡à¬¼à¬¿à¬" 10344 }, 10345 "os":{ 10346 "name":"Ossetian", 10347 "nativeName":"иÑон æвзаг" 10348 }, 10349 "pa":{ 10350 "name":"Punjabi", 10351 "nativeName":"ਪੰà¨à¨¾à¨¬à©, Ù¾ÙØ¬Ø§Ø¨Ûâ" 10352 }, 10353 "pi":{ 10354 "name":"PÄli", 10355 "nativeName":"पाऴि" 10356 }, 10357 "fa":{ 10358 "name":"Persian", 10359 "nativeName":"ÙØ§Ø±Ø³Û" 10360 }, 10361 "pl":{ 10362 "name":"Polish", 10363 "nativeName":"polski" 10364 }, 10365 "ps":{ 10366 "name":"Pashto", 10367 "nativeName":"Ù¾ÚØªÙ" 10368 }, 10369 "pt":{ 10370 "name":"Portuguese", 10371 "nativeName":"Português" 10372 }, 10373 "qu":{ 10374 "name":"Quechua", 10375 "nativeName":"Runa Simi, Kichwa" 10376 }, 10377 "rm":{ 10378 "name":"Romansh", 10379 "nativeName":"rumantsch grischun" 10380 }, 10381 "rn":{ 10382 "name":"Kirundi", 10383 "nativeName":"kiRundi" 10384 }, 10385 "ro":{ 10386 "name":"Romanian", 10387 "nativeName":"românÄ" 10388 }, 10389 "ru":{ 10390 "name":"Russian", 10391 "nativeName":"ÑÑÑÑкий ÑзÑк" 10392 }, 10393 "sa":{ 10394 "name":"Sanskrit", 10395 "nativeName":"सà¤à¤¸à¥à¤à¥à¤¤à¤®à¥" 10396 }, 10397 "sc":{ 10398 "name":"Sardinian", 10399 "nativeName":"sardu" 10400 }, 10401 "sd":{ 10402 "name":"Sindhi", 10403 "nativeName":"सिनà¥à¤§à¥, سÙÚÙØ Ø³ÙØ¯Ú¾Ûâ" 10404 }, 10405 "se":{ 10406 "name":"Northern Sami", 10407 "nativeName":"Davvisámegiella" 10408 }, 10409 "sm":{ 10410 "name":"Samoan", 10411 "nativeName":"gagana faa Samoa" 10412 }, 10413 "sg":{ 10414 "name":"Sango", 10415 "nativeName":"yângâ tî sängö" 10416 }, 10417 "sr":{ 10418 "name":"Serbian", 10419 "nativeName":"ÑÑпÑки Ñезик" 10420 }, 10421 "gd":{ 10422 "name":"Gaelic", 10423 "nativeName":"Gà idhlig" 10424 }, 10425 "sn":{ 10426 "name":"Shona", 10427 "nativeName":"chiShona" 10428 }, 10429 "si":{ 10430 "name":"Sinhalese", 10431 "nativeName":"à·à·à¶à·à¶½" 10432 }, 10433 "sk":{ 10434 "name":"Slovak", 10435 "nativeName":"slovenÄina" 10436 }, 10437 "sl":{ 10438 "name":"Slovene", 10439 "nativeName":"slovenÅ¡Äina" 10440 }, 10441 "so":{ 10442 "name":"Somali", 10443 "nativeName":"Soomaaliga, af Soomaali" 10444 }, 10445 "st":{ 10446 "name":"Southern Sotho", 10447 "nativeName":"Sesotho" 10448 }, 10449 "es":{ 10450 "name":"Spanish", 10451 "nativeName":"español, castellano" 10452 }, 10453 "su":{ 10454 "name":"Sundanese", 10455 "nativeName":"Basa Sunda" 10456 }, 10457 "sw":{ 10458 "name":"Swahili", 10459 "nativeName":"Kiswahili" 10460 }, 10461 "ss":{ 10462 "name":"Swati", 10463 "nativeName":"SiSwati" 10464 }, 10465 "sv":{ 10466 "name":"Swedish", 10467 "nativeName":"svenska" 10468 }, 10469 "ta":{ 10470 "name":"Tamil", 10471 "nativeName":"தமிழà¯" 10472 }, 10473 "te":{ 10474 "name":"Telugu", 10475 "nativeName":"à°¤à±à°²à±à°à±" 10476 }, 10477 "tg":{ 10478 "name":"Tajik", 10479 "nativeName":"Ñоҷикӣ, toÄikÄ«, تاجÛÚ©Ûâ" 10480 }, 10481 "th":{ 10482 "name":"Thai", 10483 "nativeName":"à¹à¸à¸¢" 10484 }, 10485 "ti":{ 10486 "name":"Tigrinya", 10487 "nativeName":"áµááá" 10488 }, 10489 "bo":{ 10490 "name":"Tibetan", 10491 "nativeName":"à½à½¼à½à¼à½¡à½²à½" 10492 }, 10493 "tk":{ 10494 "name":"Turkmen", 10495 "nativeName":"Türkmen, ТүÑкмен" 10496 }, 10497 "tl":{ 10498 "name":"Tagalog", 10499 "nativeName":"Wikang Tagalog, áááá á áááááá" 10500 }, 10501 "tn":{ 10502 "name":"Tswana", 10503 "nativeName":"Setswana" 10504 }, 10505 "to":{ 10506 "name":"Tonga", 10507 "nativeName":"faka Tonga" 10508 }, 10509 "tr":{ 10510 "name":"Turkish", 10511 "nativeName":"Türkçe" 10512 }, 10513 "ts":{ 10514 "name":"Tsonga", 10515 "nativeName":"Xitsonga" 10516 }, 10517 "tt":{ 10518 "name":"Tatar", 10519 "nativeName":"ÑаÑаÑÑа, tatarça, ØªØ§ØªØ§Ø±ÚØ§â" 10520 }, 10521 "tw":{ 10522 "name":"Twi", 10523 "nativeName":"Twi" 10524 }, 10525 "ty":{ 10526 "name":"Tahitian", 10527 "nativeName":"Reo Tahiti" 10528 }, 10529 "ug":{ 10530 "name":"Uyghur", 10531 "nativeName":"UyÆ£urqÉ, ئÛÙØºÛرÚÛâ" 10532 }, 10533 "uk":{ 10534 "name":"Ukrainian", 10535 "nativeName":"ÑкÑаÑнÑÑка" 10536 }, 10537 "ur":{ 10538 "name":"Urdu", 10539 "nativeName":"اردÙ" 10540 }, 10541 "uz":{ 10542 "name":"Uzbek", 10543 "nativeName":"zbek, Ðзбек, Ø£ÛØ²Ø¨ÛÙâ" 10544 }, 10545 "ve":{ 10546 "name":"Venda", 10547 "nativeName":"Tshivená¸a" 10548 }, 10549 "vi":{ 10550 "name":"Vietnamese", 10551 "nativeName":"Tiếng Viá»t" 10552 }, 10553 "vo":{ 10554 "name":"Volapük", 10555 "nativeName":"Volapük" 10556 }, 10557 "wa":{ 10558 "name":"Walloon", 10559 "nativeName":"Walon" 10560 }, 10561 "cy":{ 10562 "name":"Welsh", 10563 "nativeName":"Cymraeg" 10564 }, 10565 "wo":{ 10566 "name":"Wolof", 10567 "nativeName":"Wollof" 10568 }, 10569 "fy":{ 10570 "name":"Western Frisian", 10571 "nativeName":"Frysk" 10572 }, 10573 "xh":{ 10574 "name":"Xhosa", 10575 "nativeName":"isiXhosa" 10576 }, 10577 "yi":{ 10578 "name":"Yiddish", 10579 "nativeName":"××Ö´××ש" 10580 }, 10581 "yo":{ 10582 "name":"Yoruba", 10583 "nativeName":"Yorùbá" 10584 }, 10585 "za":{ 10586 "name":"Zhuang", 10587 "nativeName":"Saɯ cueÅÆ , Saw cuengh" 10588 }, 10589 "ar-dz":{ 10590 "name":"Arabic (Algeria)", 10591 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¬Ø²Ø§Ø¦Ø±)" 10592 }, 10593 "ar-bh":{ 10594 "name":"Arabic (Bahrain)", 10595 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¨ØØ±ÙÙ)" 10596 }, 10597 "ar-eg":{ 10598 "name":"Arabic (Egypt)", 10599 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (٠صر)" 10600 }, 10601 "ar-iq":{ 10602 "name":"Arabic (Iraq)", 10603 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¹Ø±Ø§Ù)" 10604 }, 10605 "ar-jo":{ 10606 "name":"Arabic (Jordan)", 10607 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ£Ø±Ø¯Ù)" 10608 }, 10609 "ar-kw":{ 10610 "name":"Arabic (Kuwait)", 10611 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙÙØª)" 10612 }, 10613 "ar-lb":{ 10614 "name":"Arabic (Lebanon)", 10615 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ¨ÙاÙ)" 10616 }, 10617 "ar-ly":{ 10618 "name":"Arabic (Libya)", 10619 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙÙØ¨Ùا)" 10620 }, 10621 "ar-ma":{ 10622 "name":"Arabic (Morocco)", 10623 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© اÙÙ ØºØ±Ø¨ÙØ©)" 10624 }, 10625 "ar-om":{ 10626 "name":"Arabic (Oman)", 10627 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ع٠اÙ)" 10628 }, 10629 "ar-qa":{ 10630 "name":"Arabic (Qatar)", 10631 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (ÙØ·Ø±)" 10632 }, 10633 "ar-sa":{ 10634 "name":"Arabic (Saudi Arabia)", 10635 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙ Ù ÙÙØ© Ø§ÙØ¹Ø±Ø¨ÙØ© Ø§ÙØ³Ø¹ÙØ¯ÙØ©)" 10636 }, 10637 "ar-sy":{ 10638 "name":"Arabic (Syria)", 10639 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø³ÙØ±Ùا)" 10640 }, 10641 "ar-tn":{ 10642 "name":"Arabic (Tunisia)", 10643 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (تÙÙØ³)" 10644 }, 10645 "ar-ae":{ 10646 "name":"Arabic (U.A.E.)", 10647 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (Ø§ÙØ¥Ù ارات Ø§ÙØ¹Ø±Ø¨ÙØ© اÙÙ ØªØØ¯Ø©)" 10648 }, 10649 "ar-ye":{ 10650 "name":"Arabic (Yemen)", 10651 "nativeName":"Ø§ÙØ¹Ø±Ø¨ÙØ© (اÙÙÙ Ù)" 10652 }, 10653 "de-at":{ 10654 "name":"German (Austria)", 10655 "nativeName":"Deutsch (Ãsterreich)" 10656 }, 10657 "de-li":{ 10658 "name":"German (Liechtenstein)", 10659 "nativeName":"Deutsch (Liechtenstein)" 10660 }, 10661 "de-lu":{ 10662 "name":"German (Luxembourg)", 10663 "nativeName":"Deutsch (Luxemburg)" 10664 }, 10665 "de-ch":{ 10666 "name":"German (Switzerland)", 10667 "nativeName":"Deutsch (Schweiz)" 10668 }, 10669 "en-au":{ 10670 "name":"English (Australia)", 10671 "nativeName":"English (Australia)" 10672 }, 10673 "en-bz":{ 10674 "name":"English (Belize)", 10675 "nativeName":"English (Belize)" 10676 }, 10677 "en-ca":{ 10678 "name":"English (Canada)", 10679 "nativeName":"English (Canada)" 10680 }, 10681 "en-ie":{ 10682 "name":"English (Ireland)", 10683 "nativeName":"English (Ireland)" 10684 }, 10685 "en-jm":{ 10686 "name":"English (Jamaica)", 10687 "nativeName":"English (Jamaica)" 10688 }, 10689 "en-nz":{ 10690 "name":"English (New Zealand)", 10691 "nativeName":"" 10692 }, 10693 "en-za":{ 10694 "name":"English (South Africa)", 10695 "nativeName":"English (South Africa)" 10696 }, 10697 "en-tt":{ 10698 "name":"English (Trinidad)", 10699 "nativeName":"English (Trinidad y Tobago)" 10700 }, 10701 "en-gb":{ 10702 "name":"English (United Kingdom)", 10703 "nativeName":"English (United Kingdom)" 10704 }, 10705 "en-us":{ 10706 "name":"English (United States)", 10707 "nativeName":"English (United States)" 10708 }, 10709 "es-ar":{ 10710 "name":"Spanish (Argentina)", 10711 "nativeName":"Español (Argentina)" 10712 }, 10713 "es-bo":{ 10714 "name":"Spanish (Bolivia)", 10715 "nativeName":"Español (Bolivia)" 10716 }, 10717 "es-cl":{ 10718 "name":"Spanish (Chile)", 10719 "nativeName":"Español (Chile)" 10720 }, 10721 "es-co":{ 10722 "name":"Spanish (Colombia)", 10723 "nativeName":"Español (Colombia)" 10724 }, 10725 "es-cr":{ 10726 "name":"Spanish (Costa Rica)", 10727 "nativeName":"Español (Costa Rica)" 10728 }, 10729 "es-do":{ 10730 "name":"Spanish (Dominican Republic)", 10731 "nativeName":"Español (República Dominicana)" 10732 }, 10733 "es-ec":{ 10734 "name":"Spanish (Ecuador)", 10735 "nativeName":"Español (Ecuador)" 10736 }, 10737 "es-sv":{ 10738 "name":"Spanish (El Salvador)", 10739 "nativeName":"Español (El Salvador)" 10740 }, 10741 "es-gt":{ 10742 "name":"Spanish (Guatemala)", 10743 "nativeName":"Español (Guatemala)" 10744 }, 10745 "es-hn":{ 10746 "name":"Spanish (Honduras)", 10747 "nativeName":"Español (Honduras)" 10748 }, 10749 "es-mx":{ 10750 "name":"Spanish (Mexico)", 10751 "nativeName":"Español (México)" 10752 }, 10753 "es-ni":{ 10754 "name":"Spanish (Nicaragua)", 10755 "nativeName":"Español (Nicaragua)" 10756 }, 10757 "es-pa":{ 10758 "name":"Spanish (Panama)", 10759 "nativeName":"Español (Panamá)" 10760 }, 10761 "es-py":{ 10762 "name":"Spanish (Paraguay)", 10763 "nativeName":"Español (Paraguay)" 10764 }, 10765 "es-pe":{ 10766 "name":"Spanish (Peru)", 10767 "nativeName":"Español (Perú)" 10768 }, 10769 "es-pr":{ 10770 "name":"Spanish (Puerto Rico)", 10771 "nativeName":"Español (Puerto Rico)" 10772 }, 10773 "es-uy":{ 10774 "name":"Spanish (Uruguay)", 10775 "nativeName":"Español (Uruguay)" 10776 }, 10777 "es-ve":{ 10778 "name":"Spanish (Venezuela)", 10779 "nativeName":"Español (Venezuela)" 10780 }, 10781 "fr-be":{ 10782 "name":"French (Belgium)", 10783 "nativeName":"français (Belgique)" 10784 }, 10785 "fr-ca":{ 10786 "name":"French (Canada)", 10787 "nativeName":"français (Canada)" 10788 }, 10789 "fr-lu":{ 10790 "name":"French (Luxembourg)", 10791 "nativeName":"français (Luxembourg)" 10792 }, 10793 "fr-ch":{ 10794 "name":"French (Switzerland)", 10795 "nativeName":"français (Suisse)" 10796 }, 10797 "it-ch":{ 10798 "name":"Italian (Switzerland)", 10799 "nativeName":"italiano (Svizzera)" 10800 }, 10801 "nl-be":{ 10802 "name":"Dutch (Belgium)", 10803 "nativeName":"Nederlands (België)" 10804 }, 10805 "pt-br":{ 10806 "name":"Portuguese (Brazil)", 10807 "nativeName":"Português (Brasil)" 10808 }, 10809 "sv-fi":{ 10810 "name":"Swedish (Finland)", 10811 "nativeName":"svenska (Finland)" 10812 }, 10813 "zh-hk":{ 10814 "name":"Chinese (Hong Kong)", 10815 "nativeName":"䏿(馿¸¯ç¹å«è¡æ¿å)" 10816 }, 10817 "zh-cn":{ 10818 "name":"Chinese (PRC)", 10819 "nativeName":"䏿(ä¸å人æ°å ±åå½)" 10820 }, 10821 "zh-sg":{ 10822 "name":"Chinese (Singapore)", 10823 "nativeName":"䏿(æ°å å¡)" 10824 }, 10825 "zh-tw":{ 10826 "name":"Chinese Traditional (Taiwan)", 10827 "nativeName":"䏿ï¼å°ç£ï¼" 10828 } 10829 }; 10830 10831 AblePlayer.prototype.getLanguageName = function (key,whichName) { 10832 10833 // return language name associated with lang code "key" 10834 // whichName is either "English" or "local" (i.e., native name) 10835 10836 var lang, code, subTag; 10837 lang = isoLangs[key.toLowerCase()]; 10838 if (lang) { 10839 return (whichName === 'local') ? lang.nativeName : lang.name; 10840 } else if (key.includes('-')) { 10841 code = key.substring(0,2); 10842 subTag = key.substring(3); 10843 lang = isoLangs[code.toLowerCase()]; 10844 if (lang) { 10845 return (whichName === 'local') ? lang.nativeName + ' (' + subTag + ')' : lang.name + ' (' + subTag + ')'; 10846 } 10847 } 10848 // if all else has failed, use the key as the label 10849 return key; 10850 }; 10851 10852 } 10853 10854 function addMetadataFunctions(AblePlayer) { 10855 AblePlayer.prototype.updateMeta = function (time) { 10856 if (this.hasMeta) { 10857 if (this.metaType === "text") { 10858 this.$metaDiv.show(); 10859 this.showMeta(time || this.elapsed); 10860 } else { 10861 this.showMeta(time || this.elapsed); 10862 } 10863 } 10864 }; 10865 10866 AblePlayer.prototype.showMeta = function (now) { 10867 var tempSelectors, 10868 m, 10869 thisMeta, 10870 cues, 10871 cueText, 10872 cueLines, 10873 i, 10874 line, 10875 showDuration, 10876 focusTarget; 10877 10878 tempSelectors = []; 10879 if (this.meta.length >= 1) { 10880 cues = this.meta; 10881 } else { 10882 cues = []; 10883 } 10884 for (m = 0; m < cues.length; m++) { 10885 if (cues[m].start <= now && cues[m].end > now) { 10886 thisMeta = m; 10887 break; 10888 } 10889 } 10890 if (typeof thisMeta !== "undefined") { 10891 if (this.currentMeta !== thisMeta) { 10892 if (this.metaType === "text") { 10893 // it's time to load the new metadata cue into the container div 10894 this.$metaDiv.html( 10895 this.flattenCueForMeta(cues[thisMeta]).replace(/\n/g, "<br>") 10896 ); 10897 } else if (this.metaType === "selector") { 10898 // it's time to show content referenced by the designated selector(s) 10899 cueText = this.flattenCueForMeta(cues[thisMeta]); 10900 cueLines = cueText.split("\n"); 10901 for (i = 0; i < cueLines.length; i++) { 10902 line = cueLines[i].trim(); 10903 if (line.toLowerCase().trim() === "pause") { 10904 // don't show big play button when pausing via metadata 10905 this.hideBigPlayButton = true; 10906 this.pauseMedia(); 10907 } else if (line.toLowerCase().substring(0, 6) == "focus:") { 10908 focusTarget = line.substring(6).trim(); 10909 if ($(focusTarget).length) { 10910 $(focusTarget).trigger('focus'); 10911 } 10912 } else { 10913 if ($(line).length) { 10914 // selector exists 10915 this.currentMeta = thisMeta; 10916 showDuration = parseInt($(line).attr("data-duration")); 10917 if ( 10918 typeof showDuration !== "undefined" && 10919 !isNaN(showDuration) 10920 ) { 10921 $(line).show(); 10922 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 10923 delay(showDuration).then(() => { 10924 $(line).hide(); 10925 }); 10926 } else { 10927 // no duration specified. Just show the element until end time specified in VTT file 10928 $(line).show(); 10929 } 10930 // add to array of visible selectors so it can be hidden at end time 10931 this.visibleSelectors.push(line); 10932 tempSelectors.push(line); 10933 } 10934 } 10935 } 10936 // now step through this.visibleSelectors and remove anything that's stale 10937 if (this.visibleSelectors && this.visibleSelectors.length) { 10938 if (this.visibleSelectors.length !== tempSelectors.length) { 10939 for (i = this.visibleSelectors.length - 1; i >= 0; i--) { 10940 if ($.inArray(this.visibleSelectors[i], tempSelectors) == -1) { 10941 $(this.visibleSelectors[i]).hide(); 10942 this.visibleSelectors.splice(i, 1); 10943 } 10944 } 10945 } 10946 } 10947 } 10948 } 10949 } else { 10950 // there is currently no metadata. Empty stale content 10951 if (typeof this.$metaDiv !== "undefined") { 10952 this.$metaDiv.html(""); 10953 } 10954 if (this.visibleSelectors && this.visibleSelectors.length) { 10955 for (i = 0; i < this.visibleSelectors.length; i++) { 10956 $(this.visibleSelectors[i]).hide(); 10957 } 10958 // reset array 10959 this.visibleSelectors = []; 10960 } 10961 this.currentMeta = -1; 10962 } 10963 }; 10964 10965 // Takes a cue and returns the metadata text to display for it. 10966 AblePlayer.prototype.flattenCueForMeta = function (cue) { 10967 var result = []; 10968 10969 var flattenComponent = function (component) { 10970 var result = [], 10971 ii; 10972 if (component.type === "string") { 10973 result.push(component.value); 10974 } else if (component.type === "v") { 10975 result.push("[" + component.value + "]"); 10976 for (ii = 0; ii < component.children.length; ii++) { 10977 result.push(flattenComponent(component.children[ii])); 10978 } 10979 } else { 10980 for (ii = 0; ii < component.children.length; ii++) { 10981 result.push(flattenComponent(component.children[ii])); 10982 } 10983 } 10984 return result.join(""); 10985 }; 10986 10987 for (var ii = 0; ii < cue.components.children.length; ii++) { 10988 result.push(flattenComponent(cue.components.children[ii])); 10989 } 10990 10991 return result.join(""); 10992 }; 10993 } 10994 10995 function addMiscFunctions(AblePlayer) { 10996 AblePlayer.prototype.getNextHeadingLevel = function ($element) { 10997 // Finds the nearest heading in the ancestor tree 10998 // Loops over each parent of the current element until a heading is found 10999 // If multiple headings are found beneath a given parent, get the closest 11000 // Returns an integer (1-6) representing the next available heading level 11001 11002 var $parents, $foundHeadings, numHeadings, headingType, headingNumber; 11003 11004 $parents = $element.parents(); 11005 $parents.each(function () { 11006 $foundHeadings = $(this).children(":header"); 11007 numHeadings = $foundHeadings.length; 11008 if (numHeadings) { 11009 headingType = $foundHeadings.eq(numHeadings - 1).prop("tagName"); 11010 return false; 11011 } 11012 }); 11013 if (typeof headingType === "undefined") { 11014 // page has no headings 11015 headingNumber = 1; 11016 } else { 11017 // Increment closest heading by one if less than 6. 11018 headingNumber = parseInt(headingType[1]); 11019 headingNumber += 1; 11020 if (headingNumber > 6) { 11021 headingNumber = 6; 11022 } 11023 } 11024 return headingNumber; 11025 }; 11026 11027 AblePlayer.prototype.countProperties = function (obj) { 11028 // returns the number of properties in an object 11029 var count, prop; 11030 count = 0; 11031 for (prop in obj) { 11032 if (Object.hasOwn(obj, prop)) { 11033 ++count; 11034 } 11035 } 11036 return count; 11037 }; 11038 11039 AblePlayer.prototype.formatSecondsAsColonTime = function ( 11040 seconds, 11041 showFullTime 11042 ) { 11043 // Takes seconds and converts to string of form hh:mm:ss 11044 // If showFullTime is true, shows 00 for hours if time is less than an hour 11045 // and show milliseconds (e.g., 00:00:04.123 as in Video Track Sorter) 11046 // Otherwise, omits empty hours and milliseconds (e.g., 00:04 as in timer on controller) 11047 11048 var times,format,parts,milliSeconds,numShort,i; 11049 11050 if (showFullTime) { 11051 // preserve milliseconds, if included in seconds 11052 parts = seconds.toString().split("."); 11053 if (parts.length === 2) { 11054 milliSeconds = parts[1]; 11055 if (milliSeconds.length < 3) { 11056 numShort = 3 - milliSeconds.length; 11057 for (i = 1; i <= numShort; i++) { 11058 milliSeconds += "0"; 11059 } 11060 } 11061 } else { 11062 milliSeconds = "000"; 11063 } 11064 } 11065 times = this.secondsToTime( seconds ); 11066 format = times['value']; 11067 11068 return (showFullTime) ? format + '.' + milliSeconds : format; 11069 }; 11070 11071 AblePlayer.prototype.getSecondsFromColonTime = function (timeStr) { 11072 // Converts string of form hh:mm:ss to seconds 11073 var timeParts, hours, minutes, seconds; 11074 11075 timeParts = timeStr.split(":"); 11076 if (timeParts.length === 3) { 11077 hours = parseInt(timeParts[0]); 11078 minutes = parseInt(timeParts[1]); 11079 seconds = parseFloat(timeParts[2]); 11080 return hours * 3600 + minutes * 60 + seconds; 11081 } else if (timeParts.length === 2) { 11082 minutes = parseInt(timeParts[0]); 11083 seconds = parseFloat(timeParts[1]); 11084 return minutes * 60 + seconds; 11085 } else if (timeParts.length === 1) { 11086 seconds = parseFloat(timeParts[0]); 11087 return seconds; 11088 } 11089 }; 11090 11091 AblePlayer.prototype.capitalizeFirstLetter = function (string) { 11092 return string.charAt(0).toUpperCase() + string.slice(1); 11093 }; 11094 11095 AblePlayer.prototype.roundDown = function (value, decimals) { 11096 // round value down to the nearest X decimal points 11097 // where X is the value of the decimals parameter 11098 return Number(Math.floor(value + "e" + decimals) + "e-" + decimals); 11099 }; 11100 11101 AblePlayer.prototype.defer = function() { 11102 const self = this; 11103 const promise = new Promise((resolve, reject) => { 11104 self.resolve = resolve; 11105 self.reject = reject; 11106 self.promise = () => promise; 11107 }); 11108 }; 11109 11110 AblePlayer.prototype.getScript = function( source, callback ) { 11111 var script = document.createElement('script'); 11112 var prior = document.getElementsByTagName('script')[0]; 11113 script.async = 1; 11114 11115 script.onload = script.onreadystatechange = function( _, isAbort ) { 11116 if ( isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) { 11117 script.onload = script.onreadystatechange = null; 11118 script = undefined; 11119 11120 if ( !isAbort && callback ) { 11121 setTimeout(callback, 0); 11122 } 11123 } 11124 }; 11125 11126 script.src = source; 11127 prior.parentNode.insertBefore(script, prior); 11128 }; 11129 11130 AblePlayer.prototype.hasAttr = function (object, attribute) { 11131 // surprisingly, there is no hasAttr() function in Jquery as of 3.2.1 11132 // return true if object has attribute; otherwise false 11133 // selector is a Jquery object 11134 // attribute is a string 11135 11136 var attr = object.attr(attribute); 11137 11138 // For some browsers, `attr` is undefined; for others, 11139 // `attr` is false. Check for both. 11140 if (typeof attr !== typeof undefined && attr !== false) { 11141 return true; 11142 } else { 11143 return false; 11144 } 11145 }; 11146 11147 } 11148 11149 /* global Cookies */ 11150 11151 function addPreferenceFunctions(AblePlayer) { 11152 AblePlayer.prototype.setPrefs = function(preferences) { 11153 if ( typeof Cookies !== 'undefined' ) { 11154 Cookies.set('Able-Player', JSON.stringify(preferences), { 11155 expires: 90, 11156 sameSite: 'strict' 11157 }); 11158 } else { 11159 localStorage.setItem( 'Able-Player', JSON.stringify( preferences ) ); 11160 } 11161 }; 11162 11163 AblePlayer.prototype.getPref = function() { 11164 11165 var defaultPrefs = { 11166 preferences: {}, 11167 sign: {}, 11168 transcript: {}, 11169 voices: [] 11170 }; 11171 11172 var preferences; 11173 try { 11174 if ( typeof Cookies !== 'undefined' ) { 11175 preferences = JSON.parse( Cookies.get('Able-Player') ); 11176 } else { 11177 preferences = JSON.parse( localStorage.getItem('Able-Player') ); 11178 } 11179 } 11180 catch (err) { 11181 // Original preferences can't be parsed; update to default 11182 this.setPrefs( defaultPrefs ); 11183 preferences = defaultPrefs; 11184 } 11185 return (preferences) ? preferences : defaultPrefs; 11186 }; 11187 11188 AblePlayer.prototype.updatePreferences = function( setting ) { 11189 // useful for settings updated independently of Preferences dialog 11190 // e.g., prefAutoScrollTranscript, which is updated in control.js > handleTranscriptLockToggle() 11191 // setting is any supported preference name (e.g., "prefCaptions") 11192 // OR 'transcript' or 'sign' (not user-defined preferences, used to save position of draggable windows) 11193 var preferences, $window, windowPos, available, i, prefName, voiceLangFound, newVoice; 11194 preferences = this.getPref(); 11195 if (setting === 'transcript' || setting === 'sign') { 11196 if (setting === 'transcript') { 11197 $window = this.$transcriptArea; 11198 windowPos = $window.position(); 11199 if (typeof preferences.transcript === 'undefined') { 11200 preferences.transcript = {}; 11201 } 11202 preferences.transcript['position'] = $window.css('position'); // either 'relative' or 'absolute' 11203 preferences.transcript['zindex'] = $window.css('z-index'); 11204 preferences.transcript['top'] = windowPos.top; 11205 preferences.transcript['left'] = windowPos.left; 11206 preferences.transcript['width'] = $window.width(); 11207 preferences.transcript['height'] = $window.height(); 11208 } else if (setting === 'sign') { 11209 $window = this.$signWindow; 11210 windowPos = $window.position(); 11211 if (typeof preferences.sign === 'undefined') { 11212 preferences.sign = {}; 11213 } 11214 preferences.sign['position'] = $window.css('position'); // either 'relative' or 'absolute' 11215 preferences.sign['zindex'] = $window.css('z-index'); 11216 preferences.sign['top'] = windowPos.top; 11217 preferences.sign['left'] = windowPos.left; 11218 preferences.sign['width'] = $window.width(); 11219 preferences.sign['height'] = $window.height(); 11220 } 11221 } else if (setting === 'voice') { 11222 if (typeof preferences.voices === 'undefined') { 11223 preferences.voices = []; 11224 } 11225 // replace preferred voice for this lang in preferences.voices array, if one exists 11226 // otherwise, add it to the array 11227 voiceLangFound = false; 11228 for (var v=0; v < preferences.voices.length; v++) { 11229 if (preferences.voices[v].lang === this.prefDescVoiceLang) { 11230 voiceLangFound = true; 11231 preferences.voices[v].name = this.prefDescVoice; 11232 } 11233 } 11234 if (!voiceLangFound) { 11235 // no voice has been saved yet for this language. Add it to array. 11236 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 11237 preferences.voices.push(newVoice); 11238 } 11239 } else { 11240 available = this.getAvailablePreferences(); 11241 // Rebuild preferences with current preferences values, 11242 // replacing the one value that's been changed 11243 for (i = 0; i < available.length; i++) { 11244 prefName = available[i]['name']; 11245 if (prefName == setting) { 11246 // this is the one that requires an update 11247 preferences.preferences[prefName] = this[prefName]; 11248 } 11249 } 11250 } 11251 // Save updated preferences 11252 this.setPrefs(preferences); 11253 }; 11254 11255 AblePlayer.prototype.getPreferencesGroups = function() { 11256 11257 // return array of groups in the order in which they will appear 11258 // in the Preferences popup menu 11259 // Human-readable label for each group is defined in translation table 11260 if (this.usingYouTubeCaptions) { 11261 // no transcript is possible 11262 return ['captions','descriptions','keyboard']; 11263 } else if (this.usingVimeoCaptions) { 11264 // users cannot control caption appearance 11265 // and no transcript is possible 11266 return ['descriptions','keyboard']; 11267 } else { 11268 return ['captions','descriptions','keyboard','transcript']; 11269 } 11270 }; 11271 11272 AblePlayer.prototype.getAvailablePreferences = function() { 11273 11274 // Return the list of currently available preferences. 11275 // Preferences with no 'label' are set within player, not shown in Prefs dialog 11276 var prefs = []; 11277 11278 // Modifier keys preferences 11279 prefs.push({ 11280 'name': 'prefAltKey', // use alt key with shortcuts 11281 'label': this.translate( 'prefAltKey', 'Alt' ), 11282 'group': 'keyboard', 11283 'default': 1 11284 }); 11285 prefs.push({ 11286 'name': 'prefCtrlKey', // use ctrl key with shortcuts 11287 'label': this.translate( 'prefCtrlKey', 'Control' ), 11288 'group': 'keyboard', 11289 'default': 1 11290 }); 11291 prefs.push({ 11292 'name': 'prefShiftKey', 11293 'label': this.translate( 'prefShiftKey', 'Shift' ), 11294 'group': 'keyboard', 11295 'default': 0 11296 }); 11297 prefs.push({ 11298 'name': 'prefNoKeyShortcuts', 11299 'label': this.translate( 'prefNoKeyShortcuts', 'Disable Keyboard Shortcuts' ), 11300 'group': 'keyboard', 11301 'default': 0 11302 }); 11303 11304 // Transcript preferences 11305 prefs.push({ 11306 'name': 'prefTranscript', // transcript default state 11307 'label': null, 11308 'group': 'transcript', 11309 'default': 0 // off because turning it on has a certain WOW factor 11310 }); 11311 prefs.push({ 11312 'name': 'prefHighlight', // highlight transcript as media plays 11313 'label': this.translate( 'prefHighlight', 'Highlight transcript as media plays' ), 11314 'group': 'transcript', 11315 'default': 1 // on because many users can benefit 11316 }); 11317 prefs.push({ 11318 'name': 'prefAutoScrollTranscript', 11319 'label': null, 11320 'group': 'transcript', 11321 'default': 1 11322 }); 11323 prefs.push({ 11324 'name': 'prefTabbable', // tab-enable transcript 11325 'label': this.translate( 'prefTabbable', 'Keyboard-enable transcript' ), 11326 'group': 'transcript', 11327 'default': 0 // off because if users don't need it, it impedes tabbing elsewhere on the page 11328 }); 11329 11330 // Caption preferences 11331 prefs.push({ 11332 'name': 'prefCaptions', // closed captions default state 11333 'label': null, 11334 'group': 'captions', 11335 'default': this.defaultStateCaptions 11336 }); 11337 11338 if (!this.usingYouTubeCaptions) { 11339 11340 /* // not supported yet 11341 prefs.push({ 11342 'name': 'prefCaptionsStyle', 11343 'label': this.translate( 'prefCaptionsStyle', 'Style' ), 11344 'group': 'captions', 11345 'default': this.translate( 'captionsStylePopOn', 'Pop-on' ) 11346 }); 11347 */ 11348 // captions are always positioned above the player for audio 11349 if (this.mediaType === 'video') { 11350 prefs.push({ 11351 'name': 'prefCaptionsPosition', 11352 'label': this.translate( 'prefCaptionsPosition', 'Position' ), 11353 'group': 'captions', 11354 'default': this.defaultCaptionsPosition 11355 }); 11356 } 11357 prefs.push({ 11358 'name': 'prefCaptionsFont', 11359 'label': this.translate( 'prefCaptionsFont', 'Font' ), 11360 'group': 'captions', 11361 'default': 'sans-serif' 11362 }); 11363 } 11364 // This is the one option that is supported by YouTube IFrame API 11365 prefs.push({ 11366 'name': 'prefCaptionsSize', 11367 'label': this.translate( 'prefCaptionsSize', 'Font size' ), 11368 'group': 'captions', 11369 'default': '100%' 11370 }); 11371 11372 if (!this.usingYouTubeCaptions) { 11373 11374 prefs.push({ 11375 'name': 'prefCaptionsColor', 11376 'label': this.translate( 'prefCaptionsColor', 'Text Color' ), 11377 'group': 'captions', 11378 'default': 'white' 11379 }); 11380 prefs.push({ 11381 'name': 'prefCaptionsBGColor', 11382 'label': this.translate( 'prefCaptionsBGColor', 'Background' ), 11383 'group': 'captions', 11384 'default': 'black' 11385 }); 11386 prefs.push({ 11387 'name': 'prefCaptionsOpacity', 11388 'label': this.translate( 'prefCaptionsOpacity', 'Opacity' ), 11389 'group': 'captions', 11390 'default': '100%' 11391 }); 11392 prefs.push({ 11393 'name': 'prefCaptionsSpeak', 11394 'label': this.translate( 'prefVoicedCaptions', 'Spoken Captions' ), 11395 'group': 'captions', 11396 'default': 0 11397 }); 11398 prefs.push({ 11399 'name': 'prefCaptionsVoice', 11400 'label': this.translate( 'prefDescVoice', 'Voice' ), 11401 'group': 'captions', 11402 'default': null // will be set later, in injectPrefsForm() 11403 }); 11404 prefs.push({ 11405 'name': 'prefCaptionsPitch', 11406 'label': this.translate( 'prefDescPitch', 'Pitch' ), 11407 'group': 'captions', 11408 'default': 1 // 0 to 2 11409 }); 11410 prefs.push({ 11411 'name': 'prefCaptionsRate', 11412 'label': this.translate( 'prefCaptionRate', 'Spoken Caption Rate' ), 11413 'group': 'captions', 11414 'default': 1.2 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast) 11415 }); 11416 prefs.push({ 11417 'name': 'prefCaptionsVolume', 11418 'label': this.translate( 'volume', 'Volume' ), 11419 'group': 'captions', 11420 'default': 1 // 0 to 1 11421 }); 11422 } 11423 11424 if (this.mediaType === 'video') { 11425 // Description preferences 11426 prefs.push({ 11427 'name': 'prefDesc', // audio description default state 11428 'label': null, 11429 'group': 'descriptions', 11430 'default': this.defaultStateDescriptions 11431 }); 11432 prefs.push({ 11433 'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available) 11434 'label': null, 11435 'group': 'descriptions', 11436 'default': 'video' // video (an alternative described version) always wins 11437 }); 11438 prefs.push({ 11439 'name': 'prefDescVoice', 11440 'label': this.translate( 'prefDescVoice', 'Voice' ), 11441 'group': 'descriptions', 11442 'default': null // will be set later, in injectPrefsForm() 11443 }); 11444 prefs.push({ 11445 'name': 'prefDescPitch', 11446 'label': this.translate( 'prefDescPitch', 'Pitch' ), 11447 'group': 'descriptions', 11448 'default': 1 // 0 to 2 11449 }); 11450 prefs.push({ 11451 'name': 'prefDescRate', 11452 'label': this.translate( 'prefDescRate', 'Spoken Description Rate' ), 11453 'group': 'descriptions', 11454 'default': 1 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast) 11455 }); 11456 prefs.push({ 11457 'name': 'prefDescVolume', 11458 'label': this.translate( 'volume', 'Volume' ), 11459 'group': 'descriptions', 11460 'default': 1 // 0 to 1 11461 }); 11462 // Don't enable pause option if video described files in use. 11463 if ( this.descMethod !== 'video' ) { 11464 prefs.push({ 11465 'name': 'prefDescPause', // automatically pause when closed description starts 11466 'label': this.translate( 'prefDescPause', 'Automatically pause video when description starts' ), 11467 'group': 'descriptions', 11468 'default': this.defaultDescPause 11469 }); 11470 } 11471 prefs.push({ 11472 'name': 'prefDescVisible', // visibly show closed description (if avilable and used) 11473 'label': this.translate( 'prefDescVisible', 'Make description visible' ), 11474 'group': 'descriptions', 11475 'default': 0 // off as of 4.3.16, to avoid overloading the player with visible features 11476 }); 11477 } 11478 // Preferences without a category (not shown in Preferences dialogs) 11479 prefs.push({ 11480 'name': 'prefSign', // open sign language window by default if avilable 11481 'label': null, 11482 'group': null, 11483 'default': 0 // off because clicking an icon to see the sign window has a powerful impact 11484 }); 11485 11486 return prefs; 11487 }; 11488 11489 AblePlayer.prototype.loadCurrentPreferences = function () { 11490 11491 // Load current/default preferences into the AblePlayer object. 11492 11493 var available = this.getAvailablePreferences(); 11494 var preferences = this.getPref(); 11495 // Copy current preferences values into this object, and fill in any default values. 11496 for (var ii = 0; ii < available.length; ii++) { 11497 var prefName = available[ii]['name']; 11498 var defaultValue = available[ii]['default']; 11499 if (preferences.preferences[prefName] !== undefined) { 11500 this[prefName] = preferences.preferences[prefName]; 11501 } else { 11502 preferences.preferences[prefName] = defaultValue; 11503 this[prefName] = defaultValue; 11504 } 11505 } 11506 11507 // Also load array of preferred voices from preferences 11508 if (typeof preferences.voices !== 'undefined') { 11509 this.prefVoices = preferences.voices; 11510 } 11511 11512 this.setPrefs(preferences); 11513 }; 11514 11515 AblePlayer.prototype.injectPrefsForm = function (form) { 11516 11517 // Creates a preferences form and injects it. 11518 // form is one of the supported forms (groups) defined in getPreferencesGroups() 11519 11520 var thisObj, available, 11521 $prefsDiv, formTitle, introText, $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j, 11522 $fieldset, fieldsetClass, fieldsetId, $legend, legendId, thisPref, $thisDiv, thisClass, 11523 thisId, $thisLabel, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText, 11524 changedPref,changedSpan,changedText, currentDescState, prefDescVoice, prefCaptionVoice, $kbHeading,$kbList, 11525 kbLabels,keys,kbListText,$kbListItem, dialog,$saveButton,$cancelButton,$buttonContainer; 11526 11527 thisObj = this; 11528 available = this.getAvailablePreferences(); 11529 11530 // outer container, will be assigned role="dialog" 11531 $prefsDiv = $('<div>',{ 11532 'class': 'able-prefs-form ' 11533 }); 11534 var customClass = 'able-prefs-form-' + form; 11535 $prefsDiv.addClass(customClass); 11536 11537 // add titles and intros 11538 if (form == 'captions') { 11539 formTitle = this.translate( 'prefTitleCaptions', 'Captions Preferences' ); 11540 } else if (form == 'descriptions') { 11541 formTitle = this.translate( 'prefTitleDescriptions', 'Audio Description Preferences' ); 11542 $prefsIntro = $('<p>',{ 11543 text: this.translate( 'prefIntroDescription1', 'This media player supports audio description in two ways: ' ) 11544 }); 11545 var $prefsIntroUL = $('<ul>'); 11546 var $prefsIntroLI1 = $('<li>',{ 11547 text: this.translate( 'prefDescFormatOption1', 'alternative described version of video' ) 11548 }); 11549 var $prefsIntroLI2 = $('<li>',{ 11550 text: this.translate( 'prefDescFormatOption2', 'text-based description, announced by screen reader' ) 11551 }); 11552 11553 $prefsIntroUL.append($prefsIntroLI1,$prefsIntroLI2); 11554 let prefDescription1 = ''; 11555 let prefDescription2 = ''; 11556 let prefDescription3 = ''; 11557 let prefDescriptionNone = ''; 11558 if (this.hasOpenDesc && this.hasClosedDesc) { 11559 prefDescription1 = this.translate( 'prefDescription1', 'The current video has an alternative described version and text-based description, announced by screen reader'); 11560 } else if (this.hasOpenDesc) { 11561 prefDescription2 = this.translate( 'prefDescription2', 'The current video has an alternative described version.' ); 11562 } else if (this.hasClosedDesc) { 11563 prefDescription3 = this.translate( 'prefDescription3', 'The current video has text-based description, announced by screen reader.'); 11564 } else { 11565 prefDescriptionNone = this.translate( 'prefDescriptionNone', 'The current video has no audio description in either format.' ); 11566 } 11567 currentDescState = prefDescription1 + prefDescription2 + prefDescription3 + prefDescriptionNone; 11568 $prefsIntroP2 = $('<p>',{ 11569 html: currentDescState 11570 }); 11571 11572 p3Text = this.translate( 'prefIntroDescription3', 'Use the following form to set your preferences related to text-based audio description.' ); 11573 if (this.hasOpenDesc || this.hasClosedDesc) { 11574 p3Text += ' ' + this.translate( 'prefIntroDescription4', 'After you save your settings, audio description can be toggled on/off using the Description button.' ); 11575 } 11576 $prefsIntroP3 = $('<p>',{ 11577 text: p3Text 11578 }); 11579 11580 $prefsDiv.append( $prefsIntro, $prefsIntroUL, $prefsIntroP2, $prefsIntroP3 ); 11581 } else if (form == 'keyboard') { 11582 formTitle = this.translate( 'prefTitleKeyboard', 'Keyboard Preferences' ); 11583 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).' ); 11584 introText += ' ' + this.translate( 'prefIntroKeyboard2', 'Modifier keys (Shift, Alt, and Control) can be assigned below.' ); 11585 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.' ); 11586 $prefsIntro = $('<p>',{ 11587 text: introText 11588 }); 11589 $prefsDiv.append($prefsIntro); 11590 } else if (form == 'transcript') { 11591 formTitle = this.translate( 'prefTitleTranscript', 'Transcript Preferences' ); 11592 } 11593 11594 $fieldset = $('<div>').attr('role','group'); 11595 fieldsetClass = 'able-prefs-' + form; 11596 fieldsetId = this.mediaId + '-prefs-' + form; 11597 legendId = fieldsetId + '-legend'; 11598 $fieldset.addClass(fieldsetClass).attr('id',fieldsetId); 11599 if (form === 'keyboard') { 11600 $legend = $('<h2>' + this.translate( 'prefHeadingKeyboard1', 'Modifier keys used for shortcuts' ) + '</h2>'); 11601 $legend.attr('id',legendId); 11602 $fieldset.attr('aria-labelledby',legendId); 11603 $fieldset.append($legend); 11604 } else if (form === 'descriptions') { 11605 $legend = $('<h2>' + this.translate( 'prefHeadingTextDescription', 'Text-based audio description' ) + '</h2>'); 11606 $legend.attr('id',legendId); 11607 $fieldset.attr('aria-labelledby',legendId); 11608 $fieldset.append($legend); 11609 } 11610 for (i=0; i<available.length; i++) { 11611 11612 // only include prefs on the current form if they have a label 11613 if ((available[i]['group'] == form) && available[i]['label']) { 11614 11615 thisPref = available[i]['name']; 11616 thisClass = 'able-' + thisPref; 11617 thisId = this.mediaId + '_' + thisPref; 11618 $thisDiv = $('<div>').addClass(thisClass + ' able-player-setting'); 11619 if (form === 'captions' ) { 11620 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11621 $thisField = $('<select>',{ 11622 name: thisPref, 11623 id: thisId, 11624 }); 11625 // add a change handler that updates the style of the sample caption text 11626 let viewingOptions = ['prefCaptionsPosition','prefCaptionsFont','prefCaptionsSize','prefCaptionsColor','prefCaptionsBGColor','prefCaptionsOpacity']; 11627 if ( viewingOptions.indexOf(thisPref) !== -1 ) { 11628 $thisField.on( 'change', function() { 11629 changedPref = $(this).attr('name'); 11630 thisObj.stylizeCaptions(thisObj.$sampleCapsDiv,changedPref); 11631 }); 11632 } 11633 captionsOptions = this.getCaptionsOptions(thisPref); 11634 if ( ! ( thisPref === 'prefCaptionsVoice' && ! this.descVoices.length ) ) { 11635 $thisDiv.append($thisLabel,$thisField); 11636 } 11637 for (j=0; j < captionsOptions.length; j++) { 11638 if (thisPref === 'prefCaptionsPosition') { 11639 optionValue = captionsOptions[j]; 11640 if (optionValue === 'overlay') { 11641 optionText = this.translate( 'captionsPositionOverlay', 'Overlay' ); 11642 } else if (optionValue === 'below') { 11643 optionValue = captionsOptions[j]; 11644 optionText = this.translate( 'captionsPositionBelow', 'Below video' ); 11645 } 11646 } else if (thisPref === 'prefCaptionsFont' || thisPref === 'prefCaptionsColor' || thisPref === 'prefCaptionsBGColor' || thisPref === 'prefCaptionsSpeak' ) { 11647 optionValue = captionsOptions[j][0]; 11648 optionText = captionsOptions[j][1]; 11649 } else if (thisPref === 'prefCaptionsOpacity') { 11650 optionValue = captionsOptions[j]; 11651 optionText = captionsOptions[j]; 11652 optionText += (optionValue === '0%') ? ' (' + this.translate( 'transparent', 'transparent' ) + ')' : ' (' + this.translate( 'solid', 'solid' ) + ')'; 11653 } else if (thisPref === 'prefCaptionsSize') { 11654 optionValue = captionsOptions[j]; 11655 optionText = captionsOptions[j]; 11656 } 11657 let voicingOptions = ['prefCaptionsPitch','prefCaptionsRate','prefCaptionsVolume']; 11658 if ( optionValue && voicingOptions.indexOf(thisPref) === -1 ) { 11659 $thisOption = $('<option>',{ 11660 value: optionValue, 11661 text: optionText 11662 }); 11663 if (this[thisPref] === optionValue) { 11664 $thisOption.prop('selected',true); 11665 } 11666 $thisField.append($thisOption); 11667 } 11668 // If synth is possible, show voicing options. 11669 if ( this.synth ) { 11670 if ( thisPref === 'prefCaptionsVoice' && this.descVoices.length ) { 11671 prefCaptionVoice = this.getPrefVoice(); 11672 for (j=0; j < this.descVoices.length; j++) { 11673 optionValue = this.descVoices[j].name; 11674 optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase(); 11675 optionText = optionValue + ' (' + this.descVoices[j].lang + ')'; 11676 $thisOption = $('<option>',{ 11677 'value': optionValue, 11678 'data-lang': optionLang, 11679 text: optionText 11680 }); 11681 if (prefCaptionVoice === optionValue) { 11682 $thisOption.prop('selected',true); 11683 } 11684 $thisField.append($thisOption); 11685 } 11686 this.$voiceSelectField = $thisField; 11687 } else { 11688 if ( thisPref == 'prefCaptionsPitch' || thisPref == 'prefCaptionsRate' || thisPref == 'prefCaptionsVolume' ) { 11689 options = false; 11690 // Options values described in audio description preferences. 11691 options = (thisPref == 'prefCaptionsPitch') ? [0,0.5,1,1.5,2] : options; 11692 options = (thisPref == 'prefCaptionsRate') ? [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3] : options; 11693 options = (thisPref == 'prefCaptionsVolume') ? [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] : options; 11694 if ( options ) { 11695 for (j=0; j < options.length; j++) { 11696 optionValue = options[j]; 11697 optionText = this.makePrefsValueReadable(thisPref,optionValue); 11698 $thisOption = $('<option>',{ 11699 value: optionValue, 11700 text: optionText 11701 }); 11702 if (this[thisPref] == optionValue) { 11703 $thisOption.prop('selected',true); 11704 } 11705 $thisField.append($thisOption); 11706 } 11707 // add a change handler that announces the sample text 11708 $thisField.on('change',function() { 11709 let captionSample = thisObj.translate( 'sampleCaptionText', 'Sample caption text' ); 11710 thisObj.announceText('captionSample',captionSample); 11711 }); 11712 $thisDiv.append($thisLabel,$thisField); 11713 } 11714 } 11715 } 11716 } 11717 } 11718 } else if (form === 'descriptions') { 11719 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11720 if (thisPref === 'prefDescPause' || thisPref === 'prefDescVisible') { 11721 // these preferences are checkboxes 11722 $thisDiv.addClass('able-prefs-checkbox'); 11723 $thisField = $('<input>',{ 11724 type: 'checkbox', 11725 name: thisPref, 11726 id: thisId, 11727 value: 'true' 11728 }); 11729 // check current active value for this preference 11730 if (this[thisPref] === 1) { 11731 $thisField.prop('checked',true); 11732 } 11733 $thisDiv.append($thisField,$thisLabel); 11734 } else if (this.synth) { 11735 // Only show these options if browser supports speech synthesis 11736 $thisDiv.addClass('able-prefs-select'); 11737 $thisField = $('<select>',{ 11738 name: thisPref, 11739 id: thisId, 11740 }); 11741 if ( thisPref === 'prefDescVoice' && this.descVoices.length) { 11742 prefDescVoice = this.getPrefVoice(); 11743 for (j=0; j < this.descVoices.length; j++) { 11744 optionValue = this.descVoices[j].name; 11745 optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase(); 11746 optionText = optionValue + ' (' + this.descVoices[j].lang + ')'; 11747 $thisOption = $('<option>',{ 11748 'value': optionValue, 11749 'data-lang': optionLang, 11750 text: optionText 11751 }); 11752 if (prefDescVoice === optionValue) { 11753 $thisOption.prop('selected',true); 11754 } 11755 $thisField.append($thisOption); 11756 } 11757 this.$voiceSelectField = $thisField; 11758 } else { 11759 if (thisPref == 'prefDescPitch') { // 0 to 2 11760 options = [0,0.5,1,1.5,2]; 11761 } else if (thisPref == 'prefDescRate') { // 0.1 to 10 11762 // Tests with a variety of voices on MacOS and Windows 11763 // yielded the following choices that seem reasonable for audio description: 11764 // 0.5 - too slow (exclude this) 11765 // 0.7 - casual 11766 // 0.8 - add this 11767 // 0.9 - add this 11768 // 1 - normal 11769 // 1.1 - add this 11770 // 1.2 - add this 11771 // 1.5 - quick 11772 // 2 - speedy 11773 // 2.5 - fleet 11774 // 3 - fast! (some voices don't get any faster than this 11775 11776 // Note: if these values are modified, must also modfiy them 11777 // in makePrefsValueReadable() 11778 options = [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3]; 11779 } else if (thisPref == 'prefDescVolume') { // 0 (mute) to 1 11780 options = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]; 11781 } 11782 if (typeof options !== 'undefined') { 11783 for (j=0; j < options.length; j++) { 11784 optionValue = options[j]; 11785 optionText = this.makePrefsValueReadable(thisPref,optionValue); 11786 $thisOption = $('<option>',{ 11787 value: optionValue, 11788 text: optionText 11789 }); 11790 if (this[thisPref] == optionValue) { 11791 $thisOption.prop('selected',true); 11792 } 11793 $thisField.append($thisOption); 11794 $thisDiv.append($thisLabel,$thisField); 11795 } 11796 } 11797 } 11798 // add a change handler that announces the sample description text 11799 $thisField.on('change',function() { 11800 thisObj.announceText('sample',thisObj.currentSampleText); 11801 }); 11802 $thisDiv.append($thisLabel,$thisField); 11803 } 11804 } else { // all other fields are checkboxes 11805 $thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>'); 11806 $thisField = $('<input>',{ 11807 type: 'checkbox', 11808 name: thisPref, 11809 id: thisId, 11810 value: 'true' 11811 }); 11812 // check current active value for this preference 11813 if (this[thisPref] === 1) { 11814 $thisField.prop('checked',true); 11815 } 11816 if (form === 'keyboard') { 11817 // add a change handler that updates the list of current keyboard shortcuts 11818 $thisField.on('change',function() { 11819 changedPref = $(this).attr('name'); 11820 if (changedPref === 'prefAltKey') { 11821 changedSpan = '.able-modkey-alt'; 11822 changedText = thisObj.translate( 'prefAltKey', 'Alt' ) + ' + '; 11823 } else if (changedPref === 'prefCtrlKey') { 11824 changedSpan = '.able-modkey-ctrl'; 11825 changedText = thisObj.translate( 'prefCtrlKey', 'Control' ) + ' + '; 11826 } else if (changedPref === 'prefShiftKey') { 11827 changedSpan = '.able-modkey-shift'; 11828 changedText = thisObj.translate( 'prefShiftKey', 'Shift' ) + ' + '; 11829 } 11830 if ( changedPref !== 'prefNoKeyShortcuts' ) { 11831 if ($(this).is(':checked')) { 11832 $(changedSpan).text(changedText); 11833 } else { 11834 $(changedSpan).text(''); 11835 } 11836 } else { 11837 if ($(this).is(':checked')) { 11838 $('.able-modkey-item').addClass('hidden'); 11839 } else { 11840 $('.able-modkey-item').removeClass('hidden'); 11841 } 11842 } 11843 }); 11844 } 11845 $thisDiv.append($thisField,$thisLabel); 11846 } 11847 if (thisPref === 'prefDescVoice' && !this.descVoices.length) ; else { 11848 $fieldset.append($thisDiv); 11849 } 11850 } 11851 } 11852 $prefsDiv.append($fieldset); 11853 11854 if (form === 'captions') { 11855 // add a sample closed caption div to prefs dialog 11856 // do not show this for YouTube captions, since it's not an accurate reflection 11857 if (!this.usingYouTubeCaptions) { 11858 this.$sampleCapsDiv = $('<div>',{ 11859 'class': 'able-captions-sample' 11860 }).text( this.translate( 'sampleCaptionText', 'Sample caption text' ) ); 11861 $prefsDiv.append(this.$sampleCapsDiv); 11862 this.stylizeCaptions(this.$sampleCapsDiv); 11863 } 11864 } else if (form === 'descriptions') { 11865 if (this.synth) { 11866 // add a div with sample audio description text 11867 this.$sampleDescDiv = $('<div>',{ 11868 'class': 'able-desc-sample' 11869 }).text( this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) ); 11870 $prefsDiv.append(this.$sampleDescDiv); 11871 this.currentSampleText = this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ); 11872 } 11873 } else if (form === 'keyboard') { 11874 let shortcutClass = (this.prefNoKeyShortcuts === 1 ) ? 'able-modkey-item hidden' : 'able-modkey-item'; 11875 11876 // add a current list of keyboard shortcuts 11877 $kbHeading = $('<h2>',{ 11878 text: this.translate( 'prefHeadingKeyboard2', 'Current keyboard shortcuts' ) 11879 }); 11880 $kbList = $('<ul>'); 11881 // create arrays of kbLabels and keys 11882 kbLabels = []; 11883 keys = []; 11884 for (i=0; i<this.controls.length; i++) { 11885 if (this.controls[i] === 'play') { 11886 kbLabels.push( this.translate( 'play', 'Play' ) + '/' + this.translate( 'pause', 'Pause' ) ); 11887 keys.push('p</span>, <span class="able-help-modifiers"> ' + this.translate( 'spacebar', 'spacebar' )); 11888 } else if (this.controls[i] === 'restart') { 11889 kbLabels.push(this.translate( 'restart', 'Restart' )); 11890 keys.push('s'); 11891 } else if (this.controls[i] === 'previous') { 11892 kbLabels.push( this.translate( 'prevTrack', 'Previous track' ) ); 11893 keys.push('b'); // b = back 11894 } else if (this.controls[i] === 'next') { 11895 kbLabels.push( this.translate( 'nextTrack', 'Next track' ) ); 11896 keys.push('n'); 11897 } else if (this.controls[i] === 'rewind') { 11898 kbLabels.push(this.translate( 'rewind', 'Rewind' )); 11899 keys.push('r'); 11900 } else if (this.controls[i] === 'forward') { 11901 kbLabels.push(this.translate( 'forward', 'Forward' )); 11902 keys.push('f'); 11903 } else if (this.controls[i] === 'volume') { 11904 kbLabels.push(this.translate( 'volume', 'Volume' )); 11905 keys.push('v</span>,' + ' <span class="able-modkey">1-9'); 11906 // mute toggle 11907 kbLabels.push(this.translate( 'mute', 'Mute' ) + '/' + this.translate( 'unmute', 'Unmute' )); 11908 keys.push('m'); 11909 } else if (this.controls[i] === 'captions') { 11910 if (this.captions.length > 1) { 11911 // caption button launches a Captions popup menu 11912 kbLabels.push(this.translate( 'captions', 'Captions' )); 11913 } else { 11914 // there is only one caption track 11915 // therefore caption button is a toggle 11916 if (this.captionsOn) { 11917 kbLabels.push(this.translate( 'hideCaptions', 'Hide captions' )); 11918 } else { 11919 kbLabels.push(this.translate( 'showCaptions', 'Show captions' )); 11920 } 11921 } 11922 keys.push('c'); 11923 } else if (this.controls[i] === 'descriptions') { 11924 if (this.descOn) { 11925 kbLabels.push(this.translate( 'turnOffDescriptions', 'Turn off descriptions' )); 11926 } else { 11927 kbLabels.push(this.translate( 'turnOnDescriptions', 'Turn on descriptions' )); 11928 } 11929 keys.push('d'); 11930 } else if (this.controls[i] === 'prefs') { 11931 kbLabels.push(this.translate( 'preferences', 'Preferences' )); 11932 keys.push('e'); 11933 } 11934 } 11935 for (i=0; i<keys.length; i++) { 11936 // alt 11937 kbListText = '<span class="able-modkey-alt">'; 11938 if (this.prefAltKey === 1) { 11939 kbListText += this.translate( 'prefAltKey', 'Alt' ) + ' + '; 11940 } 11941 kbListText += '</span>'; 11942 // ctrl 11943 kbListText += '<span class="able-modkey-ctrl">'; 11944 if (this.prefCtrlKey === 1) { 11945 kbListText += this.translate( 'prefCtrlKey', 'Control' ) + ' + '; 11946 } 11947 kbListText += '</span>'; 11948 // shift 11949 kbListText += '<span class="able-modkey-shift">'; 11950 if (this.prefShiftKey === 1) { 11951 kbListText += this.translate( 'prefShiftKey', 'Shift' ) + ' + '; 11952 } 11953 kbListText += '</span>'; 11954 kbListText += '<span class="able-modkey">' + keys[i] + '</span>'; 11955 kbListText += ' = ' + kbLabels[i]; 11956 $kbListItem = $('<li>',{ 11957 'class': shortcutClass, 11958 html: kbListText, 11959 }); 11960 $kbList.append($kbListItem); 11961 } 11962 // add Escape key 11963 kbListText = '<span class="able-modkey">' + this.translate( 'escapeKey', 'Escape' ) + '</span>'; 11964 kbListText += ' = ' + this.translate( 'escapeKeyFunction', 'Close current dialog or popup menu' ); 11965 $kbListItem = $('<li>',{ 11966 html: kbListText 11967 }); 11968 $kbList.append($kbListItem); 11969 // put it all together 11970 $prefsDiv.append($kbHeading,$kbList); 11971 } 11972 11973 // $prefsDiv (dialog) must be appended to the BODY! 11974 $('body').append($prefsDiv); 11975 dialog = new AccessibleDialog( 11976 $prefsDiv, 11977 this.$prefsButton, 11978 formTitle, 11979 thisObj.translate( 'closeButtonLabel', 'Close' ) 11980 ); 11981 11982 // Add save and cancel buttons. 11983 $buttonContainer = $( '<div class="able-prefs-buttons"></div>' ); 11984 $saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>'); 11985 $cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>'); 11986 $saveButton.on( 'click', function () { 11987 dialog.hide(); 11988 thisObj.savePrefsFromForm(); 11989 }); 11990 $cancelButton.on( 'click', function () { 11991 dialog.hide(); 11992 thisObj.resetPrefsForm(); 11993 }); 11994 $buttonContainer.append( $saveButton,$cancelButton ); 11995 $prefsDiv.append($buttonContainer); 11996 // Associate the dialog's H1 as aria-labelledby for groups of fields 11997 // (alternative to fieldset and legend) 11998 if (form === 'captions' || form === 'transcript') { 11999 $fieldset.attr('aria-labelledby',dialog.titleH1.attr('id')); 12000 } 12001 12002 // add global reference for future control 12003 if (form === 'captions') { 12004 this.captionPrefsDialog = dialog; 12005 } else if (form === 'descriptions') { 12006 this.descPrefsDialog = dialog; 12007 } else if (form === 'keyboard') { 12008 this.keyboardPrefsDialog = dialog; 12009 } else if (form === 'transcript') { 12010 this.transcriptPrefsDialog = dialog; 12011 } 12012 12013 // Add click handler for dialog close button 12014 // (button is added in dialog.js) 12015 $('div.able-prefs-form button.modalCloseButton').on( 'click', function() { 12016 thisObj.resetPrefsForm(); 12017 }); 12018 // Add handler for escape key 12019 $('div.able-prefs-form').on( 'keydown', function(e) { 12020 if (e.key === 'Escape') { 12021 thisObj.resetPrefsForm(); 12022 } 12023 }); 12024 }; 12025 12026 AblePlayer.prototype.getPrefVoice = function () { 12027 12028 // return user's preferred voice for the current language from preferences.voices 12029 var lang, preferences, i; 12030 12031 if (this.selectedDescriptions) { 12032 lang = this.selectedDescriptions.language; 12033 } else if (this.captionLang) { 12034 lang = this.captionLang; 12035 } else { 12036 lang = this.lang; 12037 } 12038 preferences = this.getPref(); 12039 if (preferences.voices) { 12040 for (i=0; i < preferences.voices.length; i++) { 12041 if (preferences.voices[i].lang === lang) { 12042 return preferences.voices[i].name; 12043 } 12044 } 12045 } 12046 return null; // user has no saved preference 12047 }; 12048 12049 AblePlayer.prototype.rebuildVoicePrefsForm = function ( field ) { 12050 12051 // Called if this.descVoices changes, which may happen if: 12052 // getBrowserVoices() succeeds after an earlier failure 12053 // user changes language of captions/subtitles and descVoices changes to match the new language 12054 12055 var i, optionValue, optionText, $thisOption; 12056 12057 this.$voiceSelectField = $('#' + this.mediaId + field); 12058 this.$voiceSelectField.empty(); 12059 for (i=0; i < this.descVoices.length; i++) { 12060 optionValue = this.descVoices[i].name; 12061 optionText = optionValue + ' (' + this.descVoices[i].lang + ')'; 12062 $thisOption = $('<option>',{ 12063 'value': optionValue, 12064 'data-lang': this.descVoices[i].lang.substring(0,2).toLowerCase(), 12065 text: optionText 12066 }); 12067 if (this.prefDescVoice == optionValue) { 12068 $thisOption.prop('selected',true); 12069 } 12070 this.$voiceSelectField.append($thisOption); 12071 } 12072 }; 12073 12074 AblePlayer.prototype.makePrefsValueReadable = function(pref,value) { 12075 12076 // The values for pitch, rate, and volume (web speech API) 12077 // are strange and inconsistent between variables 12078 // this function returns text that is more readable than the values themselves 12079 12080 if (pref === 'prefDescPitch' || pref === 'prefCaptionsPitch' ) { 12081 if (value === 0) { 12082 return this.translate( 'prefDescPitch1', 'Very low' ); 12083 } else if (value === 0.5) { 12084 return this.translate( 'prefDescPitch2', 'Low' ); 12085 } else if (value === 1) { 12086 return this.translate( 'prefDescPitch3', 'Default' ); 12087 } else if (value === 1.5) { 12088 return this.translate( 'prefDescPitch4', 'High' ); 12089 } else if (value === 2) { 12090 return this.translate( 'prefDescPitch5', 'Very high' ); 12091 } 12092 } else if (pref === 'prefDescRate' || pref === 'prefCaptionsRate' ) { 12093 // default in the API is 0.1 to 10, where 1 is normal speaking voice 12094 // our custom range offers several rates close to 1 12095 // plus a couple of crazy fast ones for sport 12096 // Our more readable options (1-10) or mapped here to API values 12097 if (value === 0.7) { 12098 return 1; 12099 } else if (value === 0.8) { 12100 return 2; 12101 } else if (value === 0.9) { 12102 return 3; 12103 } else if (value === 1) { 12104 return 4; 12105 } else if (value === 1.1) { 12106 return 5; 12107 } else if (value === 1.2) { 12108 return 6; 12109 } else if (value === 1.5) { 12110 return 7; 12111 } else if (value === 2) { 12112 return 8; 12113 } else if (value === 2.5) { 12114 return 9; 12115 } else if (value === 3) { 12116 return 10; 12117 } 12118 } else if (pref === 'prefDescVolume' || pref === 'prefCaptionsVolume' ) { 12119 // values range from 0.1 to 1.0 12120 return value * 100 + '%'; 12121 } 12122 return value; 12123 }; 12124 12125 AblePlayer.prototype.resetPrefsForm = function () { 12126 12127 // Reset preferences form with default values from preferences 12128 // Called when: 12129 // User clicks cancel or close button in Prefs Dialog 12130 // User presses Escape to close Prefs dialog 12131 // User clicks Save in Prefs dialog, & there's more than one player on page 12132 12133 var preferences, available, i, prefName; 12134 12135 preferences = this.getPref(); 12136 available = this.getAvailablePreferences(); 12137 for (i=0; i<available.length; i++) { 12138 prefName = available[i]['name']; 12139 if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 12140 // this is a caption-related select box 12141 $('select[name="' + prefName + '"]').val(preferences.preferences[prefName]); 12142 } else { // all others are checkboxes 12143 if (this[prefName] === 1) { 12144 $('input[name="' + prefName + '"]').prop('checked',true); 12145 } else { 12146 $('input[name="' + prefName + '"]').prop('checked',false); 12147 } 12148 } 12149 } 12150 // also restore style of sample caption div 12151 this.stylizeCaptions(this.$sampleCapsDiv); 12152 }; 12153 12154 AblePlayer.prototype.savePrefsFromForm = function () { 12155 12156 // Return a prefs object constructed from the form. 12157 // called when user saves the Preferences form 12158 // update preferences with new value 12159 var preferences, available, prefName, prefId, 12160 voiceSelectId, newVoice, numChanges, voiceLangFound, 12161 numCapChanges, capSizeChanged, capSizeValue, newValue; 12162 12163 numChanges = 0; 12164 numCapChanges = 0; // changes to caption-style-related preferences 12165 capSizeChanged = false; 12166 preferences = this.getPref(); 12167 available = this.getAvailablePreferences(); 12168 for (var i=0; i < available.length; i++) { 12169 // only prefs with labels are used in the Prefs form 12170 if (available[i]['label']) { 12171 prefName = available[i]['name']; 12172 prefId = this.mediaId + '_' + prefName; 12173 if (prefName === 'prefDescVoice') { 12174 if (typeof preferences.voices === 'undefined') { 12175 preferences.voices = []; 12176 } 12177 voiceSelectId = this.mediaId + '_prefDescVoice'; 12178 this.prefDescVoice = $('select#' + voiceSelectId).find(':selected').val(); 12179 this.prefDescVoiceLang = $('select#' + voiceSelectId).find(':selected').attr('data-lang'); 12180 // replace preferred voice for this lang in preferences.voices array, if one exists 12181 // otherwise, add it to the array 12182 voiceLangFound = false; 12183 for (var v=0; v < preferences.voices.length; v++) { 12184 if (preferences.voices[v].lang === this.prefDescVoiceLang) { 12185 voiceLangFound = true; 12186 preferences.voices[v].name = this.prefDescVoice; 12187 } 12188 } 12189 if (!voiceLangFound) { 12190 // no voice has been saved yet for this language. Add it to array. 12191 newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang}; 12192 preferences.voices.push(newVoice); 12193 } 12194 numChanges++; 12195 } else if (prefName == 'prefDescMethod') { 12196 // As of v4.0.10, prefDescMethod is no longer a choice 12197 this.prefDescMethod = 'video'; 12198 if (this.prefDescMethod !== preferences.preferences['prefDescMethod']) { // user's preference has changed 12199 preferences.preferences['prefDescMethod'] = this.prefDescMethod; 12200 numChanges++; 12201 } 12202 } else if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) { 12203 // this is one of the caption-related select fields 12204 newValue = $('select[id="' + prefId + '"]').val(); 12205 if (preferences.preferences[prefName] !== newValue) { // user changed setting 12206 preferences.preferences[prefName] = newValue; 12207 // also update global var for this pref (for caption fields, not done elsewhere) 12208 this[prefName] = newValue; 12209 numChanges++; 12210 numCapChanges++; 12211 } 12212 if (prefName === 'prefCaptionsSize') { 12213 capSizeChanged = true; 12214 capSizeValue = newValue; 12215 } 12216 } else if ((prefName.indexOf('Desc') !== -1) && (prefName !== 'prefDescPause') && prefName !== 'prefDescVisible') { 12217 // this is one of the description-related select fields 12218 newValue = $('select[id="' + prefId + '"]').val(); 12219 if (preferences.preferences[prefName] !== newValue) { // user changed setting 12220 preferences.preferences[prefName] = newValue; 12221 // also update global var for this pref 12222 this[prefName] = newValue; 12223 numChanges++; 12224 } 12225 } else { // all other fields are checkboxes 12226 if ($('input[id="' + prefId + '"]').is(':checked')) { 12227 preferences.preferences[prefName] = 1; 12228 if (this[prefName] === 1) ; else { 12229 // user has just turned this pref on 12230 this[prefName] = 1; 12231 numChanges++; 12232 } 12233 } else { // thisPref is not checked 12234 preferences.preferences[prefName] = 0; 12235 if (this[prefName] === 1) { 12236 // user has just turned this pref off 12237 this[prefName] = 0; 12238 numChanges++; 12239 } 12240 } 12241 } 12242 } 12243 } 12244 if (numChanges > 0) { 12245 this.setPrefs(preferences); 12246 this.showAlert( this.translate( 'prefSuccess', 'Your changes have been saved.' ) ); 12247 } else { 12248 this.showAlert( this.translate( 'prefNoChange', "You didn't make any changes" ) ); 12249 } 12250 if (this.player === 'youtube' && 12251 (typeof this.usingYouTubeCaptions !== 'undefined' && this.usingYouTubeCaptions) && 12252 capSizeChanged) { 12253 // update font size of YouTube captions 12254 this.youTubePlayer.setOption('captions','fontSize',this.translatePrefs('size',capSizeValue,'youtube')); 12255 } 12256 if (!AblePlayer.hasSingleInstance()) { 12257 // there are multiple players on this page. 12258 // update prefs for ALL of them 12259 for (const instance of AblePlayer.ablePlayerInstances) { 12260 instance.updatePlayerPrefs(); 12261 instance.loadCurrentPreferences(); 12262 instance.resetPrefsForm(); 12263 if (numCapChanges > 0) { 12264 instance.stylizeCaptions(instance.$captionsDiv); 12265 // also apply same changes to descriptions, if present 12266 if (typeof instance.$descDiv !== 'undefined') { 12267 instance.stylizeCaptions(instance.$descDiv); 12268 } 12269 } 12270 } 12271 } else { 12272 // there is only one player 12273 this.updatePlayerPrefs(); 12274 if (numCapChanges > 0) { 12275 this.stylizeCaptions(this.$captionsDiv); 12276 // also apply same changes to descriptions, if present 12277 if (typeof this.$descDiv !== 'undefined') { 12278 this.stylizeCaptions(this.$descDiv); 12279 } 12280 } 12281 } 12282 }; 12283 12284 AblePlayer.prototype.updatePlayerPrefs = function () { 12285 12286 // Update player based on current prefs. Safe to call multiple times. 12287 if (this.$transcriptDiv) { 12288 // tabbable transcript 12289 if (this.prefTabbable === 1) { 12290 this.$transcriptDiv.find('span.able-transcript-seekpoint').attr('tabindex','0'); 12291 } else { 12292 this.$transcriptDiv.find('span.able-transcript-seekpoint').removeAttr('tabindex'); 12293 } 12294 12295 // transcript highlights 12296 if (this.prefHighlight === 0) { 12297 // user doesn't want highlights; remove any existing highlights 12298 this.$transcriptDiv.find('span').removeClass('able-highlight'); 12299 } 12300 } 12301 12302 // Re-initialize caption and description in case relevant settings have changed 12303 this.updateCaption(); 12304 this.initDescription(); 12305 }; 12306 12307 AblePlayer.prototype.usingModifierKeys = function(e) { 12308 12309 // return true if user is holding down required modifier keys 12310 if ((this.prefAltKey === 1) && !e.altKey) { 12311 return false; 12312 } 12313 if ((this.prefCtrlKey === 1) && !e.ctrlKey) { 12314 return false; 12315 } 12316 if ((this.prefShiftKey === 1) && !e.shiftKey) { 12317 return false; 12318 } 12319 return true; 12320 }; 12321 } 12322 12323 function addSearchFunctions(AblePlayer) { 12324 AblePlayer.prototype.showSearchResults = function () { 12325 // search VTT file for all instances of searchTerms 12326 // Currently just supports search terms separated with one or more spaces 12327 12328 // TODO: Add support for more robust search syntax: 12329 // Search terms wrapped in quotation marks ("") must occur exactly as they appear in the quotes 12330 // Search terms with an attached minus sign (e.g., -term) are to be excluded from results 12331 // Boolean AND/OR operators 12332 // ALSO: Add localization support 12333 12334 var thisObj = this; 12335 if (this.searchDiv && this.searchString) { 12336 // sanitize search string 12337 var cleanSearchString = purify.sanitize(this.searchString); 12338 if ($("#" + this.SearchDiv)) { 12339 var searchStringHtml = "<p>" + this.translate( 'resultsSummary1', 'You searched for:') + ' '; 12340 searchStringHtml += 12341 '<span id="able-search-term-echo">' + cleanSearchString + "</span>"; 12342 searchStringHtml += "</p>"; 12343 var resultsArray = this.searchFor( 12344 cleanSearchString, 12345 this.searchIgnoreCaps 12346 ); 12347 if (resultsArray.length > 0) { 12348 var $resultsSummary = $("<p>", { 12349 class: "able-search-results-summary", 12350 }); 12351 var resultsSummaryText = this.translate( 'resultsSummary2', 'Found %1 matching items.', [ '<strong>' + resultsArray.length + '</strong>' ] ); 12352 resultsSummaryText += ' ' + this.translate( 'resultsSummary3', 'Click the time associated with any item to play the video from that point.' ); 12353 $resultsSummary.html( resultsSummaryText ); 12354 var $resultsList = $("<ul>"); 12355 for (var i = 0; i < resultsArray.length; i++) { 12356 var resultId = "aria-search-result-" + i; 12357 var $resultsItem = $("<li>", {}); 12358 var itemStartTime = this.secondsToTime(resultsArray[i]["start"]); 12359 var itemLabel = 12360 this.translate( 'searchButtonLabel', 'Play at %1', [ itemStartTime["title"] ] ); 12361 var itemStartSpan = $("<button>", { 12362 class: "able-search-results-time", 12363 "data-start": resultsArray[i]["start"], 12364 "aria-label": itemLabel, 12365 "aria-describedby": resultId, 12366 }); 12367 itemStartSpan.text(itemStartTime["value"]); 12368 // add a listener for clisk on itemStart 12369 itemStartSpan.on("click", function (e) { 12370 thisObj.seekTrigger = "search"; 12371 var spanStart = parseFloat($(this).attr("data-start")); 12372 // Add a tiny amount so that we're inside the span. 12373 spanStart += 0.01; 12374 thisObj.seeking = true; 12375 thisObj.seekTo(spanStart); 12376 }); 12377 12378 var itemText = $("<span>", { 12379 class: "able-search-result-text", 12380 id: resultId, 12381 }); 12382 itemText.html('...' + resultsArray[i]["caption"] + '...'); 12383 $resultsItem.append(itemStartSpan, itemText); 12384 $resultsList.append($resultsItem); 12385 } 12386 $('#' + this.searchDiv) 12387 .html(searchStringHtml) 12388 .append($resultsSummary, $resultsList); 12389 } else { 12390 var noResults = $('<p>').text( this.translate( 'noResultsFound', 'No results found.' ) ); 12391 $('#' + this.searchDiv) 12392 .html(searchStringHtml) 12393 .append(noResults); 12394 } 12395 } 12396 } 12397 }; 12398 12399 AblePlayer.prototype.searchFor = function (searchString, ignoreCaps) { 12400 // return chronological array of caption cues that match searchTerms 12401 var captionLang, captions, results, caption, c, i, j; 12402 results = []; 12403 // split searchTerms into an array 12404 var searchTerms = searchString.split(" "); 12405 if (this.captions.length > 0) { 12406 // Get caption track that matches this.searchLang 12407 for (i = 0; i < this.captions.length; i++) { 12408 if (this.captions[i].language === this.searchLang) { 12409 captionLang = this.searchLang; 12410 captions = this.captions[i].cues; 12411 } 12412 } 12413 if (captions.length > 0) { 12414 c = 0; 12415 for (i = 0; i < captions.length; i++) { 12416 if ( 12417 $.inArray(captions[i].components.children[0]["type"], [ 12418 "string", 12419 "i", 12420 "b", 12421 "u", 12422 "v", 12423 "c", 12424 ]) !== -1 12425 ) { 12426 caption = this.flattenCueForCaption(captions[i]); 12427 var captionNormalized = ignoreCaps 12428 ? caption.toLowerCase() 12429 : caption; 12430 for (j = 0; j < searchTerms.length; j++) { 12431 var searchTermNormalized = ignoreCaps 12432 ? searchTerms[j].toLowerCase() 12433 : searchTerms[j]; 12434 if (captionNormalized.indexOf(searchTermNormalized) !== -1) { 12435 results[c] = []; 12436 results[c]["start"] = captions[i].start; 12437 results[c]["lang"] = captionLang; 12438 results[c]["caption"] = this.highlightSearchTerm( 12439 searchTerms, 12440 caption 12441 ); 12442 c++; 12443 break; 12444 } 12445 } 12446 } 12447 } 12448 } 12449 } 12450 return results; 12451 }; 12452 12453 AblePlayer.prototype.highlightSearchTerm = function ( 12454 searchTerms, 12455 resultString 12456 ) { 12457 // highlight ALL found searchTerms in the current resultString 12458 // Need to step through the remaining terms to see if they're present as well 12459 searchTerms.forEach(function (searchTerm) { 12460 var reg = new RegExp(searchTerm, "gi"); 12461 resultString = resultString.replace( 12462 reg, 12463 '<span class="able-search-term">$&</span>' 12464 ); 12465 }); 12466 return resultString; 12467 }; 12468 12469 /** 12470 * Convert a number of seconds into readable time information. 12471 * 12472 * @param {int} totalSecondsFloat 12473 * 12474 * @returns {string[]} array 'value' HH:MM:SS and 'title' speakable time. 12475 */ 12476 AblePlayer.prototype.secondsToTime = function (totalSecondsFloat) { 12477 // return an array of totalSeconds converted into two formats 12478 // time['value'] = HH:MM:SS with hours dropped if there are none 12479 // time['title'] = a speakable rendering, so speech rec users can easily speak the link 12480 var totalSeconds = Math.floor(totalSecondsFloat); 12481 12482 var h = parseInt(totalSeconds / 3600, 10) % 24; 12483 var m = parseInt(totalSeconds / 60, 10) % 60; 12484 var s = totalSeconds % 60; 12485 var value = ''; 12486 var title = ''; 12487 if (h > 0) { 12488 value += String(h).padStart(2, '0') + ':'; 12489 title += h > 0 ? h + ' ' + (h === 1 ? this.translate( 'hour', 'hour' ) : this.translate( 'hours', 'hours' ) ) : ''; 12490 } 12491 12492 value += String(m).padStart(2, '0') + ':'; 12493 title += m > 0 ? m + ' ' + (m === 1 ? this.translate( 'minute', 'minute' ) : this.translate( 'minutes', 'minutes' ) ) : ''; 12494 12495 value += String(s).padStart(2, '0'); 12496 title += s > 0 ? s + ' ' + (s === 1 ? this.translate( 'second', 'second' ) : this.translate( 'seconds', 'seconds' ) ) : ''; 12497 12498 var time = []; 12499 time["value"] = value; 12500 time["title"] = title; 12501 12502 return time; 12503 }; 12504 } 12505 12506 /* global YT */ 12507 12508 function addSignFunctions(AblePlayer) { 12509 AblePlayer.prototype.initSignLanguage = function() { 12510 this.hasSignLanguage = false; 12511 // Sign language is only currently supported in HTML5 player and YouTube. 12512 var hasLocalSrc = ( this.$sources.first().attr('data-sign-src') !== undefined && this.$sources.first().attr('data-sign-src') !== "" ); 12513 // YouTube src can either be on a `source` element or on the `video` element. 12514 var hasRemoteSrc = ( this.$media.data('youtube-sign-src') !== undefined && this.$media.data('youtube-sign-src') !== "" ); 12515 var hasRemoteSource = ( this.$sources.first().attr('data-youtube-sign-src') !== undefined && this.$sources.first().attr('data-youtube-sign-src') !== '' ); 12516 if ( ! this.isIOS() && ( hasLocalSrc || hasRemoteSrc || hasRemoteSource ) && ( this.player === 'html5' || this.player === 'youtube' ) ) { 12517 // check to see if there's a sign language video accompanying this video 12518 // check only the first source 12519 // If sign language is provided, it must be provided for all sources 12520 let ytSignSrc = this.youTubeSignId ?? purify.sanitize( this.$sources.first().attr('data-youtube-sign-src') ); 12521 let signSrc = purify.sanitize( this.$sources.first().attr('data-sign-src') ); 12522 let signVideo = purify.sanitize( this.$media.data('youtube-sign-src') ); 12523 this.signFile = (hasLocalSrc ) ? signSrc : false; 12524 if ( hasRemoteSrc ) { 12525 this.signYoutubeId = signVideo; 12526 } else if ( hasRemoteSource ) { 12527 this.signYoutubeId = ytSignSrc; 12528 } 12529 if ( this.signFile || this.signYoutubeId ) { 12530 if (this.isIOS()) { 12531 // iOS does not allow multiple videos to play simultaneously 12532 // Therefore, sign language as rendered by Able Player unfortunately won't work 12533 if (this.debug) ; 12534 } else { 12535 if (this.debug) ; 12536 this.hasSignLanguage = true; 12537 this.injectSignPlayerCode(); 12538 } 12539 } 12540 } 12541 }; 12542 12543 AblePlayer.prototype.injectSignPlayerCode = function() { 12544 12545 // create and inject surrounding HTML structure 12546 var signVideoId, i, signSrc, srcType, $signSource; 12547 12548 signVideoId = this.mediaId + '-sign'; 12549 12550 if ( this.signFile || this.signYoutubeId ) { 12551 if ( null !== this.$signDivLocation ) { 12552 this.$signDivLocation.addClass( 'able-sign-window able-fixed' ); 12553 this.$signWindow = this.$signDivLocation; 12554 } else { 12555 this.$signWindow = $('<div>',{ 12556 'class' : 'able-sign-window', 12557 'role': 'dialog', 12558 'aria-label': this.translate( 'sign', 'Sign language' ) 12559 }); 12560 this.$signToolbar = $('<div>',{ 12561 'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls' 12562 }); 12563 this.$signWindow.append(this.$signToolbar); 12564 } 12565 12566 this.$ableWrapper.append(this.$signWindow); 12567 } 12568 12569 if ( this.signFile ) { 12570 this.$signVideo = $('<video>',{ 12571 'id' : signVideoId, 12572 'tabindex' : '-1', 12573 'muted' : true, 12574 }); 12575 this.signVideo = this.$signVideo[0]; 12576 12577 if ( this.signFile ) { 12578 $signSource = $('<source>',{ 12579 'src' : this.signFile, 12580 'type' : 'video/' + this.signFile.substr(-3) 12581 }); 12582 this.$signVideo.append($signSource); 12583 } else { 12584 // for each original <source>, add a <source> to the sign <video> 12585 for (i=0; i < this.$sources.length; i++) { 12586 signSrc = purify.sanitize( this.$sources[i].getAttribute('data-sign-src') ); 12587 srcType = this.$sources[i].getAttribute('type'); 12588 if (signSrc) { 12589 $signSource = $('<source>',{ 12590 'src' : signSrc, 12591 'type' : srcType 12592 }); 12593 this.$signVideo.append($signSource); 12594 } else { 12595 // source is missing a sign language version 12596 // can't include sign language 12597 this.hasSignLanguage = false; 12598 return; 12599 } 12600 } 12601 } 12602 this.$signWindow.append( this.$signVideo ); 12603 } else if ( this.signYoutubeId ) { 12604 this.signYoutube = this.initYouTubeSignPlayer(); 12605 } 12606 12607 // make it draggable 12608 if ( null === this.$signDivLocation ) { 12609 this.initDragDrop('sign'); 12610 } 12611 12612 if (this.prefSign === 1) { 12613 // sign window is on. Go ahead and position it and show it 12614 if ( null === this.$signDivLocation ) { 12615 this.positionDraggableWindow('sign',this.getDefaultWidth('sign')); 12616 } 12617 } else { 12618 this.$signWindow.hide(); 12619 } 12620 }; 12621 12622 12623 AblePlayer.prototype.initYouTubeSignPlayer = function () { 12624 12625 var thisObj, deferred, promise; 12626 thisObj = this; 12627 deferred = new this.defer(); 12628 promise = deferred.promise(); 12629 12630 this.youTubeSignPlayerReady = false; 12631 12632 if (AblePlayer.youTubeIframeAPIReady) { 12633 // Script already loaded and ready. 12634 thisObj.finalizeYoutubeSignInit().then(function() { 12635 deferred.resolve(); 12636 }); 12637 } else { 12638 // Has another player already started loading the script? If so, abort... 12639 if ( ! AblePlayer.loadingYouTubeIframeAPI ) { 12640 thisObj.getScript('https://www.youtube.com/iframe_api', function () { 12641 }); 12642 } 12643 12644 // Otherwise, keeping waiting for script load event... 12645 $('body').on('youTubeIframeAPIReady', function () { 12646 thisObj.finalizeYoutubeSignInit().then(function() { 12647 deferred.resolve(); 12648 }); 12649 }); 12650 } 12651 return promise; 12652 }; 12653 12654 AblePlayer.prototype.finalizeYoutubeSignInit = function () { 12655 12656 // This is called once we're sure the Youtube iFrame API is loaded -- see above 12657 var deferred, promise, thisObj, containerId, autoplay; 12658 12659 deferred = new this.defer(); 12660 promise = deferred.promise(); 12661 thisObj = this; 12662 containerId = this.mediaId + '_youtube_sign'; 12663 12664 this.$signWindow.append($('<div>').attr('id', containerId)); 12665 autoplay = (this.okToPlay) ? 1 : 0; 12666 12667 // Documentation https://developers.google.com/youtube/player_parameters 12668 this.youTubeSignPlayer = new YT.Player(containerId, { 12669 videoId: this.getYouTubeId(this.signYoutubeId), 12670 host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com', 12671 playerVars: { 12672 autoplay: autoplay, 12673 cc_lang_pref: this.captionLang, // set the caption language 12674 cc_load_policy: 0, 12675 controls: 0, // no controls, using our own 12676 disableKb: 1, // disable keyboard shortcuts, using our own 12677 enablejsapi: 1, 12678 hl: this.lang, // set the UI language to match Able Player 12679 iv_load_policy: 3, // do not show video annotations 12680 origin: window.location.origin, 12681 playsinline: this.playsInline, 12682 rel: 0, // when video ends, show only related videos from same channel (1 shows any) 12683 start: this.startTime 12684 }, 12685 events: { 12686 onReady: function (player) { 12687 player.target.mute(); 12688 player.target.unloadModule( 'captions' ); 12689 thisObj.youTubeSignPlayerReady = true; 12690 12691 deferred.resolve(); 12692 }, 12693 onError: function (e) { 12694 deferred.reject(); 12695 }, 12696 onStateChange: function (e) { 12697 thisObj.getPlayerState().then(function() { 12698 // no actions 12699 }); 12700 }, 12701 onApiChange: function() { 12702 // No actions 12703 }, 12704 onPlaybackQualityChange: function () { 12705 // no actions 12706 }, 12707 } 12708 }); 12709 12710 return promise; 12711 }; 12712 12713 } 12714 12715 function addTrackFunctions(AblePlayer) { 12716 // Loads files referenced in track elements, and performs appropriate setup. 12717 // For example, captions and text descriptions. 12718 // This will be called whenever the player is recreated. 12719 12720 AblePlayer.prototype.setupTracks = function () { 12721 var thisObj, deferred, promise, loadingPromises, loadingPromise, i, tracks, track, kind; 12722 12723 thisObj = this; 12724 12725 deferred = new this.defer(); 12726 promise = deferred.promise(); 12727 12728 loadingPromises = []; 12729 12730 if ($("#able-vts").length) { 12731 // Page includes a container for a VTS instance 12732 this.vtsTracks = []; 12733 this.hasVts = true; 12734 } else { 12735 this.hasVts = false; 12736 } 12737 12738 // Source array for populating the above arrays 12739 // varies, depending on whether there are dedicated description tracks 12740 if (this.hasDescTracks && this.descOn) { 12741 tracks = this.altTracks; 12742 } else { 12743 tracks = this.tracks; 12744 } 12745 for (i = 0; i < tracks.length; i++) { 12746 track = tracks[i]; 12747 kind = ( track.kind ) ? track.kind : 'subtitles'; 12748 12749 if (!track.src) { 12750 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 12751 // skip all the hullabaloo and go straight to setupCaptions 12752 thisObj.setupCaptions(track); 12753 } 12754 continue; 12755 } 12756 var trackSrc = track.src; 12757 loadingPromise = this.loadTextObject(trackSrc); 12758 loadingPromises.push( 12759 loadingPromise.catch(function (src) { 12760 console.warn("Failed to load captions track from " + src); 12761 }) 12762 ); 12763 loadingPromise.then( 12764 (function (track, kind) { 12765 trackSrc = track.src; 12766 var trackLang = track.language; 12767 var trackLabel = track.label; 12768 var trackDesc = track.desc; 12769 12770 return function (data) { 12771 var cues = thisObj.parseWebVTT(data).cues; 12772 if (thisObj.hasVts) { 12773 thisObj.setupVtsTracks( 12774 kind, 12775 trackLang, 12776 trackDesc, 12777 trackLabel, 12778 trackSrc, 12779 data.text 12780 ); 12781 } 12782 if (kind === 'captions' || kind === 'subtitles') { 12783 thisObj.setupCaptions(track, cues); 12784 } else if (kind === 'descriptions') { 12785 thisObj.setupDescriptions(track, cues); 12786 } else if (kind === 'chapters') { 12787 thisObj.setupChapters(track, cues); 12788 } else if (kind === 'metadata') { 12789 thisObj.setupMetadata(cues); 12790 } 12791 }; 12792 })(track, kind) 12793 ); 12794 } 12795 if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) { 12796 deferred.resolve(); 12797 } else { 12798 $.when.apply($, loadingPromises).then(function () { 12799 deferred.resolve(); 12800 }); 12801 } 12802 return promise; 12803 }; 12804 12805 AblePlayer.prototype.getTracks = function () { 12806 // define an array tracks with the following structure: 12807 // kind - string, e.g. "captions", "descriptions" 12808 // src - string, URL of WebVTT source file 12809 // language - string, lang code 12810 // label - string to display, e.g., in CC menu 12811 // def - Boolean, true if this is the default track 12812 // cues - array with startTime, endTime, and payload 12813 // desc - Boolean, true if track includes a data-desc attribute 12814 12815 var thisObj, deferred, promise, trackLang, trackLabel, isDefault, forDesc, 12816 hasDefault, hasTrackInDefLang, trackFound, i; 12817 12818 thisObj = this; 12819 hasDefault = false; 12820 12821 deferred = new this.defer(); 12822 promise = deferred.promise(); 12823 12824 this.$tracks = this.$media.find('track'); 12825 this.tracks = []; // only includes tracks that do NOT have data-desc 12826 this.altTracks = []; // only includes tracks that DO have data-desc 12827 12828 // Arrays for each kind, to be populated later 12829 this.captions = []; 12830 this.descriptions = []; 12831 this.chapters = []; 12832 this.meta = []; 12833 12834 this.hasCaptionsTrack = false; // will change to true if one or more tracks has kind="captions" 12835 this.hasDescTracks = false; // will change to true if one or more tracks has data-desc 12836 12837 if (this.$tracks.length) { 12838 this.usingYouTubeCaptions = false; 12839 // create object from HTML5 tracks 12840 this.$tracks.each(function (index, element) { 12841 if ($(this).attr('kind') === 'captions') { 12842 thisObj.hasCaptionsTrack = true; 12843 } else if ($(this).attr('kind') === 'descriptions') { 12844 thisObj.hasClosedDesc = true; 12845 } 12846 12847 // srcLang should always be included with <track>, but HTML5 spec doesn't require it 12848 // if not provided, assume track is the same language as the default player language 12849 if ($(this).attr('srclang')) { 12850 trackLang = $(this).attr('srclang'); 12851 } else { 12852 trackLang = thisObj.lang; 12853 } 12854 if ($(this).attr('label')) { 12855 trackLabel = $(this).attr('label'); 12856 } else { 12857 trackLabel = thisObj.getLanguageName(trackLang); 12858 } 12859 12860 if (typeof $(this).attr('default') !== 'undefined' && !hasDefault) { 12861 isDefault = true; 12862 hasDefault = true; 12863 } else if (trackLang === thisObj.lang) { 12864 // this track is in the default lang of the player 12865 // if there is no other default track specified 12866 // this will be the default 12867 hasTrackInDefLang = true; 12868 isDefault = false; // for now; this could change if there's no default attribute 12869 } else { 12870 isDefault = false; 12871 } 12872 if (isDefault) { 12873 // this.captionLang will also be the default language for non-caption tracks 12874 thisObj.captionLang = trackLang; 12875 } 12876 12877 if ($(this).data("desc") !== undefined) { 12878 forDesc = true; 12879 thisObj.hasDescTracks = true; 12880 } else { 12881 forDesc = false; 12882 } 12883 if (forDesc) { 12884 thisObj.altTracks.push({ 12885 kind: $(this).attr('kind'), 12886 src: $(this).attr('src'), 12887 language: trackLang, 12888 label: trackLabel, 12889 def: isDefault, 12890 desc: forDesc, 12891 }); 12892 } else { 12893 thisObj.tracks.push({ 12894 kind: $(this).attr('kind'), 12895 src: $(this).attr('src'), 12896 language: trackLang, 12897 label: trackLabel, 12898 def: isDefault, 12899 desc: forDesc, 12900 }); 12901 } 12902 12903 if (index == thisObj.$tracks.length - 1) { 12904 // This is the last track. 12905 if (!hasDefault) { 12906 if (hasTrackInDefLang) { 12907 thisObj.captionLang = thisObj.lang; 12908 trackFound = false; 12909 i = 0; 12910 while (i < thisObj.tracks.length && !trackFound) { 12911 if (thisObj.tracks[i]['language'] === thisObj.lang) { 12912 thisObj.tracks[i]['def'] = true; 12913 trackFound = true; 12914 } 12915 i++; 12916 } 12917 } else { 12918 // use the first track 12919 thisObj.tracks[0]['def'] = true; 12920 thisObj.captionLang = thisObj.tracks[0]['language']; 12921 } 12922 } 12923 // Remove 'default' attribute from all <track> elements 12924 // This data has already been saved to this.tracks 12925 // and some browsers will display the default captions, 12926 // despite all standard efforts to suppress them 12927 thisObj.$media.find("track").removeAttr("default"); 12928 } 12929 }); 12930 } 12931 if (!this.$tracks.length || !this.hasCaptionsTrack) { 12932 // this media has no track elements 12933 // if this is a youtube or vimeo player, check there for captions/subtitles 12934 if (this.player === 'youtube') { 12935 this.getYouTubeCaptionTracks().then(function () { 12936 if (thisObj.hasCaptions) { 12937 thisObj.usingYouTubeCaptions = true; 12938 if (thisObj.$captionsWrapper) { 12939 thisObj.$captionsWrapper.remove(); 12940 } 12941 } 12942 deferred.resolve(); 12943 }); 12944 } else if (this.player === 'vimeo') { 12945 this.getVimeoCaptionTracks().then(function () { 12946 if (thisObj.hasCaptions) { 12947 thisObj.usingVimeoCaptions = true; 12948 if (thisObj.$captionsWrapper) { 12949 thisObj.$captionsWrapper.remove(); 12950 } 12951 } 12952 deferred.resolve(); 12953 }); 12954 } else { 12955 // this is neither YouTube nor Vimeo 12956 // there just ain't no tracks (captions or otherwise) 12957 this.hasCaptions = false; 12958 if (thisObj.$captionsWrapper) { 12959 thisObj.$captionsWrapper.remove(); 12960 } 12961 deferred.resolve(); 12962 } 12963 } else { 12964 // there is at least one track with kind="captions" 12965 deferred.resolve(); 12966 } 12967 return promise; 12968 }; 12969 12970 AblePlayer.prototype.setupCaptions = function (track, cues) { 12971 // Setup player for display of captions (one track at a time) 12972 var inserted, i, capLabel; 12973 12974 // Insert track into captions array 12975 // in its proper alphabetical sequence by label 12976 if (typeof cues === "undefined") { 12977 cues = null; 12978 } 12979 12980 if (this.usingYouTubeCaptions || this.usingVimeoCaptions) ; else { 12981 if (this.captions.length === 0) { 12982 // this is the first 12983 this.captions.push({ 12984 language: track.language, 12985 label: track.label, 12986 def: track.def, 12987 cues: cues, 12988 }); 12989 } else { 12990 // there are already captions in the array 12991 inserted = false; 12992 for (i = 0; i < this.captions.length; i++) { 12993 capLabel = track.label; 12994 if (capLabel.toLowerCase() < this.captions[i].label.toLowerCase()) { 12995 // insert before track i 12996 this.captions.splice(i, 0, { 12997 language: track.language, 12998 label: track.label, 12999 def: track.def, 13000 cues: cues, 13001 }); 13002 inserted = true; 13003 break; 13004 } 13005 } 13006 if (!inserted) { 13007 // just add track to the end 13008 this.captions.push({ 13009 language: track.language, 13010 label: track.label, 13011 def: track.def, 13012 cues: cues, 13013 }); 13014 } 13015 } 13016 } 13017 13018 // there are captions available 13019 this.hasCaptions = true; 13020 this.currentCaption = -1; 13021 if (this.prefCaptions === 1) { 13022 this.captionsOn = true; 13023 } else if (this.prefCaptions === 0) { 13024 this.captionsOn = false; 13025 } else { 13026 // user has no prefs. Use default state. 13027 if (this.defaultStateCaptions === 1) { 13028 this.captionsOn = true; 13029 } else { 13030 this.captionsOn = false; 13031 } 13032 } 13033 if (this.mediaType === 'audio' && this.captionsOn) { 13034 this.$captionsContainer.removeClass('captions-off'); 13035 } 13036 13037 if ( 13038 !this.$captionsWrapper || 13039 (this.$captionsWrapper && 13040 !$.contains(this.$ableDiv[0], this.$captionsWrapper[0])) 13041 ) { 13042 // captionsWrapper either doesn't exist, or exists in an orphaned state 13043 // Either way, it needs to be rebuilt... 13044 this.$captionsDiv = $('<div>', { 13045 class: "able-captions", 13046 }); 13047 this.$captionsWrapper = $('<div>', { 13048 class: 'able-captions-wrapper', 13049 'aria-hidden': 'true', 13050 }).hide(); 13051 if (this.prefCaptionsPosition === 'below') { 13052 this.$captionsWrapper.addClass('able-captions-below'); 13053 } else { 13054 this.$captionsWrapper.addClass('able-captions-overlay'); 13055 } 13056 this.$captionsWrapper.append(this.$captionsDiv); 13057 this.$captionsContainer.append(this.$captionsWrapper); 13058 } 13059 }; 13060 13061 AblePlayer.prototype.setupDescriptions = function (track, cues) { 13062 // called via setupTracks() only if there is track with kind="descriptions" 13063 // prepares for delivery of text description , in case it's needed 13064 // whether and how it's delivered is controlled within description.js > initDescription() 13065 13066 this.hasClosedDesc = true; 13067 this.currentDescription = -1; 13068 this.descriptions.push({ 13069 cues: cues, 13070 language: track.language, 13071 }); 13072 }; 13073 13074 AblePlayer.prototype.setupChapters = function (track, cues) { 13075 // NOTE: WebVTT supports nested timestamps (to form an outline) 13076 // This is not currently supported. 13077 13078 this.hasChapters = true; 13079 this.chapters.push({ 13080 cues: cues, 13081 language: track.language, 13082 }); 13083 }; 13084 13085 AblePlayer.prototype.setupMetadata = function (cues) { 13086 if (this.metaType === 'text') { 13087 // Metadata is only supported if data-meta-div is provided 13088 // The player does not display metadata internally 13089 if (this.metaDiv) { 13090 if ($('#' + this.metaDiv)) { 13091 // container exists 13092 this.$metaDiv = $('#' + this.metaDiv); 13093 this.hasMeta = true; 13094 this.meta = cues; 13095 } 13096 } 13097 } else if (this.metaType === 'selector') { 13098 this.hasMeta = true; 13099 this.visibleSelectors = []; 13100 this.meta = cues; 13101 } 13102 }; 13103 13104 AblePlayer.prototype.loadTextObject = function (src) { 13105 var deferred, promise, thisObj, $tempDiv; 13106 13107 deferred = new this.defer(); 13108 promise = deferred.promise(); 13109 thisObj = this; 13110 13111 // create a temp div for holding data 13112 $tempDiv = $('<div>', { 13113 style: 'display:none', 13114 }); 13115 13116 // Fetch the content manually so it can be sanitized 13117 fetch(src) 13118 .then( response => { 13119 13120 return response.text(); 13121 }) 13122 .then( vtt => { 13123 // Split the input on double line breaks to handle each cue individually. 13124 var preParsed = vtt.split(/\r?\n\s*\r?\n/); 13125 var lines = '', line; 13126 13127 preParsed.forEach((l) => { 13128 // Sanitize each line. 13129 line = validate.sanitizeVttContent(l); 13130 lines += line + "\n\n"; 13131 }); 13132 // Load the sanitized content into the $tempDiv 13133 $tempDiv.html(lines); 13134 // Resolve the promise with the sanitized content 13135 let data = { 'src': src, 'text': lines }; 13136 deferred.resolve(data); 13137 $tempDiv.remove(); 13138 }) 13139 .catch( error => { 13140 if (thisObj.debug) ; 13141 deferred.reject(src); 13142 $tempDiv.remove(); 13143 }); 13144 13145 return promise; 13146 }; 13147 } 13148 13149 function addTranscriptFunctions(AblePlayer) { 13150 AblePlayer.prototype.setupTranscript = function () { 13151 var deferred = new this.defer(); 13152 var promise = deferred.promise(); 13153 13154 if (this.usingYouTubeCaptions || this.usingVimeoCaptions || this.hideTranscriptButton ) { 13155 // a transcript is not possible or is disabled. 13156 this.transcriptType = null; 13157 deferred.resolve(); 13158 } else { 13159 if (!this.transcriptType) { 13160 // previously set transcriptType to null since there are no <track> elements 13161 // check again to see if captions have been collected from other sources (e.g., YouTube) 13162 13163 if (this.captions.length) { 13164 // captions are possible! Use the default type (popup) 13165 // if other types ('external' and 'manual') were desired, transcriptType would not be null here 13166 this.transcriptType = "popup"; 13167 } 13168 } 13169 if (this.transcriptType) { 13170 if ( this.transcriptType === "popup" || this.transcriptType === "external" ) { 13171 this.injectTranscriptArea(); 13172 deferred.resolve(); 13173 } else if (this.transcriptType === "manual") { 13174 this.setupManualTranscript(); 13175 deferred.resolve(); 13176 } 13177 } else { 13178 // there is no transcript 13179 deferred.resolve(); 13180 } 13181 } 13182 return promise; 13183 }; 13184 13185 AblePlayer.prototype.injectTranscriptArea = function () { 13186 var $autoScrollLabel, 13187 $autoScrollContainer, 13188 $languageSelectWrapper, 13189 $languageSelectLabel, 13190 i, 13191 $option; 13192 13193 this.$transcriptArea = $("<div>", { 13194 class: "able-transcript-area", 13195 role: "dialog", 13196 "aria-label": this.translate( 'transcriptTitle', 'Transcript' ), 13197 }); 13198 13199 this.$transcriptToolbar = $("<div>", { 13200 class: "able-window-toolbar able-" + this.toolbarIconColor + "-controls", 13201 }); 13202 13203 this.$transcriptDiv = $("<div>", { 13204 class: "able-transcript", 13205 }); 13206 13207 // Transcript toolbar content 13208 13209 // Add auto Scroll checkbox 13210 this.$autoScrollTranscriptCheckbox = $("<input>", { 13211 id: "autoscroll-transcript-checkbox-" + this.mediaId, 13212 type: "checkbox", 13213 }); 13214 $autoScrollLabel = $("<label>", { 13215 for: "autoscroll-transcript-checkbox-" + this.mediaId, 13216 }).text( this.translate( 'autoScroll', 'Auto scroll' ) ); 13217 $autoScrollContainer = $( '<div>', { 13218 'class': 'autoscroll-transcript' 13219 }); 13220 $autoScrollContainer.append( 13221 $autoScrollLabel, 13222 this.$autoScrollTranscriptCheckbox 13223 ); 13224 this.$transcriptToolbar.append( $autoScrollContainer ); 13225 13226 // Add field for selecting a transcript language 13227 // Only necessary if there is more than one language 13228 if (this.captions.length > 1) { 13229 $languageSelectWrapper = $("<div>", { 13230 class: "transcript-language-select-wrapper", 13231 }); 13232 $languageSelectLabel = $("<label>", { 13233 for: "transcript-language-select-" + this.mediaId, 13234 }).text( this.translate( 'language', 'Language' ) ); 13235 this.$transcriptLanguageSelect = $("<select>", { 13236 id: "transcript-language-select-" + this.mediaId, 13237 }); 13238 for (i = 0; i < this.captions.length; i++) { 13239 $option = $("<option></option>", { 13240 value: this.captions[i]["language"], 13241 lang: this.captions[i]["language"], 13242 }).text(this.captions[i]["label"]); 13243 if (this.captions[i]["def"]) { 13244 $option.prop("selected", true); 13245 } 13246 this.$transcriptLanguageSelect.append($option); 13247 } 13248 } 13249 if ($languageSelectWrapper) { 13250 $languageSelectWrapper.append( 13251 $languageSelectLabel, 13252 this.$transcriptLanguageSelect 13253 ); 13254 this.$transcriptToolbar.append($languageSelectWrapper); 13255 } 13256 this.$transcriptArea.append(this.$transcriptToolbar, this.$transcriptDiv); 13257 13258 // If client has provided separate transcript location, put it there. 13259 // Otherwise append it to the body 13260 if (this.transcriptDivLocation) { 13261 this.$transcriptArea.removeAttr( 'role' ); 13262 this.$transcriptArea.removeAttr( 'aria-label' ); 13263 $("#" + this.transcriptDivLocation).append(this.$transcriptArea); 13264 } else { 13265 this.$ableWrapper.append(this.$transcriptArea); 13266 } 13267 13268 // make it draggable (popup only; NOT external transcript) 13269 if (!this.transcriptDivLocation) { 13270 this.initDragDrop("transcript"); 13271 if (this.prefTranscript === 1) { 13272 // transcript is on. Go ahead and position it 13273 this.positionDraggableWindow( 13274 "transcript", 13275 this.getDefaultWidth("transcript") 13276 ); 13277 } 13278 } 13279 13280 // If client has provided separate transcript location, override user's preference for hiding transcript 13281 if (!this.prefTranscript && !this.transcriptDivLocation) { 13282 this.$transcriptArea.hide(); 13283 } 13284 }; 13285 13286 AblePlayer.prototype.addTranscriptAreaEvents = function () { 13287 var thisObj = this; 13288 13289 this.$autoScrollTranscriptCheckbox.on( 'click', function () { 13290 thisObj.handleTranscriptLockToggle( 13291 thisObj.$autoScrollTranscriptCheckbox.prop("checked") 13292 ); 13293 }); 13294 13295 this.$transcriptDiv.on( 13296 "mousewheel DOMMouseScroll click scroll", 13297 function (e) { 13298 // Propagation is stopped in transcript click handler, so clicks are on the scrollbar 13299 // or outside of a clickable span. 13300 if (!thisObj.scrollingTranscript) { 13301 thisObj.autoScrollTranscript = false; 13302 thisObj.refreshControls("transcript"); 13303 } 13304 thisObj.scrollingTranscript = false; 13305 } 13306 ); 13307 13308 if (typeof this.$transcriptLanguageSelect !== "undefined") { 13309 this.$transcriptLanguageSelect.on('click', function (e) { 13310 // execute default behavior 13311 // prevent propagation of mouse event to toolbar or window 13312 e.stopPropagation(); 13313 }); 13314 13315 this.$transcriptLanguageSelect.on("change", function () { 13316 var language = thisObj.$transcriptLanguageSelect.val(); 13317 13318 thisObj.syncTrackLanguages("transcript", language); 13319 }); 13320 } 13321 }; 13322 13323 AblePlayer.prototype.transcriptSrcHasRequiredParts = function () { 13324 // check the external transcript to be sure it has all required components 13325 // return true or false 13326 // in the process, define all the needed variables and properties 13327 13328 if ($("#" + this.transcriptSrc).length) { 13329 this.$transcriptArea = $("#" + this.transcriptSrc); 13330 if (this.$transcriptArea.find(".able-window-toolbar").length) { 13331 this.$transcriptToolbar = this.$transcriptArea 13332 .find(".able-window-toolbar") 13333 .eq(0); 13334 if (this.$transcriptArea.find(".able-transcript").length) { 13335 this.$transcriptDiv = this.$transcriptArea 13336 .find(".able-transcript") 13337 .eq(0); 13338 if (this.$transcriptArea.find(".able-transcript-seekpoint").length) { 13339 this.$transcriptSeekpoints = this.$transcriptArea.find( 13340 ".able-transcript-seekpoint" 13341 ); 13342 return true; 13343 } 13344 } 13345 } 13346 } 13347 return false; 13348 }; 13349 13350 AblePlayer.prototype.setupManualTranscript = function () { 13351 var $autoScrollInput, $autoScrollLabel; 13352 13353 $autoScrollInput = $("<input>", { 13354 id: "autoscroll-transcript-checkbox-" + this.mediaId, 13355 type: "checkbox", 13356 }); 13357 $autoScrollLabel = $("<label>", { 13358 for: "autoscroll-transcript-checkbox-" + this.mediaId, 13359 }).text( this.translate( 'autoScroll', 'Auto scroll' ) ); 13360 13361 // Add an auto-scroll checkbox to the toolbar. 13362 this.$autoScrollTranscriptCheckbox = $autoScrollInput; 13363 this.$transcriptToolbar.append( 13364 $autoScrollLabel, 13365 this.$autoScrollTranscriptCheckbox 13366 ); 13367 }; 13368 13369 AblePlayer.prototype.updateTranscript = function () { 13370 if (!this.transcriptType) { 13371 return; 13372 } 13373 if (this.playerCreated && !this.$transcriptArea) { 13374 return; 13375 } 13376 if (this.transcriptType === "external" || this.transcriptType === "popup") { 13377 var chapters, captions, descriptions; 13378 13379 // Language of transcript might be different than language of captions 13380 // But both are in sync by default 13381 if (this.transcriptLang) { 13382 captions = this.transcriptCaptions.cues; 13383 } else { 13384 if (this.transcriptCaptions) { 13385 this.transcriptLang = this.transcriptCaptions.language; 13386 captions = this.transcriptCaptions.cues; 13387 } else if (this.selectedCaptions) { 13388 this.transcriptLang = this.captionLang; 13389 captions = this.selectedCaptions.cues; 13390 } 13391 } 13392 13393 // setup chapters 13394 if (this.transcriptChapters) { 13395 chapters = this.transcriptChapters.cues; 13396 } else if (this.chapters.length > 0) { 13397 // Try and match the caption language. 13398 if (this.transcriptLang) { 13399 for (var i = 0; i < this.chapters.length; i++) { 13400 if (this.chapters[i].language === this.transcriptLang) { 13401 chapters = this.chapters[i].cues; 13402 } 13403 } 13404 } 13405 if (typeof chapters === "undefined") { 13406 chapters = this.chapters[0].cues || []; 13407 } 13408 } 13409 13410 // setup descriptions 13411 if (this.transcriptDescriptions) { 13412 descriptions = this.transcriptDescriptions.cues; 13413 } else if (this.descriptions.length > 0) { 13414 // Try and match the caption language. 13415 if (this.transcriptLang) { 13416 for (i = 0; i < this.descriptions.length; i++) { 13417 if (this.descriptions[i].language === this.transcriptLang) { 13418 descriptions = this.descriptions[i].cues; 13419 } 13420 } 13421 } 13422 if (!descriptions) { 13423 descriptions = this.descriptions[0].cues || []; 13424 } 13425 } 13426 13427 var div = this.generateTranscript( 13428 chapters || [], 13429 captions || [], 13430 descriptions || [] 13431 ); 13432 this.$transcriptDiv.html(div); 13433 // reset transcript selected <option> to this.transcriptLang 13434 if (this.$transcriptLanguageSelect) { 13435 this.$transcriptLanguageSelect 13436 .find("option:selected") 13437 .prop("selected", false); 13438 this.$transcriptLanguageSelect 13439 .find("option[lang=" + this.transcriptLang + "]") 13440 .prop("selected", true); 13441 } 13442 } 13443 13444 var thisObj = this; 13445 13446 // Make transcript tabbable if preference is turned on. 13447 if (this.prefTabbable === 1) { 13448 this.$transcriptDiv 13449 .find("span.able-transcript-seekpoint") 13450 .attr("tabindex", "0"); 13451 } 13452 13453 // handle clicks on text within transcript 13454 // Note: This event listeners handles clicks only, not keydown events 13455 // Pressing Enter on an element that is not natively clickable does NOT trigger click() 13456 // Keydown events are handled elsehwere, both globally (ableplayer-base.js) and locally (event.js) 13457 if (this.$transcriptArea.length > 0) { 13458 this.$transcriptArea 13459 .find("span.able-transcript-seekpoint") 13460 .on( 'click', function (e) { 13461 thisObj.seekTrigger = "transcript"; 13462 var spanStart = parseFloat($(this).attr("data-start")); 13463 // Add a tiny amount so that we're inside the span. 13464 spanStart += 0.01; 13465 // Each click within the transcript triggers two click events (not sure why) 13466 // this.seekingFromTranscript is a stopgab to prevent two calls to SeekTo() 13467 if (!thisObj.seekingFromTranscript) { 13468 thisObj.seekingFromTranscript = true; 13469 thisObj.seekTo(spanStart); 13470 } else { 13471 // don't seek a second time, but do reset var 13472 thisObj.seekingFromTranscript = false; 13473 } 13474 }); 13475 } 13476 }; 13477 13478 AblePlayer.prototype.highlightTranscript = function (currentTime) { 13479 // Show highlight in transcript marking current caption. 13480 13481 if (!this.transcriptType) { 13482 return; 13483 } 13484 13485 var start, end, isChapterHeading; 13486 var thisObj = this; 13487 13488 currentTime = parseFloat(currentTime); 13489 13490 // Highlight the current transcript item. 13491 this.$transcriptArea 13492 .find("span.able-transcript-seekpoint") 13493 .each(function () { 13494 start = parseFloat($(this).attr("data-start")); 13495 end = parseFloat($(this).attr("data-end")); 13496 // be sure this isn't a chapter (don't highlight chapter headings) 13497 if ($(this).parent().hasClass("able-transcript-chapter-heading")) { 13498 isChapterHeading = true; 13499 } else { 13500 isChapterHeading = false; 13501 } 13502 13503 if (currentTime >= start && currentTime <= end && !isChapterHeading) { 13504 // If this item isn't already highlighted, it should be 13505 if (!$(this).hasClass("able-highlight")) { 13506 // remove all previous highlights before adding one to current span 13507 thisObj.$transcriptArea 13508 .find(".able-highlight") 13509 .removeClass("able-highlight"); 13510 $(this).addClass("able-highlight"); 13511 thisObj.movingHighlight = true; 13512 } 13513 return false; 13514 } 13515 }); 13516 thisObj.currentHighlight = thisObj.$transcriptArea.find(".able-highlight"); 13517 if (thisObj.currentHighlight.length === 0) { 13518 // Nothing highlighted. 13519 thisObj.currentHighlight = null; 13520 } 13521 }; 13522 13523 AblePlayer.prototype.generateTranscript = function ( 13524 chapters, 13525 captions, 13526 descriptions 13527 ) { 13528 var thisObj = this; 13529 13530 var $main = $('<div class="able-transcript-container"></div>'); 13531 var transcriptTitle, firstStart; 13532 13533 // set language for transcript container 13534 $main.attr("lang", this.transcriptLang); 13535 13536 if (typeof this.transcriptTitle !== "undefined") { 13537 transcriptTitle = this.transcriptTitle; 13538 } else if (this.lyricsMode) { 13539 transcriptTitle = this.translate( 'lyricsTitle', 'Lyrics' ); 13540 } else { 13541 transcriptTitle = this.translate( 'transcriptTitle', 'Transcript' ); 13542 } 13543 13544 if (!this.transcriptDivLocation) { 13545 // only add an HTML heading to internal transcript 13546 // external transcript is expected to have its own heading 13547 var headingNumber = this.playerHeadingLevel; 13548 headingNumber += 1; 13549 var chapterHeadingNumber = headingNumber + 1; 13550 13551 let transcriptHeading; 13552 if (headingNumber <= 6) { 13553 transcriptHeading = "h" + headingNumber.toString(); 13554 } else { 13555 transcriptHeading = "div"; 13556 } 13557 var $transcriptHeadingTag = $("<" + transcriptHeading + ">"); 13558 $transcriptHeadingTag.addClass("able-transcript-heading"); 13559 if (headingNumber > 6) { 13560 $transcriptHeadingTag.attr({ 13561 role: "heading", 13562 "aria-level": headingNumber, 13563 }); 13564 } 13565 $transcriptHeadingTag.text(transcriptTitle); 13566 13567 // set language of transcript heading to language of player 13568 // this is independent of language of transcript 13569 $transcriptHeadingTag.attr("lang", this.lang); 13570 13571 $main.append($transcriptHeadingTag); 13572 } 13573 13574 var nextChapter = 0; 13575 var nextCap = 0; 13576 var nextDesc = 0; 13577 13578 var addChapter = function (div, chap) { 13579 let chapterHeading; 13580 if (chapterHeadingNumber <= 6) { 13581 chapterHeading = "h" + chapterHeadingNumber.toString(); 13582 } else { 13583 chapterHeading = "div"; 13584 } 13585 13586 var $chapterHeadingTag = $("<" + chapterHeading + ">", { 13587 class: "able-transcript-chapter-heading", 13588 }); 13589 if (chapterHeadingNumber > 6) { 13590 $chapterHeadingTag.attr({ 13591 role: "heading", 13592 "aria-level": chapterHeadingNumber, 13593 }); 13594 } 13595 13596 var flattenComponentForChapter = function (comp) { 13597 var result = []; 13598 if (comp.type === "string") { 13599 result.push(comp.value); 13600 } else { 13601 for (var i = 0; i < comp.children.length; i++) { 13602 result = result.concat( 13603 flattenComponentForChapter(comp.children[i]) 13604 ); 13605 } 13606 } 13607 return result; 13608 }; 13609 13610 var $chapSpan = $("<span>", { 13611 class: "able-transcript-seekpoint", 13612 }); 13613 for (var i = 0; i < chap.components.children.length; i++) { 13614 var results = flattenComponentForChapter(chap.components.children[i]); 13615 for (var jj = 0; jj < results.length; jj++) { 13616 $chapSpan.append(results[jj]); 13617 } 13618 } 13619 $chapSpan.attr("data-start", chap.start.toString()); 13620 $chapSpan.attr("data-end", chap.end.toString()); 13621 $chapterHeadingTag.append($chapSpan); 13622 13623 div.append($chapterHeadingTag); 13624 }; 13625 13626 var addDescription = function (div, desc) { 13627 var $descDiv = $("<div>", { 13628 class: "able-transcript-desc", 13629 }); 13630 var $descHiddenSpan = $("<span>", { 13631 class: "able-hidden", 13632 }); 13633 $descHiddenSpan.attr("lang", thisObj.lang); 13634 $descHiddenSpan.text( thisObj.translate( 'prefHeadingDescription', 'Audio description' ) + ": "); 13635 $descDiv.append($descHiddenSpan); 13636 13637 var flattenComponentForDescription = function (comp) { 13638 var result = []; 13639 if (comp.type === "string") { 13640 result.push(comp.value); 13641 } else { 13642 for (var i = 0; i < comp.children.length; i++) { 13643 result = result.concat( 13644 flattenComponentForDescription(comp.children[i]) 13645 ); 13646 } 13647 } 13648 return result; 13649 }; 13650 13651 var $descSpan = $("<span>", { 13652 class: "able-transcript-seekpoint", 13653 }); 13654 for (var i = 0; i < desc.components.children.length; i++) { 13655 var results = flattenComponentForDescription( 13656 desc.components.children[i] 13657 ); 13658 for (var jj = 0; jj < results.length; jj++) { 13659 $descSpan.append(results[jj]); 13660 } 13661 } 13662 $descSpan.attr("data-start", desc.start.toString()); 13663 $descSpan.attr("data-end", desc.end.toString()); 13664 $descDiv.append($descSpan); 13665 13666 div.append($descDiv); 13667 }; 13668 13669 var addCaption = function (div, cap) { 13670 var $capSpan = $("<span>", { 13671 class: "able-transcript-seekpoint able-transcript-caption", 13672 }); 13673 13674 var flattenComponentForCaption = function (comp) { 13675 var result = []; 13676 13677 var parts = 0; 13678 13679 var flattenString = function (str) { 13680 parts++; 13681 13682 var flatStr; 13683 var result = []; 13684 if (str === "") { 13685 return result; 13686 } 13687 13688 var openBracket = str.indexOf("["); 13689 var closeBracket = str.indexOf("]"); 13690 var openParen = str.indexOf("("); 13691 var closeParen = str.indexOf(")"); 13692 13693 var hasBrackets = openBracket !== -1 && closeBracket !== -1; 13694 var hasParens = openParen !== -1 && closeParen !== -1; 13695 13696 if (hasParens || hasBrackets) { 13697 let silentSpanBreak; 13698 if (parts > 1) { 13699 // force a line break between sections that contain parens or brackets 13700 silentSpanBreak = "<br/>"; 13701 } else { 13702 silentSpanBreak = ""; 13703 } 13704 var silentSpanOpen = 13705 silentSpanBreak + '<span class="able-unspoken">'; 13706 var silentSpanClose = "</span>"; 13707 if (hasParens && hasBrackets) { 13708 // string has both! 13709 if (openBracket < openParen) { 13710 // brackets come first. Parse parens separately 13711 hasParens = false; 13712 } else { 13713 // parens come first. Parse brackets separately 13714 hasBrackets = false; 13715 } 13716 } 13717 } 13718 if (hasParens) { 13719 flatStr = str.substring(0, openParen); 13720 flatStr += silentSpanOpen; 13721 flatStr += str.substring(openParen, closeParen + 1); 13722 flatStr += silentSpanClose; 13723 flatStr += flattenString(str.substring(closeParen + 1)); 13724 result.push(flatStr); 13725 } else if (hasBrackets) { 13726 flatStr = str.substring(0, openBracket); 13727 flatStr += silentSpanOpen; 13728 flatStr += str.substring(openBracket, closeBracket + 1); 13729 flatStr += silentSpanClose; 13730 flatStr += flattenString(str.substring(closeBracket + 1)); 13731 result.push(flatStr); 13732 } else { 13733 result.push(str); 13734 } 13735 return result; 13736 }; 13737 13738 if (comp.type === "string") { 13739 result = result.concat(flattenString(comp.value)); 13740 } else if (comp.type === "v") { 13741 var $vSpan = $("<span>", { 13742 class: "able-unspoken", 13743 }); 13744 // don't display "title=" when rendering the voice tag title in the transcript 13745 comp.value = comp.value.replace(/^title="|"$/g, ""); 13746 $vSpan.text("(" + comp.value + ")"); 13747 result.push($vSpan); 13748 for (var i = 0; i < comp.children.length; i++) { 13749 let subResults = flattenComponentForCaption(comp.children[i]); 13750 for (let jj = 0; jj < subResults.length; jj++) { 13751 result.push(subResults[jj]); 13752 } 13753 } 13754 } else if (comp.type === "b" || comp.type === "i") { 13755 let $tag; 13756 if (comp.type === "b") { 13757 $tag = $("<strong>"); 13758 } else if (comp.type === "i") { 13759 $tag = $("<em>"); 13760 } 13761 for (i = 0; i < comp.children.length; i++) { 13762 let subResults = flattenComponentForCaption(comp.children[i]); 13763 for (let jj = 0; jj < subResults.length; jj++) { 13764 $tag.append(subResults[jj]); 13765 } 13766 } 13767 if (comp.type === "b" || comp.type == "i") { 13768 result.push($tag); 13769 } 13770 } else { 13771 for (i = 0; i < comp.children.length; i++) { 13772 result = result.concat( 13773 flattenComponentForCaption(comp.children[i]) 13774 ); 13775 } 13776 } 13777 return result; 13778 }; 13779 13780 for (var i = 0; i < cap.components.children.length; i++) { 13781 var next_child_tagname; 13782 if ( i < cap.components.children.length - 1 ) { 13783 next_child_tagname = cap.components.children[i + 1].tagName; 13784 } 13785 var results = flattenComponentForCaption(cap.components.children[i]); 13786 for (var jj = 0; jj < results.length; jj++) { 13787 var result = results[jj]; 13788 if (typeof result === "string") { 13789 if (thisObj.lyricsMode) { 13790 // add <br> WITHIN each caption (if payload includes "\n") 13791 result = result.replace(/\n/g,'<br>'); 13792 13793 // add <br> BETWEEN each caption, but do not consider sibling style tags within this caption as the next caption! 13794 if ( !next_child_tagname || ( next_child_tagname !== 'i' && next_child_tagname !== 'b' ) ) { 13795 result += '<br>'; 13796 } 13797 } else { 13798 // just add a space between captions 13799 result += " "; 13800 } 13801 } 13802 $capSpan.append(result); 13803 } 13804 } 13805 $capSpan.attr("data-start", cap.start.toString()); 13806 $capSpan.attr("data-end", cap.end.toString()); 13807 div.append($capSpan); 13808 div.append(" \n"); 13809 }; 13810 13811 // keep looping as long as any one of the three arrays has content 13812 while ( 13813 nextChapter < chapters.length || 13814 nextDesc < descriptions.length || 13815 nextCap < captions.length 13816 ) { 13817 if ( 13818 nextChapter < chapters.length && 13819 nextDesc < descriptions.length && 13820 nextCap < captions.length 13821 ) { 13822 // they all three have content 13823 firstStart = Math.min( 13824 chapters[nextChapter].start, 13825 descriptions[nextDesc].start, 13826 captions[nextCap].start 13827 ); 13828 } else if ( 13829 nextChapter < chapters.length && 13830 nextDesc < descriptions.length 13831 ) { 13832 // chapters & descriptions have content 13833 firstStart = Math.min( 13834 chapters[nextChapter].start, 13835 descriptions[nextDesc].start 13836 ); 13837 } else if (nextChapter < chapters.length && nextCap < captions.length) { 13838 // chapters & captions have content 13839 firstStart = Math.min( 13840 chapters[nextChapter].start, 13841 captions[nextCap].start 13842 ); 13843 } else if (nextDesc < descriptions.length && nextCap < captions.length) { 13844 // descriptions & captions have content 13845 firstStart = Math.min( 13846 descriptions[nextDesc].start, 13847 captions[nextCap].start 13848 ); 13849 } else { 13850 firstStart = null; 13851 } 13852 if (firstStart !== null) { 13853 if ( 13854 typeof chapters[nextChapter] !== "undefined" && 13855 chapters[nextChapter].start === firstStart 13856 ) { 13857 addChapter($main, chapters[nextChapter]); 13858 nextChapter += 1; 13859 } else if ( 13860 typeof descriptions[nextDesc] !== "undefined" && 13861 descriptions[nextDesc].start === firstStart 13862 ) { 13863 addDescription($main, descriptions[nextDesc]); 13864 nextDesc += 1; 13865 } else { 13866 addCaption($main, captions[nextCap]); 13867 nextCap += 1; 13868 } 13869 } else { 13870 if (nextChapter < chapters.length) { 13871 addChapter($main, chapters[nextChapter]); 13872 nextChapter += 1; 13873 } else if (nextDesc < descriptions.length) { 13874 addDescription($main, descriptions[nextDesc]); 13875 nextDesc += 1; 13876 } else if (nextCap < captions.length) { 13877 addCaption($main, captions[nextCap]); 13878 nextCap += 1; 13879 } 13880 } 13881 } 13882 // organize transcript into blocks using [] and () as starting points 13883 var $components = $main.children(); 13884 var spanCount = 0; 13885 $components.each(function () { 13886 if ($(this).hasClass("able-transcript-caption")) { 13887 if ( 13888 $(this).text().indexOf("[") !== -1 || 13889 $(this).text().indexOf("(") !== -1 13890 ) { 13891 // this caption includes a bracket or parenth. Start a new block 13892 // close the previous block first 13893 if (spanCount > 0) { 13894 $main = wrapTranscriptBlocks( $main ); 13895 spanCount = 0; 13896 } 13897 } 13898 $(this).addClass("able-block-temp"); 13899 spanCount++; 13900 } else { 13901 // this is not a caption. Close the caption block 13902 if (spanCount > 0) { 13903 $main = wrapTranscriptBlocks( $main ); 13904 spanCount = 0; 13905 } 13906 } 13907 }); 13908 // Close out remaining temp blocks. 13909 $main = wrapTranscriptBlocks( $main ); 13910 13911 return $main; 13912 }; 13913 13914 var wrapTranscriptBlocks = function( $main ) { 13915 $main.find(".able-block-temp") 13916 .removeClass("able-block-temp") 13917 .wrapAll('<div class="able-transcript-block"></div>'); 13918 13919 return $main; 13920 }; 13921 } 13922 13923 var playerHeading$k = "Reproductor"; 13924 var audioPlayer$k = "Reproductor d'à udio"; 13925 var videoPlayer$k = "Reproductor de vÃdeo"; 13926 var faster$k = "Rà pid"; 13927 var slower$k = "Lent"; 13928 var play$k = "Reprodueix"; 13929 var pause$k = "Pausa"; 13930 var restart$k = "Reinicia"; 13931 var prevTrack$k = "Pista anterior"; 13932 var nextTrack$k = "Pista següent"; 13933 var rewind$k = "Endarrere"; 13934 var forward$k = "Endavant"; 13935 var captions$k = "SubtÃtols"; 13936 var showCaptions$k = "Mostra els subtÃtols"; 13937 var hideCaptions$k = "Oculta els subtÃtols"; 13938 var captionsOff$k = "Desactiva els subtÃtols"; 13939 var showTranscript$k = "Mostra la transcripció"; 13940 var hideTranscript$k = "Oculta la transcripció"; 13941 var turnOnDescriptions$k = "Activa l'audiodescripció"; 13942 var turnOffDescriptions$k = "Desactiva l'audiodescripció"; 13943 var chapters$k = "CapÃtols"; 13944 var language$k = "Idioma"; 13945 var sign$k = "Llengua de signes"; 13946 var showSign$k = "Mostra la llengua de signes"; 13947 var hideSign$k = "Oculta la llengua de signes"; 13948 var seekbarLabel$k = "LÃnia de temps"; 13949 var mute$k = "Silencia"; 13950 var unmute$k = "Activa el so"; 13951 var volume$k = "Volum"; 13952 var volumeUpDown$k = "Apuja i abaixa el volum"; 13953 var preferences$k = "Preferències"; 13954 var enterFullScreen$k = "Entra en el mode de pantalla completa"; 13955 var exitFullScreen$k = "Surt del mode de pantalla completa"; 13956 var speed$k = "Velocitat"; 13957 var spacebar$k = "Barra espaiadora"; 13958 var transcriptTitle$k = "Transcripció"; 13959 var lyricsTitle$k = "Lletra"; 13960 var autoScroll$k = "Desplaçament automà tic"; 13961 var statusPlaying$k = "S'està reproduint"; 13962 var statusPaused$k = "En pausa"; 13963 var statusStopped$k = "Aturat"; 13964 var statusBuffering$k = "Emmagatzemant"; 13965 var statusEnd$k = "Fi de pista"; 13966 var selectedTrack$j = "Pista seleccionada"; 13967 var alertDescribedVersion$j = "S'està utilitzant la versió amb audiodescripció del vÃdeo"; 13968 var alertNonDescribedVersion$j = "S'està utilitzant la versió sense audiodescripció del vÃdeo"; 13969 var prefMenuCaptions$k = "SubtÃtols"; 13970 var prefVoicedCaptions$k = "Spoken Captions"; 13971 var prefMenuDescriptions$k = "Descripcions"; 13972 var prefMenuKeyboard$k = "Teclat"; 13973 var prefMenuTranscript$k = "Transcripció"; 13974 var prefTitleCaptions$k = "Preferències dels subtÃtols"; 13975 var prefTitleDescriptions$k = "Preferències de l'audiodescripció"; 13976 var prefTitleKeyboard$k = "Preferències del teclat"; 13977 var prefTitleTranscript$j = "Preferències de la transcripció"; 13978 var prefIntroDescription1$j = "Aquest reproductor suporta l'audiodescripció de dues maneres: "; 13979 var prefDescription1$k = "L'actual vÃdeo té una versió alternativa amb audiodescripció, descripció textual anunciada pel lector de pantalla."; 13980 var prefDescription2$k = "L'actual vÃdeo té una versió alternativa amb audiodescripció."; 13981 var prefDescription3$k = "L'actual vÃdeo té descripció textual."; 13982 var prefIntroDescriptionNone$k = "L'actual vÃdeo no disposa d'audiodescripció en cap format."; 13983 var prefIntroDescription3$k = "Utilitzeu el formulari següent per definir les preferències relacionades amb l'audiodescripció textual."; 13984 var prefIntroDescription4$k = "Desprès de desar la configuració, podeu commutar l'ús de l'audiodescripció amb el mateix botó."; 13985 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ó)."; 13986 var prefIntroKeyboard2$k = "A continuació, podeu asignar les tecles modificadores (Majúscules, Alt, i la tecla d'inserció)."; 13987 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."; 13988 var prefHeadingKeyboard1$k = "Tecles modificadores emprades com a dreceres de teclat"; 13989 var prefHeadingKeyboard2$k = "Dreceres de teclat actuals"; 13990 var prefHeadingDescription$k = "Audiodescripció"; 13991 var prefHeadingTextDescription$k = "Audiodescripció textual"; 13992 var prefAltKey$k = "Alt"; 13993 var prefCtrlKey$k = "Control"; 13994 var prefShiftKey$k = "Majúscula"; 13995 var prefNoKeyShortcuts$k = "Desactiva les dreceres de teclat"; 13996 var escapeKey$k = "Escapada"; 13997 var escapeKeyFunction$k = "Tanca el dià leg o finestre emergent actual"; 13998 var prefDescPause$k = "Pausa automà ticament el vÃdeo en el moment que comenci una audiodescripció"; 13999 var prefDescVisible$k = "Fes visible la audiodescripció textual si es troba activada"; 14000 var prefDescVoice$k = "Veu"; 14001 var prefDescRate$k = "Spoken Description Rate"; 14002 var prefCaptionRate$k = "Spoken Caption Rate"; 14003 var prefDescPitch$k = "Tonalitat"; 14004 var prefDescPitch1$k = "Molt baixa"; 14005 var prefDescPitch2$k = "Baixa"; 14006 var prefDescPitch3$k = "Per defecte"; 14007 var prefDescPitch4$k = "Alta"; 14008 var prefDescPitch5$k = "Molt alta"; 14009 var sampleDescriptionText$k = "Ajusta la configuració per escoltar aquest text de mostra."; 14010 var prefHighlight$k = "Ressalta la transcripció a mesura que avança el contingut"; 14011 var prefTabbable$k = "Transcripció operable per teclat"; 14012 var prefCaptionsFont$k = "Tipus de lletra"; 14013 var prefCaptionsColor$k = "Color del text"; 14014 var prefCaptionsBGColor$k = "Fons"; 14015 var prefCaptionsSize$k = "Mida del text"; 14016 var prefCaptionsOpacity$k = "Opacitat"; 14017 var prefCaptionsStyle$k = "Estil"; 14018 var serif$k = "Serifa"; 14019 var sans$k = "Sensa serifa"; 14020 var cursive$k = "Cursiva"; 14021 var fantasy$k = "fantasia"; 14022 var monospace$k = "Monoespaiada"; 14023 var white$k = "Blanc"; 14024 var yellow$k = "Groc"; 14025 var green$k = "Verd"; 14026 var cyan$j = "Cian"; 14027 var blue$k = "Blau"; 14028 var magenta$k = "Magenta"; 14029 var red$k = "Vermell"; 14030 var black$k = "Negre"; 14031 var transparent$k = "transparent"; 14032 var solid$k = "Sòlid"; 14033 var captionsStylePopOn$k = "Aparició instantà nia"; 14034 var captionsStyleRollUp$k = "Desplaçament cap amunt"; 14035 var prefCaptionsPosition$k = "Posició"; 14036 var captionsPositionOverlay$k = "Superposició"; 14037 var captionsPositionBelow$k = "A continuació del vÃdeo"; 14038 var sampleCaptionText$k = "Text de mostra dels subtÃtols"; 14039 var prefSuccess$k = "S'han desat els canvis."; 14040 var prefNoChange$k = "No s'ha fet cap canvi."; 14041 var save$k = "Desa"; 14042 var cancel$k = "Cancel·la"; 14043 var dismissButton$k = "Ignora"; 14044 var windowButtonLabel$k = "Opcions de la finestra"; 14045 var windowMove$k = "Moure"; 14046 var windowMoveLeft$k = "Finestra desplaçada cap a l'esquerra"; 14047 var windowMoveRight$k = "Finestra desplaçada cap a la dreta"; 14048 var windowMoveUp$k = "Finestra desplaçada cap amunt"; 14049 var windowMoveDown$k = "Finestra desplaçada cap avall"; 14050 var windowMoveStopped$k = "S'ha aturat el desplaçament de la finestra"; 14051 var transcriptControls$k = "Controls de la finestra de transcripció"; 14052 var signControls$k = "Controls de la finestra de llengua de signes"; 14053 var windowMoveAlert$k = "Arrossegueu o feu servir les tecles de direcció per moure la finestra, polseu retorn per aturar."; 14054 var windowResize$k = "Redimensiona"; 14055 var windowResizeHeading$k = "Redimensiona la finestra amb l'intèrpret"; 14056 var closeButtonLabel$k = "Tanca"; 14057 var width$k = "Amplada"; 14058 var height$k = "Alçada"; 14059 var resultsSummary1$k = "Heu cercat:"; 14060 var resultsSummary2$k = "S'han trobat %1 elements coincidents."; 14061 var resultsSummary3$k = "Feu clic al moment associat a qualsevol element per reproduir el vÃdeo des d'aquell punt."; 14062 var noResultsFound$k = "No s'han trobat resultats."; 14063 var searchButtonLabel$k = "Reprodueix a %1"; 14064 var hour$k = "hora"; 14065 var minute$j = "minut"; 14066 var second$k = "segon"; 14067 var hours$k = "hores"; 14068 var minutes$k = "minuts"; 14069 var seconds$k = "segons"; 14070 var vtsHeading$k = "Gestor de transcripcions de vÃdeo"; 14071 var vtsInstructions1$k = "Utilitzeu el gestor de transcripcions de vÃdeo per modificar les pistes de text:"; 14072 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."; 14073 var vtsInstructions3$k = "Modifiqueu el contingut o els temps d'inici i final (tots són editables directament a la taula)."; 14074 var vtsInstructions4$k = "Afegiu contingut nou, com ara capÃtols o descripcions."; 14075 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."; 14076 var vtsSelectLanguage$k = "Seleccioneu una llengua"; 14077 var vtsSave$k = "Genera contingut .vtt nou"; 14078 var vtsReturn$k = "Torna a l'editor"; 14079 var vtsCancel$k = "S'ha cancel·lat el desament. Totes les edicions que heu fet s'han restaurat a la taula del GTV."; 14080 var vtsRow$k = "Fila"; 14081 var vtsKind$k = "Tipus"; 14082 var vtsStart$k = "Inici"; 14083 var vtsEnd$k = "Final"; 14084 var vtsContent$k = "Contingut"; 14085 var vtsActions$k = "Accions"; 14086 var vtsNewRow$k = "S'ha inserit una fila nova %1."; 14087 var vtsDeletedRow$k = "S'ha suprimit la fila %1."; 14088 var vtsMovedRow$k = "La fila %1 s'ha mogut %2 i ara és la fila %3."; 14089 var ca = { 14090 playerHeading: playerHeading$k, 14091 audioPlayer: audioPlayer$k, 14092 videoPlayer: videoPlayer$k, 14093 faster: faster$k, 14094 slower: slower$k, 14095 play: play$k, 14096 pause: pause$k, 14097 restart: restart$k, 14098 prevTrack: prevTrack$k, 14099 nextTrack: nextTrack$k, 14100 rewind: rewind$k, 14101 forward: forward$k, 14102 captions: captions$k, 14103 showCaptions: showCaptions$k, 14104 hideCaptions: hideCaptions$k, 14105 captionsOff: captionsOff$k, 14106 showTranscript: showTranscript$k, 14107 hideTranscript: hideTranscript$k, 14108 turnOnDescriptions: turnOnDescriptions$k, 14109 turnOffDescriptions: turnOffDescriptions$k, 14110 chapters: chapters$k, 14111 language: language$k, 14112 sign: sign$k, 14113 showSign: showSign$k, 14114 hideSign: hideSign$k, 14115 seekbarLabel: seekbarLabel$k, 14116 mute: mute$k, 14117 unmute: unmute$k, 14118 volume: volume$k, 14119 volumeUpDown: volumeUpDown$k, 14120 preferences: preferences$k, 14121 enterFullScreen: enterFullScreen$k, 14122 exitFullScreen: exitFullScreen$k, 14123 speed: speed$k, 14124 spacebar: spacebar$k, 14125 transcriptTitle: transcriptTitle$k, 14126 lyricsTitle: lyricsTitle$k, 14127 autoScroll: autoScroll$k, 14128 statusPlaying: statusPlaying$k, 14129 statusPaused: statusPaused$k, 14130 statusStopped: statusStopped$k, 14131 statusBuffering: statusBuffering$k, 14132 statusEnd: statusEnd$k, 14133 selectedTrack: selectedTrack$j, 14134 alertDescribedVersion: alertDescribedVersion$j, 14135 alertNonDescribedVersion: alertNonDescribedVersion$j, 14136 prefMenuCaptions: prefMenuCaptions$k, 14137 prefVoicedCaptions: prefVoicedCaptions$k, 14138 prefMenuDescriptions: prefMenuDescriptions$k, 14139 prefMenuKeyboard: prefMenuKeyboard$k, 14140 prefMenuTranscript: prefMenuTranscript$k, 14141 prefTitleCaptions: prefTitleCaptions$k, 14142 prefTitleDescriptions: prefTitleDescriptions$k, 14143 prefTitleKeyboard: prefTitleKeyboard$k, 14144 prefTitleTranscript: prefTitleTranscript$j, 14145 prefIntroDescription1: prefIntroDescription1$j, 14146 prefDescription1: prefDescription1$k, 14147 prefDescription2: prefDescription2$k, 14148 prefDescription3: prefDescription3$k, 14149 prefIntroDescriptionNone: prefIntroDescriptionNone$k, 14150 prefIntroDescription3: prefIntroDescription3$k, 14151 prefIntroDescription4: prefIntroDescription4$k, 14152 prefIntroKeyboard1: prefIntroKeyboard1$k, 14153 prefIntroKeyboard2: prefIntroKeyboard2$k, 14154 prefIntroKeyboard3: prefIntroKeyboard3$k, 14155 prefHeadingKeyboard1: prefHeadingKeyboard1$k, 14156 prefHeadingKeyboard2: prefHeadingKeyboard2$k, 14157 prefHeadingDescription: prefHeadingDescription$k, 14158 prefHeadingTextDescription: prefHeadingTextDescription$k, 14159 prefAltKey: prefAltKey$k, 14160 prefCtrlKey: prefCtrlKey$k, 14161 prefShiftKey: prefShiftKey$k, 14162 prefNoKeyShortcuts: prefNoKeyShortcuts$k, 14163 escapeKey: escapeKey$k, 14164 escapeKeyFunction: escapeKeyFunction$k, 14165 prefDescPause: prefDescPause$k, 14166 prefDescVisible: prefDescVisible$k, 14167 prefDescVoice: prefDescVoice$k, 14168 prefDescRate: prefDescRate$k, 14169 prefCaptionRate: prefCaptionRate$k, 14170 prefDescPitch: prefDescPitch$k, 14171 prefDescPitch1: prefDescPitch1$k, 14172 prefDescPitch2: prefDescPitch2$k, 14173 prefDescPitch3: prefDescPitch3$k, 14174 prefDescPitch4: prefDescPitch4$k, 14175 prefDescPitch5: prefDescPitch5$k, 14176 sampleDescriptionText: sampleDescriptionText$k, 14177 prefHighlight: prefHighlight$k, 14178 prefTabbable: prefTabbable$k, 14179 prefCaptionsFont: prefCaptionsFont$k, 14180 prefCaptionsColor: prefCaptionsColor$k, 14181 prefCaptionsBGColor: prefCaptionsBGColor$k, 14182 prefCaptionsSize: prefCaptionsSize$k, 14183 prefCaptionsOpacity: prefCaptionsOpacity$k, 14184 prefCaptionsStyle: prefCaptionsStyle$k, 14185 serif: serif$k, 14186 sans: sans$k, 14187 cursive: cursive$k, 14188 fantasy: fantasy$k, 14189 monospace: monospace$k, 14190 white: white$k, 14191 yellow: yellow$k, 14192 green: green$k, 14193 cyan: cyan$j, 14194 blue: blue$k, 14195 magenta: magenta$k, 14196 red: red$k, 14197 black: black$k, 14198 transparent: transparent$k, 14199 solid: solid$k, 14200 captionsStylePopOn: captionsStylePopOn$k, 14201 captionsStyleRollUp: captionsStyleRollUp$k, 14202 prefCaptionsPosition: prefCaptionsPosition$k, 14203 captionsPositionOverlay: captionsPositionOverlay$k, 14204 captionsPositionBelow: captionsPositionBelow$k, 14205 sampleCaptionText: sampleCaptionText$k, 14206 prefSuccess: prefSuccess$k, 14207 prefNoChange: prefNoChange$k, 14208 save: save$k, 14209 cancel: cancel$k, 14210 dismissButton: dismissButton$k, 14211 windowButtonLabel: windowButtonLabel$k, 14212 windowMove: windowMove$k, 14213 windowMoveLeft: windowMoveLeft$k, 14214 windowMoveRight: windowMoveRight$k, 14215 windowMoveUp: windowMoveUp$k, 14216 windowMoveDown: windowMoveDown$k, 14217 windowMoveStopped: windowMoveStopped$k, 14218 transcriptControls: transcriptControls$k, 14219 signControls: signControls$k, 14220 windowMoveAlert: windowMoveAlert$k, 14221 windowResize: windowResize$k, 14222 windowResizeHeading: windowResizeHeading$k, 14223 closeButtonLabel: closeButtonLabel$k, 14224 width: width$k, 14225 height: height$k, 14226 resultsSummary1: resultsSummary1$k, 14227 resultsSummary2: resultsSummary2$k, 14228 resultsSummary3: resultsSummary3$k, 14229 noResultsFound: noResultsFound$k, 14230 searchButtonLabel: searchButtonLabel$k, 14231 hour: hour$k, 14232 minute: minute$j, 14233 second: second$k, 14234 hours: hours$k, 14235 minutes: minutes$k, 14236 seconds: seconds$k, 14237 vtsHeading: vtsHeading$k, 14238 vtsInstructions1: vtsInstructions1$k, 14239 vtsInstructions2: vtsInstructions2$k, 14240 vtsInstructions3: vtsInstructions3$k, 14241 vtsInstructions4: vtsInstructions4$k, 14242 vtsInstructions5: vtsInstructions5$k, 14243 vtsSelectLanguage: vtsSelectLanguage$k, 14244 vtsSave: vtsSave$k, 14245 vtsReturn: vtsReturn$k, 14246 vtsCancel: vtsCancel$k, 14247 vtsRow: vtsRow$k, 14248 vtsKind: vtsKind$k, 14249 vtsStart: vtsStart$k, 14250 vtsEnd: vtsEnd$k, 14251 vtsContent: vtsContent$k, 14252 vtsActions: vtsActions$k, 14253 vtsNewRow: vtsNewRow$k, 14254 vtsDeletedRow: vtsDeletedRow$k, 14255 vtsMovedRow: vtsMovedRow$k 14256 }; 14257 14258 var playerHeading$j = "PÅehrávaÄ médiÃ"; 14259 var audioPlayer$j = "Audio player"; 14260 var videoPlayer$j = "Video player"; 14261 var faster$j = "Rychleji"; 14262 var slower$j = "Pomaleji"; 14263 var play$j = "Spustit"; 14264 var pause$j = "Pauza"; 14265 var restart$j = "PÅehrát od zaÄátku"; 14266 var prevTrack$j = "PÅedchozà stopa"; 14267 var nextTrack$j = "Dalšà stopa"; 14268 var rewind$j = "PÅetoÄit vzad"; 14269 var forward$j = "PÅetoÄit vpÅed"; 14270 var captions$j = "Titulky"; 14271 var showCaptions$j = "Zobrazit titulky"; 14272 var hideCaptions$j = "Skrýt titulky"; 14273 var captionsOff$j = "Titulky vypnuty"; 14274 var showTranscript$j = "Zobrazit pÅepis"; 14275 var hideTranscript$j = "Skrýt pÅepis"; 14276 var turnOnDescriptions$j = "Zapnout popisy"; 14277 var turnOffDescriptions$j = "vypnout popisy"; 14278 var chapters$j = "Kapitoly"; 14279 var language$j = "Jazyk"; 14280 var sign$j = "Znaková ÅeÄ"; 14281 var showSign$j = "Zobrazit znakovou ÅeÄ"; 14282 var hideSign$j = "Skrýt znakovou ÅeÄ"; 14283 var seekbarLabel$j = "Äasová osa"; 14284 var mute$j = "Vypnout zvuk"; 14285 var unmute$j = "Zapnout zvuk"; 14286 var volume$j = "Hlasitost"; 14287 var volumeUpDown$j = "Hlasitost zvýšit snÞit"; 14288 var preferences$j = "PÅedvolby"; 14289 var enterFullScreen$j = "Zobrazit na celou obrazovku"; 14290 var exitFullScreen$j = "UkonÄit celou obrazovku"; 14291 var speed$j = "Rychlost"; 14292 var spacebar$j = "mezernÃk"; 14293 var transcriptTitle$j = "PÅepis"; 14294 var lyricsTitle$j = "Text"; 14295 var autoScroll$j = "Automatické posouvánÃ"; 14296 var statusPlaying$j = "PÅehrávánÃ"; 14297 var statusPaused$j = "Pozastaveno"; 14298 var statusStopped$j = "Zastaveno"; 14299 var statusBuffering$j = "Vyrovnávacà pamÄÅ¥"; 14300 var statusEnd$j = "Konec stopy"; 14301 var selectedTrack$i = "Vybraná stopa"; 14302 var alertDescribVersion = "PoužÃvánà zvukovÄ popsané verze tohoto videa"; 14303 var alertNonDescribVersion = "Použità nepopsané verze tohoto videa"; 14304 var prefMenuCaptions$j = "Titulky"; 14305 var prefVoicedCaptions$j = "Spoken Captions"; 14306 var prefMenuDescriptions$j = "Popisy"; 14307 var prefMenuKeyboard$j = "Klávesnice"; 14308 var prefMenuTranscript$j = "PÅepis"; 14309 var prefTitleCaptions$j = "PÅedvolby titulků"; 14310 var prefTitleDescriptions$j = "PÅedvolby zvukového popisu"; 14311 var prefTitleKeyboard$j = "PÅedvolby klávesnice"; 14312 var prefTitleTranscript$i = "PÅedvolby pÅepisu"; 14313 var prefIntroDescription1$i = "Tento pÅehrávaÄ médià podporuje zvukový popis dvÄma způsoby:"; 14314 var prefDescription1$j = "Aktuálnà video má alternativnÄ popsaná verze, textový popis."; 14315 var prefDescription2$j = "Aktuálnà video má alternativnà popsaná verze videa."; 14316 var prefDescription3$j = "Aktuálnà video má textový popis, oznámený ÄteÄkou obrazovky."; 14317 var prefIntroDescriptionNone$j = "Aktuálnà video nemá žádný zvukový popis v žádném formátu."; 14318 var prefIntroDescription3$j = "Pomocà následujÃcÃho formuláÅe můžete nastavit pÅedvolby týkajÃcà se textového zvukového popisu."; 14319 var prefIntroDescription4$j = "Po uloženà nastavenà lze zvukový popis zapnout / vypnout pomocà tlaÄÃtka Popis."; 14320 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)."; 14321 var prefIntroKeyboard2$j = "NÞe lze pÅiÅadit modifikaÄnà klávesy (Shift, Alt a Control)."; 14322 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Ã."; 14323 var prefHeadingKeyboard1$j = "ModifikaÄnà klávesy použÃvané pro zástupce"; 14324 var prefHeadingKeyboard2$j = "Aktuálnà klávesové zkratky"; 14325 var prefHeadingDescription$j = "Zvukový popis"; 14326 var prefHeadingTextDescription$j = "Textový zvukový popis"; 14327 var prefAltKey$j = "Alt"; 14328 var prefCtrlKey$j = "Control"; 14329 var prefShiftKey$j = "Shift"; 14330 var prefNoKeyShortcuts$j = "Disable keyboard shortcuts"; 14331 var escapeKey$j = "Escape"; 14332 var escapeKeyFunction$j = "ZavÅÃt aktuálnà dialogové okno nebo vyskakovacà nabÃdku"; 14333 var prefDescPause$j = "Automaticky pozastavit video pÅi spuÅ¡tÄnà popisu"; 14334 var prefDescVisible$j = "Zviditelnit popis"; 14335 var prefDescVoice$j = "Voice"; 14336 var prefDescRate$j = "Spoken Description Rate"; 14337 var prefCaptionRate$j = "Spoken Caption Rate"; 14338 var prefDescPitch$j = "Pitch"; 14339 var prefDescPitch1$j = "Very low"; 14340 var prefDescPitch2$j = "Low"; 14341 var prefDescPitch3$j = "Default"; 14342 var prefDescPitch4$j = "High"; 14343 var prefDescPitch5$j = "Very high"; 14344 var sampleDescriptionText$j = "Adjust settings to hear this sample text."; 14345 var prefHighlight$j = "Zvýraznit pÅepis pÅi pÅehrávánà médiÃ"; 14346 var prefTabbable$j = "PÅepis umožÅujÃcà klávesnici"; 14347 var prefCaptionsFont$j = "PÃsmo"; 14348 var prefCaptionsColor$j = "Barva textu"; 14349 var prefCaptionsBGColor$j = "PozadÃ"; 14350 var prefCaptionsSize$j = "Velikost pÃsma"; 14351 var prefCaptionsOpacity$j = "Neprůhlednost"; 14352 var prefCaptionsStyle$j = "Styl"; 14353 var serif$j = "patkové"; 14354 var sans$j = "bezpatkové"; 14355 var cursive$j = "kurzÃva"; 14356 var fantasy$j = "fantasy"; 14357 var monospace$j = "jednoprostorový"; 14358 var white$j = "bÃlá"; 14359 var yellow$j = "žlutá"; 14360 var green$j = "zelená"; 14361 var cyan$i = "azurová"; 14362 var blue$j = "modrá"; 14363 var magenta$j = "purpurová"; 14364 var red$j = "Äervená"; 14365 var black$j = "Äerná"; 14366 var transparent$j = "transparentnÃ"; 14367 var solid$j = "jednolitý"; 14368 var captionsStylePopOn$j = "Vyskakovat"; 14369 var captionsStyleRollUp$j = "Srolovat"; 14370 var prefCaptionsPosition$j = "Pozice"; 14371 var captionsPositionOverlay$j = "PÅekrytÃ"; 14372 var captionsPositionBelow$j = "NÞe video"; 14373 var sampleCaptionText$j = "Ukázkový text titulku"; 14374 var prefSuccess$j = "VaÅ¡e zmÄny byly uloženy."; 14375 var prefNoChange$j = "Neprovedli jste žádné zmÄny."; 14376 var save$j = "Uložit"; 14377 var cancel$j = "ZruÅ¡it"; 14378 var dismissButton$j = "Dismiss"; 14379 var windowButtonLabel$j = "Možnosti okna"; 14380 var windowMove$j = "PÅesunout"; 14381 var windowMoveLeft$j = "Window moved left"; 14382 var windowMoveRight$j = "Window moved right"; 14383 var windowMoveUp$j = "Window moved up"; 14384 var windowMoveDown$j = "Window moved down"; 14385 var windowMoveStopped$j = "Window move stopped"; 14386 var transcriptControls$j = "Transcript Window Controls"; 14387 var signControls$j = "Sign Language Window Controls"; 14388 var windowMoveAlert$j = "PÅetaženÃm nebo použitÃm kláves se Å¡ipkami pÅesuÅte okno; klávesou Enter zastavÃte"; 14389 var windowResize$j = "ZmÄnit velikost"; 14390 var windowResizeHeading$j = "ZmÄnit velikost okna"; 14391 var closeButtonLabel$j = "ZavÅÃt"; 14392 var width$j = "Å ÃÅka"; 14393 var height$j = "Výška"; 14394 var resultsSummary1$j = "Hledali jste:"; 14395 var resultsSummary2$j = "Nalezeno %1 odpovÃdajÃcà položky."; 14396 var resultsSummary3$j = "KlepnutÃm na Äas spojený s libovolnou položkou pÅehrajete video od tohoto bodu."; 14397 var noResultsFound$j = "Nebyly nalezeny žádné výsledky."; 14398 var searchButtonLabel$j = "PÅehrát v %1"; 14399 var hour$j = "hodina"; 14400 var minuta = "minuta"; 14401 var second$j = "sekunda"; 14402 var hours$j = "hodiny"; 14403 var minutes$j = "minuty"; 14404 var seconds$j = "sekundy"; 14405 var vtsHeading$j = "Video Transcript Sorter"; 14406 var vtsInstructions1$j = "Use the Video Transcript Sorter to modify text tracks:"; 14407 var vtsInstructions2$j = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 14408 var vtsInstructions3$j = "Modify content or start/end times (all are directly editable within the table)."; 14409 var vtsInstructions4$j = "Add new content, such as chapters or descriptions."; 14410 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."; 14411 var vtsSelectLanguage$j = "Select a language"; 14412 var vtsSave$j = "Generate new .vtt content"; 14413 var vtsReturn$j = "Return to Editor"; 14414 var vtsCancel$j = "Cancelling saving. Any edits you made have been restored in the VTS table."; 14415 var vtsRow$j = "Row"; 14416 var vtsKind$j = "Kind"; 14417 var vtsStart$j = "Start"; 14418 var vtsEnd$j = "End"; 14419 var vtsContent$j = "Content"; 14420 var vtsActions$j = "Actions"; 14421 var vtsNewRow$j = "A new row %1 has been inserted."; 14422 var vtsDeletedRow$j = "Row %1 has been deleted."; 14423 var vtsMovedRow$j = "Row %1 has been moved %2 and is now Row %3."; 14424 var cs = { 14425 playerHeading: playerHeading$j, 14426 audioPlayer: audioPlayer$j, 14427 videoPlayer: videoPlayer$j, 14428 faster: faster$j, 14429 slower: slower$j, 14430 play: play$j, 14431 pause: pause$j, 14432 restart: restart$j, 14433 prevTrack: prevTrack$j, 14434 nextTrack: nextTrack$j, 14435 rewind: rewind$j, 14436 forward: forward$j, 14437 captions: captions$j, 14438 showCaptions: showCaptions$j, 14439 hideCaptions: hideCaptions$j, 14440 captionsOff: captionsOff$j, 14441 showTranscript: showTranscript$j, 14442 hideTranscript: hideTranscript$j, 14443 turnOnDescriptions: turnOnDescriptions$j, 14444 turnOffDescriptions: turnOffDescriptions$j, 14445 chapters: chapters$j, 14446 language: language$j, 14447 sign: sign$j, 14448 showSign: showSign$j, 14449 hideSign: hideSign$j, 14450 seekbarLabel: seekbarLabel$j, 14451 mute: mute$j, 14452 unmute: unmute$j, 14453 volume: volume$j, 14454 volumeUpDown: volumeUpDown$j, 14455 preferences: preferences$j, 14456 enterFullScreen: enterFullScreen$j, 14457 exitFullScreen: exitFullScreen$j, 14458 speed: speed$j, 14459 spacebar: spacebar$j, 14460 transcriptTitle: transcriptTitle$j, 14461 lyricsTitle: lyricsTitle$j, 14462 autoScroll: autoScroll$j, 14463 statusPlaying: statusPlaying$j, 14464 statusPaused: statusPaused$j, 14465 statusStopped: statusStopped$j, 14466 statusBuffering: statusBuffering$j, 14467 statusEnd: statusEnd$j, 14468 selectedTrack: selectedTrack$i, 14469 alertDescribVersion: alertDescribVersion, 14470 alertNonDescribVersion: alertNonDescribVersion, 14471 prefMenuCaptions: prefMenuCaptions$j, 14472 prefVoicedCaptions: prefVoicedCaptions$j, 14473 prefMenuDescriptions: prefMenuDescriptions$j, 14474 prefMenuKeyboard: prefMenuKeyboard$j, 14475 prefMenuTranscript: prefMenuTranscript$j, 14476 prefTitleCaptions: prefTitleCaptions$j, 14477 prefTitleDescriptions: prefTitleDescriptions$j, 14478 prefTitleKeyboard: prefTitleKeyboard$j, 14479 prefTitleTranscript: prefTitleTranscript$i, 14480 prefIntroDescription1: prefIntroDescription1$i, 14481 prefDescription1: prefDescription1$j, 14482 prefDescription2: prefDescription2$j, 14483 prefDescription3: prefDescription3$j, 14484 prefIntroDescriptionNone: prefIntroDescriptionNone$j, 14485 prefIntroDescription3: prefIntroDescription3$j, 14486 prefIntroDescription4: prefIntroDescription4$j, 14487 prefIntroKeyboard1: prefIntroKeyboard1$j, 14488 prefIntroKeyboard2: prefIntroKeyboard2$j, 14489 prefIntroKeyboard3: prefIntroKeyboard3$j, 14490 prefHeadingKeyboard1: prefHeadingKeyboard1$j, 14491 prefHeadingKeyboard2: prefHeadingKeyboard2$j, 14492 prefHeadingDescription: prefHeadingDescription$j, 14493 prefHeadingTextDescription: prefHeadingTextDescription$j, 14494 prefAltKey: prefAltKey$j, 14495 prefCtrlKey: prefCtrlKey$j, 14496 prefShiftKey: prefShiftKey$j, 14497 prefNoKeyShortcuts: prefNoKeyShortcuts$j, 14498 escapeKey: escapeKey$j, 14499 escapeKeyFunction: escapeKeyFunction$j, 14500 prefDescPause: prefDescPause$j, 14501 prefDescVisible: prefDescVisible$j, 14502 prefDescVoice: prefDescVoice$j, 14503 prefDescRate: prefDescRate$j, 14504 prefCaptionRate: prefCaptionRate$j, 14505 prefDescPitch: prefDescPitch$j, 14506 prefDescPitch1: prefDescPitch1$j, 14507 prefDescPitch2: prefDescPitch2$j, 14508 prefDescPitch3: prefDescPitch3$j, 14509 prefDescPitch4: prefDescPitch4$j, 14510 prefDescPitch5: prefDescPitch5$j, 14511 sampleDescriptionText: sampleDescriptionText$j, 14512 prefHighlight: prefHighlight$j, 14513 prefTabbable: prefTabbable$j, 14514 prefCaptionsFont: prefCaptionsFont$j, 14515 prefCaptionsColor: prefCaptionsColor$j, 14516 prefCaptionsBGColor: prefCaptionsBGColor$j, 14517 prefCaptionsSize: prefCaptionsSize$j, 14518 prefCaptionsOpacity: prefCaptionsOpacity$j, 14519 prefCaptionsStyle: prefCaptionsStyle$j, 14520 serif: serif$j, 14521 sans: sans$j, 14522 cursive: cursive$j, 14523 fantasy: fantasy$j, 14524 monospace: monospace$j, 14525 white: white$j, 14526 yellow: yellow$j, 14527 green: green$j, 14528 cyan: cyan$i, 14529 blue: blue$j, 14530 magenta: magenta$j, 14531 red: red$j, 14532 black: black$j, 14533 transparent: transparent$j, 14534 solid: solid$j, 14535 captionsStylePopOn: captionsStylePopOn$j, 14536 captionsStyleRollUp: captionsStyleRollUp$j, 14537 prefCaptionsPosition: prefCaptionsPosition$j, 14538 captionsPositionOverlay: captionsPositionOverlay$j, 14539 captionsPositionBelow: captionsPositionBelow$j, 14540 sampleCaptionText: sampleCaptionText$j, 14541 prefSuccess: prefSuccess$j, 14542 prefNoChange: prefNoChange$j, 14543 save: save$j, 14544 cancel: cancel$j, 14545 dismissButton: dismissButton$j, 14546 windowButtonLabel: windowButtonLabel$j, 14547 windowMove: windowMove$j, 14548 windowMoveLeft: windowMoveLeft$j, 14549 windowMoveRight: windowMoveRight$j, 14550 windowMoveUp: windowMoveUp$j, 14551 windowMoveDown: windowMoveDown$j, 14552 windowMoveStopped: windowMoveStopped$j, 14553 transcriptControls: transcriptControls$j, 14554 signControls: signControls$j, 14555 windowMoveAlert: windowMoveAlert$j, 14556 windowResize: windowResize$j, 14557 windowResizeHeading: windowResizeHeading$j, 14558 closeButtonLabel: closeButtonLabel$j, 14559 width: width$j, 14560 height: height$j, 14561 resultsSummary1: resultsSummary1$j, 14562 resultsSummary2: resultsSummary2$j, 14563 resultsSummary3: resultsSummary3$j, 14564 noResultsFound: noResultsFound$j, 14565 searchButtonLabel: searchButtonLabel$j, 14566 hour: hour$j, 14567 minuta: minuta, 14568 second: second$j, 14569 hours: hours$j, 14570 minutes: minutes$j, 14571 seconds: seconds$j, 14572 vtsHeading: vtsHeading$j, 14573 vtsInstructions1: vtsInstructions1$j, 14574 vtsInstructions2: vtsInstructions2$j, 14575 vtsInstructions3: vtsInstructions3$j, 14576 vtsInstructions4: vtsInstructions4$j, 14577 vtsInstructions5: vtsInstructions5$j, 14578 vtsSelectLanguage: vtsSelectLanguage$j, 14579 vtsSave: vtsSave$j, 14580 vtsReturn: vtsReturn$j, 14581 vtsCancel: vtsCancel$j, 14582 vtsRow: vtsRow$j, 14583 vtsKind: vtsKind$j, 14584 vtsStart: vtsStart$j, 14585 vtsEnd: vtsEnd$j, 14586 vtsContent: vtsContent$j, 14587 vtsActions: vtsActions$j, 14588 vtsNewRow: vtsNewRow$j, 14589 vtsDeletedRow: vtsDeletedRow$j, 14590 vtsMovedRow: vtsMovedRow$j 14591 }; 14592 14593 var playerHeading$i = "Medieafspiller"; 14594 var audioPlayer$i = "Audio player"; 14595 var videoPlayer$i = "Video player"; 14596 var faster$i = "Hurtigere"; 14597 var slower$i = "Langsommere"; 14598 var play$i = "Afspil"; 14599 var pause$i = "Pause"; 14600 var restart$i = "Genstart"; 14601 var prevTrack$i = "Forrige spor"; 14602 var nextTrack$i = "Næste spor"; 14603 var rewind$i = "Spol tilbage"; 14604 var forward$i = "Spol frem"; 14605 var captions$i = "Undertekster"; 14606 var showCaptions$i = "Vis undertekster"; 14607 var hideCaptions$i = "Gem undertekster"; 14608 var captionsOff$i = "SlÃ¥ undertekster fra"; 14609 var showTranscript$i = "Vis transskription"; 14610 var hideTranscript$i = "Gem transskription"; 14611 var turnOnDescriptions$i = "Start synstolkning"; 14612 var turnOffDescriptions$i = "Stop synstolkning"; 14613 var chapters$i = "Kapitler"; 14614 var language$i = "Sprog"; 14615 var sign$i = "Tegnsprog"; 14616 var showSign$i = "Vis tegnsprog"; 14617 var hideSign$i = "Gen tegnsprog"; 14618 var seekbarLabel$i = "tidslinie"; 14619 var mute$i = "Stop lyd"; 14620 var unmute$i = "Start lyd"; 14621 var volume$i = "Lydstyrke"; 14622 var volumeUpDown$i = "Lydstyrke op"; 14623 var preferences$i = "Indstillinger"; 14624 var enterFullScreen$i = "Vis i fuldskærm"; 14625 var exitFullScreen$i = "Afslut fuldskærmsvisning"; 14626 var speed$i = "Hastighed"; 14627 var spacebar$i = "mellemrumstast"; 14628 var transcriptTitle$i = "Transskription"; 14629 var lyricsTitle$i = "Lyrik"; 14630 var autoScroll$i = "Auto scroll"; 14631 var statusPlaying$i = "Afspiller"; 14632 var statusPaused$i = "PÃ¥ pause"; 14633 var statusStopped$i = "Stoppet"; 14634 var statusBuffering$i = "Henter data"; 14635 var statusEnd$i = "Slut pÃ¥ spor"; 14636 var selectedTrack$h = "Valgt spor"; 14637 var alertDescribedVersion$i = "Anvender synstolket version af denne video"; 14638 var alertNonDescribedVersion$i = "Anvende ikke synstolket version af denne video"; 14639 var prefMenuCaptions$i = "Undertekster"; 14640 var prefVoicedCaptions$i = "Spoken Captions"; 14641 var prefMenuDescriptions$i = "Synstolkning"; 14642 var prefMenuKeyboard$i = "Tastatur"; 14643 var prefMenuTranscript$i = "Transkript"; 14644 var prefTitleCaptions$i = "Indstillinger for undertekster"; 14645 var prefTitleDescriptions$i = "Indstillinger for synstolkning"; 14646 var prefTitleKeyboard$i = "Indstillinger for tastatur"; 14647 var prefTitleTranscript$h = "Indstillinger for transskription"; 14648 var prefIntroDescription1$h = "Denne medieafspiller understøtter audio pÃ¥ to mÃ¥der: "; 14649 var prefDescription1$i = "Nuværende videoer har en alternativ synstolket version, textbaseret synstolkning."; 14650 var prefDescription2$i = "Nuværende videoer har alternativ synstolket version af videoen."; 14651 var prefDescription3$i = "Nuværende videoer har textbaseret synstolkning."; 14652 var prefIntroDescriptionNone$i = "Nuværende video har ingen synstolkning i noget format."; 14653 var prefIntroDescription3$i = "Anvende følgende formular for at indstille gældende textbasererede synstolkning."; 14654 var prefIntroDescription4$i = "Efter du gemmer dine indstillinger, kan synstolkning slÃ¥es til og fra med synstolkningsknappen."; 14655 var prefIntroKeyboard1$i = "Medieafspilleren pÃ¥ denne webside kan betjenest med tastuturgenveje (se neden for en liste)."; 14656 var prefIntroKeyboard2$i = "Meta-taster (Shift, Alt, og Ctrl) kan tildeles nedenfor."; 14657 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."; 14658 var prefHeadingKeyboard1$i = "Metataster brugt til genveje"; 14659 var prefHeadingKeyboard2$i = "Nuværende tastatur genveje"; 14660 var prefHeadingDescription$i = "Synstolkning"; 14661 var prefHeadingTextDescription$i = "Tekstbaseret synstolkning"; 14662 var prefAltKey$i = "Alt"; 14663 var prefCtrlKey$i = "Ctrl"; 14664 var prefShiftKey$i = "Shift"; 14665 var prefNoKeyShortcuts$i = "Disable keyboard shortcuts"; 14666 var escapeKey$i = "Escape"; 14667 var escapeKeyFunction$i = "Luk nuværinde dialog eller popup-menu"; 14668 var prefDescPause$i = "Pause automatisk video nÃ¥r synstolkning starter"; 14669 var prefDescVisible$i = "Vis synstolkning"; 14670 var prefDescVoice$i = "Stemme"; 14671 var prefDescRate$i = "Spoken Description Rate"; 14672 var prefCaptionRate$i = "Spoken Caption Rate"; 14673 var prefDescPitch$i = "Tonehøjde"; 14674 var prefDescPitch1$i = "Meget lavt"; 14675 var prefDescPitch2$i = "Lavt"; 14676 var prefDescPitch3$i = "Normal"; 14677 var prefDescPitch4$i = "Højt"; 14678 var prefDescPitch5$i = "Meget højt"; 14679 var sampleDescriptionText$i = "Juster indstillinger for at høre denne eksempel tekst."; 14680 var prefHighlight$i = "Marker tekst som læses op i transskriptet"; 14681 var prefTabbable$i = "Tastaturaktiveret transskription"; 14682 var prefCaptionsFont$i = "Skrifttype"; 14683 var prefCaptionsColor$i = "Tekstfarve"; 14684 var prefCaptionsBGColor$i = "Baggrundsfarve"; 14685 var prefCaptionsSize$i = "Tekststørrelse"; 14686 var prefCaptionsOpacity$i = "Gennemsigtighed"; 14687 var prefCaptionsStyle$i = "Teksttype"; 14688 var serif$i = "serif"; 14689 var sans$i = "sans-serif"; 14690 var cursive$i = "kursiv"; 14691 var fantasy$i = "fantasi"; 14692 var monospace$i = "monospace"; 14693 var white$i = "hvid"; 14694 var yellow$i = "gul"; 14695 var green$i = "grøn"; 14696 var cyan$h = "cyan"; 14697 var blue$i = "blÃ¥"; 14698 var magenta$i = "lilla"; 14699 var red$i = "rød"; 14700 var black$i = "sort"; 14701 var transparent$i = "gennemsigtig"; 14702 var solid$i = "massiv"; 14703 var captionsStylePopOn$i = "Pop op"; 14704 var captionsStyleRollUp$i = "Pop ned"; 14705 var prefCaptionsPosition$i = "Position"; 14706 var captionsPositionOverlay$i = "Vis over video"; 14707 var captionsPositionBelow$i = "Vis under video"; 14708 var sampleCaptionText$i = "Eksempel pÃ¥ undertekst"; 14709 var prefSuccess$i = "Dine ændringer er gemt."; 14710 var prefNoChange$i = "Du har ikke lavet nogen ændringer."; 14711 var save$i = "Gem"; 14712 var cancel$i = "Afbryd"; 14713 var dismissButton$i = "Dismiss"; 14714 var windowButtonLabel$i = "Vindueindstillinger"; 14715 var windowMove$i = "Flyt"; 14716 var windowMoveLeft$i = "Window moved left"; 14717 var windowMoveRight$i = "Window moved right"; 14718 var windowMoveUp$i = "Window moved up"; 14719 var windowMoveDown$i = "Window moved down"; 14720 var windowMoveStopped$i = "Window move stopped"; 14721 var transcriptControls$i = "Transcript Window Controls"; 14722 var signControls$i = "Sign Language Window Controls"; 14723 var windowMoveAlert$i = "Flyt med mus eller anvende piletasterne for at flytte vinduer; Enter for at slutte"; 14724 var windowResize$i = "Ãndre størrelse"; 14725 var windowResizeHeading$i = "Ãndre vinduestørrelse"; 14726 var closeButtonLabel$i = "Luk"; 14727 var width$i = "Bredde"; 14728 var height$i = "Højde"; 14729 var resultsSummary1$i = "Du søgte efter:"; 14730 var resultsSummary2$i = "Fundne %1 matchende indhold."; 14731 var resultsSummary3$i = "Klik pÃ¥ tidslinien for at afspille derfra."; 14732 var noResultsFound$i = "Ingen resultater."; 14733 var searchButtonLabel$i = "Afspil fra %1"; 14734 var hour$i = "time"; 14735 var minute$i = "minut"; 14736 var second$i = "sekund"; 14737 var hours$i = "timer"; 14738 var minutes$i = "minutter"; 14739 var seconds$i = "sekunder"; 14740 var vtsHeading$i = "Video Transcript Sorter"; 14741 var vtsInstructions1$i = "Use the Video Transcript Sorter to modify text tracks:"; 14742 var vtsInstructions2$i = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 14743 var vtsInstructions3$i = "Modify content or start/end times (all are directly editable within the table)."; 14744 var vtsInstructions4$i = "Add new content, such as chapters or descriptions."; 14745 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."; 14746 var vtsSelectLanguage$i = "Select a language"; 14747 var vtsSave$i = "Generate new .vtt content"; 14748 var vtsReturn$i = "Return to Editor"; 14749 var vtsCancel$i = "Cancelling saving. Any edits you made have been restored in the VTS table."; 14750 var vtsRow$i = "Row"; 14751 var vtsKind$i = "Kind"; 14752 var vtsStart$i = "Start"; 14753 var vtsEnd$i = "End"; 14754 var vtsContent$i = "Content"; 14755 var vtsActions$i = "Actions"; 14756 var vtsNewRow$i = "A new row %1 has been inserted."; 14757 var vtsDeletedRow$i = "Row %1 has been deleted."; 14758 var vtsMovedRow$i = "Row %1 has been moved %2 and is now Row %3."; 14759 var da = { 14760 playerHeading: playerHeading$i, 14761 audioPlayer: audioPlayer$i, 14762 videoPlayer: videoPlayer$i, 14763 faster: faster$i, 14764 slower: slower$i, 14765 play: play$i, 14766 pause: pause$i, 14767 restart: restart$i, 14768 prevTrack: prevTrack$i, 14769 nextTrack: nextTrack$i, 14770 rewind: rewind$i, 14771 forward: forward$i, 14772 captions: captions$i, 14773 showCaptions: showCaptions$i, 14774 hideCaptions: hideCaptions$i, 14775 captionsOff: captionsOff$i, 14776 showTranscript: showTranscript$i, 14777 hideTranscript: hideTranscript$i, 14778 turnOnDescriptions: turnOnDescriptions$i, 14779 turnOffDescriptions: turnOffDescriptions$i, 14780 chapters: chapters$i, 14781 language: language$i, 14782 sign: sign$i, 14783 showSign: showSign$i, 14784 hideSign: hideSign$i, 14785 seekbarLabel: seekbarLabel$i, 14786 mute: mute$i, 14787 unmute: unmute$i, 14788 volume: volume$i, 14789 volumeUpDown: volumeUpDown$i, 14790 preferences: preferences$i, 14791 enterFullScreen: enterFullScreen$i, 14792 exitFullScreen: exitFullScreen$i, 14793 speed: speed$i, 14794 spacebar: spacebar$i, 14795 transcriptTitle: transcriptTitle$i, 14796 lyricsTitle: lyricsTitle$i, 14797 autoScroll: autoScroll$i, 14798 statusPlaying: statusPlaying$i, 14799 statusPaused: statusPaused$i, 14800 statusStopped: statusStopped$i, 14801 statusBuffering: statusBuffering$i, 14802 statusEnd: statusEnd$i, 14803 selectedTrack: selectedTrack$h, 14804 alertDescribedVersion: alertDescribedVersion$i, 14805 alertNonDescribedVersion: alertNonDescribedVersion$i, 14806 prefMenuCaptions: prefMenuCaptions$i, 14807 prefVoicedCaptions: prefVoicedCaptions$i, 14808 prefMenuDescriptions: prefMenuDescriptions$i, 14809 prefMenuKeyboard: prefMenuKeyboard$i, 14810 prefMenuTranscript: prefMenuTranscript$i, 14811 prefTitleCaptions: prefTitleCaptions$i, 14812 prefTitleDescriptions: prefTitleDescriptions$i, 14813 prefTitleKeyboard: prefTitleKeyboard$i, 14814 prefTitleTranscript: prefTitleTranscript$h, 14815 prefIntroDescription1: prefIntroDescription1$h, 14816 prefDescription1: prefDescription1$i, 14817 prefDescription2: prefDescription2$i, 14818 prefDescription3: prefDescription3$i, 14819 prefIntroDescriptionNone: prefIntroDescriptionNone$i, 14820 prefIntroDescription3: prefIntroDescription3$i, 14821 prefIntroDescription4: prefIntroDescription4$i, 14822 prefIntroKeyboard1: prefIntroKeyboard1$i, 14823 prefIntroKeyboard2: prefIntroKeyboard2$i, 14824 prefIntroKeyboard3: prefIntroKeyboard3$i, 14825 prefHeadingKeyboard1: prefHeadingKeyboard1$i, 14826 prefHeadingKeyboard2: prefHeadingKeyboard2$i, 14827 prefHeadingDescription: prefHeadingDescription$i, 14828 prefHeadingTextDescription: prefHeadingTextDescription$i, 14829 prefAltKey: prefAltKey$i, 14830 prefCtrlKey: prefCtrlKey$i, 14831 prefShiftKey: prefShiftKey$i, 14832 prefNoKeyShortcuts: prefNoKeyShortcuts$i, 14833 escapeKey: escapeKey$i, 14834 escapeKeyFunction: escapeKeyFunction$i, 14835 prefDescPause: prefDescPause$i, 14836 prefDescVisible: prefDescVisible$i, 14837 prefDescVoice: prefDescVoice$i, 14838 prefDescRate: prefDescRate$i, 14839 prefCaptionRate: prefCaptionRate$i, 14840 prefDescPitch: prefDescPitch$i, 14841 prefDescPitch1: prefDescPitch1$i, 14842 prefDescPitch2: prefDescPitch2$i, 14843 prefDescPitch3: prefDescPitch3$i, 14844 prefDescPitch4: prefDescPitch4$i, 14845 prefDescPitch5: prefDescPitch5$i, 14846 sampleDescriptionText: sampleDescriptionText$i, 14847 prefHighlight: prefHighlight$i, 14848 prefTabbable: prefTabbable$i, 14849 prefCaptionsFont: prefCaptionsFont$i, 14850 prefCaptionsColor: prefCaptionsColor$i, 14851 prefCaptionsBGColor: prefCaptionsBGColor$i, 14852 prefCaptionsSize: prefCaptionsSize$i, 14853 prefCaptionsOpacity: prefCaptionsOpacity$i, 14854 prefCaptionsStyle: prefCaptionsStyle$i, 14855 serif: serif$i, 14856 sans: sans$i, 14857 cursive: cursive$i, 14858 fantasy: fantasy$i, 14859 monospace: monospace$i, 14860 white: white$i, 14861 yellow: yellow$i, 14862 green: green$i, 14863 cyan: cyan$h, 14864 blue: blue$i, 14865 magenta: magenta$i, 14866 red: red$i, 14867 black: black$i, 14868 transparent: transparent$i, 14869 solid: solid$i, 14870 captionsStylePopOn: captionsStylePopOn$i, 14871 captionsStyleRollUp: captionsStyleRollUp$i, 14872 prefCaptionsPosition: prefCaptionsPosition$i, 14873 captionsPositionOverlay: captionsPositionOverlay$i, 14874 captionsPositionBelow: captionsPositionBelow$i, 14875 sampleCaptionText: sampleCaptionText$i, 14876 prefSuccess: prefSuccess$i, 14877 prefNoChange: prefNoChange$i, 14878 save: save$i, 14879 cancel: cancel$i, 14880 dismissButton: dismissButton$i, 14881 windowButtonLabel: windowButtonLabel$i, 14882 windowMove: windowMove$i, 14883 windowMoveLeft: windowMoveLeft$i, 14884 windowMoveRight: windowMoveRight$i, 14885 windowMoveUp: windowMoveUp$i, 14886 windowMoveDown: windowMoveDown$i, 14887 windowMoveStopped: windowMoveStopped$i, 14888 transcriptControls: transcriptControls$i, 14889 signControls: signControls$i, 14890 windowMoveAlert: windowMoveAlert$i, 14891 windowResize: windowResize$i, 14892 windowResizeHeading: windowResizeHeading$i, 14893 closeButtonLabel: closeButtonLabel$i, 14894 width: width$i, 14895 height: height$i, 14896 resultsSummary1: resultsSummary1$i, 14897 resultsSummary2: resultsSummary2$i, 14898 resultsSummary3: resultsSummary3$i, 14899 noResultsFound: noResultsFound$i, 14900 searchButtonLabel: searchButtonLabel$i, 14901 hour: hour$i, 14902 minute: minute$i, 14903 second: second$i, 14904 hours: hours$i, 14905 minutes: minutes$i, 14906 seconds: seconds$i, 14907 vtsHeading: vtsHeading$i, 14908 vtsInstructions1: vtsInstructions1$i, 14909 vtsInstructions2: vtsInstructions2$i, 14910 vtsInstructions3: vtsInstructions3$i, 14911 vtsInstructions4: vtsInstructions4$i, 14912 vtsInstructions5: vtsInstructions5$i, 14913 vtsSelectLanguage: vtsSelectLanguage$i, 14914 vtsSave: vtsSave$i, 14915 vtsReturn: vtsReturn$i, 14916 vtsCancel: vtsCancel$i, 14917 vtsRow: vtsRow$i, 14918 vtsKind: vtsKind$i, 14919 vtsStart: vtsStart$i, 14920 vtsEnd: vtsEnd$i, 14921 vtsContent: vtsContent$i, 14922 vtsActions: vtsActions$i, 14923 vtsNewRow: vtsNewRow$i, 14924 vtsDeletedRow: vtsDeletedRow$i, 14925 vtsMovedRow: vtsMovedRow$i 14926 }; 14927 14928 var playerHeading$h = "Medienplayer"; 14929 var audioPlayer$h = "Audioplayer"; 14930 var videoPlayer$h = "Videoplayer"; 14931 var faster$h = "Schneller"; 14932 var slower$h = "Langsamer"; 14933 var chapters$h = "Kapitel"; 14934 var play$h = "Abspielen"; 14935 var pause$h = "Pause"; 14936 var restart$h = "Neustart"; 14937 var prevTrack$h = "Vorheriger Titel"; 14938 var nextTrack$h = "Nächster Titel"; 14939 var rewind$h = "Zurück"; 14940 var forward$h = "Vorwärts"; 14941 var captions$h = "Untertitel"; 14942 var showCaptions$h = "Untertitel anzeigen"; 14943 var hideCaptions$h = "Untertitel ausblenden"; 14944 var captionsOff$h = "Untertitel ausschalten"; 14945 var showTranscript$h = "Transkription anzeigen"; 14946 var hideTranscript$h = "Transkription entfernen"; 14947 var turnOnDescriptions$h = "Audiobeschreibung einschalten"; 14948 var turnOffDescriptions$h = "Audiobeschreibung ausschalten"; 14949 var language$h = "Sprache"; 14950 var sign$h = "Gebärdensprache"; 14951 var showSign$h = "Gebärdensprache anzeigen"; 14952 var hideSign$h = "Gebärdensprache ausblenden"; 14953 var seekbarLabel$h = "Suchleiste"; 14954 var mute$h = "Ton aus"; 14955 var unmute$h = "Ton an"; 14956 var volume$h = "Lautstärke"; 14957 var volumeUpDown$h = "Lautstärkeregler"; 14958 var preferences$h = "Einstellungen"; 14959 var enterFullScreen$h = "Vollbildmodus einschalten"; 14960 var exitFullScreen$h = "Vollbildmodus verlassen"; 14961 var speed$h = "Geschwindigkeit"; 14962 var spacebar$h = "Leertaste"; 14963 var transcriptTitle$h = "Transkription"; 14964 var lyricsTitle$h = "Text"; 14965 var autoScroll$h = "Automatisch scrollen"; 14966 var statusPlaying$h = "Gestartet"; 14967 var statusPaused$h = "Pausiert"; 14968 var statusStopped$h = "Angehalten"; 14969 var statusBuffering$h = "Daten werden empfangen..."; 14970 var statusEnd$h = "Ende des Titels"; 14971 var selectedTrack$g = "Ausgewählter Titel"; 14972 var alertDescribedVersion$h = "Das Video wird mit Audiobeschreibung abgespielt"; 14973 var alertNonDescribedVersion$h = "Das Video wird ohne Audiobeschreibung abgespielt"; 14974 var prefMenuCaptions$h = "Untertitel"; 14975 var prefVoicedCaptions$h = "Spoken Captions"; 14976 var prefMenuDescriptions$h = "Audiobeschreibungen"; 14977 var prefMenuKeyboard$h = "Tastatur"; 14978 var prefMenuTranscript$h = "Transkription"; 14979 var prefTitleCaptions$h = "Untertitel Einstellungen"; 14980 var prefTitleDescriptions$h = "Audiobeschreibung Einstellungen"; 14981 var prefTitleKeyboard$h = "Tastatur Einstellungen"; 14982 var prefTitleTranscript$g = "Transkription Einstellungen"; 14983 var prefIntroDescription1$g = "Dieser Media Player unterstützt zwei Arten von Untertiteln: "; 14984 var prefDescription1$h = "Das aktuelle Video hat eine alternative Version der Audiobeschreibung, eine textbasierte Audiobeschreibung."; 14985 var prefDescription2$h = "Das aktuelle Video hat Version des Videos, die eine Audiobeschreibung enthält."; 14986 var prefDescription3$h = "Das aktuelle Video hat Textbasierte Audiobeschreibung, die vom Screen-Reader vorgelesen wird."; 14987 var prefIntroDescriptionNone$h = "Das aktuelle Video hat keine Audiobeschreibung."; 14988 var prefIntroDescription3$h = "Mit der folgenden Auswahl steuern Sie das Abspielen der textbasierten Audiobeschreibung."; 14989 var prefIntroDescription4$h = "Wenn die Audiobeschreibung aktiviert ist, kann sie per Schaltfläche ein- und ausgeschaltet werden."; 14990 var prefIntroKeyboard1$h = "Dieser Media Player lässt sich innerhalb der gesamten Seite per Tastenkürzel bedienen (siehe unten)."; 14991 var prefIntroKeyboard2$h = "Die Modifikatortasten (Umschalt, Alt, und Strg) können hier zugeordnet werden."; 14992 var prefIntroKeyboard3$h = "Achtung: Einige Tastenkombinationen sind je nach Browser und Betriebssystem nicht möglich. Versuchen Sie gegebenenfalls andere Kombinationen."; 14993 var prefHeadingKeyboard1$h = "Modifikatortasten für die Tastenkürzel"; 14994 var prefHeadingKeyboard2$h = "Aktuell eingestellte Tastenkürzel"; 14995 var prefHeadingDescription$h = "Audiobeschreibung"; 14996 var prefHeadingTextDescription$h = "Textbasierte Audiobeschreibung"; 14997 var prefAltKey$h = "Alt"; 14998 var prefCtrlKey$h = "Strg"; 14999 var prefShiftKey$h = "Umschalttaste"; 15000 var prefNoKeyShortcuts$h = "Tastenkombinationen deaktivieren"; 15001 var escapeKey$h = "ESC Taste"; 15002 var escapeKeyFunction$h = "Dialogfenster schlieÃen"; 15003 var prefDescPause$h = "Video automatisch anhalten, wenn Szenenbeschreibungen eingeblendet werden"; 15004 var prefDescVisible$h = "Textbasierte Szenenbeschreibungen einblenden, wenn diese aktiviert sind"; 15005 var prefDescVoice$h = "Stimme"; 15006 var prefDescRate$h = "Spoken Description Rate"; 15007 var prefCaptionRate$h = "Spoken Caption Rate"; 15008 var prefDescPitch$h = "Tonlage"; 15009 var prefDescPitch1$h = "Sehr tief"; 15010 var prefDescPitch2$h = "Tief"; 15011 var prefDescPitch3$h = "Mittel"; 15012 var prefDescPitch4$h = "Hoch"; 15013 var prefDescPitch5$h = "Sehr hoch"; 15014 var sampleDescriptionText$h = "Einstellungen bearbeiten um diesen Text vorzulesen."; 15015 var prefHighlight$h = "Transkription hervorheben, während das Medium abgespielt wird"; 15016 var prefTabbable$h = "Transkription per Tastatur ein-/ausschaltbar machen"; 15017 var prefCaptionsFont$h = "Schriftart"; 15018 var prefCaptionsColor$h = "Schriftfarbe"; 15019 var prefCaptionsBGColor$h = "Hintergrund"; 15020 var prefCaptionsSize$h = "SchriftgöÃe"; 15021 var prefCaptionsOpacity$h = "Deckkraft"; 15022 var prefCaptionsStyle$h = "Stil"; 15023 var serif$h = "Serifenschrift"; 15024 var sans$h = "Serifenlose Schrift"; 15025 var cursive$h = "kursiv"; 15026 var fantasy$h = "Fantasieschrift"; 15027 var monospace$h = "nichtproportionale Schrift"; 15028 var white$h = "weiÃ"; 15029 var yellow$h = "gelb"; 15030 var green$h = "grün"; 15031 var cyan$g = "cyan"; 15032 var blue$h = "blau"; 15033 var magenta$h = "magenta"; 15034 var red$h = "rot"; 15035 var black$h = "schwarz"; 15036 var transparent$h = "transparent"; 15037 var solid$h = "undurchsichtig"; 15038 var captionsStylePopOn$h = "Pop-on"; 15039 var captionsStyleRollUp$h = "Roll-up"; 15040 var prefCaptionsPosition$h = "Position"; 15041 var captionsPositionOverlay$h = "Ãberlagert"; 15042 var captionsPositionBelow$h = "Unterhalb"; 15043 var sampleCaptionText$h = "Textbeispiel"; 15044 var prefSuccess$h = "Ihre Ãnderungen wurden gespeichert."; 15045 var prefNoChange$h = "Es gab keine Ãnderungen zu speichern."; 15046 var save$h = "Speichern"; 15047 var cancel$h = "Abbrechen"; 15048 var dismissButton$h = "Dismiss"; 15049 var windowButtonLabel$h = "Fenstereinstellungen"; 15050 var windowMove$h = "Verschieben"; 15051 var windowMoveLeft$h = "Das Fenster verschob sich nach links"; 15052 var windowMoveRight$h = "Das Fenster verschob sich nach rechts"; 15053 var windowMoveUp$h = "Das Fenster verschob sich nach oben"; 15054 var windowMoveDown$h = "Das Fenster verschob sich nach unten"; 15055 var windowMoveStopped$h = "Das Fenster hörte auf sich zu verschieben"; 15056 var transcriptControls$h = "Bedienelemente des Transkriptionsfensters"; 15057 var signControls$h = "Bedienelemente des Gebärdensprache-Fensters"; 15058 var windowMoveAlert$h = "Fenster mit Pfeiltasten oder Maus verschieben; beenden mit Eingabetaste"; 15059 var windowResize$h = "GröÃe verändern"; 15060 var windowResizeHeading$h = "GröÃe des Gebärdensprache-Fenster"; 15061 var closeButtonLabel$h = "SchlieÃen"; 15062 var width$h = "Breite"; 15063 var height$h = "Höhe"; 15064 var resultsSummary1$h = "Suche nach:"; 15065 var resultsSummary2$h = "Gefunden %1 Treffer."; 15066 var resultsSummary3$h = "Auf den Zeitindex klicken, um das Video vom Zeitpunkt des jeweiligen Suchergebnisses abzuspielen."; 15067 var noResultsFound$h = "Keine Treffer."; 15068 var searchButtonLabel$h = "Abspielen von %1"; 15069 var hour$h = "Stunde"; 15070 var minute$h = "Minute"; 15071 var second$h = "Sekunde"; 15072 var hours$h = "Stunden"; 15073 var minutes$h = "Minuten"; 15074 var seconds$h = "Sekunden"; 15075 var vtsHeading$h = "Transkriptions-Sortierer für Videos"; 15076 var vtsInstructions1$h = "Verwenden Sie den Transkription-Sortierer für Videos, um Textspuren zu ändern:"; 15077 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."; 15078 var vtsInstructions3$h = "Verändern Sie Inhalt oder Anfangs- bzw. Endzeiten (alle sind direkt innerhalb der Tabelle editierbar)."; 15079 var vtsInstructions4$h = "Fügen Sie neuen Inhalt, wie Kapitel oder Beschreibungen hinzu."; 15080 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."; 15081 var vtsSelectLanguage$h = "Wählen Sie eine Sprache aus"; 15082 var vtsSave$h = "Erzeugen Sie neuen .vtt-Inhalt"; 15083 var vtsReturn$h = "Gehen Sie zurück zum Editor"; 15084 var vtsCancel$h = "Abbruch des Speicherns. Jegliche Bearbeitungen, die Sie vorgenommen haben, wurden in der VTS-Tabelle gesichert."; 15085 var vtsRow$h = "Reihe"; 15086 var vtsKind$h = "Art"; 15087 var vtsStart$h = "Anfang"; 15088 var vtsEnd$h = "Ende"; 15089 var vtsContent$h = "Inhalt"; 15090 var vtsActions$h = "Aktionen"; 15091 var vtsNewRow$h = "Eine neue Reihe %1 wurde eingefügt."; 15092 var vtsDeletedRow$h = "Reihe %1 wurde gelöscht."; 15093 var vtsMovedRow$h = "Reihe %1 wurde verschoben %2 ist nun Reihe %3."; 15094 var de = { 15095 playerHeading: playerHeading$h, 15096 audioPlayer: audioPlayer$h, 15097 videoPlayer: videoPlayer$h, 15098 faster: faster$h, 15099 slower: slower$h, 15100 chapters: chapters$h, 15101 play: play$h, 15102 pause: pause$h, 15103 restart: restart$h, 15104 prevTrack: prevTrack$h, 15105 nextTrack: nextTrack$h, 15106 rewind: rewind$h, 15107 forward: forward$h, 15108 captions: captions$h, 15109 showCaptions: showCaptions$h, 15110 hideCaptions: hideCaptions$h, 15111 captionsOff: captionsOff$h, 15112 showTranscript: showTranscript$h, 15113 hideTranscript: hideTranscript$h, 15114 turnOnDescriptions: turnOnDescriptions$h, 15115 turnOffDescriptions: turnOffDescriptions$h, 15116 language: language$h, 15117 sign: sign$h, 15118 showSign: showSign$h, 15119 hideSign: hideSign$h, 15120 seekbarLabel: seekbarLabel$h, 15121 mute: mute$h, 15122 unmute: unmute$h, 15123 volume: volume$h, 15124 volumeUpDown: volumeUpDown$h, 15125 preferences: preferences$h, 15126 enterFullScreen: enterFullScreen$h, 15127 exitFullScreen: exitFullScreen$h, 15128 speed: speed$h, 15129 spacebar: spacebar$h, 15130 transcriptTitle: transcriptTitle$h, 15131 lyricsTitle: lyricsTitle$h, 15132 autoScroll: autoScroll$h, 15133 statusPlaying: statusPlaying$h, 15134 statusPaused: statusPaused$h, 15135 statusStopped: statusStopped$h, 15136 statusBuffering: statusBuffering$h, 15137 statusEnd: statusEnd$h, 15138 selectedTrack: selectedTrack$g, 15139 alertDescribedVersion: alertDescribedVersion$h, 15140 alertNonDescribedVersion: alertNonDescribedVersion$h, 15141 prefMenuCaptions: prefMenuCaptions$h, 15142 prefVoicedCaptions: prefVoicedCaptions$h, 15143 prefMenuDescriptions: prefMenuDescriptions$h, 15144 prefMenuKeyboard: prefMenuKeyboard$h, 15145 prefMenuTranscript: prefMenuTranscript$h, 15146 prefTitleCaptions: prefTitleCaptions$h, 15147 prefTitleDescriptions: prefTitleDescriptions$h, 15148 prefTitleKeyboard: prefTitleKeyboard$h, 15149 prefTitleTranscript: prefTitleTranscript$g, 15150 prefIntroDescription1: prefIntroDescription1$g, 15151 prefDescription1: prefDescription1$h, 15152 prefDescription2: prefDescription2$h, 15153 prefDescription3: prefDescription3$h, 15154 prefIntroDescriptionNone: prefIntroDescriptionNone$h, 15155 prefIntroDescription3: prefIntroDescription3$h, 15156 prefIntroDescription4: prefIntroDescription4$h, 15157 prefIntroKeyboard1: prefIntroKeyboard1$h, 15158 prefIntroKeyboard2: prefIntroKeyboard2$h, 15159 prefIntroKeyboard3: prefIntroKeyboard3$h, 15160 prefHeadingKeyboard1: prefHeadingKeyboard1$h, 15161 prefHeadingKeyboard2: prefHeadingKeyboard2$h, 15162 prefHeadingDescription: prefHeadingDescription$h, 15163 prefHeadingTextDescription: prefHeadingTextDescription$h, 15164 prefAltKey: prefAltKey$h, 15165 prefCtrlKey: prefCtrlKey$h, 15166 prefShiftKey: prefShiftKey$h, 15167 prefNoKeyShortcuts: prefNoKeyShortcuts$h, 15168 escapeKey: escapeKey$h, 15169 escapeKeyFunction: escapeKeyFunction$h, 15170 prefDescPause: prefDescPause$h, 15171 prefDescVisible: prefDescVisible$h, 15172 prefDescVoice: prefDescVoice$h, 15173 prefDescRate: prefDescRate$h, 15174 prefCaptionRate: prefCaptionRate$h, 15175 prefDescPitch: prefDescPitch$h, 15176 prefDescPitch1: prefDescPitch1$h, 15177 prefDescPitch2: prefDescPitch2$h, 15178 prefDescPitch3: prefDescPitch3$h, 15179 prefDescPitch4: prefDescPitch4$h, 15180 prefDescPitch5: prefDescPitch5$h, 15181 sampleDescriptionText: sampleDescriptionText$h, 15182 prefHighlight: prefHighlight$h, 15183 prefTabbable: prefTabbable$h, 15184 prefCaptionsFont: prefCaptionsFont$h, 15185 prefCaptionsColor: prefCaptionsColor$h, 15186 prefCaptionsBGColor: prefCaptionsBGColor$h, 15187 prefCaptionsSize: prefCaptionsSize$h, 15188 prefCaptionsOpacity: prefCaptionsOpacity$h, 15189 prefCaptionsStyle: prefCaptionsStyle$h, 15190 serif: serif$h, 15191 sans: sans$h, 15192 cursive: cursive$h, 15193 fantasy: fantasy$h, 15194 monospace: monospace$h, 15195 white: white$h, 15196 yellow: yellow$h, 15197 green: green$h, 15198 cyan: cyan$g, 15199 blue: blue$h, 15200 magenta: magenta$h, 15201 red: red$h, 15202 black: black$h, 15203 transparent: transparent$h, 15204 solid: solid$h, 15205 captionsStylePopOn: captionsStylePopOn$h, 15206 captionsStyleRollUp: captionsStyleRollUp$h, 15207 prefCaptionsPosition: prefCaptionsPosition$h, 15208 captionsPositionOverlay: captionsPositionOverlay$h, 15209 captionsPositionBelow: captionsPositionBelow$h, 15210 sampleCaptionText: sampleCaptionText$h, 15211 prefSuccess: prefSuccess$h, 15212 prefNoChange: prefNoChange$h, 15213 save: save$h, 15214 cancel: cancel$h, 15215 dismissButton: dismissButton$h, 15216 windowButtonLabel: windowButtonLabel$h, 15217 windowMove: windowMove$h, 15218 windowMoveLeft: windowMoveLeft$h, 15219 windowMoveRight: windowMoveRight$h, 15220 windowMoveUp: windowMoveUp$h, 15221 windowMoveDown: windowMoveDown$h, 15222 windowMoveStopped: windowMoveStopped$h, 15223 transcriptControls: transcriptControls$h, 15224 signControls: signControls$h, 15225 windowMoveAlert: windowMoveAlert$h, 15226 windowResize: windowResize$h, 15227 windowResizeHeading: windowResizeHeading$h, 15228 closeButtonLabel: closeButtonLabel$h, 15229 width: width$h, 15230 height: height$h, 15231 resultsSummary1: resultsSummary1$h, 15232 resultsSummary2: resultsSummary2$h, 15233 resultsSummary3: resultsSummary3$h, 15234 noResultsFound: noResultsFound$h, 15235 searchButtonLabel: searchButtonLabel$h, 15236 hour: hour$h, 15237 minute: minute$h, 15238 second: second$h, 15239 hours: hours$h, 15240 minutes: minutes$h, 15241 seconds: seconds$h, 15242 vtsHeading: vtsHeading$h, 15243 vtsInstructions1: vtsInstructions1$h, 15244 vtsInstructions2: vtsInstructions2$h, 15245 vtsInstructions3: vtsInstructions3$h, 15246 vtsInstructions4: vtsInstructions4$h, 15247 vtsInstructions5: vtsInstructions5$h, 15248 vtsSelectLanguage: vtsSelectLanguage$h, 15249 vtsSave: vtsSave$h, 15250 vtsReturn: vtsReturn$h, 15251 vtsCancel: vtsCancel$h, 15252 vtsRow: vtsRow$h, 15253 vtsKind: vtsKind$h, 15254 vtsStart: vtsStart$h, 15255 vtsEnd: vtsEnd$h, 15256 vtsContent: vtsContent$h, 15257 vtsActions: vtsActions$h, 15258 vtsNewRow: vtsNewRow$h, 15259 vtsDeletedRow: vtsDeletedRow$h, 15260 vtsMovedRow: vtsMovedRow$h 15261 }; 15262 15263 var playerHeading$g = "Media player"; 15264 var audioPlayer$g = "Audio player"; 15265 var videoPlayer$g = "Video player"; 15266 var faster$g = "Faster"; 15267 var slower$g = "Slower"; 15268 var play$g = "Play"; 15269 var pause$g = "Pause"; 15270 var restart$g = "Restart"; 15271 var prevTrack$g = "Previous track"; 15272 var nextTrack$g = "Next track"; 15273 var rewind$g = "Rewind"; 15274 var forward$g = "Forward"; 15275 var captions$g = "Captions"; 15276 var showCaptions$g = "Show captions"; 15277 var hideCaptions$g = "Hide captions"; 15278 var captionsOff$g = "Captions off"; 15279 var showTranscript$g = "Show transcript"; 15280 var hideTranscript$g = "Hide transcript"; 15281 var turnOnDescriptions$g = "Turn on descriptions"; 15282 var turnOffDescriptions$g = "Turn off descriptions"; 15283 var chapters$g = "Chapters"; 15284 var language$g = "Language"; 15285 var sign$g = "Sign language"; 15286 var showSign$g = "Show sign language"; 15287 var hideSign$g = "Hide sign language"; 15288 var seekbarLabel$g = "timeline"; 15289 var mute$g = "Mute"; 15290 var unmute$g = "Unmute"; 15291 var volume$g = "Volume"; 15292 var volumeUpDown$g = "Volume up down"; 15293 var preferences$g = "Preferences"; 15294 var enterFullScreen$g = "Enter full screen"; 15295 var exitFullScreen$g = "Exit full screen"; 15296 var speed$g = "Speed"; 15297 var spacebar$g = "spacebar"; 15298 var transcriptTitle$g = "Transcript"; 15299 var lyricsTitle$g = "Lyrics"; 15300 var autoScroll$g = "Auto scroll"; 15301 var statusPlaying$g = "Playing"; 15302 var statusPaused$g = "Paused"; 15303 var statusStopped$g = "Stopped"; 15304 var statusBuffering$g = "Buffering"; 15305 var statusEnd$g = "End of track"; 15306 var selectedTrack$f = "Selected Track"; 15307 var alertDescribedVersion$g = "Using the audio described version of this video"; 15308 var alertNonDescribedVersion$g = "Using the non-described version of this video"; 15309 var prefMenuCaptions$g = "Captions"; 15310 var prefVoicedCaptions$g = "Spoken Captions"; 15311 var prefMenuDescriptions$g = "Descriptions"; 15312 var prefMenuKeyboard$g = "Keyboard"; 15313 var prefMenuTranscript$g = "Transcript"; 15314 var prefTitleCaptions$g = "Captions Preferences"; 15315 var prefTitleDescriptions$g = "Audio Description Preferences"; 15316 var prefTitleKeyboard$g = "Keyboard Preferences"; 15317 var prefTitleTranscript$f = "Transcript Preferences"; 15318 var prefIntroDescription1$f = "This media player supports audio description in two ways: "; 15319 var prefDescription1$g = "The current video has an alternative described version and text-based description, announced by screen reader."; 15320 var prefDescription2$g = "The current video has text-based description."; 15321 var prefDescription3$g = "The current video has an alternative described version."; 15322 var prefIntroDescriptionNone$g = "The current video has no audio description in either format."; 15323 var prefIntroDescription3$g = "Use the following form to set your preferences related to text-based audio description."; 15324 var prefIntroDescription4$g = "After you save your settings, audio description can be toggled on/off using the Description button."; 15325 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)."; 15326 var prefIntroKeyboard2$g = "Modifier keys (Shift, Alt, and Control) can be assigned below."; 15327 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."; 15328 var prefHeadingKeyboard1$g = "Modifier keys used for shortcuts"; 15329 var prefHeadingKeyboard2$g = "Current keyboard shortcuts"; 15330 var prefHeadingDescription$g = "Audio description"; 15331 var prefHeadingTextDescription$g = "Text-based audio description"; 15332 var prefAltKey$g = "Alt"; 15333 var prefCtrlKey$g = "Control"; 15334 var prefShiftKey$g = "Shift"; 15335 var prefNoKeyShortcuts$g = "Disable keyboard shortcuts"; 15336 var escapeKey$g = "Escape"; 15337 var escapeKeyFunction$g = "Close current dialog or popup menu"; 15338 var prefDescPause$g = "Automatically pause video when description starts"; 15339 var prefDescVisible$g = "Make description visible"; 15340 var prefDescVoice$g = "Voice"; 15341 var prefDescRate$g = "Spoken Description Rate"; 15342 var prefCaptionRate$g = "Spoken Caption Rate"; 15343 var prefDescPitch$g = "Pitch"; 15344 var prefDescPitch1$g = "Very low"; 15345 var prefDescPitch2$g = "Low"; 15346 var prefDescPitch3$g = "Default"; 15347 var prefDescPitch4$g = "High"; 15348 var prefDescPitch5$g = "Very high"; 15349 var sampleDescriptionText$g = "Adjust settings to hear this sample text."; 15350 var prefHighlight$g = "Highlight transcript as media plays"; 15351 var prefTabbable$g = "Keyboard-enable transcript"; 15352 var prefCaptionsFont$g = "Font"; 15353 var prefCaptionsColor$g = "Text Color"; 15354 var prefCaptionsBGColor$g = "Background"; 15355 var prefCaptionsSize$g = "Font Size"; 15356 var prefCaptionsOpacity$g = "Opacity"; 15357 var prefCaptionsStyle$g = "Style"; 15358 var serif$g = "serif"; 15359 var sans$g = "sans-serif"; 15360 var cursive$g = "cursive"; 15361 var fantasy$g = "fantasy"; 15362 var monospace$g = "monospace"; 15363 var white$g = "white"; 15364 var yellow$g = "yellow"; 15365 var green$g = "green"; 15366 var cyan$f = "cyan"; 15367 var blue$g = "blue"; 15368 var magenta$g = "magenta"; 15369 var red$g = "red"; 15370 var black$g = "black"; 15371 var transparent$g = "transparent"; 15372 var solid$g = "solid"; 15373 var captionsStylePopOn$g = "Pop-on"; 15374 var captionsStyleRollUp$g = "Roll-up"; 15375 var prefCaptionsPosition$g = "Position"; 15376 var captionsPositionOverlay$g = "Overlay"; 15377 var captionsPositionBelow$g = "Below video"; 15378 var sampleCaptionText$g = "Sample caption text"; 15379 var prefSuccess$g = "Your changes have been saved."; 15380 var prefNoChange$g = "You didn't make any changes."; 15381 var save$g = "Save"; 15382 var cancel$g = "Cancel"; 15383 var dismissButton$g = "Dismiss"; 15384 var windowButtonLabel$g = "Window options"; 15385 var windowMove$g = "Move"; 15386 var windowMoveLeft$g = "Window moved left"; 15387 var windowMoveRight$g = "Window moved right"; 15388 var windowMoveUp$g = "Window moved up"; 15389 var windowMoveDown$g = "Window moved down"; 15390 var windowMoveStopped$g = "Window move stopped"; 15391 var transcriptControls$g = "Transcript Window Controls"; 15392 var signControls$g = "Sign Language Window Controls"; 15393 var windowMoveAlert$g = "Drag or use arrow keys to move the window; Enter to stop"; 15394 var windowResize$g = "Resize"; 15395 var windowResizeHeading$g = "Resize Window"; 15396 var closeButtonLabel$g = "Close"; 15397 var width$g = "Width"; 15398 var height$g = "Height"; 15399 var resultsSummary1$g = "You searched for:"; 15400 var resultsSummary2$g = "Found %1 matching items."; 15401 var resultsSummary3$g = "Click the time associated with any item to play the video from that point."; 15402 var noResultsFound$g = "No results found."; 15403 var searchButtonLabel$g = "Play at %1"; 15404 var hour$g = "hour"; 15405 var minute$g = "minute"; 15406 var second$g = "second"; 15407 var hours$g = "hours"; 15408 var minutes$g = "minutes"; 15409 var seconds$g = "seconds"; 15410 var vtsHeading$g = "Video Transcript Sorter"; 15411 var vtsInstructions1$g = "Use the Video Transcript Sorter to modify text tracks:"; 15412 var vtsInstructions2$g = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 15413 var vtsInstructions3$g = "Modify content or start/end times (all are directly editable within the table)."; 15414 var vtsInstructions4$g = "Add new content, such as chapters or descriptions."; 15415 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."; 15416 var vtsSelectLanguage$g = "Select a language"; 15417 var vtsSave$g = "Generate new .vtt content"; 15418 var vtsReturn$g = "Return to Editor"; 15419 var vtsCancel$g = "Cancelling saving. Any edits you made have been restored in the VTS table."; 15420 var vtsRow$g = "Row"; 15421 var vtsKind$g = "Kind"; 15422 var vtsStart$g = "Start"; 15423 var vtsEnd$g = "End"; 15424 var vtsContent$g = "Content"; 15425 var vtsActions$g = "Actions"; 15426 var vtsNewRow$g = "A new row %1 has been inserted."; 15427 var vtsDeletedRow$g = "Row %1 has been deleted."; 15428 var vtsMovedRow$g = "Row %1 has been moved %2 and is now Row %3."; 15429 var en = { 15430 playerHeading: playerHeading$g, 15431 audioPlayer: audioPlayer$g, 15432 videoPlayer: videoPlayer$g, 15433 faster: faster$g, 15434 slower: slower$g, 15435 play: play$g, 15436 pause: pause$g, 15437 restart: restart$g, 15438 prevTrack: prevTrack$g, 15439 nextTrack: nextTrack$g, 15440 rewind: rewind$g, 15441 forward: forward$g, 15442 captions: captions$g, 15443 showCaptions: showCaptions$g, 15444 hideCaptions: hideCaptions$g, 15445 captionsOff: captionsOff$g, 15446 showTranscript: showTranscript$g, 15447 hideTranscript: hideTranscript$g, 15448 turnOnDescriptions: turnOnDescriptions$g, 15449 turnOffDescriptions: turnOffDescriptions$g, 15450 chapters: chapters$g, 15451 language: language$g, 15452 sign: sign$g, 15453 showSign: showSign$g, 15454 hideSign: hideSign$g, 15455 seekbarLabel: seekbarLabel$g, 15456 mute: mute$g, 15457 unmute: unmute$g, 15458 volume: volume$g, 15459 volumeUpDown: volumeUpDown$g, 15460 preferences: preferences$g, 15461 enterFullScreen: enterFullScreen$g, 15462 exitFullScreen: exitFullScreen$g, 15463 speed: speed$g, 15464 spacebar: spacebar$g, 15465 transcriptTitle: transcriptTitle$g, 15466 lyricsTitle: lyricsTitle$g, 15467 autoScroll: autoScroll$g, 15468 statusPlaying: statusPlaying$g, 15469 statusPaused: statusPaused$g, 15470 statusStopped: statusStopped$g, 15471 statusBuffering: statusBuffering$g, 15472 statusEnd: statusEnd$g, 15473 selectedTrack: selectedTrack$f, 15474 alertDescribedVersion: alertDescribedVersion$g, 15475 alertNonDescribedVersion: alertNonDescribedVersion$g, 15476 prefMenuCaptions: prefMenuCaptions$g, 15477 prefVoicedCaptions: prefVoicedCaptions$g, 15478 prefMenuDescriptions: prefMenuDescriptions$g, 15479 prefMenuKeyboard: prefMenuKeyboard$g, 15480 prefMenuTranscript: prefMenuTranscript$g, 15481 prefTitleCaptions: prefTitleCaptions$g, 15482 prefTitleDescriptions: prefTitleDescriptions$g, 15483 prefTitleKeyboard: prefTitleKeyboard$g, 15484 prefTitleTranscript: prefTitleTranscript$f, 15485 prefIntroDescription1: prefIntroDescription1$f, 15486 prefDescription1: prefDescription1$g, 15487 prefDescription2: prefDescription2$g, 15488 prefDescription3: prefDescription3$g, 15489 prefIntroDescriptionNone: prefIntroDescriptionNone$g, 15490 prefIntroDescription3: prefIntroDescription3$g, 15491 prefIntroDescription4: prefIntroDescription4$g, 15492 prefIntroKeyboard1: prefIntroKeyboard1$g, 15493 prefIntroKeyboard2: prefIntroKeyboard2$g, 15494 prefIntroKeyboard3: prefIntroKeyboard3$g, 15495 prefHeadingKeyboard1: prefHeadingKeyboard1$g, 15496 prefHeadingKeyboard2: prefHeadingKeyboard2$g, 15497 prefHeadingDescription: prefHeadingDescription$g, 15498 prefHeadingTextDescription: prefHeadingTextDescription$g, 15499 prefAltKey: prefAltKey$g, 15500 prefCtrlKey: prefCtrlKey$g, 15501 prefShiftKey: prefShiftKey$g, 15502 prefNoKeyShortcuts: prefNoKeyShortcuts$g, 15503 escapeKey: escapeKey$g, 15504 escapeKeyFunction: escapeKeyFunction$g, 15505 prefDescPause: prefDescPause$g, 15506 prefDescVisible: prefDescVisible$g, 15507 prefDescVoice: prefDescVoice$g, 15508 prefDescRate: prefDescRate$g, 15509 prefCaptionRate: prefCaptionRate$g, 15510 prefDescPitch: prefDescPitch$g, 15511 prefDescPitch1: prefDescPitch1$g, 15512 prefDescPitch2: prefDescPitch2$g, 15513 prefDescPitch3: prefDescPitch3$g, 15514 prefDescPitch4: prefDescPitch4$g, 15515 prefDescPitch5: prefDescPitch5$g, 15516 sampleDescriptionText: sampleDescriptionText$g, 15517 prefHighlight: prefHighlight$g, 15518 prefTabbable: prefTabbable$g, 15519 prefCaptionsFont: prefCaptionsFont$g, 15520 prefCaptionsColor: prefCaptionsColor$g, 15521 prefCaptionsBGColor: prefCaptionsBGColor$g, 15522 prefCaptionsSize: prefCaptionsSize$g, 15523 prefCaptionsOpacity: prefCaptionsOpacity$g, 15524 prefCaptionsStyle: prefCaptionsStyle$g, 15525 serif: serif$g, 15526 sans: sans$g, 15527 cursive: cursive$g, 15528 fantasy: fantasy$g, 15529 monospace: monospace$g, 15530 white: white$g, 15531 yellow: yellow$g, 15532 green: green$g, 15533 cyan: cyan$f, 15534 blue: blue$g, 15535 magenta: magenta$g, 15536 red: red$g, 15537 black: black$g, 15538 transparent: transparent$g, 15539 solid: solid$g, 15540 captionsStylePopOn: captionsStylePopOn$g, 15541 captionsStyleRollUp: captionsStyleRollUp$g, 15542 prefCaptionsPosition: prefCaptionsPosition$g, 15543 captionsPositionOverlay: captionsPositionOverlay$g, 15544 captionsPositionBelow: captionsPositionBelow$g, 15545 sampleCaptionText: sampleCaptionText$g, 15546 prefSuccess: prefSuccess$g, 15547 prefNoChange: prefNoChange$g, 15548 save: save$g, 15549 cancel: cancel$g, 15550 dismissButton: dismissButton$g, 15551 windowButtonLabel: windowButtonLabel$g, 15552 windowMove: windowMove$g, 15553 windowMoveLeft: windowMoveLeft$g, 15554 windowMoveRight: windowMoveRight$g, 15555 windowMoveUp: windowMoveUp$g, 15556 windowMoveDown: windowMoveDown$g, 15557 windowMoveStopped: windowMoveStopped$g, 15558 transcriptControls: transcriptControls$g, 15559 signControls: signControls$g, 15560 windowMoveAlert: windowMoveAlert$g, 15561 windowResize: windowResize$g, 15562 windowResizeHeading: windowResizeHeading$g, 15563 closeButtonLabel: closeButtonLabel$g, 15564 width: width$g, 15565 height: height$g, 15566 resultsSummary1: resultsSummary1$g, 15567 resultsSummary2: resultsSummary2$g, 15568 resultsSummary3: resultsSummary3$g, 15569 noResultsFound: noResultsFound$g, 15570 searchButtonLabel: searchButtonLabel$g, 15571 hour: hour$g, 15572 minute: minute$g, 15573 second: second$g, 15574 hours: hours$g, 15575 minutes: minutes$g, 15576 seconds: seconds$g, 15577 vtsHeading: vtsHeading$g, 15578 vtsInstructions1: vtsInstructions1$g, 15579 vtsInstructions2: vtsInstructions2$g, 15580 vtsInstructions3: vtsInstructions3$g, 15581 vtsInstructions4: vtsInstructions4$g, 15582 vtsInstructions5: vtsInstructions5$g, 15583 vtsSelectLanguage: vtsSelectLanguage$g, 15584 vtsSave: vtsSave$g, 15585 vtsReturn: vtsReturn$g, 15586 vtsCancel: vtsCancel$g, 15587 vtsRow: vtsRow$g, 15588 vtsKind: vtsKind$g, 15589 vtsStart: vtsStart$g, 15590 vtsEnd: vtsEnd$g, 15591 vtsContent: vtsContent$g, 15592 vtsActions: vtsActions$g, 15593 vtsNewRow: vtsNewRow$g, 15594 vtsDeletedRow: vtsDeletedRow$g, 15595 vtsMovedRow: vtsMovedRow$g 15596 }; 15597 15598 var playerHeading$f = "Media player"; 15599 var audioPlayer$f = "Audio player"; 15600 var videoPlayer$f = "Video player"; 15601 var faster$f = "Rápido"; 15602 var slower$f = "Lento"; 15603 var play$f = "Play"; 15604 var pause$f = "Pausa"; 15605 var restart$f = "Reiniciar"; 15606 var prevTrack$f = "Pista Anterior"; 15607 var nextTrack$f = "Siguiente Pista"; 15608 var rewind$f = "Rebobinar"; 15609 var forward$f = "Adelantar"; 15610 var captions$f = "SubtÃtulos"; 15611 var showCaptions$f = "Mostrar subtÃtulos"; 15612 var hideCaptions$f = "Ocultar subtÃtulos"; 15613 var captionsOff$f = "Sin subtÃtulos"; 15614 var showTranscript$f = "Mostrar transcripción"; 15615 var hideTranscript$f = "Ocultar transcripción"; 15616 var turnOnDescriptions$f = "Habilitar descripciones"; 15617 var turnOffDescriptions$f = "Deshabilitar descripciones"; 15618 var chapters$f = "CapÃtulos"; 15619 var language$f = "Idioma"; 15620 var sign$f = "Lengua de señas"; 15621 var showSign$f = "Mostrar lengua de señas"; 15622 var hideSign$f = "Ocultar lengua de señas"; 15623 var seekbarLabel$f = "timeline"; 15624 var mute$f = "Silenciar"; 15625 var unmute$f = "Habilitar sonido"; 15626 var volume$f = "Volumen"; 15627 var volumeUpDown$f = "Bajar sonido"; 15628 var preferences$f = "Preferencias"; 15629 var enterFullScreen$f = "Ver a pantalla completa"; 15630 var exitFullScreen$f = "Salir de pantalla completa"; 15631 var speed$f = "Velocidad"; 15632 var spacebar$f = "Barra espaciadora"; 15633 var transcriptTitle$f = "Transcript"; 15634 var lyricsTitle$f = "Letra"; 15635 var autoScroll$f = "Desplazamiento automático"; 15636 var statusPlaying$f = "Reproduciendo"; 15637 var statusPaused$f = "Pausado"; 15638 var statusStopped$f = "Detenido"; 15639 var statusBuffering$f = "Almacenando"; 15640 var statusEnd$f = "Fin de pista"; 15641 var selectedTrack$e = "Pista seleccionada"; 15642 var alertDescribedVersion$f = "Utilizando la versión audiodescrita del vÃdeo"; 15643 var alertNonDescribedVersion$f = "Utilizando la versión no descrita de este vÃdeo"; 15644 var prefMenuCaptions$f = "SubtÃtulos"; 15645 var prefVoicedCaptions$f = "Spoken Captions"; 15646 var prefMenuDescriptions$f = "Descripciones"; 15647 var prefMenuKeyboard$f = "Teclado"; 15648 var prefMenuTranscript$f = "Transcripción"; 15649 var prefTitleCaptions$f = "Preferencias de subtÃtulos"; 15650 var prefTitleDescriptions$f = "Preferencias de audiodescripción"; 15651 var prefTitleKeyboard$f = "Preferencias de teclado"; 15652 var prefTitleTranscript$e = "Preferencias de transcripción"; 15653 var prefIntroDescription1$e = "Este reproductor soporta la audiodescripción de dos maneras: "; 15654 var prefDescription1$f = "El vÃdeo actual tiene una versión alternativa con descripción, descripción en texto."; 15655 var prefDescription2$f = "El vÃdeo actual tiene versión alternativa del vÃdeo, descrito."; 15656 var prefDescription3$f = "El vÃdeo actual tiene descripción en texto, leÃda por el lector de pantalla."; 15657 var prefIntroDescriptionNone$f = "El vÃdeo actual no tiene audiodescripción de ninguna manera."; 15658 var prefIntroDescription3$f = "Utilice el siguiente formulario para establecer sus preferencias en cuanto a la audiodescripción en texto."; 15659 var prefIntroDescription4$f = "Una vez guardadas sus preferencias, la audiodescripción puede habilitarse o deshabilitarse mediante el botón Descripción."; 15660 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)."; 15661 var prefIntroKeyboard2$f = "Las teclas modificadoras (Mayúsculas, Alt, Control) pueden definirse más abajo."; 15662 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."; 15663 var prefHeadingKeyboard1$f = "Teclas modificadoras utilizadas para atajos de teclado"; 15664 var prefHeadingKeyboard2$f = "Atajos de teclado definidos actualmente"; 15665 var prefHeadingDescription$f = "Audiodescrita"; 15666 var prefHeadingTextDescription$f = "Audiodescrita en texto"; 15667 var prefAltKey$f = "Alt"; 15668 var prefCtrlKey$f = "Control"; 15669 var prefShiftKey$f = "Mayúsculas"; 15670 var prefNoKeyShortcuts$f = "Disable keyboard shortcuts"; 15671 var escapeKey$f = "Escape"; 15672 var escapeKeyFunction$f = "Cerrar el cuadro de diálogo o menú contextual"; 15673 var prefDescPause$f = "Pausar automáticamente el video cuando arranque una descripción"; 15674 var prefDescVisible$f = "Hacer visibles las descripciones en texto si se están usando"; 15675 var prefDescVoice$f = "Voz"; 15676 var prefDescRate$f = "Spoken Description Rate"; 15677 var prefCaptionRate$f = "Spoken Caption Rate"; 15678 var prefDescPitch$f = "Tono"; 15679 var prefDescPitch1$f = "Muy grave"; 15680 var prefDescPitch2$f = "Grave"; 15681 var prefDescPitch3$f = "Normal"; 15682 var prefDescPitch4$f = "Aguda"; 15683 var prefDescPitch5$f = "Muy aguda"; 15684 var sampleDescriptionText$f = "Ajuste la configuración para escuchar este texto de muestra"; 15685 var prefHighlight$f = "Resalte la transcripción a medida que avance el contenido"; 15686 var prefTabbable$f = "Transcripción manejable por teclado"; 15687 var prefCaptionsFont$f = "Fuente"; 15688 var prefCaptionsColor$f = "Color del texto"; 15689 var prefCaptionsBGColor$f = "Fondo"; 15690 var prefCaptionsSize$f = "Tamaño de Fuente"; 15691 var prefCaptionsOpacity$f = "Opacidad"; 15692 var prefCaptionsStyle$f = "Estilo"; 15693 var serif$f = "serif"; 15694 var sans$f = "sans-serif"; 15695 var cursive$f = "cursiva"; 15696 var fantasy$f = "fantasÃa"; 15697 var monospace$f = "mono espaciada"; 15698 var white$f = "blanco"; 15699 var yellow$f = "amarillo"; 15700 var green$f = "verde"; 15701 var cyan$e = "cyan"; 15702 var blue$f = "azul"; 15703 var magenta$f = "magenta"; 15704 var red$f = "rojo"; 15705 var black$f = "negro"; 15706 var transparent$f = "transparente"; 15707 var solid$f = "sólido"; 15708 var captionsStylePopOn$f = "Pop-on"; 15709 var captionsStyleRollUp$f = "Roll-up"; 15710 var prefCaptionsPosition$f = "Posición"; 15711 var captionsPositionOverlay$f = "Cubrir"; 15712 var captionsPositionBelow$f = "Debajo del vÃdeo"; 15713 var sampleCaptionText$f = "Texto de ejemplo de subtÃtulo"; 15714 var prefSuccess$f = "Los cambios han sido guardados."; 15715 var prefNoChange$f = "No se ha hecho ningún cambio."; 15716 var save$f = "Guardar"; 15717 var cancel$f = "Cancelar"; 15718 var dismissButton$f = "Dismiss"; 15719 var windowButtonLabel$f = "Opciones en Windows"; 15720 var windowMove$f = "Mover"; 15721 var windowMoveLeft$f = "Window moved left"; 15722 var windowMoveRight$f = "Window moved right"; 15723 var windowMoveUp$f = "Window moved up"; 15724 var windowMoveDown$f = "Window moved down"; 15725 var windowMoveStopped$f = "Window move stopped"; 15726 var transcriptControls$f = "Transcript Window Controls"; 15727 var signControls$f = "Sign Language Window Controls"; 15728 var windowMoveAlert$f = "Arrastre o use las teclas de flecha para mover la ventana, pulse Enter para parar."; 15729 var windowResize$f = "Redimensionar"; 15730 var windowResizeHeading$f = "Redimensionar la ventana con el intérprete"; 15731 var closeButtonLabel$f = "Cerrar"; 15732 var width$f = "Ancho"; 15733 var height$f = "Alto"; 15734 var resultsSummary1$f = "You searched for:"; 15735 var resultsSummary2$f = "Found %1 matching items."; 15736 var resultsSummary3$f = "Click the time associated with any item to play the video from that point."; 15737 var noResultsFound$f = "No results found."; 15738 var searchButtonLabel$f = "Play at %1"; 15739 var hour$f = "hour"; 15740 var minute$f = "minute"; 15741 var second$f = "second"; 15742 var hours$f = "hours"; 15743 var minutes$f = "minutes"; 15744 var seconds$f = "seconds"; 15745 var vtsHeading$f = "Video Transcript Sorter"; 15746 var vtsInstructions1$f = "Use the Video Transcript Sorter to modify text tracks:"; 15747 var vtsInstructions2$f = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 15748 var vtsInstructions3$f = "Modify content or start/end times (all are directly editable within the table)."; 15749 var vtsInstructions4$f = "Add new content, such as chapters or descriptions."; 15750 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."; 15751 var vtsSelectLanguage$f = "Select a language"; 15752 var vtsSave$f = "Generate new .vtt content"; 15753 var vtsReturn$f = "Return to Editor"; 15754 var vtsCancel$f = "Cancelling saving. Any edits you made have been restored in the VTS table."; 15755 var vtsRow$f = "Row"; 15756 var vtsKind$f = "Kind"; 15757 var vtsStart$f = "Start"; 15758 var vtsEnd$f = "End"; 15759 var vtsContent$f = "Content"; 15760 var vtsActions$f = "Actions"; 15761 var vtsNewRow$f = "A new row %1 has been inserted."; 15762 var vtsDeletedRow$f = "Row %1 has been deleted."; 15763 var vtsMovedRow$f = "Row %1 has been moved %2 and is now Row %3."; 15764 var es = { 15765 playerHeading: playerHeading$f, 15766 audioPlayer: audioPlayer$f, 15767 videoPlayer: videoPlayer$f, 15768 faster: faster$f, 15769 slower: slower$f, 15770 play: play$f, 15771 pause: pause$f, 15772 restart: restart$f, 15773 prevTrack: prevTrack$f, 15774 nextTrack: nextTrack$f, 15775 rewind: rewind$f, 15776 forward: forward$f, 15777 captions: captions$f, 15778 showCaptions: showCaptions$f, 15779 hideCaptions: hideCaptions$f, 15780 captionsOff: captionsOff$f, 15781 showTranscript: showTranscript$f, 15782 hideTranscript: hideTranscript$f, 15783 turnOnDescriptions: turnOnDescriptions$f, 15784 turnOffDescriptions: turnOffDescriptions$f, 15785 chapters: chapters$f, 15786 language: language$f, 15787 sign: sign$f, 15788 showSign: showSign$f, 15789 hideSign: hideSign$f, 15790 seekbarLabel: seekbarLabel$f, 15791 mute: mute$f, 15792 unmute: unmute$f, 15793 volume: volume$f, 15794 volumeUpDown: volumeUpDown$f, 15795 preferences: preferences$f, 15796 enterFullScreen: enterFullScreen$f, 15797 exitFullScreen: exitFullScreen$f, 15798 speed: speed$f, 15799 spacebar: spacebar$f, 15800 transcriptTitle: transcriptTitle$f, 15801 lyricsTitle: lyricsTitle$f, 15802 autoScroll: autoScroll$f, 15803 statusPlaying: statusPlaying$f, 15804 statusPaused: statusPaused$f, 15805 statusStopped: statusStopped$f, 15806 statusBuffering: statusBuffering$f, 15807 statusEnd: statusEnd$f, 15808 selectedTrack: selectedTrack$e, 15809 alertDescribedVersion: alertDescribedVersion$f, 15810 alertNonDescribedVersion: alertNonDescribedVersion$f, 15811 prefMenuCaptions: prefMenuCaptions$f, 15812 prefVoicedCaptions: prefVoicedCaptions$f, 15813 prefMenuDescriptions: prefMenuDescriptions$f, 15814 prefMenuKeyboard: prefMenuKeyboard$f, 15815 prefMenuTranscript: prefMenuTranscript$f, 15816 prefTitleCaptions: prefTitleCaptions$f, 15817 prefTitleDescriptions: prefTitleDescriptions$f, 15818 prefTitleKeyboard: prefTitleKeyboard$f, 15819 prefTitleTranscript: prefTitleTranscript$e, 15820 prefIntroDescription1: prefIntroDescription1$e, 15821 prefDescription1: prefDescription1$f, 15822 prefDescription2: prefDescription2$f, 15823 prefDescription3: prefDescription3$f, 15824 prefIntroDescriptionNone: prefIntroDescriptionNone$f, 15825 prefIntroDescription3: prefIntroDescription3$f, 15826 prefIntroDescription4: prefIntroDescription4$f, 15827 prefIntroKeyboard1: prefIntroKeyboard1$f, 15828 prefIntroKeyboard2: prefIntroKeyboard2$f, 15829 prefIntroKeyboard3: prefIntroKeyboard3$f, 15830 prefHeadingKeyboard1: prefHeadingKeyboard1$f, 15831 prefHeadingKeyboard2: prefHeadingKeyboard2$f, 15832 prefHeadingDescription: prefHeadingDescription$f, 15833 prefHeadingTextDescription: prefHeadingTextDescription$f, 15834 prefAltKey: prefAltKey$f, 15835 prefCtrlKey: prefCtrlKey$f, 15836 prefShiftKey: prefShiftKey$f, 15837 prefNoKeyShortcuts: prefNoKeyShortcuts$f, 15838 escapeKey: escapeKey$f, 15839 escapeKeyFunction: escapeKeyFunction$f, 15840 prefDescPause: prefDescPause$f, 15841 prefDescVisible: prefDescVisible$f, 15842 prefDescVoice: prefDescVoice$f, 15843 prefDescRate: prefDescRate$f, 15844 prefCaptionRate: prefCaptionRate$f, 15845 prefDescPitch: prefDescPitch$f, 15846 prefDescPitch1: prefDescPitch1$f, 15847 prefDescPitch2: prefDescPitch2$f, 15848 prefDescPitch3: prefDescPitch3$f, 15849 prefDescPitch4: prefDescPitch4$f, 15850 prefDescPitch5: prefDescPitch5$f, 15851 sampleDescriptionText: sampleDescriptionText$f, 15852 prefHighlight: prefHighlight$f, 15853 prefTabbable: prefTabbable$f, 15854 prefCaptionsFont: prefCaptionsFont$f, 15855 prefCaptionsColor: prefCaptionsColor$f, 15856 prefCaptionsBGColor: prefCaptionsBGColor$f, 15857 prefCaptionsSize: prefCaptionsSize$f, 15858 prefCaptionsOpacity: prefCaptionsOpacity$f, 15859 prefCaptionsStyle: prefCaptionsStyle$f, 15860 serif: serif$f, 15861 sans: sans$f, 15862 cursive: cursive$f, 15863 fantasy: fantasy$f, 15864 monospace: monospace$f, 15865 white: white$f, 15866 yellow: yellow$f, 15867 green: green$f, 15868 cyan: cyan$e, 15869 blue: blue$f, 15870 magenta: magenta$f, 15871 red: red$f, 15872 black: black$f, 15873 transparent: transparent$f, 15874 solid: solid$f, 15875 captionsStylePopOn: captionsStylePopOn$f, 15876 captionsStyleRollUp: captionsStyleRollUp$f, 15877 prefCaptionsPosition: prefCaptionsPosition$f, 15878 captionsPositionOverlay: captionsPositionOverlay$f, 15879 captionsPositionBelow: captionsPositionBelow$f, 15880 sampleCaptionText: sampleCaptionText$f, 15881 prefSuccess: prefSuccess$f, 15882 prefNoChange: prefNoChange$f, 15883 save: save$f, 15884 cancel: cancel$f, 15885 dismissButton: dismissButton$f, 15886 windowButtonLabel: windowButtonLabel$f, 15887 windowMove: windowMove$f, 15888 windowMoveLeft: windowMoveLeft$f, 15889 windowMoveRight: windowMoveRight$f, 15890 windowMoveUp: windowMoveUp$f, 15891 windowMoveDown: windowMoveDown$f, 15892 windowMoveStopped: windowMoveStopped$f, 15893 transcriptControls: transcriptControls$f, 15894 signControls: signControls$f, 15895 windowMoveAlert: windowMoveAlert$f, 15896 windowResize: windowResize$f, 15897 windowResizeHeading: windowResizeHeading$f, 15898 closeButtonLabel: closeButtonLabel$f, 15899 width: width$f, 15900 height: height$f, 15901 resultsSummary1: resultsSummary1$f, 15902 resultsSummary2: resultsSummary2$f, 15903 resultsSummary3: resultsSummary3$f, 15904 noResultsFound: noResultsFound$f, 15905 searchButtonLabel: searchButtonLabel$f, 15906 hour: hour$f, 15907 minute: minute$f, 15908 second: second$f, 15909 hours: hours$f, 15910 minutes: minutes$f, 15911 seconds: seconds$f, 15912 vtsHeading: vtsHeading$f, 15913 vtsInstructions1: vtsInstructions1$f, 15914 vtsInstructions2: vtsInstructions2$f, 15915 vtsInstructions3: vtsInstructions3$f, 15916 vtsInstructions4: vtsInstructions4$f, 15917 vtsInstructions5: vtsInstructions5$f, 15918 vtsSelectLanguage: vtsSelectLanguage$f, 15919 vtsSave: vtsSave$f, 15920 vtsReturn: vtsReturn$f, 15921 vtsCancel: vtsCancel$f, 15922 vtsRow: vtsRow$f, 15923 vtsKind: vtsKind$f, 15924 vtsStart: vtsStart$f, 15925 vtsEnd: vtsEnd$f, 15926 vtsContent: vtsContent$f, 15927 vtsActions: vtsActions$f, 15928 vtsNewRow: vtsNewRow$f, 15929 vtsDeletedRow: vtsDeletedRow$f, 15930 vtsMovedRow: vtsMovedRow$f 15931 }; 15932 15933 var playerHeading$e = "Lecteur multimédia"; 15934 var audioPlayer$e = "Audio player"; 15935 var videoPlayer$e = "Video player"; 15936 var faster$e = "Plus rapidement"; 15937 var slower$e = "Plus lentement"; 15938 var play$e = "Lecture"; 15939 var pause$e = "Pause"; 15940 var restart$e = "Redémarrer"; 15941 var prevTrack$e = "Piste Précédente"; 15942 var nextTrack$e = "Piste Suivante"; 15943 var rewind$e = "Reculer"; 15944 var forward$e = "Avancer"; 15945 var captions$e = "Sous-titres"; 15946 var showCaptions$e = "Afficher les sous-titres"; 15947 var hideCaptions$e = "Masquer les sous-titres"; 15948 var captionsOff$e = "Sous-titres désactivés"; 15949 var showTranscript$e = "Afficher la transcription"; 15950 var hideTranscript$e = "Masquer la transcription"; 15951 var turnOnDescriptions$e = "Activer les descriptions"; 15952 var turnOffDescriptions$e = "Désactiver les descriptions"; 15953 var chapters$e = "Chapitres"; 15954 var language$e = "Langue"; 15955 var sign$e = "Langage gestuel"; 15956 var showSign$e = "Afficher le langage gestuel"; 15957 var hideSign$e = "Masque le langage gestuel"; 15958 var seekbarLabel$e = "timeline"; 15959 var mute$e = "Son désactivé"; 15960 var unmute$e = "Son activé"; 15961 var volume$e = "Volume"; 15962 var volumeUpDown$e = "Monter baisser le volume"; 15963 var preferences$e = "Préférences"; 15964 var enterFullScreen$e = "Activer le mode plein écran"; 15965 var exitFullScreen$e = "Quitter le mode plein écran"; 15966 var speed$e = "Vitesse"; 15967 var spacebar$e = "barre dâespacement"; 15968 var transcriptTitle$e = "Transcription"; 15969 var lyricsTitle$e = "Paroles"; 15970 var autoScroll$e = "Défilement automatique"; 15971 var statusPlaying$e = "Lecture en cours"; 15972 var statusPaused$e = "Lecture sur pause"; 15973 var statusStopped$e = "Lecture interrompue"; 15974 var statusBuffering$e = "Tamponnage"; 15975 var statusEnd$e = "Fin de la piste"; 15976 var selectedTrack$d = "Piste choisie"; 15977 var alertDescribedVersion$e = "Utilisation de la version avec description sonore de cette vidéo"; 15978 var alertNonDescribedVersion$e = "Utilisation de la version non décrite de cette vidéo"; 15979 var prefMenuCaptions$e = "Sous-titres"; 15980 var prefVoicedCaptions$e = "Spoken Captions"; 15981 var prefMenuDescriptions$e = "Descriptions"; 15982 var prefMenuKeyboard$e = "Clavier"; 15983 var prefMenuTranscript$e = "Transcription"; 15984 var prefTitleCaptions$e = "Préférences liées au sous-titrage"; 15985 var prefTitleDescriptions$e = "Préférences liées aux descriptions sonores"; 15986 var prefTitleKeyboard$e = "Préférences liées au clavier"; 15987 var prefTitleTranscript$d = "Préférence liées à la transcription"; 15988 var prefIntroDescription1$d = "Ce lecteur multimédia permet dâentendre les descriptions sonores de deux façons:"; 15989 var prefDescription1$e = "Il y a une version autre version avec description, description textuelle."; 15990 var prefDescription2$e = "Il y a une version autre version de la vidéo avec description."; 15991 var prefDescription3$e = "Il y a une version description textuelle, lue à lâaide dâun lecteur dâécran."; 15992 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."; 15993 var prefIntroDescription3$e = "Utilisez le formulaire suivant pour établir vos préférences liées aux descriptions sonores textuelle."; 15994 var prefIntroDescription4$e = "Après avoir enregistré vos préférences, vous pouvez activer ou désactiver la description sonore avec le bouton Description."; 15995 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)."; 15996 var prefIntroKeyboard2$e = "Des rôles peuvent être assignés aux touches de modification (Shift, Alt, Ctrl) ci-dessous."; 15997 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."; 15998 var prefHeadingKeyboard1$e = "Touches de modification utilisées pour des raccourcis"; 15999 var prefHeadingKeyboard2$e = "Raccourcis du clavier assignés actuellement"; 16000 var prefHeadingDescription$e = "Description sonore"; 16001 var prefHeadingTextDescription$e = "Description sonore textuelle"; 16002 var prefAltKey$e = "Alt"; 16003 var prefCtrlKey$e = "Ctrl"; 16004 var prefShiftKey$e = "Shift"; 16005 var prefNoKeyShortcuts$e = "Disable keyboard shortcuts"; 16006 var escapeKey$e = "Esc"; 16007 var escapeKeyFunction$e = "Fermer la fenêtre de dialogue ou le menu contextuel"; 16008 var prefDescPause$e = "Mettre la vidéo en pause automatiquement quand la description commence"; 16009 var prefDescVisible$e = "Affichez la description"; 16010 var prefDescVoice$e = "Voix"; 16011 var prefDescRate$e = "Spoken Description Rate"; 16012 var prefCaptionRate$e = "Spoken Caption Rate"; 16013 var prefDescPitch$e = "Volume"; 16014 var prefDescPitch1$e = "Très faible"; 16015 var prefDescPitch2$e = "Faible"; 16016 var prefDescPitch3$e = "Par défaut"; 16017 var prefDescPitch4$e = "Fort"; 16018 var prefDescPitch5$e = "Très fort"; 16019 var sampleDescriptionText$e = "Régler les paramètres afin d'entendre cet extrait de texte"; 16020 var prefHighlight$e = "Surligner la transcription pendant la lecture"; 16021 var prefTabbable$e = "Transcription activée par clavier"; 16022 var prefCaptionsFont$e = "Police de caractères"; 16023 var prefCaptionsColor$e = "Couleur du texte"; 16024 var prefCaptionsBGColor$e = "Arrière-plan"; 16025 var prefCaptionsSize$e = "Taille de la police"; 16026 var prefCaptionsOpacity$e = "Opacité"; 16027 var prefCaptionsStyle$e = "Style"; 16028 var serif$e = "avec empattement"; 16029 var sans$e = "sans empattement"; 16030 var cursive$e = "écriture cursive"; 16031 var fantasy$e = "écriture de fantaisie"; 16032 var monospace$e = "à taille fixe"; 16033 var white$e = "blanc"; 16034 var yellow$e = "jaune"; 16035 var green$e = "vert"; 16036 var cyan$d = "cyan"; 16037 var blue$e = "bleu"; 16038 var magenta$e = "magenta"; 16039 var red$e = "rouge"; 16040 var black$e = "noir"; 16041 var transparent$e = "transparent"; 16042 var solid$e = "solide"; 16043 var captionsStylePopOn$e = "Pop-on"; 16044 var captionsStyleRollUp$e = "Roll-up"; 16045 var prefCaptionsPosition$e = "Position"; 16046 var captionsPositionOverlay$e = "Superposés"; 16047 var captionsPositionBelow$e = "Sous la vidéo"; 16048 var sampleCaptionText$e = "Ãchantillon de sous-titre"; 16049 var prefSuccess$e = "Vos changements ont été enregistrés."; 16050 var prefNoChange$e = "Vous nâavez pas fait de changements."; 16051 var save$e = "Enregistrer"; 16052 var cancel$e = "Annuler"; 16053 var dismissButton$e = "Dismiss"; 16054 var windowButtonLabel$e = "Options de fenêtre"; 16055 var windowMove$e = "Déplacer"; 16056 var windowMoveLeft$e = "Window moved left"; 16057 var windowMoveRight$e = "Window moved right"; 16058 var windowMoveUp$e = "Window moved up"; 16059 var windowMoveDown$e = "Window moved down"; 16060 var windowMoveStopped$e = "Window move stopped"; 16061 var transcriptControls$e = "Transcript Window Controls"; 16062 var signControls$e = "Sign Language Window Controls"; 16063 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."; 16064 var windowResize$e = "Redimensionner"; 16065 var windowResizeHeading$e = "Redimensionner la fenêtre de lâinterprète"; 16066 var closeButtonLabel$e = "Fermer"; 16067 var width$e = "Largeur"; 16068 var height$e = "Hauteur"; 16069 var resultsSummary1$e = "Vos résultats de recherche"; 16070 var resultsSummary2$e = "Trouvé %1 éléments correspondants."; 16071 var resultsSummary3$e = "Cliquez sur le temps associé à n'importe quel élément pour lire la vidéo à partir de ce point."; 16072 var noResultsFound$e = "Aucun résultat trouvé."; 16073 var searchButtonLabel$e = "Lecture à %1"; 16074 var hour$e = "heure"; 16075 var minute$e = "minute"; 16076 var second$e = "seconde"; 16077 var hours$e = "heures"; 16078 var minutes$e = "minutes"; 16079 var seconds$e = "seconds"; 16080 var vtsHeading$e = "Video Transcript Sorter"; 16081 var vtsInstructions1$e = "Use the Video Transcript Sorter to modify text tracks:"; 16082 var vtsInstructions2$e = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16083 var vtsInstructions3$e = "Modify content or start/end times (all are directly editable within the table)."; 16084 var vtsInstructions4$e = "Add new content, such as chapters or descriptions."; 16085 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."; 16086 var vtsSelectLanguage$e = "Select a language"; 16087 var vtsSave$e = "Generate new .vtt content"; 16088 var vtsReturn$e = "Return to Editor"; 16089 var vtsCancel$e = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16090 var vtsRow$e = "Row"; 16091 var vtsKind$e = "Kind"; 16092 var vtsStart$e = "Start"; 16093 var vtsEnd$e = "End"; 16094 var vtsContent$e = "Content"; 16095 var vtsActions$e = "Actions"; 16096 var vtsNewRow$e = "A new row %1 has been inserted."; 16097 var vtsDeletedRow$e = "Row %1 has been deleted."; 16098 var vtsMovedRow$e = "Row %1 has been moved %2 and is now Row %3."; 16099 var fr = { 16100 playerHeading: playerHeading$e, 16101 audioPlayer: audioPlayer$e, 16102 videoPlayer: videoPlayer$e, 16103 faster: faster$e, 16104 slower: slower$e, 16105 play: play$e, 16106 pause: pause$e, 16107 restart: restart$e, 16108 prevTrack: prevTrack$e, 16109 nextTrack: nextTrack$e, 16110 rewind: rewind$e, 16111 forward: forward$e, 16112 captions: captions$e, 16113 showCaptions: showCaptions$e, 16114 hideCaptions: hideCaptions$e, 16115 captionsOff: captionsOff$e, 16116 showTranscript: showTranscript$e, 16117 hideTranscript: hideTranscript$e, 16118 turnOnDescriptions: turnOnDescriptions$e, 16119 turnOffDescriptions: turnOffDescriptions$e, 16120 chapters: chapters$e, 16121 language: language$e, 16122 sign: sign$e, 16123 showSign: showSign$e, 16124 hideSign: hideSign$e, 16125 seekbarLabel: seekbarLabel$e, 16126 mute: mute$e, 16127 unmute: unmute$e, 16128 volume: volume$e, 16129 volumeUpDown: volumeUpDown$e, 16130 preferences: preferences$e, 16131 enterFullScreen: enterFullScreen$e, 16132 exitFullScreen: exitFullScreen$e, 16133 speed: speed$e, 16134 spacebar: spacebar$e, 16135 transcriptTitle: transcriptTitle$e, 16136 lyricsTitle: lyricsTitle$e, 16137 autoScroll: autoScroll$e, 16138 statusPlaying: statusPlaying$e, 16139 statusPaused: statusPaused$e, 16140 statusStopped: statusStopped$e, 16141 statusBuffering: statusBuffering$e, 16142 statusEnd: statusEnd$e, 16143 selectedTrack: selectedTrack$d, 16144 alertDescribedVersion: alertDescribedVersion$e, 16145 alertNonDescribedVersion: alertNonDescribedVersion$e, 16146 prefMenuCaptions: prefMenuCaptions$e, 16147 prefVoicedCaptions: prefVoicedCaptions$e, 16148 prefMenuDescriptions: prefMenuDescriptions$e, 16149 prefMenuKeyboard: prefMenuKeyboard$e, 16150 prefMenuTranscript: prefMenuTranscript$e, 16151 prefTitleCaptions: prefTitleCaptions$e, 16152 prefTitleDescriptions: prefTitleDescriptions$e, 16153 prefTitleKeyboard: prefTitleKeyboard$e, 16154 prefTitleTranscript: prefTitleTranscript$d, 16155 prefIntroDescription1: prefIntroDescription1$d, 16156 prefDescription1: prefDescription1$e, 16157 prefDescription2: prefDescription2$e, 16158 prefDescription3: prefDescription3$e, 16159 prefIntroDescriptionNone: prefIntroDescriptionNone$e, 16160 prefIntroDescription3: prefIntroDescription3$e, 16161 prefIntroDescription4: prefIntroDescription4$e, 16162 prefIntroKeyboard1: prefIntroKeyboard1$e, 16163 prefIntroKeyboard2: prefIntroKeyboard2$e, 16164 prefIntroKeyboard3: prefIntroKeyboard3$e, 16165 prefHeadingKeyboard1: prefHeadingKeyboard1$e, 16166 prefHeadingKeyboard2: prefHeadingKeyboard2$e, 16167 prefHeadingDescription: prefHeadingDescription$e, 16168 prefHeadingTextDescription: prefHeadingTextDescription$e, 16169 prefAltKey: prefAltKey$e, 16170 prefCtrlKey: prefCtrlKey$e, 16171 prefShiftKey: prefShiftKey$e, 16172 prefNoKeyShortcuts: prefNoKeyShortcuts$e, 16173 escapeKey: escapeKey$e, 16174 escapeKeyFunction: escapeKeyFunction$e, 16175 prefDescPause: prefDescPause$e, 16176 prefDescVisible: prefDescVisible$e, 16177 prefDescVoice: prefDescVoice$e, 16178 prefDescRate: prefDescRate$e, 16179 prefCaptionRate: prefCaptionRate$e, 16180 prefDescPitch: prefDescPitch$e, 16181 prefDescPitch1: prefDescPitch1$e, 16182 prefDescPitch2: prefDescPitch2$e, 16183 prefDescPitch3: prefDescPitch3$e, 16184 prefDescPitch4: prefDescPitch4$e, 16185 prefDescPitch5: prefDescPitch5$e, 16186 sampleDescriptionText: sampleDescriptionText$e, 16187 prefHighlight: prefHighlight$e, 16188 prefTabbable: prefTabbable$e, 16189 prefCaptionsFont: prefCaptionsFont$e, 16190 prefCaptionsColor: prefCaptionsColor$e, 16191 prefCaptionsBGColor: prefCaptionsBGColor$e, 16192 prefCaptionsSize: prefCaptionsSize$e, 16193 prefCaptionsOpacity: prefCaptionsOpacity$e, 16194 prefCaptionsStyle: prefCaptionsStyle$e, 16195 serif: serif$e, 16196 sans: sans$e, 16197 cursive: cursive$e, 16198 fantasy: fantasy$e, 16199 monospace: monospace$e, 16200 white: white$e, 16201 yellow: yellow$e, 16202 green: green$e, 16203 cyan: cyan$d, 16204 blue: blue$e, 16205 magenta: magenta$e, 16206 red: red$e, 16207 black: black$e, 16208 transparent: transparent$e, 16209 solid: solid$e, 16210 captionsStylePopOn: captionsStylePopOn$e, 16211 captionsStyleRollUp: captionsStyleRollUp$e, 16212 prefCaptionsPosition: prefCaptionsPosition$e, 16213 captionsPositionOverlay: captionsPositionOverlay$e, 16214 captionsPositionBelow: captionsPositionBelow$e, 16215 sampleCaptionText: sampleCaptionText$e, 16216 prefSuccess: prefSuccess$e, 16217 prefNoChange: prefNoChange$e, 16218 save: save$e, 16219 cancel: cancel$e, 16220 dismissButton: dismissButton$e, 16221 windowButtonLabel: windowButtonLabel$e, 16222 windowMove: windowMove$e, 16223 windowMoveLeft: windowMoveLeft$e, 16224 windowMoveRight: windowMoveRight$e, 16225 windowMoveUp: windowMoveUp$e, 16226 windowMoveDown: windowMoveDown$e, 16227 windowMoveStopped: windowMoveStopped$e, 16228 transcriptControls: transcriptControls$e, 16229 signControls: signControls$e, 16230 windowMoveAlert: windowMoveAlert$e, 16231 windowResize: windowResize$e, 16232 windowResizeHeading: windowResizeHeading$e, 16233 closeButtonLabel: closeButtonLabel$e, 16234 width: width$e, 16235 height: height$e, 16236 resultsSummary1: resultsSummary1$e, 16237 resultsSummary2: resultsSummary2$e, 16238 resultsSummary3: resultsSummary3$e, 16239 noResultsFound: noResultsFound$e, 16240 searchButtonLabel: searchButtonLabel$e, 16241 hour: hour$e, 16242 minute: minute$e, 16243 second: second$e, 16244 hours: hours$e, 16245 minutes: minutes$e, 16246 seconds: seconds$e, 16247 vtsHeading: vtsHeading$e, 16248 vtsInstructions1: vtsInstructions1$e, 16249 vtsInstructions2: vtsInstructions2$e, 16250 vtsInstructions3: vtsInstructions3$e, 16251 vtsInstructions4: vtsInstructions4$e, 16252 vtsInstructions5: vtsInstructions5$e, 16253 vtsSelectLanguage: vtsSelectLanguage$e, 16254 vtsSave: vtsSave$e, 16255 vtsReturn: vtsReturn$e, 16256 vtsCancel: vtsCancel$e, 16257 vtsRow: vtsRow$e, 16258 vtsKind: vtsKind$e, 16259 vtsStart: vtsStart$e, 16260 vtsEnd: vtsEnd$e, 16261 vtsContent: vtsContent$e, 16262 vtsActions: vtsActions$e, 16263 vtsNewRow: vtsNewRow$e, 16264 vtsDeletedRow: vtsDeletedRow$e, 16265 vtsMovedRow: vtsMovedRow$e 16266 }; 16267 16268 var playerHeading$d = "× ×× ××××"; 16269 var audioPlayer$d = "Audio player"; 16270 var videoPlayer$d = "Video player"; 16271 var faster$d = "××ר ××תר"; 16272 var slower$d = "××× ××תר"; 16273 var play$d = "× ××"; 16274 var pause$d = "×פסק×"; 16275 var restart$d = "×ת××× ×××ש"; 16276 var prevTrack$d = "××ס××× ××§×××"; 16277 var nextTrack$d = "××ס××× ×××"; 16278 var rewind$d = "××ר×"; 16279 var forward$d = "×§××××"; 16280 var captions$d = "××ת××××"; 16281 var showCaptions$d = "×צ×ת ××ת××××"; 16282 var hideCaptions$d = "×סתרת ××ת××××"; 16283 var captionsOff$d = "××× ××ת××××"; 16284 var showTranscript$d = "××¦× ×ª××××"; 16285 var hideTranscript$d = "×סתר ת××××"; 16286 var turnOnDescriptions$d = "××¤×¢× ×ª××××"; 16287 var turnOffDescriptions$d = "ס××ר ת××××"; 16288 var chapters$d = "פרק××"; 16289 var language$d = "שפ×"; 16290 var sign$d = "שפת ס××× ××"; 16291 var showSign$d = "××¦× ×©×¤×ª ס××× ××"; 16292 var hideSign$d = "×סתר שפת ס××× ××"; 16293 var seekbarLabel$d = "צ×ר ×××"; 16294 var mute$d = "×שתק×"; 16295 var unmute$d = "×פסקת ×שתק×"; 16296 var volume$d = "עצ××"; 16297 var volumeUpDown$d = "Volume up down"; 16298 var preferences$d = "××¢×פ×ת"; 16299 var enterFullScreen$d = "××¢×ר ×××¡× ×××"; 16300 var exitFullScreen$d = "×צ××× ×××¡× ×××"; 16301 var speed$d = "×××ר×ת"; 16302 var spacebar$d = "×קש ×ר×××"; 16303 var transcriptTitle$d = "ת××××"; 16304 var lyricsTitle$d = "×××××"; 16305 var autoScroll$d = "××××× ×××××××ת"; 16306 var statusPlaying$d = "×× ××"; 16307 var statusPaused$d = "××פסק"; 16308 var statusStopped$d = "× ×¢×¦×¨"; 16309 var statusBuffering$d = "×צ×צ×"; 16310 var statusEnd$d = "ס××£ ××ס×××"; 16311 var selectTrack = "×ס××× × ××ר"; 16312 var alertDescribedVersion$d = "ש×××ש ×××רסת ××××× ××ת××רת ×©× ×¡×¨××× ××"; 16313 var alertNonDescribedVersion$d = "ש×××ש ×××¨×¡× ××× ×ת××רת ×©× ×¡×¨××× ××"; 16314 var prefMenuCaptions$d = "××ת××××"; 16315 var prefVoicedCaptions$d = "Spoken Captions"; 16316 var prefMenuDescriptions$d = "ת×××ר××"; 16317 var prefMenuKeyboard$d = "××§××ת"; 16318 var prefMenuTranscript$d = "ת××××"; 16319 var prefTitleCaptions$d = "××¢×פ×ת ××ת××××"; 16320 var prefTitleDescriptions$d = "××¢×פ×ת ת×××ר ש××¢"; 16321 var prefTitleKeyboard$d = "××¢×פ×ת ××§××ת"; 16322 var PrefTitleTranscript = "××¢×פ×ת ת××××"; 16323 var prefIntrodescription1 = "× ×× ××××× ×ª××× ×ת×××ר ×ש××¢ ××©×ª× ×ר×××:"; 16324 var prefDescription1$d = "××××××× ×× ×××× ×ש ××¨×¡× ×××פ×ת ××ת××רתת×××ר ×××סס ×קס×"; 16325 var prefDescription2$d = "××××××× ×× ×××× ×ש××¨×¡× ×××פ×ת ××ת××רת ×©× ×××××"; 16326 var prefDescription3$d = "××××××× ×× ×××× ×ש ת×××ר ×××סס ×קס×, ×××××¢× ×¢× ××× ×§××¨× ×ס×"; 16327 var prefIntroDescriptionNone$d = "×סר××× ×× ×××× ××× ×ª×××ר ש××¢ ×ת×× ×ת."; 16328 var prefIntroDescription3$d = "×שת×ש ×××פס ××× ××× ××§×××¢ ×ת ×××¢×פ×ת ש×× ×קש×ר×ת ×ת×××ר ש××¢."; 16329 var prefIntroDescription4$d = "×××ר שתש××ר ×ת ××××ר×ת ש××, ת×××ר ×ש××¢ ×××× ××××ת ×××× ×¢× / ××××× ×××צע×ת ×××¦× ×ª×××ר."; 16330 var prefIntroKeyboard1$d = "× ×× ××××× ×××£ ××× ××¨× × ×× ×××× ××××ת ×××¤×¢× ××× ××§×× ×××£ ×××צע×ת ×§×צ××¨× ×קש×× (ר×× ×××× ×¨×©×××)."; 16331 var prefIntroKeyboard2$d = "× ××ª× ××©× ×ת ×ת ××§×©× ×ש×× ×× (Shift, Alt ×- Control)."; 16332 var prefIntroKeyboard3$d = "×ער×: ××ת×× ×©×××§ ×צ×ר××¤× ××קש×× ××ª× ×ש×× ×¢× ××קש×× ××ש×ש×× ×ת ××פ××¤× × / ×× ××ש××× ×ª××× × ××ר××, × ×¡× ×©×××××× ×©×× ×× ×©× ×פת××ת ש×× ×× ××× ××צ×× ××× ××ת××× ××."; 16333 var prefHeadingKeyboard1$d = "××§×©× ××©× × ××ש×ש×× ××§×צ××¨× ×ר×"; 16334 var prefHeadingKeyboard2$d = "×§×צ××¨× ×קש×× × ××××××"; 16335 var prefHeadingDescription$d = "ת×××ר ש××¢"; 16336 var prefHeadingTextDescription$d = "ת×××ר ×××סס ××§×¡× ×××סס ×קס×"; 16337 var prefAltKey$d = "Alt"; 16338 var prefCtrlKey$d = "Control"; 16339 var prefShiftKey$d = "Shift"; 16340 var prefNoKeyShortcuts$d = "Disable keyboard shortcuts"; 16341 var escapeKey$d = "Escape"; 16342 var escapeKeyFunction$d = "ס××ר ×ת ת××ת ××× ×©×× ×× ××××ת ×× ×ª×¤×¨×× ×§×פץ"; 16343 var prefDescPause$d = "×ש×× ×ת ××××××× ××××¤× ××××××× ××שר ×ת×××ר ×ת×××"; 16344 var prefDescVisible$d = "×פ×× ×ª×××ר ××××"; 16345 var prefDescVoice$d = "Voice"; 16346 var prefDescRate$d = "Spoken Description Rate"; 16347 var prefCaptionRate$d = "Spoken Caption Rate"; 16348 var prefDescPitch$d = "Pitch"; 16349 var prefDescPitch1$d = "Very low"; 16350 var prefDescPitch2$d = "Low"; 16351 var prefDescPitch3$d = "Default"; 16352 var prefDescPitch4$d = "High"; 16353 var prefDescPitch5$d = "Very high"; 16354 var sampleDescriptionText$d = "Adjust settings to hear this sample text."; 16355 var prefHighlight$d = "×××ש ת×××× ××× ×ש××§ ××××"; 16356 var prefTabbable$d = "××¤×¢× ×ª××××"; 16357 var prefCaptionsFont$d = "××פ×"; 16358 var prefCaptionsColor$d = "צ××¢ ×קס×"; 16359 var prefCaptionsBGColor$d = "רקע"; 16360 var prefCaptionsSize$d = "×××× ××פ×"; 16361 var prefCaptionsOpacity$d = "×××××ת"; 16362 var prefCaptionsStyle$d = "ס×× ××"; 16363 var serif$d = "serif"; 16364 var sans$d = "sans-serif"; 16365 var cursive$d = "cursive"; 16366 var fantasy$d = "fantasy"; 16367 var monospace$d = "monospace"; 16368 var white$d = "×××"; 16369 var yellow$d = "צ×××"; 16370 var green$d = "×ר××§"; 16371 var cyan$c = "××רק××"; 16372 var blue$d = "××××"; 16373 var magenta$d = "××× ××"; 16374 var red$d = "××××"; 16375 var black$d = "ש××ר"; 16376 var transparent$d = "שק××£"; 16377 var solid$d = "×××"; 16378 var captionsStylePopOn$d = "Pop-on"; 16379 var captionsStyleRollUp$d = "Roll-up"; 16380 var prefCaptionsPosition$d = "×××§××"; 16381 var captionsPositionOverlay$d = "Overlay"; 16382 var captionsPositionBelow$d = "×ת×ת ××××××"; 16383 var sampleCaptionText$d = "××××ת ××§×¡× ××ת××××ת"; 16384 var prefSuccess$d = "×ש×× ×××× ×©××× × ×©×ר×."; 16385 var prefNoChange$d = "×× ××¦×¢×ª× ×× ×©×× ××."; 16386 var save$d = "ש××ר×"; 16387 var cancel$d = "×××××"; 16388 var dismissButton$d = "Dismiss"; 16389 var windowButtonLabel$d = "×פשר×××ת ××××"; 16390 var windowMove$d = "××××"; 16391 var windowMoveLeft$d = "Window moved left"; 16392 var windowMoveRight$d = "Window moved right"; 16393 var windowMoveUp$d = "Window moved up"; 16394 var windowMoveDown$d = "Window moved down"; 16395 var windowMoveStopped$d = "Window move stopped"; 16396 var transcriptControls$d = "Transcript Window Controls"; 16397 var signControls$d = "Sign Language Window Controls"; 16398 var windowMoveAlert$d = "×ר×ר ×× ×שת××©× ××§×©× ××צ×× ××××ת ×××××; ×פת×ר Enter ××פסק×"; 16399 var windowResize$d = "ש××× ××××"; 16400 var windowResizeHeading$d = "ש×× ×× ×××× ××××"; 16401 var closeButtonLabel$d = "ס××רת"; 16402 var width$d = "ר×××"; 16403 var height$d = "××××"; 16404 var resultsSummary1$d = "You searched for:"; 16405 var resultsSummary2$d = "Found %1 matching items."; 16406 var resultsSummary3$d = "Click the time associated with any item to play the video from that point."; 16407 var noResultsFound$d = "No results found."; 16408 var searchButtonLabel$d = "Play at %1"; 16409 var hour$d = "hour"; 16410 var minute$d = "minute"; 16411 var second$d = "second"; 16412 var hours$d = "hours"; 16413 var minutes$d = "minutes"; 16414 var seconds$d = "seconds"; 16415 var vtsHeading$d = "Video Transcript Sorter"; 16416 var vtsInstructions1$d = "Use the Video Transcript Sorter to modify text tracks:"; 16417 var vtsInstructions2$d = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16418 var vtsInstructions3$d = "Modify content or start/end times (all are directly editable within the table)."; 16419 var vtsInstructions4$d = "Add new content, such as chapters or descriptions."; 16420 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."; 16421 var vtsSelectLanguage$d = "Select a language"; 16422 var vtsSave$d = "Generate new .vtt content"; 16423 var vtsReturn$d = "Return to Editor"; 16424 var vtsCancel$d = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16425 var vtsRow$d = "Row"; 16426 var vtsKind$d = "Kind"; 16427 var vtsStart$d = "Start"; 16428 var vtsEnd$d = "End"; 16429 var vtsContent$d = "Content"; 16430 var vtsActions$d = "Actions"; 16431 var vtsNewRow$d = "A new row %1 has been inserted."; 16432 var vtsDeletedRow$d = "Row %1 has been deleted."; 16433 var vtsMovedRow$d = "Row %1 has been moved %2 and is now Row %3."; 16434 var he = { 16435 playerHeading: playerHeading$d, 16436 audioPlayer: audioPlayer$d, 16437 videoPlayer: videoPlayer$d, 16438 faster: faster$d, 16439 slower: slower$d, 16440 play: play$d, 16441 pause: pause$d, 16442 restart: restart$d, 16443 prevTrack: prevTrack$d, 16444 nextTrack: nextTrack$d, 16445 rewind: rewind$d, 16446 forward: forward$d, 16447 captions: captions$d, 16448 showCaptions: showCaptions$d, 16449 hideCaptions: hideCaptions$d, 16450 captionsOff: captionsOff$d, 16451 showTranscript: showTranscript$d, 16452 hideTranscript: hideTranscript$d, 16453 turnOnDescriptions: turnOnDescriptions$d, 16454 turnOffDescriptions: turnOffDescriptions$d, 16455 chapters: chapters$d, 16456 language: language$d, 16457 sign: sign$d, 16458 showSign: showSign$d, 16459 hideSign: hideSign$d, 16460 seekbarLabel: seekbarLabel$d, 16461 mute: mute$d, 16462 unmute: unmute$d, 16463 volume: volume$d, 16464 volumeUpDown: volumeUpDown$d, 16465 preferences: preferences$d, 16466 enterFullScreen: enterFullScreen$d, 16467 exitFullScreen: exitFullScreen$d, 16468 speed: speed$d, 16469 spacebar: spacebar$d, 16470 transcriptTitle: transcriptTitle$d, 16471 lyricsTitle: lyricsTitle$d, 16472 autoScroll: autoScroll$d, 16473 statusPlaying: statusPlaying$d, 16474 statusPaused: statusPaused$d, 16475 statusStopped: statusStopped$d, 16476 statusBuffering: statusBuffering$d, 16477 statusEnd: statusEnd$d, 16478 selectTrack: selectTrack, 16479 alertDescribedVersion: alertDescribedVersion$d, 16480 alertNonDescribedVersion: alertNonDescribedVersion$d, 16481 prefMenuCaptions: prefMenuCaptions$d, 16482 prefVoicedCaptions: prefVoicedCaptions$d, 16483 prefMenuDescriptions: prefMenuDescriptions$d, 16484 prefMenuKeyboard: prefMenuKeyboard$d, 16485 prefMenuTranscript: prefMenuTranscript$d, 16486 prefTitleCaptions: prefTitleCaptions$d, 16487 prefTitleDescriptions: prefTitleDescriptions$d, 16488 prefTitleKeyboard: prefTitleKeyboard$d, 16489 PrefTitleTranscript: PrefTitleTranscript, 16490 prefIntrodescription1: prefIntrodescription1, 16491 prefDescription1: prefDescription1$d, 16492 prefDescription2: prefDescription2$d, 16493 prefDescription3: prefDescription3$d, 16494 prefIntroDescriptionNone: prefIntroDescriptionNone$d, 16495 prefIntroDescription3: prefIntroDescription3$d, 16496 prefIntroDescription4: prefIntroDescription4$d, 16497 prefIntroKeyboard1: prefIntroKeyboard1$d, 16498 prefIntroKeyboard2: prefIntroKeyboard2$d, 16499 prefIntroKeyboard3: prefIntroKeyboard3$d, 16500 prefHeadingKeyboard1: prefHeadingKeyboard1$d, 16501 prefHeadingKeyboard2: prefHeadingKeyboard2$d, 16502 prefHeadingDescription: prefHeadingDescription$d, 16503 prefHeadingTextDescription: prefHeadingTextDescription$d, 16504 prefAltKey: prefAltKey$d, 16505 prefCtrlKey: prefCtrlKey$d, 16506 prefShiftKey: prefShiftKey$d, 16507 prefNoKeyShortcuts: prefNoKeyShortcuts$d, 16508 escapeKey: escapeKey$d, 16509 escapeKeyFunction: escapeKeyFunction$d, 16510 prefDescPause: prefDescPause$d, 16511 prefDescVisible: prefDescVisible$d, 16512 prefDescVoice: prefDescVoice$d, 16513 prefDescRate: prefDescRate$d, 16514 prefCaptionRate: prefCaptionRate$d, 16515 prefDescPitch: prefDescPitch$d, 16516 prefDescPitch1: prefDescPitch1$d, 16517 prefDescPitch2: prefDescPitch2$d, 16518 prefDescPitch3: prefDescPitch3$d, 16519 prefDescPitch4: prefDescPitch4$d, 16520 prefDescPitch5: prefDescPitch5$d, 16521 sampleDescriptionText: sampleDescriptionText$d, 16522 prefHighlight: prefHighlight$d, 16523 prefTabbable: prefTabbable$d, 16524 prefCaptionsFont: prefCaptionsFont$d, 16525 prefCaptionsColor: prefCaptionsColor$d, 16526 prefCaptionsBGColor: prefCaptionsBGColor$d, 16527 prefCaptionsSize: prefCaptionsSize$d, 16528 prefCaptionsOpacity: prefCaptionsOpacity$d, 16529 prefCaptionsStyle: prefCaptionsStyle$d, 16530 serif: serif$d, 16531 sans: sans$d, 16532 cursive: cursive$d, 16533 fantasy: fantasy$d, 16534 monospace: monospace$d, 16535 white: white$d, 16536 yellow: yellow$d, 16537 green: green$d, 16538 cyan: cyan$c, 16539 blue: blue$d, 16540 magenta: magenta$d, 16541 red: red$d, 16542 black: black$d, 16543 transparent: transparent$d, 16544 solid: solid$d, 16545 captionsStylePopOn: captionsStylePopOn$d, 16546 captionsStyleRollUp: captionsStyleRollUp$d, 16547 prefCaptionsPosition: prefCaptionsPosition$d, 16548 captionsPositionOverlay: captionsPositionOverlay$d, 16549 captionsPositionBelow: captionsPositionBelow$d, 16550 sampleCaptionText: sampleCaptionText$d, 16551 prefSuccess: prefSuccess$d, 16552 prefNoChange: prefNoChange$d, 16553 save: save$d, 16554 cancel: cancel$d, 16555 dismissButton: dismissButton$d, 16556 windowButtonLabel: windowButtonLabel$d, 16557 windowMove: windowMove$d, 16558 windowMoveLeft: windowMoveLeft$d, 16559 windowMoveRight: windowMoveRight$d, 16560 windowMoveUp: windowMoveUp$d, 16561 windowMoveDown: windowMoveDown$d, 16562 windowMoveStopped: windowMoveStopped$d, 16563 transcriptControls: transcriptControls$d, 16564 signControls: signControls$d, 16565 windowMoveAlert: windowMoveAlert$d, 16566 windowResize: windowResize$d, 16567 windowResizeHeading: windowResizeHeading$d, 16568 closeButtonLabel: closeButtonLabel$d, 16569 width: width$d, 16570 height: height$d, 16571 resultsSummary1: resultsSummary1$d, 16572 resultsSummary2: resultsSummary2$d, 16573 resultsSummary3: resultsSummary3$d, 16574 noResultsFound: noResultsFound$d, 16575 searchButtonLabel: searchButtonLabel$d, 16576 hour: hour$d, 16577 minute: minute$d, 16578 second: second$d, 16579 hours: hours$d, 16580 minutes: minutes$d, 16581 seconds: seconds$d, 16582 vtsHeading: vtsHeading$d, 16583 vtsInstructions1: vtsInstructions1$d, 16584 vtsInstructions2: vtsInstructions2$d, 16585 vtsInstructions3: vtsInstructions3$d, 16586 vtsInstructions4: vtsInstructions4$d, 16587 vtsInstructions5: vtsInstructions5$d, 16588 vtsSelectLanguage: vtsSelectLanguage$d, 16589 vtsSave: vtsSave$d, 16590 vtsReturn: vtsReturn$d, 16591 vtsCancel: vtsCancel$d, 16592 vtsRow: vtsRow$d, 16593 vtsKind: vtsKind$d, 16594 vtsStart: vtsStart$d, 16595 vtsEnd: vtsEnd$d, 16596 vtsContent: vtsContent$d, 16597 vtsActions: vtsActions$d, 16598 vtsNewRow: vtsNewRow$d, 16599 vtsDeletedRow: vtsDeletedRow$d, 16600 vtsMovedRow: vtsMovedRow$d 16601 }; 16602 16603 var playerHeading$c = "Pemutar media"; 16604 var audioPlayer$c = "Audio player"; 16605 var videoPlayer$c = "Video player"; 16606 var faster$c = "Percepat"; 16607 var slower$c = "Perlambat"; 16608 var play$c = "Mulai"; 16609 var pause$c = "Jeda"; 16610 var restart$c = "Ulangi"; 16611 var prevTrack$c = "Trek sebelumnya"; 16612 var nextTrack$c = "Trek berikutnya"; 16613 var rewind$c = "Mundur"; 16614 var forward$c = "Maju"; 16615 var captions$c = "Takarir"; 16616 var showCaptions$c = "Tampilkan takarir"; 16617 var hideCaptions$c = "Sembunyikan takarir"; 16618 var captionsOff$c = "Tanpa takarir"; 16619 var showTranscript$c = "Tampilkan transkripsi"; 16620 var hideTranscript$c = "Sembunyikan transkripsi"; 16621 var turnOnDescriptions$c = "Nyalakan deskripsi"; 16622 var turnOffDescriptions$c = "Matikan deskripsi"; 16623 var chapters$c = "Bagian"; 16624 var language$c = "Bahasa"; 16625 var sign$c = "Bahasa isyarat"; 16626 var showSign$c = "Tampilkan bahasa isyarat"; 16627 var hideSign$c = "Sembunyikan bahasa isyarat"; 16628 var seekbarLabel$c = "lini masa"; 16629 var mute$c = "Matikan Suara"; 16630 var unmute$c = "Nyalakan Suara"; 16631 var volume$c = "Volume"; 16632 var volumeUpDown$c = "Naik turun volume"; 16633 var preferences$c = "Preferensi"; 16634 var enterFullScreen$c = "Masuk ke mode layar penuh"; 16635 var exitFullScreen$c = "Keluar dari mode layar penuh"; 16636 var speed$c = "Kecepatan"; 16637 var spacebar$c = "tombol spasi"; 16638 var transcriptTitle$c = "Transkripsi"; 16639 var lyricsTitle$c = "Lirik"; 16640 var autoScroll$c = "Gulir otomatis"; 16641 var statusPlaying$c = "Memutar"; 16642 var statusPaused$c = "Dijedakan"; 16643 var statusStopped$c = "Dihentikan"; 16644 var statusBuffering$c = "Sedang Bufer"; 16645 var statusEnd$c = "Akhir dari trek"; 16646 var selectedTrack$c = "Trek Pilihan"; 16647 var alertDescribedVersion$c = "Menggunakan versi dengan deskripsi audio pada video ini"; 16648 var alertNonDescribedVersion$c = "Menggunakan versi tanpa deskripsi pada video ini"; 16649 var prefMenuCaptions$c = "Takarir"; 16650 var prefVoicedCaptions$c = "Spoken Captions"; 16651 var prefMenuDescriptions$c = "Deskripsi"; 16652 var prefMenuKeyboard$c = "Kibor"; 16653 var prefMenuTranscript$c = "Transkripsi"; 16654 var prefTitleCaptions$c = "Preferensi Takarir"; 16655 var prefTitleDescriptions$c = "Preferensi Deskripsi Audio"; 16656 var prefTitleKeyboard$c = "Preferensi Kibor"; 16657 var prefTitleTranscript$c = "Preferensi Transkripsi"; 16658 var prefIntroDescription1$c = "Pemutar media ini mendukung deskripsi audio dengan dua cara: "; 16659 var prefDescription1$c = "Video ini memiliki versi alternatif berdeskripsi, deskripsi berbasis teks."; 16660 var prefDescription2$c = "Video ini memiliki versi alternatif video dengan deskripsi."; 16661 var prefDescription3$c = "Video ini memiliki deskripsi berbasis teks, dibacakan oleh pembaca layar."; 16662 var prefIntroDescriptionNone$c = "Video ini tidak memiliki deskripsi audio dalam format mana pun."; 16663 var prefIntroDescription3$c = "Gunakan isian berikut untuk memasang preferensi terkait deskripsi audio berbasis teks."; 16664 var prefIntroDescription4$c = "Setelah Anda menyimpan pengaturan, deskripsi audio dapat dinyala/matikan melalui tombol Deskripsi."; 16665 var prefIntroKeyboard1$c = "Pemutar media dalam halaman ini dapat dioperasikan dari bagian mana pun pada halaman dengan menggunakan pintasan kibor (lihat daftar di bawah)."; 16666 var prefIntroKeyboard2$c = "Fungsi tombol kombinasi (Shift, Alt, Control) dapat dipasangkan di bawah ini."; 16667 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."; 16668 var prefHeadingKeyboard1$c = "Tombol kombinasi yang digunakan untuk pintasan"; 16669 var prefHeadingKeyboard2$c = "Pintasan kibor saat ini"; 16670 var prefHeadingDescription$c = "Deskripsi audio"; 16671 var prefHeadingTextDescription$c = "Deskripsi audio berbasis teks"; 16672 var prefAltKey$c = "Alt"; 16673 var prefCtrlKey$c = "Control"; 16674 var prefShiftKey$c = "Shift"; 16675 var prefNoKeyShortcuts$c = "Disable keyboard shortcuts"; 16676 var escapeKey$c = "Escape"; 16677 var escapeKeyFunction$c = "Tutup dialog atau menu sembulan yang tampil"; 16678 var prefDescPause$c = "Otomatis jedakan video ketika deskripsi dimulai"; 16679 var prefDescVisible$c = "Tampilkan deskripsi"; 16680 var prefDescVoice$c = "Suara"; 16681 var prefDescRate$c = "Spoken Description Rate"; 16682 var prefCaptionRate$c = "Spoken Caption Rate"; 16683 var prefDescPitch$c = "Titinada"; 16684 var prefDescPitch1$c = "Sangat rendah"; 16685 var prefDescPitch2$c = "Rendah"; 16686 var prefDescPitch3$c = "Bawaan"; 16687 var prefDescPitch4$c = "Tinggi"; 16688 var prefDescPitch5$c = "Sangat tinggi"; 16689 var sampleDescriptionText$c = "Sesuaikan pengaturan untuk mendengarkan teks ini."; 16690 var prefHighlight$c = "Soroti transkripsi yang sesuai saat media diputar"; 16691 var prefTabbable$c = "Transkripsi dengan kontrol kibor"; 16692 var prefCaptionsFont$c = "Jenis Huruf"; 16693 var prefCaptionsColor$c = "Warna Teks"; 16694 var prefCaptionsBGColor$c = "Warna Latar"; 16695 var prefCaptionsSize$c = "Ukuran Huruf"; 16696 var prefCaptionsOpacity$c = "Opasitas"; 16697 var prefCaptionsStyle$c = "Gaya"; 16698 var serif$c = "serif"; 16699 var sans$c = "sans-serif"; 16700 var cursive$c = "kursif"; 16701 var fantasy$c = "fantasi"; 16702 var monospace$c = "monospasi"; 16703 var white$c = "putih"; 16704 var yellow$c = "kuning"; 16705 var green$c = "hijau"; 16706 var cyan$b = "sian"; 16707 var blue$c = "biru"; 16708 var magenta$c = "magenta"; 16709 var red$c = "merah"; 16710 var black$c = "hitam"; 16711 var transparent$c = "transparan"; 16712 var solid$c = "solid"; 16713 var captionsStylePopOn$c = "Muncul"; 16714 var captionsStyleRollUp$c = "Bergulir naik"; 16715 var prefCaptionsPosition$c = "Posisi"; 16716 var captionsPositionOverlay$c = "Di atas video"; 16717 var captionsPositionBelow$c = "Di bawah video"; 16718 var sampleCaptionText$c = "Contoh teks takarir"; 16719 var prefSuccess$c = "Perubahan telah disimpan."; 16720 var prefNoChange$c = "Anda tidak melakukan perubahan apa pun."; 16721 var save$c = "Simpan"; 16722 var cancel$c = "Batal"; 16723 var dismissButton$c = "Dismiss"; 16724 var windowButtonLabel$c = "Opsi jendela"; 16725 var windowMove$c = "Geser"; 16726 var windowMoveLeft$c = "Window moved left"; 16727 var windowMoveRight$c = "Window moved right"; 16728 var windowMoveUp$c = "Window moved up"; 16729 var windowMoveDown$c = "Window moved down"; 16730 var windowMoveStopped$c = "Window move stopped"; 16731 var transcriptControls$c = "Transcript Window Controls"; 16732 var signControls$c = "Sign Language Window Controls"; 16733 var windowMoveAlert$c = "Seret atau gunakan tombol panah untuk menggeser jendela; Tekan Enter untuk berhenti"; 16734 var windowResize$c = "Ubah ukuran"; 16735 var windowResizeHeading$c = "Ubah Ukuran Jendela"; 16736 var closeButtonLabel$c = "Tutup"; 16737 var width$c = "Lebar"; 16738 var height$c = "Tinggi"; 16739 var resultsSummary1$c = "Anda mencari:"; 16740 var resultsSummary2$c = "Ditemukan %1 hal yang sesuai."; 16741 var resultsSummary3$c = "Klik waktu yang terkait dengan suatu hal untuk memutar video dari titik tersebut."; 16742 var noResultsFound$c = "Tidak ada hasil yang ditemukan."; 16743 var searchButtonLabel$c = "Diputar pada %1"; 16744 var hour$c = "jam"; 16745 var minute$c = "menit"; 16746 var second$c = "detik"; 16747 var hours$c = "jam"; 16748 var minutes$c = "menit"; 16749 var seconds$c = "detik"; 16750 var vtsHeading$c = "Video Transcript Sorter"; 16751 var vtsInstructions1$c = "Use the Video Transcript Sorter to modify text tracks:"; 16752 var vtsInstructions2$c = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 16753 var vtsInstructions3$c = "Modify content or start/end times (all are directly editable within the table)."; 16754 var vtsInstructions4$c = "Add new content, such as chapters or descriptions."; 16755 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."; 16756 var vtsSelectLanguage$c = "Select a language"; 16757 var vtsSave$c = "Generate new .vtt content"; 16758 var vtsReturn$c = "Return to Editor"; 16759 var vtsCancel$c = "Cancelling saving. Any edits you made have been restored in the VTS table."; 16760 var vtsRow$c = "Row"; 16761 var vtsKind$c = "Kind"; 16762 var vtsStart$c = "Start"; 16763 var vtsEnd$c = "End"; 16764 var vtsContent$c = "Content"; 16765 var vtsActions$c = "Actions"; 16766 var vtsNewRow$c = "A new row %1 has been inserted."; 16767 var vtsDeletedRow$c = "Row %1 has been deleted."; 16768 var vtsMovedRow$c = "Row %1 has been moved %2 and is now Row %3."; 16769 var id = { 16770 playerHeading: playerHeading$c, 16771 audioPlayer: audioPlayer$c, 16772 videoPlayer: videoPlayer$c, 16773 faster: faster$c, 16774 slower: slower$c, 16775 play: play$c, 16776 pause: pause$c, 16777 restart: restart$c, 16778 prevTrack: prevTrack$c, 16779 nextTrack: nextTrack$c, 16780 rewind: rewind$c, 16781 forward: forward$c, 16782 captions: captions$c, 16783 showCaptions: showCaptions$c, 16784 hideCaptions: hideCaptions$c, 16785 captionsOff: captionsOff$c, 16786 showTranscript: showTranscript$c, 16787 hideTranscript: hideTranscript$c, 16788 turnOnDescriptions: turnOnDescriptions$c, 16789 turnOffDescriptions: turnOffDescriptions$c, 16790 chapters: chapters$c, 16791 language: language$c, 16792 sign: sign$c, 16793 showSign: showSign$c, 16794 hideSign: hideSign$c, 16795 seekbarLabel: seekbarLabel$c, 16796 mute: mute$c, 16797 unmute: unmute$c, 16798 volume: volume$c, 16799 volumeUpDown: volumeUpDown$c, 16800 preferences: preferences$c, 16801 enterFullScreen: enterFullScreen$c, 16802 exitFullScreen: exitFullScreen$c, 16803 speed: speed$c, 16804 spacebar: spacebar$c, 16805 transcriptTitle: transcriptTitle$c, 16806 lyricsTitle: lyricsTitle$c, 16807 autoScroll: autoScroll$c, 16808 statusPlaying: statusPlaying$c, 16809 statusPaused: statusPaused$c, 16810 statusStopped: statusStopped$c, 16811 statusBuffering: statusBuffering$c, 16812 statusEnd: statusEnd$c, 16813 selectedTrack: selectedTrack$c, 16814 alertDescribedVersion: alertDescribedVersion$c, 16815 alertNonDescribedVersion: alertNonDescribedVersion$c, 16816 prefMenuCaptions: prefMenuCaptions$c, 16817 prefVoicedCaptions: prefVoicedCaptions$c, 16818 prefMenuDescriptions: prefMenuDescriptions$c, 16819 prefMenuKeyboard: prefMenuKeyboard$c, 16820 prefMenuTranscript: prefMenuTranscript$c, 16821 prefTitleCaptions: prefTitleCaptions$c, 16822 prefTitleDescriptions: prefTitleDescriptions$c, 16823 prefTitleKeyboard: prefTitleKeyboard$c, 16824 prefTitleTranscript: prefTitleTranscript$c, 16825 prefIntroDescription1: prefIntroDescription1$c, 16826 prefDescription1: prefDescription1$c, 16827 prefDescription2: prefDescription2$c, 16828 prefDescription3: prefDescription3$c, 16829 prefIntroDescriptionNone: prefIntroDescriptionNone$c, 16830 prefIntroDescription3: prefIntroDescription3$c, 16831 prefIntroDescription4: prefIntroDescription4$c, 16832 prefIntroKeyboard1: prefIntroKeyboard1$c, 16833 prefIntroKeyboard2: prefIntroKeyboard2$c, 16834 prefIntroKeyboard3: prefIntroKeyboard3$c, 16835 prefHeadingKeyboard1: prefHeadingKeyboard1$c, 16836 prefHeadingKeyboard2: prefHeadingKeyboard2$c, 16837 prefHeadingDescription: prefHeadingDescription$c, 16838 prefHeadingTextDescription: prefHeadingTextDescription$c, 16839 prefAltKey: prefAltKey$c, 16840 prefCtrlKey: prefCtrlKey$c, 16841 prefShiftKey: prefShiftKey$c, 16842 prefNoKeyShortcuts: prefNoKeyShortcuts$c, 16843 escapeKey: escapeKey$c, 16844 escapeKeyFunction: escapeKeyFunction$c, 16845 prefDescPause: prefDescPause$c, 16846 prefDescVisible: prefDescVisible$c, 16847 prefDescVoice: prefDescVoice$c, 16848 prefDescRate: prefDescRate$c, 16849 prefCaptionRate: prefCaptionRate$c, 16850 prefDescPitch: prefDescPitch$c, 16851 prefDescPitch1: prefDescPitch1$c, 16852 prefDescPitch2: prefDescPitch2$c, 16853 prefDescPitch3: prefDescPitch3$c, 16854 prefDescPitch4: prefDescPitch4$c, 16855 prefDescPitch5: prefDescPitch5$c, 16856 sampleDescriptionText: sampleDescriptionText$c, 16857 prefHighlight: prefHighlight$c, 16858 prefTabbable: prefTabbable$c, 16859 prefCaptionsFont: prefCaptionsFont$c, 16860 prefCaptionsColor: prefCaptionsColor$c, 16861 prefCaptionsBGColor: prefCaptionsBGColor$c, 16862 prefCaptionsSize: prefCaptionsSize$c, 16863 prefCaptionsOpacity: prefCaptionsOpacity$c, 16864 prefCaptionsStyle: prefCaptionsStyle$c, 16865 serif: serif$c, 16866 sans: sans$c, 16867 cursive: cursive$c, 16868 fantasy: fantasy$c, 16869 monospace: monospace$c, 16870 white: white$c, 16871 yellow: yellow$c, 16872 green: green$c, 16873 cyan: cyan$b, 16874 blue: blue$c, 16875 magenta: magenta$c, 16876 red: red$c, 16877 black: black$c, 16878 transparent: transparent$c, 16879 solid: solid$c, 16880 captionsStylePopOn: captionsStylePopOn$c, 16881 captionsStyleRollUp: captionsStyleRollUp$c, 16882 prefCaptionsPosition: prefCaptionsPosition$c, 16883 captionsPositionOverlay: captionsPositionOverlay$c, 16884 captionsPositionBelow: captionsPositionBelow$c, 16885 sampleCaptionText: sampleCaptionText$c, 16886 prefSuccess: prefSuccess$c, 16887 prefNoChange: prefNoChange$c, 16888 save: save$c, 16889 cancel: cancel$c, 16890 dismissButton: dismissButton$c, 16891 windowButtonLabel: windowButtonLabel$c, 16892 windowMove: windowMove$c, 16893 windowMoveLeft: windowMoveLeft$c, 16894 windowMoveRight: windowMoveRight$c, 16895 windowMoveUp: windowMoveUp$c, 16896 windowMoveDown: windowMoveDown$c, 16897 windowMoveStopped: windowMoveStopped$c, 16898 transcriptControls: transcriptControls$c, 16899 signControls: signControls$c, 16900 windowMoveAlert: windowMoveAlert$c, 16901 windowResize: windowResize$c, 16902 windowResizeHeading: windowResizeHeading$c, 16903 closeButtonLabel: closeButtonLabel$c, 16904 width: width$c, 16905 height: height$c, 16906 resultsSummary1: resultsSummary1$c, 16907 resultsSummary2: resultsSummary2$c, 16908 resultsSummary3: resultsSummary3$c, 16909 noResultsFound: noResultsFound$c, 16910 searchButtonLabel: searchButtonLabel$c, 16911 hour: hour$c, 16912 minute: minute$c, 16913 second: second$c, 16914 hours: hours$c, 16915 minutes: minutes$c, 16916 seconds: seconds$c, 16917 vtsHeading: vtsHeading$c, 16918 vtsInstructions1: vtsInstructions1$c, 16919 vtsInstructions2: vtsInstructions2$c, 16920 vtsInstructions3: vtsInstructions3$c, 16921 vtsInstructions4: vtsInstructions4$c, 16922 vtsInstructions5: vtsInstructions5$c, 16923 vtsSelectLanguage: vtsSelectLanguage$c, 16924 vtsSave: vtsSave$c, 16925 vtsReturn: vtsReturn$c, 16926 vtsCancel: vtsCancel$c, 16927 vtsRow: vtsRow$c, 16928 vtsKind: vtsKind$c, 16929 vtsStart: vtsStart$c, 16930 vtsEnd: vtsEnd$c, 16931 vtsContent: vtsContent$c, 16932 vtsActions: vtsActions$c, 16933 vtsNewRow: vtsNewRow$c, 16934 vtsDeletedRow: vtsDeletedRow$c, 16935 vtsMovedRow: vtsMovedRow$c 16936 }; 16937 16938 var playerHeading$b = "Lettore multimediale"; 16939 var audioPlayer$b = "Audio player"; 16940 var videoPlayer$b = "Video player"; 16941 var faster$b = "Più veloce"; 16942 var slower$b = "Più lento"; 16943 var play$b = "Riproduci"; 16944 var pause$b = "Pausa"; 16945 var restart$b = "Torna all'inizio"; 16946 var prevTrack$b = "Traccia precedente"; 16947 var nextTrack$b = "Traccia successiva"; 16948 var rewind$b = "Indietro"; 16949 var forward$b = "Avanti"; 16950 var captions$b = "Sottotitoli"; 16951 var showCaptions$b = "Mostra sottotitoli"; 16952 var hideCaptions$b = "Nascondi sottotitoli"; 16953 var captionsOff$b = "Disattiva sottotitoli"; 16954 var showTranscript$b = "Mostra trascrizione"; 16955 var hideTranscript$b = "Nascondi trascrizione"; 16956 var turnOnDescriptions$b = "Attiva audiodescrizioni"; 16957 var turnOffDescriptions$b = "Disattiva audiodescrizioni"; 16958 var chapters$b = "Capitoli"; 16959 var language$b = "Lingua"; 16960 var sign$b = "Lingua dei segni"; 16961 var showSign$b = "Mostra lingua dei segni"; 16962 var hideSign$b = "Nascondi lingua dei segni"; 16963 var seekbarLabel$b = "Tempo trascorso"; 16964 var mute$b = "Muto"; 16965 var unmute$b = "Riattiva l'audio"; 16966 var volume$b = "Volume"; 16967 var volumeUpDown$b = "Incremento e diminuzione volume"; 16968 var preferences$b = "Preferenze"; 16969 var enterFullScreen$b = "Attiva schermo intero"; 16970 var exitFullScreen$b = "Disattiva schermo intero"; 16971 var speed$b = "Velocità "; 16972 var spacebar$b = "barra spaziatrice"; 16973 var transcriptTitle$b = "Trascrizione"; 16974 var lyricsTitle$b = "Testi"; 16975 var autoScroll$b = "Auto scorrimento"; 16976 var statusPlaying$b = "In riproduzione"; 16977 var statusPaused$b = "In pausa"; 16978 var statusStopped$b = "Fermato"; 16979 var statusBuffering$b = "Buffering"; 16980 var statusEnd$b = "Fine traccia"; 16981 var selectedTrack$b = "Traccia selezionata"; 16982 var alertDescribedVersion$b = "Sto usando la versione audiodescritta di questo video"; 16983 var alertNonDescribedVersion$b = "Sto usando la versione non audiodescritta di questo video"; 16984 var prefMenuCaptions$b = "Sottotitoli"; 16985 var prefVoicedCaptions$b = "Spoken Captions"; 16986 var prefMenuDescriptions$b = "Audioescrizioni"; 16987 var prefMenuKeyboard$b = "Tastiera"; 16988 var prefMenuTranscript$b = "Trascrizione"; 16989 var prefTitleCaptions$b = "Preferenze sottotitoli"; 16990 var prefTitleDescriptions$b = "Preferenze audiodescrizione"; 16991 var prefTitleKeyboard$b = "Preferenze tastiera"; 16992 var prefTitleTranscript$b = "Preferenze trascrizione"; 16993 var prefIntroDescription1$b = "Questo lettore multimediale supporta le audiodescrizioni in due modi:"; 16994 var prefDescription1$b = "il video corrente ha Una versione di descrizione alternativa, Descrizione testuale."; 16995 var prefDescription2$b = "il video corrente ha Versione di descrizione alternativa per il video."; 16996 var prefDescription3$b = "il video corrente ha Descrizione testuale, letta dal lettore di schermo."; 16997 var prefIntroDescriptionNone$b = "Il video corrente non ha audiodescrizioni."; 16998 var prefIntroDescription3$b = "Usa il seguente modulo per impostare le tue preferenze relative all'audiodescrizione testuale."; 16999 var prefIntroDescription4$b = "Dopo aver salvato le tue impostazioni, le audiodescrizioni possono essere attivate o disattivate usando i pulsanti descrizione."; 17000 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."; 17001 var prefIntroKeyboard2$b = "I tasti di scelta rapida, (Shift, Alt, e Control) possono essere assegnati di seguito."; 17002 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."; 17003 var prefHeadingKeyboard1$b = "Tasti di scelta rapida"; 17004 var prefHeadingKeyboard2$b = "Tasti di scelta rapida correnti"; 17005 var prefHeadingDescription$b = "Audiodescrizione"; 17006 var prefHeadingTextDescription$b = "audiodescrizione testuale"; 17007 var prefAltKey$b = "Alt"; 17008 var prefCtrlKey$b = "Control"; 17009 var prefShiftKey$b = "Shift"; 17010 var prefNoKeyShortcuts$b = "Disable keyboard shortcuts"; 17011 var escapeKey$b = "Escape"; 17012 var escapeKeyFunction$b = "Chiude il menu o la finestra corrente"; 17013 var prefDescPause$b = "Mette automaticamente il video in pausa, quando inizia la descrizione"; 17014 var prefDescVisible$b = "Rende la descrizione visibile"; 17015 var prefDescVoice$b = "Voice"; 17016 var prefDescRate$b = "Spoken Description Rate"; 17017 var prefCaptionRate$b = "Spoken Caption Rate"; 17018 var prefDescPitch$b = "Pitch"; 17019 var prefDescPitch1$b = "Very low"; 17020 var prefDescPitch2$b = "Low"; 17021 var prefDescPitch3$b = "Default"; 17022 var prefDescPitch4$b = "High"; 17023 var prefDescPitch5$b = "Very high"; 17024 var sampleDescriptionText$b = "Adjust settings to hear this sample text."; 17025 var prefHighlight$b = "Evidenzia la descrizione"; 17026 var prefTabbable$b = "Abilita la descrizione"; 17027 var prefCaptionsFont$b = "Carattere"; 17028 var prefCaptionsColor$b = "Colore del testo"; 17029 var prefCaptionsBGColor$b = "Colore di sfondo"; 17030 var prefCaptionsSize$b = "Dimensione del carattere"; 17031 var prefCaptionsOpacity$b = "Opacità "; 17032 var prefCaptionsStyle$b = "Stile"; 17033 var serif$b = "serif"; 17034 var sans$b = "sans-serif"; 17035 var cursive$b = "corsivo"; 17036 var fantasy$b = "fantasy"; 17037 var monospace$b = "monospace"; 17038 var white$b = "bianco"; 17039 var yellow$b = "giallo"; 17040 var green$b = "verde"; 17041 var ciano = "ciano"; 17042 var blue$b = "blue"; 17043 var magenta$b = "magenta"; 17044 var red$b = "rosso"; 17045 var black$b = "nero"; 17046 var transparent$b = "trasparente"; 17047 var solid$b = "solido"; 17048 var captionsStylePopOn$b = "A comparsa"; 17049 var captionsStyleRollUp$b = "A scorrimento"; 17050 var prefCaptionsPosition$b = "Posizione"; 17051 var captionsPositionOverlay$b = "Copre"; 17052 var captionsPositionBelow$b = "Sotto al video"; 17053 var sampleCaptionText$b = "Testo di esempio sottotitoli"; 17054 var prefSuccess$b = "I cambiamenti sono stati salvati."; 17055 var prefNoChange$b = "Non è stato effettuato alcun cambiamento."; 17056 var save$b = "Salva"; 17057 var cancel$b = "Annulla"; 17058 var dismissButton$b = "Dismiss"; 17059 var windowButtonLabel$b = "Opzioni finestra"; 17060 var windowMove$b = "Sposta"; 17061 var windowMoveLeft$b = "Window moved left"; 17062 var windowMoveRight$b = "Window moved right"; 17063 var windowMoveUp$b = "Window moved up"; 17064 var windowMoveDown$b = "Window moved down"; 17065 var windowMoveStopped$b = "Window move stopped"; 17066 var transcriptControls$b = "Transcript Window Controls"; 17067 var signControls$b = "Sign Language Window Controls"; 17068 var windowMoveAlert$b = "Trascina o usa le frecce per spostare la finestra. Invio per fermare."; 17069 var windowResize$b = "Ridimensiona"; 17070 var windowResizeHeading$b = "Ridimensiona finestra"; 17071 var closeButtonLabel$b = "Chiudi"; 17072 var width$b = "Larghezza"; 17073 var height$b = "Altezza"; 17074 var resultsSummary1$b = "You searched for:"; 17075 var resultsSummary2$b = "Found %1 matching items."; 17076 var resultsSummary3$b = "Click the time associated with any item to play the video from that point."; 17077 var noResultsFound$b = "No results found."; 17078 var searchButtonLabel$b = "Play at %1"; 17079 var hour$b = "hour"; 17080 var minute$b = "minute"; 17081 var second$b = "second"; 17082 var hours$b = "hours"; 17083 var minutes$b = "minutes"; 17084 var seconds$b = "seconds"; 17085 var vtsHeading$b = "Video Transcript Sorter"; 17086 var vtsInstructions1$b = "Use the Video Transcript Sorter to modify text tracks:"; 17087 var vtsInstructions2$b = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17088 var vtsInstructions3$b = "Modify content or start/end times (all are directly editable within the table)."; 17089 var vtsInstructions4$b = "Add new content, such as chapters or descriptions."; 17090 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."; 17091 var vtsSelectLanguage$b = "Select a language"; 17092 var vtsSave$b = "Generate new .vtt content"; 17093 var vtsReturn$b = "Return to Editor"; 17094 var vtsCancel$b = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17095 var vtsRow$b = "Row"; 17096 var vtsKind$b = "Kind"; 17097 var vtsStart$b = "Start"; 17098 var vtsEnd$b = "End"; 17099 var vtsContent$b = "Content"; 17100 var vtsActions$b = "Actions"; 17101 var vtsNewRow$b = "A new row %1 has been inserted."; 17102 var vtsDeletedRow$b = "Row %1 has been deleted."; 17103 var vtsMovedRow$b = "Row %1 has been moved %2 and is now Row %3."; 17104 var it = { 17105 playerHeading: playerHeading$b, 17106 audioPlayer: audioPlayer$b, 17107 videoPlayer: videoPlayer$b, 17108 faster: faster$b, 17109 slower: slower$b, 17110 play: play$b, 17111 pause: pause$b, 17112 restart: restart$b, 17113 prevTrack: prevTrack$b, 17114 nextTrack: nextTrack$b, 17115 rewind: rewind$b, 17116 forward: forward$b, 17117 captions: captions$b, 17118 showCaptions: showCaptions$b, 17119 hideCaptions: hideCaptions$b, 17120 captionsOff: captionsOff$b, 17121 showTranscript: showTranscript$b, 17122 hideTranscript: hideTranscript$b, 17123 turnOnDescriptions: turnOnDescriptions$b, 17124 turnOffDescriptions: turnOffDescriptions$b, 17125 chapters: chapters$b, 17126 language: language$b, 17127 sign: sign$b, 17128 showSign: showSign$b, 17129 hideSign: hideSign$b, 17130 seekbarLabel: seekbarLabel$b, 17131 mute: mute$b, 17132 unmute: unmute$b, 17133 volume: volume$b, 17134 volumeUpDown: volumeUpDown$b, 17135 preferences: preferences$b, 17136 enterFullScreen: enterFullScreen$b, 17137 exitFullScreen: exitFullScreen$b, 17138 speed: speed$b, 17139 spacebar: spacebar$b, 17140 transcriptTitle: transcriptTitle$b, 17141 lyricsTitle: lyricsTitle$b, 17142 autoScroll: autoScroll$b, 17143 statusPlaying: statusPlaying$b, 17144 statusPaused: statusPaused$b, 17145 statusStopped: statusStopped$b, 17146 statusBuffering: statusBuffering$b, 17147 statusEnd: statusEnd$b, 17148 selectedTrack: selectedTrack$b, 17149 alertDescribedVersion: alertDescribedVersion$b, 17150 alertNonDescribedVersion: alertNonDescribedVersion$b, 17151 prefMenuCaptions: prefMenuCaptions$b, 17152 prefVoicedCaptions: prefVoicedCaptions$b, 17153 prefMenuDescriptions: prefMenuDescriptions$b, 17154 prefMenuKeyboard: prefMenuKeyboard$b, 17155 prefMenuTranscript: prefMenuTranscript$b, 17156 prefTitleCaptions: prefTitleCaptions$b, 17157 prefTitleDescriptions: prefTitleDescriptions$b, 17158 prefTitleKeyboard: prefTitleKeyboard$b, 17159 prefTitleTranscript: prefTitleTranscript$b, 17160 prefIntroDescription1: prefIntroDescription1$b, 17161 prefDescription1: prefDescription1$b, 17162 prefDescription2: prefDescription2$b, 17163 prefDescription3: prefDescription3$b, 17164 prefIntroDescriptionNone: prefIntroDescriptionNone$b, 17165 prefIntroDescription3: prefIntroDescription3$b, 17166 prefIntroDescription4: prefIntroDescription4$b, 17167 prefIntroKeyboard1: prefIntroKeyboard1$b, 17168 prefIntroKeyboard2: prefIntroKeyboard2$b, 17169 prefIntroKeyboard3: prefIntroKeyboard3$b, 17170 prefHeadingKeyboard1: prefHeadingKeyboard1$b, 17171 prefHeadingKeyboard2: prefHeadingKeyboard2$b, 17172 prefHeadingDescription: prefHeadingDescription$b, 17173 prefHeadingTextDescription: prefHeadingTextDescription$b, 17174 prefAltKey: prefAltKey$b, 17175 prefCtrlKey: prefCtrlKey$b, 17176 prefShiftKey: prefShiftKey$b, 17177 prefNoKeyShortcuts: prefNoKeyShortcuts$b, 17178 escapeKey: escapeKey$b, 17179 escapeKeyFunction: escapeKeyFunction$b, 17180 prefDescPause: prefDescPause$b, 17181 prefDescVisible: prefDescVisible$b, 17182 prefDescVoice: prefDescVoice$b, 17183 prefDescRate: prefDescRate$b, 17184 prefCaptionRate: prefCaptionRate$b, 17185 prefDescPitch: prefDescPitch$b, 17186 prefDescPitch1: prefDescPitch1$b, 17187 prefDescPitch2: prefDescPitch2$b, 17188 prefDescPitch3: prefDescPitch3$b, 17189 prefDescPitch4: prefDescPitch4$b, 17190 prefDescPitch5: prefDescPitch5$b, 17191 sampleDescriptionText: sampleDescriptionText$b, 17192 prefHighlight: prefHighlight$b, 17193 prefTabbable: prefTabbable$b, 17194 prefCaptionsFont: prefCaptionsFont$b, 17195 prefCaptionsColor: prefCaptionsColor$b, 17196 prefCaptionsBGColor: prefCaptionsBGColor$b, 17197 prefCaptionsSize: prefCaptionsSize$b, 17198 prefCaptionsOpacity: prefCaptionsOpacity$b, 17199 prefCaptionsStyle: prefCaptionsStyle$b, 17200 serif: serif$b, 17201 sans: sans$b, 17202 cursive: cursive$b, 17203 fantasy: fantasy$b, 17204 monospace: monospace$b, 17205 white: white$b, 17206 yellow: yellow$b, 17207 green: green$b, 17208 ciano: ciano, 17209 blue: blue$b, 17210 magenta: magenta$b, 17211 red: red$b, 17212 black: black$b, 17213 transparent: transparent$b, 17214 solid: solid$b, 17215 captionsStylePopOn: captionsStylePopOn$b, 17216 captionsStyleRollUp: captionsStyleRollUp$b, 17217 prefCaptionsPosition: prefCaptionsPosition$b, 17218 captionsPositionOverlay: captionsPositionOverlay$b, 17219 captionsPositionBelow: captionsPositionBelow$b, 17220 sampleCaptionText: sampleCaptionText$b, 17221 prefSuccess: prefSuccess$b, 17222 prefNoChange: prefNoChange$b, 17223 save: save$b, 17224 cancel: cancel$b, 17225 dismissButton: dismissButton$b, 17226 windowButtonLabel: windowButtonLabel$b, 17227 windowMove: windowMove$b, 17228 windowMoveLeft: windowMoveLeft$b, 17229 windowMoveRight: windowMoveRight$b, 17230 windowMoveUp: windowMoveUp$b, 17231 windowMoveDown: windowMoveDown$b, 17232 windowMoveStopped: windowMoveStopped$b, 17233 transcriptControls: transcriptControls$b, 17234 signControls: signControls$b, 17235 windowMoveAlert: windowMoveAlert$b, 17236 windowResize: windowResize$b, 17237 windowResizeHeading: windowResizeHeading$b, 17238 closeButtonLabel: closeButtonLabel$b, 17239 width: width$b, 17240 height: height$b, 17241 resultsSummary1: resultsSummary1$b, 17242 resultsSummary2: resultsSummary2$b, 17243 resultsSummary3: resultsSummary3$b, 17244 noResultsFound: noResultsFound$b, 17245 searchButtonLabel: searchButtonLabel$b, 17246 hour: hour$b, 17247 minute: minute$b, 17248 second: second$b, 17249 hours: hours$b, 17250 minutes: minutes$b, 17251 seconds: seconds$b, 17252 vtsHeading: vtsHeading$b, 17253 vtsInstructions1: vtsInstructions1$b, 17254 vtsInstructions2: vtsInstructions2$b, 17255 vtsInstructions3: vtsInstructions3$b, 17256 vtsInstructions4: vtsInstructions4$b, 17257 vtsInstructions5: vtsInstructions5$b, 17258 vtsSelectLanguage: vtsSelectLanguage$b, 17259 vtsSave: vtsSave$b, 17260 vtsReturn: vtsReturn$b, 17261 vtsCancel: vtsCancel$b, 17262 vtsRow: vtsRow$b, 17263 vtsKind: vtsKind$b, 17264 vtsStart: vtsStart$b, 17265 vtsEnd: vtsEnd$b, 17266 vtsContent: vtsContent$b, 17267 vtsActions: vtsActions$b, 17268 vtsNewRow: vtsNewRow$b, 17269 vtsDeletedRow: vtsDeletedRow$b, 17270 vtsMovedRow: vtsMovedRow$b 17271 }; 17272 17273 var playerHeading$a = "ã¡ãã£ã¢ãã¬ã¤ã¤ã¼"; 17274 var audioPlayer$a = "Audio player"; 17275 var videoPlayer$a = "Video player"; 17276 var faster$a = "ã¯ãã"; 17277 var slower$a = "ããã"; 17278 var play$a = "åç"; 17279 var pause$a = "䏿忢"; 17280 var restart$a = "åé"; 17281 var prevTrack$a = "åã®ãã©ãã¯"; 17282 var nextTrack$a = "次ã®ãã©ãã¯"; 17283 var rewind$a = "å·»ãæ»ã"; 17284 var forward$a = "æ©éã"; 17285 var captions$a = "ãã£ãã·ã§ã³"; 17286 var showCaptions$a = "ãã£ãã·ã§ã³ã表示ãã"; 17287 var hideCaptions$a = "ãã£ãã·ã§ã³ãé表示ã«ãã"; 17288 var captionsOff$a = "ãã£ãã·ã§ã³ãæ¶ã"; 17289 var showTranscript$a = "æ¸ãèµ·ããã®è¡¨ç¤º"; 17290 var hideTranscript$a = "æ¸ãèµ·ãããé表示ã«ãã"; 17291 var turnOnDescriptions$a = "é³å£°è§£èª¬ãåºã"; 17292 var turnOffDescriptions$a = "é³å£°è§£èª¬ãåºããªã"; 17293 var chapters$a = "ãã£ãã¿ã¼"; 17294 var language$a = "è¨èª"; 17295 var sign$a = "æè©±"; 17296 var showSign$a = "æè©±ã表示"; 17297 var hideSign$a = "æè©±ãé表示"; 17298 var seekbarLabel$a = "ã¿ã¤ã ã©ã¤ã³"; 17299 var mute$a = "æ¶é³"; 17300 var unmute$a = "æ¶é³è§£é¤"; 17301 var volume$a = "é³é"; 17302 var volumeUpDown$a = "é³éã®ä¸ä¸"; 17303 var preferences$a = "è¨å®"; 17304 var enterFullScreen$a = "å ¨ç»é¢è¡¨ç¤º"; 17305 var exitFullScreen$a = "å ¨ç»é¢è¡¨ç¤ºã®çµäº"; 17306 var speed$a = "åçé度"; 17307 var spacebar$a = "ã¹ãã¼ã¹ãã¼"; 17308 var transcriptTitle$a = "æ¸ãèµ·ãã"; 17309 var lyricsTitle$a = "æè©"; 17310 var autoScroll$a = "èªåã¹ã¯ãã¼ã«"; 17311 var statusPlaying$a = "åçä¸"; 17312 var statusPaused$a = "䏿忢ä¸"; 17313 var statusStopped$a = "忢"; 17314 var statusBuffering$a = "ãããã¡ãªã³ã°ä¸"; 17315 var statusEnd$a = "ãã©ãã¯ã®çµãã"; 17316 var selectedTrack$a = "鏿ããããã©ãã¯"; 17317 var alertDescribedVersion$a = "ãã®åç»ã®é³å£°è§£èª¬ä»ããã¼ã¸ã§ã³ã使ãã¾ã"; 17318 var alertNonDescribedVersion$a = "ãã®åç»ã®è§£èª¬ãªããã¼ã¸ã§ã³ã使ãã¾ã"; 17319 var prefMenuCaptions$a = "ãã£ãã·ã§ã³"; 17320 var prefVoicedCaptions$a = "Spoken Captions"; 17321 var prefMenuDescriptions$a = "é³å£°è§£èª¬"; 17322 var prefMenuKeyboard$a = "ãã¼ãã¼ã"; 17323 var prefMenuTranscript$a = "åå¹"; 17324 var prefTitleCaptions$a = "ãã£ãã·ã§ã³ã®è¨å®"; 17325 var prefTitleDescriptions$a = "é³å£°è§£èª¬ã®è¨å®"; 17326 var prefTitleKeyboard$a = "ãã¼ãã¼ãã®è¨å®"; 17327 var prefTitleTranscript$a = "åå¹ã®è¨å®"; 17328 var prefIntroDescription1$a = "ãã®ã¡ãã£ã¢ãã¬ã¤ã¤ã¼ã¯æ¬¡ã®2ã¤ã®æ¹æ³ã§é³å£°è§£èª¬ããµãã¼ããã¾ã: "; 17329 var prefDescription1$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: 解説ä»ãã®ä»£æ¿ãã¼ã¸ã§ã³ ããã¹ãã«ãã解説"; 17330 var prefDescription2$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: 解説ä»ãã®ä»£æ¿ãã¼ã¸ã§ã³ã®ãããª"; 17331 var prefDescription3$a = "ç¾å¨ã®åç»ã§ã¯æ¬¡ã®æ¹æ³ã鏿å¯è½ã§ã: ããã¹ãã«ãã解説(ã¹ã¯ãªã¼ã³ãªã¼ãã¼ã«ãã£ã¦èªã¿ä¸ãããã)"; 17332 var prefIntroDescriptionNone$a = "ç¾å¨ã®åç»ã«ã¯ã©ã¡ãã®å½¢å¼ã®é³å£°è§£èª¬ãå«ã¾ãã¦ãã¾ããã"; 17333 var prefIntroDescription3$a = "次ã®ãã©ã¼ã ã使ã£ã¦ãé³å£°è§£èª¬ã«é¢é£ããè¨å®ãä¿åã§ãã¾ãã"; 17334 var prefIntroDescription4$a = "è¨å®ãä¿åãããããé³å£°è§£èª¬ãã¿ã³ã«ãã£ã¦é³å£°è§£èª¬ã®è¡¨ç¤ºã»é表示ãåãæ¿ãããã¨ãã§ãã¾ãã"; 17335 var prefIntroKeyboard1$a = "ãã®ãã¼ã¸ã®ã¡ãã£ã¢ãã¬ã¤ã¤ã¼ã¯ããã¼ãã¼ãã»ã·ã§ã¼ãã«ããã使ã£ã¦ãã®ãã¼ã¸ã®ã©ãããã§ãæä½ã§ãã¾ã(ä¸ã®ä¸è¦§ãåç §ãã¦ãã ãã)ã"; 17336 var prefIntroKeyboard2$a = "修飾ãã¼(ShiftãAlt 㨠Control) ã¯ä»¥ä¸ã§å²ãå½ã¦ããã¨ãã§ãã¾ãã"; 17337 var prefIntroKeyboard3$a = "注æ: ããã¤ãã®ãã¼ã®çµã¿åããã¯ããã©ã¦ã¶ãã¢ããªã±ã¼ã·ã§ã³ã»ã½ããã§ä½¿ããã¦ãããã®ã¨è¡çªããå¯è½æ§ãããã¾ãããå©ç¨ã®ç°å¢ã§æ£ããåä½ãããæ§ã ãªãã¼ã®çµã¿åããã試ãã¦ãã ããã"; 17338 var prefHeadingKeyboard1$a = "ã·ã§ã¼ãã«ããã®ä¿®é£¾ãã¼"; 17339 var prefHeadingKeyboard2$a = "ç¾å¨ã®ãã¼ãã¼ãã»ã·ã§ã¼ãã«ãã"; 17340 var prefHeadingDescription$a = "é³å£°è§£èª¬"; 17341 var prefHeadingTextDescription$a = "ããã¹ãã«ããé³å£°è§£èª¬"; 17342 var prefAltKey$a = "Alt"; 17343 var prefCtrlKey$a = "Control"; 17344 var prefShiftKey$a = "Shift"; 17345 var prefNoKeyShortcuts$a = "Disable keyboard shortcuts"; 17346 var escapeKey$a = "ã¨ã¹ã±ã¼ã"; 17347 var escapeKeyFunction$a = "ç¾å¨ã®ãã¤ã¢ãã°ããããã¢ããã¡ãã¥ã¼ãéãã"; 17348 var prefDescPause$a = "解説ã表示ããããåç»ãèªåçã«åæ¢ãã"; 17349 var prefDescVisible$a = "解説ãè¦ããããã«ãã"; 17350 var prefDescVoice$a = "é³"; 17351 var prefDescRate$a = "Spoken Description Rate"; 17352 var prefCaptionRate$a = "Spoken Caption Rate"; 17353 var prefDescPitch$a = "ããã"; 17354 var prefDescPitch1$a = "é常ã«ä½ã"; 17355 var prefDescPitch2$a = "ä½ã"; 17356 var prefDescPitch3$a = "æ¨æº"; 17357 var prefDescPitch4$a = "é«ã"; 17358 var prefDescPitch5$a = "é常ã«é«ã"; 17359 var sampleDescriptionText$a = "ãã®ãµã³ãã«ã»ããã¹ããèãããã«è¨å®ãåããã¾ãã"; 17360 var prefHighlight$a = "ã¡ãã£ã¢ã®åçã«åããã¦åå¹ããã¤ã©ã¤ããã"; 17361 var prefTabbable$a = "ãã¼ãã¼ãæä½å¯è½ãªåå¹"; 17362 var prefCaptionsFont$a = "ãã©ã³ã"; 17363 var prefCaptionsColor$a = "æåè²"; 17364 var prefCaptionsBGColor$a = "èæ¯è²"; 17365 var prefCaptionsSize$a = "ãã©ã³ããµã¤ãº"; 17366 var prefCaptionsOpacity$a = "ä¸éæåº¦"; 17367 var prefCaptionsStyle$a = "æ¸å¼"; 17368 var serif$a = "ã»ãªã"; 17369 var sans$a = "ãµã³ã»ãªã"; 17370 var cursive$a = "cursive"; 17371 var fantasy$a = "fantasy"; 17372 var monospace$a = "monospace"; 17373 var white$a = "ç½"; 17374 var yellow$a = "é»è²"; 17375 var green$a = "ç·"; 17376 var cyan$a = "ã·ã¢ã³"; 17377 var blue$a = "é"; 17378 var magenta$a = "ãã¼ã³ã¿"; 17379 var red$a = "赤"; 17380 var black$a = "é»"; 17381 var transparent$a = "éæ"; 17382 var solid$a = "ä¸éæ"; 17383 var captionsStylePopOn$a = "ãããã»ãªã³"; 17384 var captionsStyleRollUp$a = "ãã¼ã«ã»ã¢ãã"; 17385 var prefCaptionsPosition$a = "ä½ç½®"; 17386 var captionsPositionOverlay$a = "ãªã¼ãã¼ã¬ã¤è¡¨ç¤º"; 17387 var captionsPositionBelow$a = "åç»ã®ä¸é¨"; 17388 var sampleCaptionText$a = "ãã£ãã·ã§ã³è¡¨ç¤ºã®ä¾"; 17389 var prefSuccess$a = "夿´ãä¿åããã¾ããã"; 17390 var prefNoChange$a = "è¨å®ã夿´ããã¦ãã¾ããã"; 17391 var save$a = "ä¿å"; 17392 var cancel$a = "ãã£ã³ã»ã«"; 17393 var dismissButton$a = "Dismiss"; 17394 var windowButtonLabel$a = "ã¦ã£ã³ãã¦ã®è¨å®"; 17395 var windowMove$a = "ç§»å"; 17396 var windowMoveLeft$a = "Window moved left"; 17397 var windowMoveRight$a = "Window moved right"; 17398 var windowMoveUp$a = "Window moved up"; 17399 var windowMoveDown$a = "Window moved down"; 17400 var windowMoveStopped$a = "Window move stopped"; 17401 var transcriptControls$a = "Transcript Window Controls"; 17402 var signControls$a = "Sign Language Window Controls"; 17403 var windowMoveAlert$a = "ãã¦ã¹ããã©ãã°ãããç¢å°ãã¼ã§ã¦ã£ã³ãã¦ãç§»åã§ãã¾ã; Enterã§çµäº"; 17404 var windowResize$a = "ãµã¤ãºãå¤ãã"; 17405 var windowResizeHeading$a = "ã¦ã£ã³ãã¦ã®ãµã¤ãºå¤æ´"; 17406 var closeButtonLabel$a = "éãã"; 17407 var width$a = "å¹ "; 17408 var height$a = "é«ã"; 17409 var resultsSummary1$a = "æ¬¡ãæ¤ç´¢:"; 17410 var resultsSummary2$a = "è¦ã¤ããã¾ãã %1 åã®æ¤ç´¢çµæ"; 17411 var resultsSummary3$a = "ã¢ã¤ãã ã«é¢é£ä»ãããã¦ããæéãã¯ãªãã¯ãã¦ããã®æç¹ãããããªãåçãã¾ãã"; 17412 var noResultsFound$a = "è¦ã¤ããã¾ããã§ããã"; 17413 var searchButtonLabel$a = "次ããåç %1"; 17414 var hour$a = "æé"; 17415 var minute$a = "å"; 17416 var second$a = "ç§"; 17417 var hours$a = "æé"; 17418 var minutes$a = "å"; 17419 var seconds$a = "ç§"; 17420 var vtsHeading$a = "Video Transcript Sorter"; 17421 var vtsInstructions1$a = "Use the Video Transcript Sorter to modify text tracks:"; 17422 var vtsInstructions2$a = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17423 var vtsInstructions3$a = "Modify content or start/end times (all are directly editable within the table)."; 17424 var vtsInstructions4$a = "Add new content, such as chapters or descriptions."; 17425 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."; 17426 var vtsSelectLanguage$a = "Select a language"; 17427 var vtsSave$a = "Generate new .vtt content"; 17428 var vtsReturn$a = "Return to Editor"; 17429 var vtsCancel$a = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17430 var vtsRow$a = "Row"; 17431 var vtsKind$a = "Kind"; 17432 var vtsStart$a = "Start"; 17433 var vtsEnd$a = "End"; 17434 var vtsContent$a = "Content"; 17435 var vtsActions$a = "Actions"; 17436 var vtsNewRow$a = "A new row %1 has been inserted."; 17437 var vtsDeletedRow$a = "Row %1 has been deleted."; 17438 var vtsMovedRow$a = "Row %1 has been moved %2 and is now Row %3."; 17439 var ja = { 17440 playerHeading: playerHeading$a, 17441 audioPlayer: audioPlayer$a, 17442 videoPlayer: videoPlayer$a, 17443 faster: faster$a, 17444 slower: slower$a, 17445 play: play$a, 17446 pause: pause$a, 17447 restart: restart$a, 17448 prevTrack: prevTrack$a, 17449 nextTrack: nextTrack$a, 17450 rewind: rewind$a, 17451 forward: forward$a, 17452 captions: captions$a, 17453 showCaptions: showCaptions$a, 17454 hideCaptions: hideCaptions$a, 17455 captionsOff: captionsOff$a, 17456 showTranscript: showTranscript$a, 17457 hideTranscript: hideTranscript$a, 17458 turnOnDescriptions: turnOnDescriptions$a, 17459 turnOffDescriptions: turnOffDescriptions$a, 17460 chapters: chapters$a, 17461 language: language$a, 17462 sign: sign$a, 17463 showSign: showSign$a, 17464 hideSign: hideSign$a, 17465 seekbarLabel: seekbarLabel$a, 17466 mute: mute$a, 17467 unmute: unmute$a, 17468 volume: volume$a, 17469 volumeUpDown: volumeUpDown$a, 17470 preferences: preferences$a, 17471 enterFullScreen: enterFullScreen$a, 17472 exitFullScreen: exitFullScreen$a, 17473 speed: speed$a, 17474 spacebar: spacebar$a, 17475 transcriptTitle: transcriptTitle$a, 17476 lyricsTitle: lyricsTitle$a, 17477 autoScroll: autoScroll$a, 17478 statusPlaying: statusPlaying$a, 17479 statusPaused: statusPaused$a, 17480 statusStopped: statusStopped$a, 17481 statusBuffering: statusBuffering$a, 17482 statusEnd: statusEnd$a, 17483 selectedTrack: selectedTrack$a, 17484 alertDescribedVersion: alertDescribedVersion$a, 17485 alertNonDescribedVersion: alertNonDescribedVersion$a, 17486 prefMenuCaptions: prefMenuCaptions$a, 17487 prefVoicedCaptions: prefVoicedCaptions$a, 17488 prefMenuDescriptions: prefMenuDescriptions$a, 17489 prefMenuKeyboard: prefMenuKeyboard$a, 17490 prefMenuTranscript: prefMenuTranscript$a, 17491 prefTitleCaptions: prefTitleCaptions$a, 17492 prefTitleDescriptions: prefTitleDescriptions$a, 17493 prefTitleKeyboard: prefTitleKeyboard$a, 17494 prefTitleTranscript: prefTitleTranscript$a, 17495 prefIntroDescription1: prefIntroDescription1$a, 17496 prefDescription1: prefDescription1$a, 17497 prefDescription2: prefDescription2$a, 17498 prefDescription3: prefDescription3$a, 17499 prefIntroDescriptionNone: prefIntroDescriptionNone$a, 17500 prefIntroDescription3: prefIntroDescription3$a, 17501 prefIntroDescription4: prefIntroDescription4$a, 17502 prefIntroKeyboard1: prefIntroKeyboard1$a, 17503 prefIntroKeyboard2: prefIntroKeyboard2$a, 17504 prefIntroKeyboard3: prefIntroKeyboard3$a, 17505 prefHeadingKeyboard1: prefHeadingKeyboard1$a, 17506 prefHeadingKeyboard2: prefHeadingKeyboard2$a, 17507 prefHeadingDescription: prefHeadingDescription$a, 17508 prefHeadingTextDescription: prefHeadingTextDescription$a, 17509 prefAltKey: prefAltKey$a, 17510 prefCtrlKey: prefCtrlKey$a, 17511 prefShiftKey: prefShiftKey$a, 17512 prefNoKeyShortcuts: prefNoKeyShortcuts$a, 17513 escapeKey: escapeKey$a, 17514 escapeKeyFunction: escapeKeyFunction$a, 17515 prefDescPause: prefDescPause$a, 17516 prefDescVisible: prefDescVisible$a, 17517 prefDescVoice: prefDescVoice$a, 17518 prefDescRate: prefDescRate$a, 17519 prefCaptionRate: prefCaptionRate$a, 17520 prefDescPitch: prefDescPitch$a, 17521 prefDescPitch1: prefDescPitch1$a, 17522 prefDescPitch2: prefDescPitch2$a, 17523 prefDescPitch3: prefDescPitch3$a, 17524 prefDescPitch4: prefDescPitch4$a, 17525 prefDescPitch5: prefDescPitch5$a, 17526 sampleDescriptionText: sampleDescriptionText$a, 17527 prefHighlight: prefHighlight$a, 17528 prefTabbable: prefTabbable$a, 17529 prefCaptionsFont: prefCaptionsFont$a, 17530 prefCaptionsColor: prefCaptionsColor$a, 17531 prefCaptionsBGColor: prefCaptionsBGColor$a, 17532 prefCaptionsSize: prefCaptionsSize$a, 17533 prefCaptionsOpacity: prefCaptionsOpacity$a, 17534 prefCaptionsStyle: prefCaptionsStyle$a, 17535 serif: serif$a, 17536 sans: sans$a, 17537 cursive: cursive$a, 17538 fantasy: fantasy$a, 17539 monospace: monospace$a, 17540 white: white$a, 17541 yellow: yellow$a, 17542 green: green$a, 17543 cyan: cyan$a, 17544 blue: blue$a, 17545 magenta: magenta$a, 17546 red: red$a, 17547 black: black$a, 17548 transparent: transparent$a, 17549 solid: solid$a, 17550 captionsStylePopOn: captionsStylePopOn$a, 17551 captionsStyleRollUp: captionsStyleRollUp$a, 17552 prefCaptionsPosition: prefCaptionsPosition$a, 17553 captionsPositionOverlay: captionsPositionOverlay$a, 17554 captionsPositionBelow: captionsPositionBelow$a, 17555 sampleCaptionText: sampleCaptionText$a, 17556 prefSuccess: prefSuccess$a, 17557 prefNoChange: prefNoChange$a, 17558 save: save$a, 17559 cancel: cancel$a, 17560 dismissButton: dismissButton$a, 17561 windowButtonLabel: windowButtonLabel$a, 17562 windowMove: windowMove$a, 17563 windowMoveLeft: windowMoveLeft$a, 17564 windowMoveRight: windowMoveRight$a, 17565 windowMoveUp: windowMoveUp$a, 17566 windowMoveDown: windowMoveDown$a, 17567 windowMoveStopped: windowMoveStopped$a, 17568 transcriptControls: transcriptControls$a, 17569 signControls: signControls$a, 17570 windowMoveAlert: windowMoveAlert$a, 17571 windowResize: windowResize$a, 17572 windowResizeHeading: windowResizeHeading$a, 17573 closeButtonLabel: closeButtonLabel$a, 17574 width: width$a, 17575 height: height$a, 17576 resultsSummary1: resultsSummary1$a, 17577 resultsSummary2: resultsSummary2$a, 17578 resultsSummary3: resultsSummary3$a, 17579 noResultsFound: noResultsFound$a, 17580 searchButtonLabel: searchButtonLabel$a, 17581 hour: hour$a, 17582 minute: minute$a, 17583 second: second$a, 17584 hours: hours$a, 17585 minutes: minutes$a, 17586 seconds: seconds$a, 17587 vtsHeading: vtsHeading$a, 17588 vtsInstructions1: vtsInstructions1$a, 17589 vtsInstructions2: vtsInstructions2$a, 17590 vtsInstructions3: vtsInstructions3$a, 17591 vtsInstructions4: vtsInstructions4$a, 17592 vtsInstructions5: vtsInstructions5$a, 17593 vtsSelectLanguage: vtsSelectLanguage$a, 17594 vtsSave: vtsSave$a, 17595 vtsReturn: vtsReturn$a, 17596 vtsCancel: vtsCancel$a, 17597 vtsRow: vtsRow$a, 17598 vtsKind: vtsKind$a, 17599 vtsStart: vtsStart$a, 17600 vtsEnd: vtsEnd$a, 17601 vtsContent: vtsContent$a, 17602 vtsActions: vtsActions$a, 17603 vtsNewRow: vtsNewRow$a, 17604 vtsDeletedRow: vtsDeletedRow$a, 17605 vtsMovedRow: vtsMovedRow$a 17606 }; 17607 17608 var playerHeading$9 = "Pemain media"; 17609 var audioPlayer$9 = "Audio player"; 17610 var videoPlayer$9 = "Video player"; 17611 var faster$9 = "Lebih laju"; 17612 var slower$9 = "Lebih perlahan"; 17613 var play$9 = "Main"; 17614 var pause$9 = "Jeda"; 17615 var restart$9 = "Mula semula"; 17616 var prevTrack$9 = "Trek sebelumnya"; 17617 var nextTrack$9 = "Trek seterusnya"; 17618 var rewind$9 = "Undur"; 17619 var forward$9 = "Majukan"; 17620 var captions$9 = "Sarikata"; 17621 var showCaptions$9 = "Tunjuk sarikata"; 17622 var hideCaptions$9 = "Sembunyi sarikata"; 17623 var captionsOff$9 = "Sarikata dimatikan"; 17624 var showTranscript$9 = "Tunjuk transkrip"; 17625 var hideTranscript$9 = "Sembunyi transkrip"; 17626 var turnOnDescriptions$9 = "Hidupkan deskripsi"; 17627 var turnOffDescriptions$9 = "Matikan deskripsi"; 17628 var chapters$9 = "Bab"; 17629 var language$9 = "Bahasa"; 17630 var sign$9 = "Bahasa isyarat"; 17631 var showSign$9 = "Tunjuk bahasa isyarat"; 17632 var hideSign$9 = "Sembunyi bahasa isyarat"; 17633 var seekbarLabel$9 = "garis masa"; 17634 var mute$9 = "Bisukan"; 17635 var unmute$9 = "Nyahbisukan"; 17636 var volume$9 = "Volum"; 17637 var volumeUpDown$9 = "Naik turun volum"; 17638 var preferences$9 = "Keutamaan"; 17639 var enterFullScreen$9 = "Masuk skrin penuh"; 17640 var exitFullScreen$9 = "Keluar skrin penuh"; 17641 var speed$9 = "Kelajuan"; 17642 var spacebar$9 = "bar ruang"; 17643 var transcriptTitle$9 = "Transkrip"; 17644 var lyricsTitle$9 = "Lirik"; 17645 var autoScroll$9 = "Skrol automatik"; 17646 var statusPlaying$9 = "Sedang dimainkan"; 17647 var statusPaused$9 = "Dijeda"; 17648 var statusStopped$9 = "Dihentikan"; 17649 var statusBuffering$9 = "Memuatkan"; 17650 var statusEnd$9 = "Akhir trek"; 17651 var selectedTrack$9 = "Trek dipilih"; 17652 var alertDescribedVersion$9 = "Menggunakan versi video dengan deskripsi audio"; 17653 var alertNonDescribedVersion$9 = "Menggunakan versi video tanpa deskripsi audio"; 17654 var prefMenuCaptions$9 = "Sarikata"; 17655 var prefVoicedCaptions$9 = "Spoken Captions"; 17656 var prefMenuDescriptions$9 = "Deskripsi"; 17657 var prefMenuKeyboard$9 = "Papan kekunci"; 17658 var prefMenuTranscript$9 = "Transkrip"; 17659 var prefTitleCaptions$9 = "Keutamaan Sarikata"; 17660 var prefTitleDescriptions$9 = "Keutamaan Deskripsi Audio"; 17661 var prefTitleKeyboard$9 = "Keutamaan Papan Kekunci"; 17662 var prefTitleTranscript$9 = "Keutamaan Transkrip"; 17663 var prefIntroDescription1$9 = "Pemain media ini menyokong deskripsi audio dalam dua cara: "; 17664 var prefDescription1$9 = "Video semasa mempunyai versi berdeskripsi alternatif, deskripsi berasaskan teks."; 17665 var prefDescription2$9 = "Video semasa mempunyai versi video berdeskripsi alternatif."; 17666 var prefDescription3$9 = "Video semasa mempunyai deskripsi berasaskan teks, diumumkan oleh pembaca skrin."; 17667 var prefIntroDescriptionNone$9 = "Video semasa tiada deskripsi audio dalam mana-mana format."; 17668 var prefIntroDescription3$9 = "Gunakan borang berikut untuk menetapkan keutamaan berkaitan deskripsi audio berasaskan teks."; 17669 var prefIntroDescription4$9 = "Selepas anda menyimpan tetapan, deskripsi audio boleh dihidupkan/dimatikan menggunakan butang Deskripsi."; 17670 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)."; 17671 var prefIntroKeyboard2$9 = "Kekunci pengubah (Shift, Alt, dan Control) boleh ditetapkan di bawah."; 17672 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."; 17673 var prefHeadingKeyboard1$9 = "Kekunci pengubah untuk pintasan"; 17674 var prefHeadingKeyboard2$9 = "Pintasan papan kekunci semasa"; 17675 var prefHeadingDescription$9 = "Deskripsi audio"; 17676 var prefHeadingTextDescription$9 = "Deskripsi audio berasaskan teks"; 17677 var prefAltKey$9 = "Alt"; 17678 var prefCtrlKey$9 = "Control"; 17679 var prefShiftKey$9 = "Shift"; 17680 var prefNoKeyShortcuts$9 = "Disable keyboard shortcuts"; 17681 var escapeKey$9 = "Escape"; 17682 var escapeKeyFunction$9 = "Tutup dialog atau menu popup semasa"; 17683 var prefDescPause$9 = "Jeda video secara automatik apabila deskripsi bermula"; 17684 var prefDescVisible$9 = "Paparkan deskripsi"; 17685 var prefDescVoice$9 = "Suara"; 17686 var prefDescRate$9 = "Spoken Description Rate"; 17687 var prefCaptionRate$9 = "Spoken Caption Rate"; 17688 var prefDescPitch$9 = "Nada"; 17689 var prefDescPitch1$9 = "Sangat rendah"; 17690 var prefDescPitch2$9 = "Rendah"; 17691 var prefDescPitch3$9 = "Lalai"; 17692 var prefDescPitch4$9 = "Tinggi"; 17693 var prefDescPitch5$9 = "Sangat tinggi"; 17694 var sampleDescriptionText$9 = "Laraskan tetapan untuk mendengar teks contoh ini."; 17695 var prefHighlight$9 = "Serlahkan transkrip semasa media dimainkan"; 17696 var prefTabbable$9 = "Aktifkan transkrip dengan papan kekunci"; 17697 var prefCaptionsFont$9 = "Fon"; 17698 var prefCaptionsColor$9 = "Warna Teks"; 17699 var prefCaptionsBGColor$9 = "Latar Belakang"; 17700 var prefCaptionsSize$9 = "Saiz Fon"; 17701 var prefCaptionsOpacity$9 = "Kelegapan"; 17702 var prefCaptionsStyle$9 = "Gaya"; 17703 var serif$9 = "serif"; 17704 var sans$9 = "sans-serif"; 17705 var cursive$9 = "cursive"; 17706 var fantasy$9 = "fantasy"; 17707 var monospace$9 = "monospace"; 17708 var white$9 = "putih"; 17709 var yellow$9 = "kuning"; 17710 var green$9 = "hijau"; 17711 var cyan$9 = "sian"; 17712 var blue$9 = "biru"; 17713 var magenta$9 = "magenta"; 17714 var red$9 = "merah"; 17715 var black$9 = "hitam"; 17716 var transparent$9 = "telus"; 17717 var solid$9 = "pepejal"; 17718 var captionsStylePopOn$9 = "Pop-on"; 17719 var captionsStyleRollUp$9 = "Roll-up"; 17720 var prefCaptionsPosition$9 = "Kedudukan"; 17721 var captionsPositionOverlay$9 = "Lapisan atas"; 17722 var captionsPositionBelow$9 = "Di bawah video"; 17723 var sampleCaptionText$9 = "Teks contoh sarikata"; 17724 var prefSuccess$9 = "Perubahan anda telah disimpan."; 17725 var prefNoChange$9 = "Anda tidak membuat sebarang perubahan."; 17726 var save$9 = "Simpan"; 17727 var cancel$9 = "Batal"; 17728 var dismissButton$9 = "Dismiss"; 17729 var windowButtonLabel$9 = "Pilihan tetingkap"; 17730 var windowMove$9 = "Alih"; 17731 var windowMoveLeft$9 = "Window moved left"; 17732 var windowMoveRight$9 = "Window moved right"; 17733 var windowMoveUp$9 = "Window moved up"; 17734 var windowMoveDown$9 = "Window moved down"; 17735 var windowMoveStopped$9 = "Window move stopped"; 17736 var transcriptControls$9 = "Transcript Window Controls"; 17737 var signControls$9 = "Sign Language Window Controls"; 17738 var windowMoveAlert$9 = "Seret atau gunakan kekunci anak panah untuk mengalih tetingkap; Enter untuk berhenti"; 17739 var windowResize$9 = "Ubah saiz"; 17740 var windowResizeHeading$9 = "Ubah Saiz Tetingkap"; 17741 var closeButtonLabel$9 = "Tutup"; 17742 var width$9 = "Lebar"; 17743 var height$9 = "Tinggi"; 17744 var resultsSummary1$9 = "Anda mencari:"; 17745 var resultsSummary2$9 = "Ditemui %1 item sepadan."; 17746 var resultsSummary3$9 = "Klik masa yang berkaitan dengan mana-mana item untuk mainkan video dari titik itu."; 17747 var noResultsFound$9 = "Tiada hasil ditemui."; 17748 var searchButtonLabel$9 = "Main pada %1"; 17749 var hour$9 = "jam"; 17750 var minute$9 = "minit"; 17751 var second$9 = "saat"; 17752 var hours$9 = "jam"; 17753 var minutes$9 = "minit"; 17754 var seconds$9 = "saat"; 17755 var vtsHeading$9 = "Video Transcript Sorter"; 17756 var vtsInstructions1$9 = "Use the Video Transcript Sorter to modify text tracks:"; 17757 var vtsInstructions2$9 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 17758 var vtsInstructions3$9 = "Modify content or start/end times (all are directly editable within the table)."; 17759 var vtsInstructions4$9 = "Add new content, such as chapters or descriptions."; 17760 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."; 17761 var vtsSelectLanguage$9 = "Select a language"; 17762 var vtsSave$9 = "Generate new .vtt content"; 17763 var vtsReturn$9 = "Return to Editor"; 17764 var vtsCancel$9 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 17765 var vtsRow$9 = "Row"; 17766 var vtsKind$9 = "Kind"; 17767 var vtsStart$9 = "Start"; 17768 var vtsEnd$9 = "End"; 17769 var vtsContent$9 = "Content"; 17770 var vtsActions$9 = "Actions"; 17771 var vtsNewRow$9 = "A new row %1 has been inserted."; 17772 var vtsDeletedRow$9 = "Row %1 has been deleted."; 17773 var vtsMovedRow$9 = "Row %1 has been moved %2 and is now Row %3."; 17774 var ms = { 17775 playerHeading: playerHeading$9, 17776 audioPlayer: audioPlayer$9, 17777 videoPlayer: videoPlayer$9, 17778 faster: faster$9, 17779 slower: slower$9, 17780 play: play$9, 17781 pause: pause$9, 17782 restart: restart$9, 17783 prevTrack: prevTrack$9, 17784 nextTrack: nextTrack$9, 17785 rewind: rewind$9, 17786 forward: forward$9, 17787 captions: captions$9, 17788 showCaptions: showCaptions$9, 17789 hideCaptions: hideCaptions$9, 17790 captionsOff: captionsOff$9, 17791 showTranscript: showTranscript$9, 17792 hideTranscript: hideTranscript$9, 17793 turnOnDescriptions: turnOnDescriptions$9, 17794 turnOffDescriptions: turnOffDescriptions$9, 17795 chapters: chapters$9, 17796 language: language$9, 17797 sign: sign$9, 17798 showSign: showSign$9, 17799 hideSign: hideSign$9, 17800 seekbarLabel: seekbarLabel$9, 17801 mute: mute$9, 17802 unmute: unmute$9, 17803 volume: volume$9, 17804 volumeUpDown: volumeUpDown$9, 17805 preferences: preferences$9, 17806 enterFullScreen: enterFullScreen$9, 17807 exitFullScreen: exitFullScreen$9, 17808 speed: speed$9, 17809 spacebar: spacebar$9, 17810 transcriptTitle: transcriptTitle$9, 17811 lyricsTitle: lyricsTitle$9, 17812 autoScroll: autoScroll$9, 17813 statusPlaying: statusPlaying$9, 17814 statusPaused: statusPaused$9, 17815 statusStopped: statusStopped$9, 17816 statusBuffering: statusBuffering$9, 17817 statusEnd: statusEnd$9, 17818 selectedTrack: selectedTrack$9, 17819 alertDescribedVersion: alertDescribedVersion$9, 17820 alertNonDescribedVersion: alertNonDescribedVersion$9, 17821 prefMenuCaptions: prefMenuCaptions$9, 17822 prefVoicedCaptions: prefVoicedCaptions$9, 17823 prefMenuDescriptions: prefMenuDescriptions$9, 17824 prefMenuKeyboard: prefMenuKeyboard$9, 17825 prefMenuTranscript: prefMenuTranscript$9, 17826 prefTitleCaptions: prefTitleCaptions$9, 17827 prefTitleDescriptions: prefTitleDescriptions$9, 17828 prefTitleKeyboard: prefTitleKeyboard$9, 17829 prefTitleTranscript: prefTitleTranscript$9, 17830 prefIntroDescription1: prefIntroDescription1$9, 17831 prefDescription1: prefDescription1$9, 17832 prefDescription2: prefDescription2$9, 17833 prefDescription3: prefDescription3$9, 17834 prefIntroDescriptionNone: prefIntroDescriptionNone$9, 17835 prefIntroDescription3: prefIntroDescription3$9, 17836 prefIntroDescription4: prefIntroDescription4$9, 17837 prefIntroKeyboard1: prefIntroKeyboard1$9, 17838 prefIntroKeyboard2: prefIntroKeyboard2$9, 17839 prefIntroKeyboard3: prefIntroKeyboard3$9, 17840 prefHeadingKeyboard1: prefHeadingKeyboard1$9, 17841 prefHeadingKeyboard2: prefHeadingKeyboard2$9, 17842 prefHeadingDescription: prefHeadingDescription$9, 17843 prefHeadingTextDescription: prefHeadingTextDescription$9, 17844 prefAltKey: prefAltKey$9, 17845 prefCtrlKey: prefCtrlKey$9, 17846 prefShiftKey: prefShiftKey$9, 17847 prefNoKeyShortcuts: prefNoKeyShortcuts$9, 17848 escapeKey: escapeKey$9, 17849 escapeKeyFunction: escapeKeyFunction$9, 17850 prefDescPause: prefDescPause$9, 17851 prefDescVisible: prefDescVisible$9, 17852 prefDescVoice: prefDescVoice$9, 17853 prefDescRate: prefDescRate$9, 17854 prefCaptionRate: prefCaptionRate$9, 17855 prefDescPitch: prefDescPitch$9, 17856 prefDescPitch1: prefDescPitch1$9, 17857 prefDescPitch2: prefDescPitch2$9, 17858 prefDescPitch3: prefDescPitch3$9, 17859 prefDescPitch4: prefDescPitch4$9, 17860 prefDescPitch5: prefDescPitch5$9, 17861 sampleDescriptionText: sampleDescriptionText$9, 17862 prefHighlight: prefHighlight$9, 17863 prefTabbable: prefTabbable$9, 17864 prefCaptionsFont: prefCaptionsFont$9, 17865 prefCaptionsColor: prefCaptionsColor$9, 17866 prefCaptionsBGColor: prefCaptionsBGColor$9, 17867 prefCaptionsSize: prefCaptionsSize$9, 17868 prefCaptionsOpacity: prefCaptionsOpacity$9, 17869 prefCaptionsStyle: prefCaptionsStyle$9, 17870 serif: serif$9, 17871 sans: sans$9, 17872 cursive: cursive$9, 17873 fantasy: fantasy$9, 17874 monospace: monospace$9, 17875 white: white$9, 17876 yellow: yellow$9, 17877 green: green$9, 17878 cyan: cyan$9, 17879 blue: blue$9, 17880 magenta: magenta$9, 17881 red: red$9, 17882 black: black$9, 17883 transparent: transparent$9, 17884 solid: solid$9, 17885 captionsStylePopOn: captionsStylePopOn$9, 17886 captionsStyleRollUp: captionsStyleRollUp$9, 17887 prefCaptionsPosition: prefCaptionsPosition$9, 17888 captionsPositionOverlay: captionsPositionOverlay$9, 17889 captionsPositionBelow: captionsPositionBelow$9, 17890 sampleCaptionText: sampleCaptionText$9, 17891 prefSuccess: prefSuccess$9, 17892 prefNoChange: prefNoChange$9, 17893 save: save$9, 17894 cancel: cancel$9, 17895 dismissButton: dismissButton$9, 17896 windowButtonLabel: windowButtonLabel$9, 17897 windowMove: windowMove$9, 17898 windowMoveLeft: windowMoveLeft$9, 17899 windowMoveRight: windowMoveRight$9, 17900 windowMoveUp: windowMoveUp$9, 17901 windowMoveDown: windowMoveDown$9, 17902 windowMoveStopped: windowMoveStopped$9, 17903 transcriptControls: transcriptControls$9, 17904 signControls: signControls$9, 17905 windowMoveAlert: windowMoveAlert$9, 17906 windowResize: windowResize$9, 17907 windowResizeHeading: windowResizeHeading$9, 17908 closeButtonLabel: closeButtonLabel$9, 17909 width: width$9, 17910 height: height$9, 17911 resultsSummary1: resultsSummary1$9, 17912 resultsSummary2: resultsSummary2$9, 17913 resultsSummary3: resultsSummary3$9, 17914 noResultsFound: noResultsFound$9, 17915 searchButtonLabel: searchButtonLabel$9, 17916 hour: hour$9, 17917 minute: minute$9, 17918 second: second$9, 17919 hours: hours$9, 17920 minutes: minutes$9, 17921 seconds: seconds$9, 17922 vtsHeading: vtsHeading$9, 17923 vtsInstructions1: vtsInstructions1$9, 17924 vtsInstructions2: vtsInstructions2$9, 17925 vtsInstructions3: vtsInstructions3$9, 17926 vtsInstructions4: vtsInstructions4$9, 17927 vtsInstructions5: vtsInstructions5$9, 17928 vtsSelectLanguage: vtsSelectLanguage$9, 17929 vtsSave: vtsSave$9, 17930 vtsReturn: vtsReturn$9, 17931 vtsCancel: vtsCancel$9, 17932 vtsRow: vtsRow$9, 17933 vtsKind: vtsKind$9, 17934 vtsStart: vtsStart$9, 17935 vtsEnd: vtsEnd$9, 17936 vtsContent: vtsContent$9, 17937 vtsActions: vtsActions$9, 17938 vtsNewRow: vtsNewRow$9, 17939 vtsDeletedRow: vtsDeletedRow$9, 17940 vtsMovedRow: vtsMovedRow$9 17941 }; 17942 17943 var playerHeading$8 = "Mediespiller"; 17944 var audioPlayer$8 = "Audio player"; 17945 var videoPlayer$8 = "Video player"; 17946 var faster$8 = "Raskere"; 17947 var slower$8 = "Saktere"; 17948 var play$8 = "Spill av"; 17949 var pause$8 = "Pause"; 17950 var restart$8 = "Start pÃ¥ nytt"; 17951 var prevTrack$8 = "Forrige spor"; 17952 var nextTrack$8 = "Neste spor"; 17953 var rewind$8 = "Spol tilbake"; 17954 var forward$8 = "Spol fremover"; 17955 var captions$8 = "Undertekster"; 17956 var showCaptions$8 = "Vis undertekster"; 17957 var hideCaptions$8 = "Skjul undertekster"; 17958 var captionsOff$8 = "SlÃ¥ av undertekster"; 17959 var showTranscript$8 = "Vis transkripsjon"; 17960 var hideTranscript$8 = "Skjul transkripsjon"; 17961 var turnOnDescriptions$8 = "SlÃ¥ pÃ¥ synstolking"; 17962 var turnOffDescriptions$8 = "SlÃ¥ av synstolking"; 17963 var chapters$8 = "Kapitler"; 17964 var language$8 = "SprÃ¥k"; 17965 var sign$8 = "TegnsprÃ¥k"; 17966 var showSign$8 = "Vis tegnsprÃ¥k"; 17967 var hideSign$8 = "Skjul tegnsprÃ¥k"; 17968 var seekbarLabel$8 = "tidslinje"; 17969 var mute$8 = "Lyd av"; 17970 var unmute$8 = "Lyd pÃ¥"; 17971 var volume$8 = "Volum"; 17972 var volumeUpDown$8 = "Volumkontroll"; 17973 var preferences$8 = "Innstillinger"; 17974 var enterFullScreen$8 = "Vis fullskjerm"; 17975 var exitFullScreen$8 = "Avslutt fullskjerm"; 17976 var speed$8 = "Hastighet"; 17977 var spacebar$8 = "ordskiller"; 17978 var transcriptTitle$8 = "Transkripsjon"; 17979 var lyricsTitle$8 = "Teksting, verselinjer"; 17980 var autoScroll$8 = "Automatisk rulling"; 17981 var statusPlaying$8 = "Spiller av"; 17982 var statusPaused$8 = "Satt pÃ¥ pause"; 17983 var statusStopped$8 = "Stoppet"; 17984 var statusBuffering$8 = "Mottar data"; 17985 var statusEnd$8 = "Slutt pÃ¥ sporet"; 17986 var selectedTrack$8 = "Valgt spor"; 17987 var alertDescribedVersion$8 = "Bruker videoversjon med synstolking"; 17988 var alertNonDescribedVersion$8 = "Bruker videoversjon uten synstolking"; 17989 var prefMenuCaptions$8 = "Undertekster"; 17990 var prefVoicedCaptions$8 = "Spoken Captions"; 17991 var prefMenuDescriptions$8 = "Synstolking"; 17992 var prefMenuKeyboard$8 = "Tastatur"; 17993 var prefMenuTranscript$8 = "Transkripsjon"; 17994 var prefTitleCaptions$8 = "Undertekstinnstillinger"; 17995 var prefTitleDescriptions$8 = "Synstolkingsinnstillinger"; 17996 var prefTitleKeyboard$8 = "Tastaturinnstillinger"; 17997 var prefTitleTranscript$8 = "Transkripsjonsinnstillinger"; 17998 var prefIntroDescription1$8 = "Denne mediespilleren støtter lydbeskrivelse pÃ¥ to mÃ¥ter: "; 17999 var prefDescription1$8 = "Denne videoen har en alternativ synstolket versjon, tekstbasert synstolking."; 18000 var prefDescription2$8 = "Denne videoen har alternativ synstolket versjon av video."; 18001 var prefDescription3$8 = "Denne videoen har tekstbasert synstolket versjon opplest av skjermleser."; 18002 var prefIntroDescriptionNone$8 = "Denne videoen har ikke synstolking i noen av formatene."; 18003 var prefIntroDescription3$8 = "Bruk følgende skjema for Ã¥ gjøre dine valg angÃ¥ende tekstbasert synstolking."; 18004 var prefIntroDescription4$8 = "Etter at du har lagret dine valg kan synstolking slÃ¥s av og pÃ¥ med synstolkingsknappen."; 18005 var prefIntroKeyboard1$8 = "Mediespilleren pÃ¥ denne nettsiden kan styres fra hvor som helst pÃ¥ siden ved hjelp av tastatursnarveier (se liste nedenfor)."; 18006 var prefIntroKeyboard2$8 = "Metataster (Shift, Alt, og Control) kan legges til nedenfor."; 18007 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."; 18008 var prefHeadingKeyboard1$8 = "Metataster som brukes til tastekombinasjoner"; 18009 var prefHeadingKeyboard2$8 = "NÃ¥værende tastekombinasjoner"; 18010 var prefHeadingDescription$8 = "Synstolking"; 18011 var prefHeadingTextDescription$8 = "Tekstbasert synstolking"; 18012 var prefAltKey$8 = "Alt"; 18013 var prefCtrlKey$8 = "Control"; 18014 var prefShiftKey$8 = "Shift"; 18015 var prefNoKeyShortcuts$8 = "Disable keyboard shortcuts"; 18016 var escapeKey$8 = "Escape"; 18017 var escapeKeyFunction$8 = "Lukk dialogboks eller popup-meny"; 18018 var prefDescPause$8 = "Sett video automatisk pÃ¥ pause nÃ¥r synstolking starter"; 18019 var prefDescVisible$8 = "Vis synstolking"; 18020 var prefDescVoice$8 = "Stemme"; 18021 var prefDescRate$8 = "Spoken Description Rate"; 18022 var prefCaptionRate$8 = "Spoken Caption Rate"; 18023 var prefDescPitch$8 = "Toneleie"; 18024 var prefDescPitch1$8 = "Meget lavt"; 18025 var prefDescPitch2$8 = "Lavt"; 18026 var prefDescPitch3$8 = "Normalt"; 18027 var prefDescPitch4$8 = "Høyt"; 18028 var prefDescPitch5$8 = "Meget høyt"; 18029 var sampleDescriptionText$8 = "Juster innstillingene for Ã¥ høre denne eksempelteksten."; 18030 var prefHighlight$8 = "Marker det som leses i transkripsjon ved avspilling"; 18031 var prefTabbable$8 = "Tastaturaktivert transkripsjon"; 18032 var prefCaptionsFont$8 = "Skrifttype"; 18033 var prefCaptionsColor$8 = "Skriftfarge"; 18034 var prefCaptionsBGColor$8 = "Bakgrunn"; 18035 var prefCaptionsSize$8 = "Skriftstørrelse"; 18036 var prefCaptionsOpacity$8 = "Ugjennomsiktighet"; 18037 var prefCaptionsStyle$8 = "Stil"; 18038 var serif$8 = "serif"; 18039 var sans$8 = "sans-serif"; 18040 var cursive$8 = "kursiv"; 18041 var fantasy$8 = "fantasi"; 18042 var monospace$8 = "ikke proporsjonal"; 18043 var white$8 = "hvit"; 18044 var yellow$8 = "gul"; 18045 var green$8 = "grønn"; 18046 var cyan$8 = "cyan"; 18047 var blue$8 = "blÃ¥"; 18048 var magenta$8 = "magenta"; 18049 var red$8 = "rød"; 18050 var black$8 = "sort"; 18051 var transparent$8 = "gjennomsiktig"; 18052 var solid$8 = "ugjennomsiktig"; 18053 var captionsStylePopOn$8 = "Dukker opp"; 18054 var captionsStyleRollUp$8 = "Ruller opp"; 18055 var prefCaptionsPosition$8 = "Posisjon"; 18056 var captionsPositionOverlay$8 = "Legg oppÃ¥ video"; 18057 var captionsPositionBelow$8 = "Legg under video"; 18058 var sampleCaptionText$8 = "Eksempel pÃ¥ undertekst"; 18059 var prefSuccess$8 = "Dine endringer er lagret."; 18060 var prefNoChange$8 = "Du gjorde ingen endringer."; 18061 var save$8 = "Lagre"; 18062 var cancel$8 = "Avbryt"; 18063 var dismissButton$8 = "Dismiss"; 18064 var windowButtonLabel$8 = "Vindusinnstillinger"; 18065 var windowMove$8 = "Flytt"; 18066 var windowMoveLeft$8 = "Window moved left"; 18067 var windowMoveRight$8 = "Window moved right"; 18068 var windowMoveUp$8 = "Window moved up"; 18069 var windowMoveDown$8 = "Window moved down"; 18070 var windowMoveStopped$8 = "Window move stopped"; 18071 var transcriptControls$8 = "Transcript Window Controls"; 18072 var signControls$8 = "Sign Language Window Controls"; 18073 var windowMoveAlert$8 = "Dra eller bruk piltaster for Ã¥ flytte vinduet; Enter-tast for Ã¥ stoppe"; 18074 var windowResize$8 = "Endre størrelse"; 18075 var windowResizeHeading$8 = "Endre vindusstørrelse"; 18076 var closeButtonLabel$8 = "Lukk"; 18077 var width$8 = "Bredde"; 18078 var height$8 = "Høyde"; 18079 var resultsSummary1$8 = "Du søkte etter:"; 18080 var resultsSummary2$8 = "Funnet %1 passende treff."; 18081 var resultsSummary3$8 = "Klikk tidspunkt pÃ¥ aktuelt treff for Ã¥ avspille video derfra."; 18082 var noResultsFound$8 = "Ingen treff funnet."; 18083 var searchButtonLabel$8 = "Spill av fra %1"; 18084 var hour$8 = "time"; 18085 var minute$8 = "minutt"; 18086 var second$8 = "sekund"; 18087 var hours$8 = "timer"; 18088 var minutes$8 = "minutter"; 18089 var seconds$8 = "sekunder"; 18090 var vtsHeading$8 = "Video Transcript Sorter"; 18091 var vtsInstructions1$8 = "Use the Video Transcript Sorter to modify text tracks:"; 18092 var vtsInstructions2$8 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 18093 var vtsInstructions3$8 = "Modify content or start/end times (all are directly editable within the table)."; 18094 var vtsInstructions4$8 = "Add new content, such as chapters or descriptions."; 18095 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."; 18096 var vtsSelectLanguage$8 = "Select a language"; 18097 var vtsSave$8 = "Generate new .vtt content"; 18098 var vtsReturn$8 = "Return to Editor"; 18099 var vtsCancel$8 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 18100 var vtsRow$8 = "Row"; 18101 var vtsKind$8 = "Kind"; 18102 var vtsStart$8 = "Start"; 18103 var vtsEnd$8 = "End"; 18104 var vtsContent$8 = "Content"; 18105 var vtsActions$8 = "Actions"; 18106 var vtsNewRow$8 = "A new row %1 has been inserted."; 18107 var vtsDeletedRow$8 = "Row %1 has been deleted."; 18108 var vtsMovedRow$8 = "Row %1 has been moved %2 and is now Row %3."; 18109 var nb = { 18110 playerHeading: playerHeading$8, 18111 audioPlayer: audioPlayer$8, 18112 videoPlayer: videoPlayer$8, 18113 faster: faster$8, 18114 slower: slower$8, 18115 play: play$8, 18116 pause: pause$8, 18117 restart: restart$8, 18118 prevTrack: prevTrack$8, 18119 nextTrack: nextTrack$8, 18120 rewind: rewind$8, 18121 forward: forward$8, 18122 captions: captions$8, 18123 showCaptions: showCaptions$8, 18124 hideCaptions: hideCaptions$8, 18125 captionsOff: captionsOff$8, 18126 showTranscript: showTranscript$8, 18127 hideTranscript: hideTranscript$8, 18128 turnOnDescriptions: turnOnDescriptions$8, 18129 turnOffDescriptions: turnOffDescriptions$8, 18130 chapters: chapters$8, 18131 language: language$8, 18132 sign: sign$8, 18133 showSign: showSign$8, 18134 hideSign: hideSign$8, 18135 seekbarLabel: seekbarLabel$8, 18136 mute: mute$8, 18137 unmute: unmute$8, 18138 volume: volume$8, 18139 volumeUpDown: volumeUpDown$8, 18140 preferences: preferences$8, 18141 enterFullScreen: enterFullScreen$8, 18142 exitFullScreen: exitFullScreen$8, 18143 speed: speed$8, 18144 spacebar: spacebar$8, 18145 transcriptTitle: transcriptTitle$8, 18146 lyricsTitle: lyricsTitle$8, 18147 autoScroll: autoScroll$8, 18148 statusPlaying: statusPlaying$8, 18149 statusPaused: statusPaused$8, 18150 statusStopped: statusStopped$8, 18151 statusBuffering: statusBuffering$8, 18152 statusEnd: statusEnd$8, 18153 selectedTrack: selectedTrack$8, 18154 alertDescribedVersion: alertDescribedVersion$8, 18155 alertNonDescribedVersion: alertNonDescribedVersion$8, 18156 prefMenuCaptions: prefMenuCaptions$8, 18157 prefVoicedCaptions: prefVoicedCaptions$8, 18158 prefMenuDescriptions: prefMenuDescriptions$8, 18159 prefMenuKeyboard: prefMenuKeyboard$8, 18160 prefMenuTranscript: prefMenuTranscript$8, 18161 prefTitleCaptions: prefTitleCaptions$8, 18162 prefTitleDescriptions: prefTitleDescriptions$8, 18163 prefTitleKeyboard: prefTitleKeyboard$8, 18164 prefTitleTranscript: prefTitleTranscript$8, 18165 prefIntroDescription1: prefIntroDescription1$8, 18166 prefDescription1: prefDescription1$8, 18167 prefDescription2: prefDescription2$8, 18168 prefDescription3: prefDescription3$8, 18169 prefIntroDescriptionNone: prefIntroDescriptionNone$8, 18170 prefIntroDescription3: prefIntroDescription3$8, 18171 prefIntroDescription4: prefIntroDescription4$8, 18172 prefIntroKeyboard1: prefIntroKeyboard1$8, 18173 prefIntroKeyboard2: prefIntroKeyboard2$8, 18174 prefIntroKeyboard3: prefIntroKeyboard3$8, 18175 prefHeadingKeyboard1: prefHeadingKeyboard1$8, 18176 prefHeadingKeyboard2: prefHeadingKeyboard2$8, 18177 prefHeadingDescription: prefHeadingDescription$8, 18178 prefHeadingTextDescription: prefHeadingTextDescription$8, 18179 prefAltKey: prefAltKey$8, 18180 prefCtrlKey: prefCtrlKey$8, 18181 prefShiftKey: prefShiftKey$8, 18182 prefNoKeyShortcuts: prefNoKeyShortcuts$8, 18183 escapeKey: escapeKey$8, 18184 escapeKeyFunction: escapeKeyFunction$8, 18185 prefDescPause: prefDescPause$8, 18186 prefDescVisible: prefDescVisible$8, 18187 prefDescVoice: prefDescVoice$8, 18188 prefDescRate: prefDescRate$8, 18189 prefCaptionRate: prefCaptionRate$8, 18190 prefDescPitch: prefDescPitch$8, 18191 prefDescPitch1: prefDescPitch1$8, 18192 prefDescPitch2: prefDescPitch2$8, 18193 prefDescPitch3: prefDescPitch3$8, 18194 prefDescPitch4: prefDescPitch4$8, 18195 prefDescPitch5: prefDescPitch5$8, 18196 sampleDescriptionText: sampleDescriptionText$8, 18197 prefHighlight: prefHighlight$8, 18198 prefTabbable: prefTabbable$8, 18199 prefCaptionsFont: prefCaptionsFont$8, 18200 prefCaptionsColor: prefCaptionsColor$8, 18201 prefCaptionsBGColor: prefCaptionsBGColor$8, 18202 prefCaptionsSize: prefCaptionsSize$8, 18203 prefCaptionsOpacity: prefCaptionsOpacity$8, 18204 prefCaptionsStyle: prefCaptionsStyle$8, 18205 serif: serif$8, 18206 sans: sans$8, 18207 cursive: cursive$8, 18208 fantasy: fantasy$8, 18209 monospace: monospace$8, 18210 white: white$8, 18211 yellow: yellow$8, 18212 green: green$8, 18213 cyan: cyan$8, 18214 blue: blue$8, 18215 magenta: magenta$8, 18216 red: red$8, 18217 black: black$8, 18218 transparent: transparent$8, 18219 solid: solid$8, 18220 captionsStylePopOn: captionsStylePopOn$8, 18221 captionsStyleRollUp: captionsStyleRollUp$8, 18222 prefCaptionsPosition: prefCaptionsPosition$8, 18223 captionsPositionOverlay: captionsPositionOverlay$8, 18224 captionsPositionBelow: captionsPositionBelow$8, 18225 sampleCaptionText: sampleCaptionText$8, 18226 prefSuccess: prefSuccess$8, 18227 prefNoChange: prefNoChange$8, 18228 save: save$8, 18229 cancel: cancel$8, 18230 dismissButton: dismissButton$8, 18231 windowButtonLabel: windowButtonLabel$8, 18232 windowMove: windowMove$8, 18233 windowMoveLeft: windowMoveLeft$8, 18234 windowMoveRight: windowMoveRight$8, 18235 windowMoveUp: windowMoveUp$8, 18236 windowMoveDown: windowMoveDown$8, 18237 windowMoveStopped: windowMoveStopped$8, 18238 transcriptControls: transcriptControls$8, 18239 signControls: signControls$8, 18240 windowMoveAlert: windowMoveAlert$8, 18241 windowResize: windowResize$8, 18242 windowResizeHeading: windowResizeHeading$8, 18243 closeButtonLabel: closeButtonLabel$8, 18244 width: width$8, 18245 height: height$8, 18246 resultsSummary1: resultsSummary1$8, 18247 resultsSummary2: resultsSummary2$8, 18248 resultsSummary3: resultsSummary3$8, 18249 noResultsFound: noResultsFound$8, 18250 searchButtonLabel: searchButtonLabel$8, 18251 hour: hour$8, 18252 minute: minute$8, 18253 second: second$8, 18254 hours: hours$8, 18255 minutes: minutes$8, 18256 seconds: seconds$8, 18257 vtsHeading: vtsHeading$8, 18258 vtsInstructions1: vtsInstructions1$8, 18259 vtsInstructions2: vtsInstructions2$8, 18260 vtsInstructions3: vtsInstructions3$8, 18261 vtsInstructions4: vtsInstructions4$8, 18262 vtsInstructions5: vtsInstructions5$8, 18263 vtsSelectLanguage: vtsSelectLanguage$8, 18264 vtsSave: vtsSave$8, 18265 vtsReturn: vtsReturn$8, 18266 vtsCancel: vtsCancel$8, 18267 vtsRow: vtsRow$8, 18268 vtsKind: vtsKind$8, 18269 vtsStart: vtsStart$8, 18270 vtsEnd: vtsEnd$8, 18271 vtsContent: vtsContent$8, 18272 vtsActions: vtsActions$8, 18273 vtsNewRow: vtsNewRow$8, 18274 vtsDeletedRow: vtsDeletedRow$8, 18275 vtsMovedRow: vtsMovedRow$8 18276 }; 18277 18278 var playerHeading$7 = "Mediaspeler"; 18279 var audioPlayer$7 = "Audiospeler"; 18280 var videoPlayer$7 = "Videospeler"; 18281 var faster$7 = "Sneller"; 18282 var slower$7 = "Langzamer"; 18283 var play$7 = "Afspelen"; 18284 var pause$7 = "Pauzeren"; 18285 var restart$7 = "Herstarten"; 18286 var prevTrack$7 = "Vorige track"; 18287 var nextTrack$7 = "Volgend track"; 18288 var rewind$7 = "Terug"; 18289 var forward$7 = "Verder"; 18290 var captions$7 = "Ondertiteling"; 18291 var showCaptions$7 = "Toon ondertiteling"; 18292 var hideCaptions$7 = "Verberg ondertiteling"; 18293 var captionsOff$7 = "Ondertiteling uit"; 18294 var showTranscript$7 = "Toon transcript"; 18295 var hideTranscript$7 = "Verberg transcript"; 18296 var turnOnDescriptions$7 = "Beschrijvingen aanzetten"; 18297 var turnOffDescriptions$7 = "Beschrijvingen uitzetten"; 18298 var chapters$7 = "Hoofdstukken"; 18299 var language$7 = "Taal"; 18300 var sign$7 = "Gebarentaal"; 18301 var showSign$7 = "Toon gebarentaal"; 18302 var seekbarLabel$7 = "tijdlijn"; 18303 var hideSign$7 = "Verberg gebarentaal"; 18304 var mute$7 = "Dempen"; 18305 var unmute$7 = "Dempen uit"; 18306 var volume$7 = "Volume"; 18307 var volumeUpDown$7 = "Volume hoger lager"; 18308 var preferences$7 = "Voorkeuren"; 18309 var enterFullScreen$7 = "Ga naar volledig scherm"; 18310 var exitFullScreen$7 = "Verlaat volledig scherm"; 18311 var speed$7 = "Snelheid"; 18312 var spacebar$7 = "spatietoets"; 18313 var transcriptTitle$7 = "Transcript"; 18314 var lyricsTitle$7 = "Tekst"; 18315 var autoScroll$7 = "Auto-scroll"; 18316 var statusPlaying$7 = "Aan het spelen"; 18317 var statusPaused$7 = "Gepauzeerd"; 18318 var statusStopped$7 = "Gestopt"; 18319 var statusBuffering$7 = "Aan het bufferen"; 18320 var statusEnd$7 = "Einde van track"; 18321 var selectedTrack$7 = "Geselecteerde track"; 18322 var alertDescribedVersion$7 = "Versie met audiobeschrijving wordt gebruikt"; 18323 var alertNonDescribedVersion$7 = "Versie zonder audiobeschrijving wordt gebruikt"; 18324 var prefMenuCaptions$7 = "Ondertiteling"; 18325 var prefVoicedCaptions$7 = "Spoken Captions"; 18326 var prefMenuDescriptions$7 = "Beschrijvingen"; 18327 var prefMenuKeyboard$7 = "Toetsenbord"; 18328 var prefMenuTranscript$7 = "Transcript"; 18329 var prefTitleCaptions$7 = "Voorkeuren ondertiteling"; 18330 var prefTitleDescriptions$7 = "Voorkeuren audiobeschrijving"; 18331 var prefTitleKeyboard$7 = "Voorkeuren toetsenbord"; 18332 var prefTitleTranscript$7 = "Voorkeuren transcript"; 18333 var prefIntroDescription1$7 = "Deze videospeler ondersteund audiobeschrijving op twee manieren: "; 18334 var prefDescription1$7 = "De huidige video heeft een alternatief beschreven versie, op tekst gebaseerde beschrijving."; 18335 var prefDescription2$7 = "De huidige video heeft Alternatieve beschreven versie van de video."; 18336 var prefDescription3$7 = "De huidige video heeft op tekst gebaseerde beschrijving, uitgesproken door de schermlezer."; 18337 var prefIntroDescriptionNone$7 = "De huidige video heeft in beide formaten geen audiobeschrijving."; 18338 var prefIntroDescription3$7 = "Gebruik het volgende formulier om je voorkeuren gerelateerd aan tekst-gebaseerde audiobeschrijving in te stellen."; 18339 var prefIntroDescription4$7 = "Na het opslaan van je instellingen, kan audiobeschrijving aan of uit gezet worden met de Beschrijving-knop."; 18340 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)."; 18341 var prefIntroKeyboard2$7 = "Mutatie knoppen (Shift, Alt, and Control) kunnen hier onder toegekend worden."; 18342 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."; 18343 var prefHeadingKeyboard1$7 = "Mutatie knoppen gebruikt voor sneltoetsen"; 18344 var prefHeadingKeyboard2$7 = "Huidige toetsenbord sneltoetsen"; 18345 var prefHeadingDescription$7 = "Audiobeschrijving"; 18346 var prefHeadingTextDescription$7 = "Tekst-gebaseerde audiobeschrijving"; 18347 var prefAltKey$7 = "Alt"; 18348 var prefCtrlKey$7 = "Control"; 18349 var prefShiftKey$7 = "Shift"; 18350 var prefNoKeyShortcuts$7 = "Schakel sneltoetsen uit"; 18351 var escapeKey$7 = "Escape"; 18352 var escapeKeyFunction$7 = "Sluit huidige dialoog of pop-up menu"; 18353 var prefDescPause$7 = "Pauzeer de video automatisch als de beschrijving aan wordt gezet"; 18354 var prefDescVisible$7 = "Als er een tekst-gebaseerde beschrijving is, maak deze dan zichtbaar"; 18355 var prefDescVoice$7 = "Stem"; 18356 var prefDescRate$7 = "Spoken Description Rate"; 18357 var prefCaptionRate$7 = "Spoken Caption Rate"; 18358 var prefDescPitch$7 = "Toonhoogte"; 18359 var prefDescPitch1$7 = "Zeer laag"; 18360 var prefDescPitch2$7 = "Laag"; 18361 var prefDescPitch3$7 = "Normaal"; 18362 var prefDescPitch4$7 = "Hoog"; 18363 var prefDescPitch5$7 = "Zeer hoog"; 18364 var sampleDescriptionText$7 = "Pas de instellingen aan om deze voorbeeldtekst te beluisteren."; 18365 var prefHighlight$7 = "Highlight transcript terwijl media speelt"; 18366 var prefTabbable$7 = "Maak transcript bedienbaar met toetsenbord"; 18367 var prefCaptionsFont$7 = "Lettertype"; 18368 var prefCaptionsColor$7 = "Tekstkleur"; 18369 var prefCaptionsBGColor$7 = "Achtergrondkleur"; 18370 var prefCaptionsSize$7 = "Grootte lettertype "; 18371 var prefCaptionsOpacity$7 = "Ondoorzichtigheid"; 18372 var prefCaptionsStyle$7 = "Stijl"; 18373 var serif$7 = "serif"; 18374 var sans$7 = "sans-serif"; 18375 var cursive$7 = "cursief"; 18376 var fantasy$7 = "fantasy"; 18377 var monospace$7 = "monospace"; 18378 var white$7 = "wit"; 18379 var yellow$7 = "geel"; 18380 var green$7 = "groen"; 18381 var cyan$7 = "cyaan"; 18382 var blue$7 = "blauw"; 18383 var magenta$7 = "magenta"; 18384 var red$7 = "rood"; 18385 var black$7 = "zwart"; 18386 var transparent$7 = "transparant"; 18387 var solid$7 = "solide"; 18388 var captionsStylePopOn$7 = "Verschijn"; 18389 var captionsStyleRollUp$7 = "Rol omhoog"; 18390 var prefCaptionsPosition$7 = "Positie"; 18391 var captionsPositionOverlay$7 = "Overlay"; 18392 var captionsPositionBelow$7 = "Onder video"; 18393 var sampleCaptionText$7 = "Voorbeeld ondertiteling"; 18394 var prefSuccess$7 = "Je wijzigingen zijn opgeslagen."; 18395 var prefNoChange$7 = "Je hebt geen wijzigingen gemaakt."; 18396 var save$7 = "Opslaan"; 18397 var cancel$7 = "Annuleren"; 18398 var dismissButton$7 = "Verwijder"; 18399 var windowButtonLabel$7 = "Instellingen venster"; 18400 var windowMove$7 = "Verplaats"; 18401 var windowMoveLeft$7 = "Venster naar links verschoven"; 18402 var windowMoveRight$7 = "Venster naar rechts verschoven"; 18403 var windowMoveUp$7 = "Venster omhoog geschoven."; 18404 var windowMoveDown$7 = "Venster omlaag geschoven."; 18405 var windowMoveStopped$7 = "Verplaatsing venster gestopt"; 18406 var transcriptControls$7 = "Bediening venster transcript"; 18407 var signControls$7 = "Bediening venster gebarentaal"; 18408 var windowMoveAlert$7 = "Versleep of gebruik de pijltjestoetsen om te verplaatsen. Druk op Enter om te stoppen."; 18409 var windowResize$7 = "Verkleinen of vergroten"; 18410 var windowResizeHeading$7 = "Verander grootte van venster"; 18411 var closeButtonLabel$7 = "Sluit"; 18412 var width$7 = "Breedte"; 18413 var height$7 = "Hoogte"; 18414 var resultsSummary1$7 = "Je zocht naar:"; 18415 var resultsSummary2$7 = "Er zijn %1 overeenkomende items gevonden."; 18416 var resultsSummary3$7 = "Klik op de tijd die bij een item staat om de video vanaf dat punt af te spelen."; 18417 var noResultsFound$7 = "Geen resultaten gevonden."; 18418 var searchButtonLabel$7 = "Speel af op %1"; 18419 var hour$7 = "uur"; 18420 var minute$7 = "minuut"; 18421 var second$7 = "seconde"; 18422 var hours$7 = "hours"; 18423 var minutes$7 = "minuten"; 18424 var seconds$7 = "seconden"; 18425 var vtsHeading$7 = "Video Transcript Sorter"; 18426 var vtsInstructions1$7 = "Gebruik de Video Transcript Sorter om teksttracks te wijzigen:"; 18427 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."; 18428 var vtsInstructions3$7 = "Wijzig de inhoud of de begin-/eindtijden (alles is direct bewerkbaar in de tabel)."; 18429 var vtsInstructions4$7 = "Voeg nieuwe inhoud toe, zoals hoofdstukken of beschrijvingen."; 18430 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."; 18431 var vtsSelectLanguage$7 = "Selecteer een taal"; 18432 var vtsSave$7 = "Genereer nieuwe .vtt inhoud"; 18433 var vtsReturn$7 = "Terug naar de editor"; 18434 var vtsCancel$7 = "Opslaan annuleren. Alle bewerkingen die je hebt gedaan, zijn hersteld in de VTS-tabel"; 18435 var vtsRow$7 = "Rij"; 18436 var vtsKind$7 = "Sorteer"; 18437 var vtsStart$7 = "Start"; 18438 var vtsEnd$7 = "Eind"; 18439 var vtsContent$7 = "Inhoud"; 18440 var vtsActions$7 = "Acties"; 18441 var vtsNewRow$7 = "Er is een nieuwe rij %1 ingevoegd."; 18442 var vtsDeletedRow$7 = "Rij %1 is verwijderd."; 18443 var vtsMovedRow$7 = "Rij %1 is verplaatst naar %2 en is nu rij %3."; 18444 var nl = { 18445 playerHeading: playerHeading$7, 18446 audioPlayer: audioPlayer$7, 18447 videoPlayer: videoPlayer$7, 18448 faster: faster$7, 18449 slower: slower$7, 18450 play: play$7, 18451 pause: pause$7, 18452 restart: restart$7, 18453 prevTrack: prevTrack$7, 18454 nextTrack: nextTrack$7, 18455 rewind: rewind$7, 18456 forward: forward$7, 18457 captions: captions$7, 18458 showCaptions: showCaptions$7, 18459 hideCaptions: hideCaptions$7, 18460 captionsOff: captionsOff$7, 18461 showTranscript: showTranscript$7, 18462 hideTranscript: hideTranscript$7, 18463 turnOnDescriptions: turnOnDescriptions$7, 18464 turnOffDescriptions: turnOffDescriptions$7, 18465 chapters: chapters$7, 18466 language: language$7, 18467 sign: sign$7, 18468 showSign: showSign$7, 18469 seekbarLabel: seekbarLabel$7, 18470 hideSign: hideSign$7, 18471 mute: mute$7, 18472 unmute: unmute$7, 18473 volume: volume$7, 18474 volumeUpDown: volumeUpDown$7, 18475 preferences: preferences$7, 18476 enterFullScreen: enterFullScreen$7, 18477 exitFullScreen: exitFullScreen$7, 18478 speed: speed$7, 18479 spacebar: spacebar$7, 18480 transcriptTitle: transcriptTitle$7, 18481 lyricsTitle: lyricsTitle$7, 18482 autoScroll: autoScroll$7, 18483 statusPlaying: statusPlaying$7, 18484 statusPaused: statusPaused$7, 18485 statusStopped: statusStopped$7, 18486 statusBuffering: statusBuffering$7, 18487 statusEnd: statusEnd$7, 18488 selectedTrack: selectedTrack$7, 18489 alertDescribedVersion: alertDescribedVersion$7, 18490 alertNonDescribedVersion: alertNonDescribedVersion$7, 18491 prefMenuCaptions: prefMenuCaptions$7, 18492 prefVoicedCaptions: prefVoicedCaptions$7, 18493 prefMenuDescriptions: prefMenuDescriptions$7, 18494 prefMenuKeyboard: prefMenuKeyboard$7, 18495 prefMenuTranscript: prefMenuTranscript$7, 18496 prefTitleCaptions: prefTitleCaptions$7, 18497 prefTitleDescriptions: prefTitleDescriptions$7, 18498 prefTitleKeyboard: prefTitleKeyboard$7, 18499 prefTitleTranscript: prefTitleTranscript$7, 18500 prefIntroDescription1: prefIntroDescription1$7, 18501 prefDescription1: prefDescription1$7, 18502 prefDescription2: prefDescription2$7, 18503 prefDescription3: prefDescription3$7, 18504 prefIntroDescriptionNone: prefIntroDescriptionNone$7, 18505 prefIntroDescription3: prefIntroDescription3$7, 18506 prefIntroDescription4: prefIntroDescription4$7, 18507 prefIntroKeyboard1: prefIntroKeyboard1$7, 18508 prefIntroKeyboard2: prefIntroKeyboard2$7, 18509 prefIntroKeyboard3: prefIntroKeyboard3$7, 18510 prefHeadingKeyboard1: prefHeadingKeyboard1$7, 18511 prefHeadingKeyboard2: prefHeadingKeyboard2$7, 18512 prefHeadingDescription: prefHeadingDescription$7, 18513 prefHeadingTextDescription: prefHeadingTextDescription$7, 18514 prefAltKey: prefAltKey$7, 18515 prefCtrlKey: prefCtrlKey$7, 18516 prefShiftKey: prefShiftKey$7, 18517 prefNoKeyShortcuts: prefNoKeyShortcuts$7, 18518 escapeKey: escapeKey$7, 18519 escapeKeyFunction: escapeKeyFunction$7, 18520 prefDescPause: prefDescPause$7, 18521 prefDescVisible: prefDescVisible$7, 18522 prefDescVoice: prefDescVoice$7, 18523 prefDescRate: prefDescRate$7, 18524 prefCaptionRate: prefCaptionRate$7, 18525 prefDescPitch: prefDescPitch$7, 18526 prefDescPitch1: prefDescPitch1$7, 18527 prefDescPitch2: prefDescPitch2$7, 18528 prefDescPitch3: prefDescPitch3$7, 18529 prefDescPitch4: prefDescPitch4$7, 18530 prefDescPitch5: prefDescPitch5$7, 18531 sampleDescriptionText: sampleDescriptionText$7, 18532 prefHighlight: prefHighlight$7, 18533 prefTabbable: prefTabbable$7, 18534 prefCaptionsFont: prefCaptionsFont$7, 18535 prefCaptionsColor: prefCaptionsColor$7, 18536 prefCaptionsBGColor: prefCaptionsBGColor$7, 18537 prefCaptionsSize: prefCaptionsSize$7, 18538 prefCaptionsOpacity: prefCaptionsOpacity$7, 18539 prefCaptionsStyle: prefCaptionsStyle$7, 18540 serif: serif$7, 18541 sans: sans$7, 18542 cursive: cursive$7, 18543 fantasy: fantasy$7, 18544 monospace: monospace$7, 18545 white: white$7, 18546 yellow: yellow$7, 18547 green: green$7, 18548 cyan: cyan$7, 18549 blue: blue$7, 18550 magenta: magenta$7, 18551 red: red$7, 18552 black: black$7, 18553 transparent: transparent$7, 18554 solid: solid$7, 18555 captionsStylePopOn: captionsStylePopOn$7, 18556 captionsStyleRollUp: captionsStyleRollUp$7, 18557 prefCaptionsPosition: prefCaptionsPosition$7, 18558 captionsPositionOverlay: captionsPositionOverlay$7, 18559 captionsPositionBelow: captionsPositionBelow$7, 18560 sampleCaptionText: sampleCaptionText$7, 18561 prefSuccess: prefSuccess$7, 18562 prefNoChange: prefNoChange$7, 18563 save: save$7, 18564 cancel: cancel$7, 18565 dismissButton: dismissButton$7, 18566 windowButtonLabel: windowButtonLabel$7, 18567 windowMove: windowMove$7, 18568 windowMoveLeft: windowMoveLeft$7, 18569 windowMoveRight: windowMoveRight$7, 18570 windowMoveUp: windowMoveUp$7, 18571 windowMoveDown: windowMoveDown$7, 18572 windowMoveStopped: windowMoveStopped$7, 18573 transcriptControls: transcriptControls$7, 18574 signControls: signControls$7, 18575 windowMoveAlert: windowMoveAlert$7, 18576 windowResize: windowResize$7, 18577 windowResizeHeading: windowResizeHeading$7, 18578 closeButtonLabel: closeButtonLabel$7, 18579 width: width$7, 18580 height: height$7, 18581 resultsSummary1: resultsSummary1$7, 18582 resultsSummary2: resultsSummary2$7, 18583 resultsSummary3: resultsSummary3$7, 18584 noResultsFound: noResultsFound$7, 18585 searchButtonLabel: searchButtonLabel$7, 18586 hour: hour$7, 18587 minute: minute$7, 18588 second: second$7, 18589 hours: hours$7, 18590 minutes: minutes$7, 18591 seconds: seconds$7, 18592 vtsHeading: vtsHeading$7, 18593 vtsInstructions1: vtsInstructions1$7, 18594 vtsInstructions2: vtsInstructions2$7, 18595 vtsInstructions3: vtsInstructions3$7, 18596 vtsInstructions4: vtsInstructions4$7, 18597 vtsInstructions5: vtsInstructions5$7, 18598 vtsSelectLanguage: vtsSelectLanguage$7, 18599 vtsSave: vtsSave$7, 18600 vtsReturn: vtsReturn$7, 18601 vtsCancel: vtsCancel$7, 18602 vtsRow: vtsRow$7, 18603 vtsKind: vtsKind$7, 18604 vtsStart: vtsStart$7, 18605 vtsEnd: vtsEnd$7, 18606 vtsContent: vtsContent$7, 18607 vtsActions: vtsActions$7, 18608 vtsNewRow: vtsNewRow$7, 18609 vtsDeletedRow: vtsDeletedRow$7, 18610 vtsMovedRow: vtsMovedRow$7 18611 }; 18612 18613 var playerHeading$6 = "Odtwarzacz mediów"; 18614 var audioPlayer$6 = "Audio player"; 18615 var videoPlayer$6 = "Video player"; 18616 var faster$6 = "Szybciej"; 18617 var slower$6 = "Wolniej"; 18618 var play$6 = "Odtwarzaj"; 18619 var pause$6 = "Pauza"; 18620 var restart$6 = "Restart"; 18621 var prevTrack$6 = "Poprzednia Åcieżka"; 18622 var nextTrack$6 = "NastÄpna Åcieżka"; 18623 var rewind$6 = "PrzewiÅ w tyÅ"; 18624 var forward$6 = "PrzewiÅ w przód"; 18625 var captions$6 = "Napisy"; 18626 var showCaptions$6 = "Pokaż napisy"; 18627 var hideCaptions$6 = "Ukryj napisy"; 18628 var captionsOff$6 = "WyÅÄ cz napisy"; 18629 var showTranscript$6 = "Pokaż transkrypcjÄ"; 18630 var hideTranscript$6 = "Ukryj transkrypcjÄ"; 18631 var turnOnDescriptions$6 = "WÅÄ cz opis audio"; 18632 var turnOffDescriptions$6 = "WyÅÄ cz opis audio"; 18633 var chapters$6 = "RozdziaÅy"; 18634 var language$6 = "JÄzyk"; 18635 var sign$6 = "JÄzyk migowy"; 18636 var showSign$6 = "Pokaż jÄzyk migowy"; 18637 var hideSign$6 = "Ukryj jÄzyk migowy"; 18638 var seekbarLabel$6 = "Linia czasu"; 18639 var mute$6 = "Wycisz"; 18640 var unmute$6 = "PogÅoÅnij"; 18641 var volume$6 = "GÅoÅnoÅÄ"; 18642 var volumeUpDown$6 = "GÅoÅniej/Ciszej"; 18643 var preferences$6 = "Preferencje"; 18644 var enterFullScreen$6 = "Wejdź na peÅny ekran"; 18645 var exitFullScreen$6 = "Wyjdź z peÅnego ekranu"; 18646 var speed$6 = "Tempo"; 18647 var spacebar$6 = "spacja"; 18648 var transcriptTitle$6 = "Transkrypcja"; 18649 var lyricsTitle$6 = "Tekst"; 18650 var autoScroll$6 = "Auto przewijanie"; 18651 var statusPlaying$6 = "Odtwarzanie"; 18652 var statusPaused$6 = "Wstrzymane"; 18653 var statusStopped$6 = "Zatrzymane"; 18654 var statusBuffering$6 = "Buforowanie"; 18655 var statusEnd$6 = "Koniec Åcieżki"; 18656 var selectedTrack$6 = "Wybrana Åcieżka"; 18657 var alertDescribedVersion$6 = "Użyj wersji filmu z opisem audio"; 18658 var alertNonDescribedVersion$6 = "Użyj wersji filmu bez opisu audio"; 18659 var prefMenuCaptions$6 = "Napisy"; 18660 var prefVoicedCaptions$6 = "Spoken Captions"; 18661 var prefMenuDescriptions$6 = "Audiodeskrypcja"; 18662 var prefMenuKeyboard$6 = "Klawiatura"; 18663 var prefMenuTranscript$6 = "Transkrypcja"; 18664 var prefTitleCaptions$6 = "Preferencje napisów"; 18665 var prefTitleDescriptions$6 = "Preferencje audiodeskrypcji"; 18666 var prefTitleKeyboard$6 = "Preferencje klawiatury"; 18667 var prefTitleTranscript$6 = "Preferencje transkrypcji"; 18668 var prefIntroDescription1$6 = "Ten odtwarzacz mediów obsÅuguje audiodeskrypcjÄ na dwa sposoby: "; 18669 var prefDescription1$6 = "BieÅ¼Ä cy film ma alternatywna wersja opisu audio, tekst opisu audio."; 18670 var prefDescription2$6 = "BieÅ¼Ä cy film ma alternatywna wersja wideo z opisem audio."; 18671 var prefDescription3$6 = "BieÅ¼Ä cy film ma tekst opisu audio, ogÅaszany przez czytnik ekranu."; 18672 var prefIntroDescriptionNone$6 = "BieÅ¼Ä cy film nie ma audiodeskprycji."; 18673 var prefIntroDescription3$6 = "Użyj formularza poniższej, aby okreÅliÄ ustawienia odtwarzania tekstowego opisu audio"; 18674 var prefIntroDescription4$6 = "Po zapisaniu ustawieÅ opisu audio można wÅÄ czaÄ/wyÅÄ czaÄ za pomocÄ przycisku Audiodeskrypcja."; 18675 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)."; 18676 var prefIntroKeyboard2$6 = "Klawisze modyfikujÄ ce (Shift, Alt i Control) można przypisaÄ poniżej"; 18677 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."; 18678 var prefHeadingKeyboard1$6 = "Klawisze modyfikujÄ ce używane do skrótów"; 18679 var prefHeadingKeyboard2$6 = "BieÅ¼Ä ce skróty klawiaturowe"; 18680 var prefHeadingDescription$6 = "Audiodeskrypcja"; 18681 var prefHeadingTextDescription$6 = "Tekst oparty na audiodeskrypcji"; 18682 var prefAltKey$6 = "Alt"; 18683 var prefCtrlKey$6 = "Control"; 18684 var prefShiftKey$6 = "Shift"; 18685 var prefNoKeyShortcuts$6 = "Disable keyboard shortcuts"; 18686 var escapeKey$6 = "Escape"; 18687 var escapeKeyFunction$6 = "Zamknij bieÅ¼Ä ce okno dialogowe lub menu podrÄczne"; 18688 var prefDescPause$6 = "Automatycznie wstrzymuj wideo po rozpoczÄciu audiodeskrypcji"; 18689 var prefDescVisible$6 = "WyÅwietlaj opis audio"; 18690 var prefDescVoice$6 = "GÅos"; 18691 var prefDescRate$6 = "Spoken Description Rate"; 18692 var prefCaptionRate$6 = "Spoken Caption Rate"; 18693 var prefDescPitch$6 = "WysokoÅÄ"; 18694 var prefDescPitch1$6 = "Bardzo niska"; 18695 var prefDescPitch2$6 = "Niska"; 18696 var prefDescPitch3$6 = "DomyÅlna"; 18697 var prefDescPitch4$6 = "Wysoka"; 18698 var prefDescPitch5$6 = "Bardzo wysoko"; 18699 var sampleDescriptionText$6 = "Edytuj ustawienia, aby odczytaÄ ten tekst na gÅos."; 18700 var prefHighlight$6 = "PodÅwietl transkrypcjÄ podczas odtwarzania mediów"; 18701 var prefTabbable$6 = "WÅÄ cz transkrypcjÄ za pomocÄ klawiatury"; 18702 var prefCaptionsFont$6 = "Czcionka"; 18703 var prefCaptionsColor$6 = "Kolor tekstu"; 18704 var prefCaptionsBGColor$6 = "Kolor tÅa"; 18705 var prefCaptionsSize$6 = "Rozmiar czcionki"; 18706 var prefCaptionsOpacity$6 = "Krycie"; 18707 var prefCaptionsStyle$6 = "Styl"; 18708 var serif$6 = "szeryfowa"; 18709 var sans$6 = "bezszeryfowa"; 18710 var cursive$6 = "pochyÅa"; 18711 var fantasy$6 = "fantazyjna"; 18712 var monospace$6 = "staÅa szerokoÅÄ"; 18713 var white$6 = "biaÅy"; 18714 var yellow$6 = "żóÅty"; 18715 var green$6 = "zielony"; 18716 var cyan$6 = "cyjan"; 18717 var blue$6 = "niebieski"; 18718 var magenta$6 = "magenta"; 18719 var red$6 = "czerwony"; 18720 var black$6 = "czarny"; 18721 var transparent$6 = "transparentne"; 18722 var solid$6 = "solidne"; 18723 var captionsStylePopOn$6 = "RozwiÅ"; 18724 var captionsStyleRollUp$6 = "ZwiÅ"; 18725 var prefCaptionsPosition$6 = "Pozycja"; 18726 var captionsPositionOverlay$6 = "NakÅadka"; 18727 var captionsPositionBelow$6 = "Poniżej wideo"; 18728 var sampleCaptionText$6 = "PrzykÅadowy tekst napisu"; 18729 var prefSuccess$6 = "Zmiany zostaÅy zapisane."; 18730 var prefNoChange$6 = "Nie wprowadzono żadnych zmian."; 18731 var save$6 = "Zapisz"; 18732 var cancel$6 = "Anuluj"; 18733 var dismissButton$6 = "Dismiss"; 18734 var windowButtonLabel$6 = "Opcje okna"; 18735 var windowMove$6 = "PrzenieÅ"; 18736 var windowMoveLeft$6 = "Window moved left"; 18737 var windowMoveRight$6 = "Window moved right"; 18738 var windowMoveUp$6 = "Window moved up"; 18739 var windowMoveDown$6 = "Window moved down"; 18740 var windowMoveStopped$6 = "Window move stopped"; 18741 var transcriptControls$6 = "Transcript Window Controls"; 18742 var signControls$6 = "Sign Language Window Controls"; 18743 var windowMoveAlert$6 = "PrzeciÄ gnij lub użyj strzaÅek, aby przesunÄ Ä okno; Enter, aby zatrzymaÄ"; 18744 var windowResize$6 = "ZmieÅ rozmiar"; 18745 var windowResizeHeading$6 = "ZmieÅ rozmiar okna"; 18746 var closeButtonLabel$6 = "Zamknij"; 18747 var width$6 = "SzerokoÅÄ"; 18748 var height$6 = "WysokoÅÄ"; 18749 var resultsSummary1$6 = "Szukano:"; 18750 var resultsSummary2$6 = "Znaleziono %1 pasujÄ ce elementy."; 18751 var resultsSummary3$6 = "Kliknij czas powiÄ zany z dowolnym elementem, aby odtworzyÄ wideo od tego momentu."; 18752 var noResultsFound$6 = "Nie znaleziono żadnych wyników"; 18753 var searchButtonLabel$6 = "Odtwarzaj od %1"; 18754 var hour$6 = "godzina"; 18755 var minute$6 = "minuta"; 18756 var second$6 = "sekunda"; 18757 var hours$6 = "godziny"; 18758 var minutes$6 = "minuty"; 18759 var seconds$6 = "sekundy"; 18760 var vtsHeading$6 = "Video Transcript Sorter"; 18761 var vtsInstructions1$6 = "Use the Video Transcript Sorter to modify text tracks:"; 18762 var vtsInstructions2$6 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 18763 var vtsInstructions3$6 = "Modify content or start/end times (all are directly editable within the table)."; 18764 var vtsInstructions4$6 = "Add new content, such as chapters or descriptions."; 18765 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."; 18766 var vtsSelectLanguage$6 = "Select a language"; 18767 var vtsSave$6 = "Generate new .vtt content"; 18768 var vtsReturn$6 = "Return to Editor"; 18769 var vtsCancel$6 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 18770 var vtsRow$6 = "Row"; 18771 var vtsKind$6 = "Kind"; 18772 var vtsStart$6 = "Start"; 18773 var vtsEnd$6 = "End"; 18774 var vtsContent$6 = "Content"; 18775 var vtsActions$6 = "Actions"; 18776 var vtsNewRow$6 = "A new row %1 has been inserted."; 18777 var vtsDeletedRow$6 = "Row %1 has been deleted."; 18778 var vtsMovedRow$6 = "Row %1 has been moved %2 and is now Row %3."; 18779 var pl = { 18780 playerHeading: playerHeading$6, 18781 audioPlayer: audioPlayer$6, 18782 videoPlayer: videoPlayer$6, 18783 faster: faster$6, 18784 slower: slower$6, 18785 play: play$6, 18786 pause: pause$6, 18787 restart: restart$6, 18788 prevTrack: prevTrack$6, 18789 nextTrack: nextTrack$6, 18790 rewind: rewind$6, 18791 forward: forward$6, 18792 captions: captions$6, 18793 showCaptions: showCaptions$6, 18794 hideCaptions: hideCaptions$6, 18795 captionsOff: captionsOff$6, 18796 showTranscript: showTranscript$6, 18797 hideTranscript: hideTranscript$6, 18798 turnOnDescriptions: turnOnDescriptions$6, 18799 turnOffDescriptions: turnOffDescriptions$6, 18800 chapters: chapters$6, 18801 language: language$6, 18802 sign: sign$6, 18803 showSign: showSign$6, 18804 hideSign: hideSign$6, 18805 seekbarLabel: seekbarLabel$6, 18806 mute: mute$6, 18807 unmute: unmute$6, 18808 volume: volume$6, 18809 volumeUpDown: volumeUpDown$6, 18810 preferences: preferences$6, 18811 enterFullScreen: enterFullScreen$6, 18812 exitFullScreen: exitFullScreen$6, 18813 speed: speed$6, 18814 spacebar: spacebar$6, 18815 transcriptTitle: transcriptTitle$6, 18816 lyricsTitle: lyricsTitle$6, 18817 autoScroll: autoScroll$6, 18818 statusPlaying: statusPlaying$6, 18819 statusPaused: statusPaused$6, 18820 statusStopped: statusStopped$6, 18821 statusBuffering: statusBuffering$6, 18822 statusEnd: statusEnd$6, 18823 selectedTrack: selectedTrack$6, 18824 alertDescribedVersion: alertDescribedVersion$6, 18825 alertNonDescribedVersion: alertNonDescribedVersion$6, 18826 prefMenuCaptions: prefMenuCaptions$6, 18827 prefVoicedCaptions: prefVoicedCaptions$6, 18828 prefMenuDescriptions: prefMenuDescriptions$6, 18829 prefMenuKeyboard: prefMenuKeyboard$6, 18830 prefMenuTranscript: prefMenuTranscript$6, 18831 prefTitleCaptions: prefTitleCaptions$6, 18832 prefTitleDescriptions: prefTitleDescriptions$6, 18833 prefTitleKeyboard: prefTitleKeyboard$6, 18834 prefTitleTranscript: prefTitleTranscript$6, 18835 prefIntroDescription1: prefIntroDescription1$6, 18836 prefDescription1: prefDescription1$6, 18837 prefDescription2: prefDescription2$6, 18838 prefDescription3: prefDescription3$6, 18839 prefIntroDescriptionNone: prefIntroDescriptionNone$6, 18840 prefIntroDescription3: prefIntroDescription3$6, 18841 prefIntroDescription4: prefIntroDescription4$6, 18842 prefIntroKeyboard1: prefIntroKeyboard1$6, 18843 prefIntroKeyboard2: prefIntroKeyboard2$6, 18844 prefIntroKeyboard3: prefIntroKeyboard3$6, 18845 prefHeadingKeyboard1: prefHeadingKeyboard1$6, 18846 prefHeadingKeyboard2: prefHeadingKeyboard2$6, 18847 prefHeadingDescription: prefHeadingDescription$6, 18848 prefHeadingTextDescription: prefHeadingTextDescription$6, 18849 prefAltKey: prefAltKey$6, 18850 prefCtrlKey: prefCtrlKey$6, 18851 prefShiftKey: prefShiftKey$6, 18852 prefNoKeyShortcuts: prefNoKeyShortcuts$6, 18853 escapeKey: escapeKey$6, 18854 escapeKeyFunction: escapeKeyFunction$6, 18855 prefDescPause: prefDescPause$6, 18856 prefDescVisible: prefDescVisible$6, 18857 prefDescVoice: prefDescVoice$6, 18858 prefDescRate: prefDescRate$6, 18859 prefCaptionRate: prefCaptionRate$6, 18860 prefDescPitch: prefDescPitch$6, 18861 prefDescPitch1: prefDescPitch1$6, 18862 prefDescPitch2: prefDescPitch2$6, 18863 prefDescPitch3: prefDescPitch3$6, 18864 prefDescPitch4: prefDescPitch4$6, 18865 prefDescPitch5: prefDescPitch5$6, 18866 sampleDescriptionText: sampleDescriptionText$6, 18867 prefHighlight: prefHighlight$6, 18868 prefTabbable: prefTabbable$6, 18869 prefCaptionsFont: prefCaptionsFont$6, 18870 prefCaptionsColor: prefCaptionsColor$6, 18871 prefCaptionsBGColor: prefCaptionsBGColor$6, 18872 prefCaptionsSize: prefCaptionsSize$6, 18873 prefCaptionsOpacity: prefCaptionsOpacity$6, 18874 prefCaptionsStyle: prefCaptionsStyle$6, 18875 serif: serif$6, 18876 sans: sans$6, 18877 cursive: cursive$6, 18878 fantasy: fantasy$6, 18879 monospace: monospace$6, 18880 white: white$6, 18881 yellow: yellow$6, 18882 green: green$6, 18883 cyan: cyan$6, 18884 blue: blue$6, 18885 magenta: magenta$6, 18886 red: red$6, 18887 black: black$6, 18888 transparent: transparent$6, 18889 solid: solid$6, 18890 captionsStylePopOn: captionsStylePopOn$6, 18891 captionsStyleRollUp: captionsStyleRollUp$6, 18892 prefCaptionsPosition: prefCaptionsPosition$6, 18893 captionsPositionOverlay: captionsPositionOverlay$6, 18894 captionsPositionBelow: captionsPositionBelow$6, 18895 sampleCaptionText: sampleCaptionText$6, 18896 prefSuccess: prefSuccess$6, 18897 prefNoChange: prefNoChange$6, 18898 save: save$6, 18899 cancel: cancel$6, 18900 dismissButton: dismissButton$6, 18901 windowButtonLabel: windowButtonLabel$6, 18902 windowMove: windowMove$6, 18903 windowMoveLeft: windowMoveLeft$6, 18904 windowMoveRight: windowMoveRight$6, 18905 windowMoveUp: windowMoveUp$6, 18906 windowMoveDown: windowMoveDown$6, 18907 windowMoveStopped: windowMoveStopped$6, 18908 transcriptControls: transcriptControls$6, 18909 signControls: signControls$6, 18910 windowMoveAlert: windowMoveAlert$6, 18911 windowResize: windowResize$6, 18912 windowResizeHeading: windowResizeHeading$6, 18913 closeButtonLabel: closeButtonLabel$6, 18914 width: width$6, 18915 height: height$6, 18916 resultsSummary1: resultsSummary1$6, 18917 resultsSummary2: resultsSummary2$6, 18918 resultsSummary3: resultsSummary3$6, 18919 noResultsFound: noResultsFound$6, 18920 searchButtonLabel: searchButtonLabel$6, 18921 hour: hour$6, 18922 minute: minute$6, 18923 second: second$6, 18924 hours: hours$6, 18925 minutes: minutes$6, 18926 seconds: seconds$6, 18927 vtsHeading: vtsHeading$6, 18928 vtsInstructions1: vtsInstructions1$6, 18929 vtsInstructions2: vtsInstructions2$6, 18930 vtsInstructions3: vtsInstructions3$6, 18931 vtsInstructions4: vtsInstructions4$6, 18932 vtsInstructions5: vtsInstructions5$6, 18933 vtsSelectLanguage: vtsSelectLanguage$6, 18934 vtsSave: vtsSave$6, 18935 vtsReturn: vtsReturn$6, 18936 vtsCancel: vtsCancel$6, 18937 vtsRow: vtsRow$6, 18938 vtsKind: vtsKind$6, 18939 vtsStart: vtsStart$6, 18940 vtsEnd: vtsEnd$6, 18941 vtsContent: vtsContent$6, 18942 vtsActions: vtsActions$6, 18943 vtsNewRow: vtsNewRow$6, 18944 vtsDeletedRow: vtsDeletedRow$6, 18945 vtsMovedRow: vtsMovedRow$6 18946 }; 18947 18948 var playerHeading$5 = "Reprodutor de mÃdias"; 18949 var audioPlayer$5 = "Audio player"; 18950 var videoPlayer$5 = "Video player"; 18951 var faster$5 = "Rápido"; 18952 var slower$5 = "Lento"; 18953 var play$5 = "Reproduzir"; 18954 var pause$5 = "Pausar"; 18955 var restart$5 = "Reiniciar"; 18956 var prevTrack$5 = "Faixa anterior"; 18957 var nextTrack$5 = "Próxima faixa"; 18958 var rewind$5 = "Retroceder"; 18959 var forward$5 = "Avançar"; 18960 var captions$5 = "Legendas"; 18961 var showCaptions$5 = "Mostrar legendas"; 18962 var hideCaptions$5 = "Ocultar legendas"; 18963 var captionsOff$5 = "Legendas desligadas"; 18964 var showTranscript$5 = "Mostrar transcrição"; 18965 var hideTranscript$5 = "Ocultar transcrição"; 18966 var turnOnDescriptions$5 = "Habilitar descrições"; 18967 var turnOffDescriptions$5 = "Desabilitar descrições"; 18968 var chapters$5 = "CapÃtulos"; 18969 var language$5 = "Idioma"; 18970 var sign$5 = "LÃngua de sinais"; 18971 var showSign$5 = "Mostrar lÃngua de sinais"; 18972 var hideSign$5 = "Ocultar lÃngua de sinais"; 18973 var seekbarLabel$5 = "Linha do tempo"; 18974 var mute$5 = "Silenciar"; 18975 var unmute$5 = "Ativar som"; 18976 var volume$5 = "Volume"; 18977 var volumeUpDown$5 = "Ampliar/reduzir volume"; 18978 var preferences$5 = "Preferências"; 18979 var enterFullScreen$5 = "Entrar em tela cheia"; 18980 var exitFullScreen$5 = "Sair da tela cheia"; 18981 var speed$5 = "Velocidade"; 18982 var spacebar$5 = "barra de espaço"; 18983 var transcriptTitle$5 = "Transcrição"; 18984 var lyricsTitle$5 = "Letras"; 18985 var autoScroll$5 = "Rolagem automática"; 18986 var statusPlaying$5 = "Reproduzindo"; 18987 var statusPaused$5 = "Pausado"; 18988 var statusStopped$5 = "Parado"; 18989 var statusBuffering$5 = "Carregando"; 18990 var statusEnd$5 = "Fim da faixa"; 18991 var selectedTrack$5 = "Faixa Selecionada"; 18992 var alertDescribedVersion$5 = "Usando a versão com audiodescrição deste vÃdeo"; 18993 var alertNonDescribedVersion$5 = "Utilizando a versão sem audiodescrição desse vÃdeo"; 18994 var prefMenuCaptions$5 = "CapÃtulos"; 18995 var prefVoicedCaptions$5 = "Spoken Captions"; 18996 var prefMenuDescriptions$5 = "Descrições"; 18997 var prefMenuKeyboard$5 = "Teclado"; 18998 var prefMenuTranscript$5 = "Transcrição"; 18999 var prefTitleCaptions$5 = "Preferências de Legenda"; 19000 var prefTitleDescriptions$5 = "Preferências da Audiodescrição"; 19001 var prefTitleKeyboard$5 = "Preferências do Teclado"; 19002 var prefTitleTranscript$5 = "Preferências da Transcrição"; 19003 var prefIntroDescription1$5 = "Esse reprodutor de mÃdias suportam audiodescrição de duas maneiras: "; 19004 var prefDescription1$5 = "O vÃdeo atual está uma versão alternativa de descrição, texto baseado em descrição."; 19005 var prefDescription2$5 = "O vÃdeo atual está versão alternativa de descrição do vÃdeo."; 19006 var prefDescription3$5 = "O vÃdeo atual está texto baseado em descrição, anunciado pelo leitor de tela."; 19007 var prefIntroDescriptionNone$5 = "O vÃdeo atual não possui audiodescrição em nenhum formato."; 19008 var prefIntroDescription3$5 = "Use o formulário a seguir para definir suas preferências relacionadas à texto baseado em audiodescrição."; 19009 var prefIntroDescription4$5 = "Depois que salvar suas configurações, a audiodescrição poderá ser ligada/desligada usando o botão Descrição."; 19010 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)."; 19011 var prefIntroKeyboard2$5 = "Modificar teclas (Shift, Alt and Control) podem ser gravadas abaixo."; 19012 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ê."; 19013 var prefHeadingKeyboard1$5 = "Teclas modificadas dos atalhos"; 19014 var prefHeadingKeyboard2$5 = "Atual atalho de teclado"; 19015 var prefHeadingDescription$5 = "Audiodescrição"; 19016 var prefHeadingTextDescription$5 = "Texto baseado em audiodescrição"; 19017 var prefAltKey$5 = "Alt"; 19018 var prefCtrlKey$5 = "Control"; 19019 var prefShiftKey$5 = "Shift"; 19020 var prefNoKeyShortcuts$5 = "Disable keyboard shortcuts"; 19021 var escapeKey$5 = "ESC"; 19022 var escapeKeyFunction$5 = "Fechar diálogo ou caixa pop-up atual"; 19023 var prefDescPause$5 = "Automaticamente pausar um vÃdeo quando a descrição iniciar"; 19024 var prefDescVisible$5 = "Fazer com que a descrição esteja visÃvel"; 19025 var prefDescVoice$5 = "Voz"; 19026 var prefDescRate$5 = "Spoken Description Rate"; 19027 var prefCaptionRate$5 = "Spoken Caption Rate"; 19028 var prefDescPitch$5 = "Tom"; 19029 var prefDescPitch1$5 = "Muito baixo"; 19030 var prefDescPitch2$5 = "Baixo"; 19031 var prefDescPitch3$5 = "Padrão"; 19032 var prefDescPitch4$5 = "Alto"; 19033 var prefDescPitch5$5 = "Muito alto"; 19034 var sampleDescriptionText$5 = "Ajuste as configurações para ouvir este exemplo de texto."; 19035 var prefHighlight$5 = "Destacar a descrição conforme a mÃdia for reproduzida"; 19036 var prefTabbable$5 = "Transcrição de teclado habilitada"; 19037 var prefCaptionsFont$5 = "Fonte"; 19038 var prefCaptionsColor$5 = "Cor do Texto"; 19039 var prefCaptionsBGColor$5 = "Cor do plano de Fundo"; 19040 var prefCaptionsSize$5 = "Tamanho da Fonte"; 19041 var prefCaptionsOpacity$5 = "transparência"; 19042 var prefCaptionsStyle$5 = "Estilo"; 19043 var serif$5 = "serifada"; 19044 var sans$5 = "sem serifa"; 19045 var cursive$5 = "cursiva"; 19046 var fantasy$5 = "fantasiosa"; 19047 var monospace$5 = "monoespaçada"; 19048 var white$5 = "branca"; 19049 var yellow$5 = "amarela"; 19050 var green$5 = "verde"; 19051 var cyan$5 = "ciano"; 19052 var blue$5 = "azul"; 19053 var magenta$5 = "magenta"; 19054 var red$5 = "vermelha"; 19055 var black$5 = "preta"; 19056 var transparent$5 = "transparente"; 19057 var solid$5 = "sólida"; 19058 var captionsStylePopOn$5 = "Aparecer"; 19059 var captionsStyleRollUp$5 = "Rolar pra cima"; 19060 var prefCaptionsPosition$5 = "Posição"; 19061 var captionsPositionOverlay$5 = "Sobrepôr"; 19062 var captionsPositionBelow$5 = "Abaixo do vÃdeo"; 19063 var sampleCaptionText$5 = "Um texto de exemplo para legendas"; 19064 var prefSuccess$5 = "Suas alterações foram salvas"; 19065 var prefNoChange$5 = "Você não fez nenhuma alteração"; 19066 var save$5 = "Salvar"; 19067 var cancel$5 = "Cancelar"; 19068 var dismissButton$5 = "Dismiss"; 19069 var windowButtonLabel$5 = "Opções de janela"; 19070 var windowMove$5 = "Mover"; 19071 var windowMoveLeft$5 = "Window moved left"; 19072 var windowMoveRight$5 = "Window moved right"; 19073 var windowMoveUp$5 = "Window moved up"; 19074 var windowMoveDown$5 = "Window moved down"; 19075 var windowMoveStopped$5 = "Window move stopped"; 19076 var transcriptControls$5 = "Transcript Window Controls"; 19077 var signControls$5 = "Sign Language Window Controls"; 19078 var windowMoveAlert$5 = "Arraste ou use as setas do seu teclado para mover; Enter para parar"; 19079 var windowResize$5 = "Redimensionar"; 19080 var windowResizeHeading$5 = "Redimensionar Janela"; 19081 var closeButtonLabel$5 = "Fechar"; 19082 var width$5 = "Largura"; 19083 var height$5 = "Altura"; 19084 var resultsSummary1$5 = "Você buscou por:"; 19085 var resultsSummary2$5 = "Encontrado %1 itens correspondentes"; 19086 var resultsSummary3$5 = "Ative o item de tempo associado para reproduzir o vÃdeo a partir desse ponto."; 19087 var noResultsFound$5 = "Nenhum resultado encontrado"; 19088 var searchButtonLabel$5 = "Tocar no %1"; 19089 var hour$5 = "hora"; 19090 var minute$5 = "minuto"; 19091 var second$5 = "segundos"; 19092 var hours$5 = "horas"; 19093 var minutes$5 = "minutos"; 19094 var seconds$5 = "segundos"; 19095 var vtsHeading$5 = "Video Transcript Sorter"; 19096 var vtsInstructions1$5 = "Use the Video Transcript Sorter to modify text tracks:"; 19097 var vtsInstructions2$5 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 19098 var vtsInstructions3$5 = "Modify content or start/end times (all are directly editable within the table)."; 19099 var vtsInstructions4$5 = "Add new content, such as chapters or descriptions."; 19100 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."; 19101 var vtsSelectLanguage$5 = "Select a language"; 19102 var vtsSave$5 = "Generate new .vtt content"; 19103 var vtsReturn$5 = "Return to Editor"; 19104 var vtsCancel$5 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 19105 var vtsRow$5 = "Row"; 19106 var vtsKind$5 = "Kind"; 19107 var vtsStart$5 = "Start"; 19108 var vtsEnd$5 = "End"; 19109 var vtsContent$5 = "Content"; 19110 var vtsActions$5 = "Actions"; 19111 var vtsNewRow$5 = "A new row %1 has been inserted."; 19112 var vtsDeletedRow$5 = "Row %1 has been deleted."; 19113 var vtsMovedRow$5 = "Row %1 has been moved %2 and is now Row %3."; 19114 var pt_br = { 19115 playerHeading: playerHeading$5, 19116 audioPlayer: audioPlayer$5, 19117 videoPlayer: videoPlayer$5, 19118 faster: faster$5, 19119 slower: slower$5, 19120 play: play$5, 19121 pause: pause$5, 19122 restart: restart$5, 19123 prevTrack: prevTrack$5, 19124 nextTrack: nextTrack$5, 19125 rewind: rewind$5, 19126 forward: forward$5, 19127 captions: captions$5, 19128 showCaptions: showCaptions$5, 19129 hideCaptions: hideCaptions$5, 19130 captionsOff: captionsOff$5, 19131 showTranscript: showTranscript$5, 19132 hideTranscript: hideTranscript$5, 19133 turnOnDescriptions: turnOnDescriptions$5, 19134 turnOffDescriptions: turnOffDescriptions$5, 19135 chapters: chapters$5, 19136 language: language$5, 19137 sign: sign$5, 19138 showSign: showSign$5, 19139 hideSign: hideSign$5, 19140 seekbarLabel: seekbarLabel$5, 19141 mute: mute$5, 19142 unmute: unmute$5, 19143 volume: volume$5, 19144 volumeUpDown: volumeUpDown$5, 19145 preferences: preferences$5, 19146 enterFullScreen: enterFullScreen$5, 19147 exitFullScreen: exitFullScreen$5, 19148 speed: speed$5, 19149 spacebar: spacebar$5, 19150 transcriptTitle: transcriptTitle$5, 19151 lyricsTitle: lyricsTitle$5, 19152 autoScroll: autoScroll$5, 19153 statusPlaying: statusPlaying$5, 19154 statusPaused: statusPaused$5, 19155 statusStopped: statusStopped$5, 19156 statusBuffering: statusBuffering$5, 19157 statusEnd: statusEnd$5, 19158 selectedTrack: selectedTrack$5, 19159 alertDescribedVersion: alertDescribedVersion$5, 19160 alertNonDescribedVersion: alertNonDescribedVersion$5, 19161 prefMenuCaptions: prefMenuCaptions$5, 19162 prefVoicedCaptions: prefVoicedCaptions$5, 19163 prefMenuDescriptions: prefMenuDescriptions$5, 19164 prefMenuKeyboard: prefMenuKeyboard$5, 19165 prefMenuTranscript: prefMenuTranscript$5, 19166 prefTitleCaptions: prefTitleCaptions$5, 19167 prefTitleDescriptions: prefTitleDescriptions$5, 19168 prefTitleKeyboard: prefTitleKeyboard$5, 19169 prefTitleTranscript: prefTitleTranscript$5, 19170 prefIntroDescription1: prefIntroDescription1$5, 19171 prefDescription1: prefDescription1$5, 19172 prefDescription2: prefDescription2$5, 19173 prefDescription3: prefDescription3$5, 19174 prefIntroDescriptionNone: prefIntroDescriptionNone$5, 19175 prefIntroDescription3: prefIntroDescription3$5, 19176 prefIntroDescription4: prefIntroDescription4$5, 19177 prefIntroKeyboard1: prefIntroKeyboard1$5, 19178 prefIntroKeyboard2: prefIntroKeyboard2$5, 19179 prefIntroKeyboard3: prefIntroKeyboard3$5, 19180 prefHeadingKeyboard1: prefHeadingKeyboard1$5, 19181 prefHeadingKeyboard2: prefHeadingKeyboard2$5, 19182 prefHeadingDescription: prefHeadingDescription$5, 19183 prefHeadingTextDescription: prefHeadingTextDescription$5, 19184 prefAltKey: prefAltKey$5, 19185 prefCtrlKey: prefCtrlKey$5, 19186 prefShiftKey: prefShiftKey$5, 19187 prefNoKeyShortcuts: prefNoKeyShortcuts$5, 19188 escapeKey: escapeKey$5, 19189 escapeKeyFunction: escapeKeyFunction$5, 19190 prefDescPause: prefDescPause$5, 19191 prefDescVisible: prefDescVisible$5, 19192 prefDescVoice: prefDescVoice$5, 19193 prefDescRate: prefDescRate$5, 19194 prefCaptionRate: prefCaptionRate$5, 19195 prefDescPitch: prefDescPitch$5, 19196 prefDescPitch1: prefDescPitch1$5, 19197 prefDescPitch2: prefDescPitch2$5, 19198 prefDescPitch3: prefDescPitch3$5, 19199 prefDescPitch4: prefDescPitch4$5, 19200 prefDescPitch5: prefDescPitch5$5, 19201 sampleDescriptionText: sampleDescriptionText$5, 19202 prefHighlight: prefHighlight$5, 19203 prefTabbable: prefTabbable$5, 19204 prefCaptionsFont: prefCaptionsFont$5, 19205 prefCaptionsColor: prefCaptionsColor$5, 19206 prefCaptionsBGColor: prefCaptionsBGColor$5, 19207 prefCaptionsSize: prefCaptionsSize$5, 19208 prefCaptionsOpacity: prefCaptionsOpacity$5, 19209 prefCaptionsStyle: prefCaptionsStyle$5, 19210 serif: serif$5, 19211 sans: sans$5, 19212 cursive: cursive$5, 19213 fantasy: fantasy$5, 19214 monospace: monospace$5, 19215 white: white$5, 19216 yellow: yellow$5, 19217 green: green$5, 19218 cyan: cyan$5, 19219 blue: blue$5, 19220 magenta: magenta$5, 19221 red: red$5, 19222 black: black$5, 19223 transparent: transparent$5, 19224 solid: solid$5, 19225 captionsStylePopOn: captionsStylePopOn$5, 19226 captionsStyleRollUp: captionsStyleRollUp$5, 19227 prefCaptionsPosition: prefCaptionsPosition$5, 19228 captionsPositionOverlay: captionsPositionOverlay$5, 19229 captionsPositionBelow: captionsPositionBelow$5, 19230 sampleCaptionText: sampleCaptionText$5, 19231 prefSuccess: prefSuccess$5, 19232 prefNoChange: prefNoChange$5, 19233 save: save$5, 19234 cancel: cancel$5, 19235 dismissButton: dismissButton$5, 19236 windowButtonLabel: windowButtonLabel$5, 19237 windowMove: windowMove$5, 19238 windowMoveLeft: windowMoveLeft$5, 19239 windowMoveRight: windowMoveRight$5, 19240 windowMoveUp: windowMoveUp$5, 19241 windowMoveDown: windowMoveDown$5, 19242 windowMoveStopped: windowMoveStopped$5, 19243 transcriptControls: transcriptControls$5, 19244 signControls: signControls$5, 19245 windowMoveAlert: windowMoveAlert$5, 19246 windowResize: windowResize$5, 19247 windowResizeHeading: windowResizeHeading$5, 19248 closeButtonLabel: closeButtonLabel$5, 19249 width: width$5, 19250 height: height$5, 19251 resultsSummary1: resultsSummary1$5, 19252 resultsSummary2: resultsSummary2$5, 19253 resultsSummary3: resultsSummary3$5, 19254 noResultsFound: noResultsFound$5, 19255 searchButtonLabel: searchButtonLabel$5, 19256 hour: hour$5, 19257 minute: minute$5, 19258 second: second$5, 19259 hours: hours$5, 19260 minutes: minutes$5, 19261 seconds: seconds$5, 19262 vtsHeading: vtsHeading$5, 19263 vtsInstructions1: vtsInstructions1$5, 19264 vtsInstructions2: vtsInstructions2$5, 19265 vtsInstructions3: vtsInstructions3$5, 19266 vtsInstructions4: vtsInstructions4$5, 19267 vtsInstructions5: vtsInstructions5$5, 19268 vtsSelectLanguage: vtsSelectLanguage$5, 19269 vtsSave: vtsSave$5, 19270 vtsReturn: vtsReturn$5, 19271 vtsCancel: vtsCancel$5, 19272 vtsRow: vtsRow$5, 19273 vtsKind: vtsKind$5, 19274 vtsStart: vtsStart$5, 19275 vtsEnd: vtsEnd$5, 19276 vtsContent: vtsContent$5, 19277 vtsActions: vtsActions$5, 19278 vtsNewRow: vtsNewRow$5, 19279 vtsDeletedRow: vtsDeletedRow$5, 19280 vtsMovedRow: vtsMovedRow$5 19281 }; 19282 19283 var playerHeading$4 = "Leitor multimédia"; 19284 var audioPlayer$4 = "Audio player"; 19285 var videoPlayer$4 = "Video player"; 19286 var faster$4 = "Mais rápido"; 19287 var slower$4 = "Mais lento"; 19288 var play$4 = "Reproduzir"; 19289 var pause$4 = "Pausa"; 19290 var restart$4 = "Reiniciar"; 19291 var prevTrack$4 = "Faixa anterior"; 19292 var nextTrack$4 = "Faixa seguinte"; 19293 var rewind$4 = "Retroceder"; 19294 var forward$4 = "Avançar"; 19295 var captions$4 = "Legendas"; 19296 var showCaptions$4 = "Mostrar legendas"; 19297 var hideCaptions$4 = "Esconder legendas"; 19298 var captionsOff$4 = "Desativar legendas"; 19299 var showTranscript$4 = "Mostrar transcrição"; 19300 var hideTranscript$4 = "Esconder transcrição"; 19301 var turnOnDescriptions$4 = "Ativar descrições"; 19302 var turnOffDescriptions$4 = "Desativar descrições"; 19303 var chapters$4 = "CapÃtulos"; 19304 var language$4 = "Idioma"; 19305 var sign$4 = "Linguagem gestual"; 19306 var showSign$4 = "Mostrar linguagem gestual"; 19307 var hideSign$4 = "Esconder linguagem gestual"; 19308 var seekbarLabel$4 = "linha temporal"; 19309 var mute$4 = "Desativar som"; 19310 var unmute$4 = "Ativar som"; 19311 var volume$4 = "Volume"; 19312 var volumeUpDown$4 = "Volume up down"; 19313 var preferences$4 = "Preferências"; 19314 var enterFullScreen$4 = "Ativar a vista de ecrã inteiro"; 19315 var exitFullScreen$4 = "Sair da vista de ecrã inteiro"; 19316 var speed$4 = "Velocidade"; 19317 var spacebar$4 = "barra de espaço"; 19318 var transcriptTitle$4 = "Transcrição"; 19319 var lyricsTitle$4 = "Letra"; 19320 var autoScroll$4 = "Scroll automático"; 19321 var statusPlaying$4 = "A reproduzir"; 19322 var statusPaused$4 = "Pausado"; 19323 var statusStopped$4 = "Parado"; 19324 var statusBuffering$4 = "A carregar"; 19325 var statusEnd$4 = "Fim da faixa"; 19326 var selectedTrack$4 = "Faixa selecionada"; 19327 var alertDescribedVersion$4 = "A usar versão de áudio descrita deste vÃdeo"; 19328 var alertNonDescribedVersion$4 = "A usar versão não descrita deste vÃdeo"; 19329 var prefMenuCaptions$4 = "Legendas"; 19330 var prefVoicedCaptions$4 = "Spoken Captions"; 19331 var prefMenuDescriptions$4 = "Descrições"; 19332 var prefMenuKeyboard$4 = "Teclado"; 19333 var prefMenuTranscript$4 = "Transcrição"; 19334 var prefTitleCaptions$4 = "Preferências das Legendas"; 19335 var prefTitleDescriptions$4 = "Preferências da Descrição de Aúdio"; 19336 var prefTitleKeyboard$4 = "Preferências de Peclado"; 19337 var prefTitleTranscript$4 = "Preferências de Transcrição"; 19338 var prefIntroDescription1$4 = "Este leitor multimédia suporta descrição de áudio de duas formas: "; 19339 var prefDescription1$4 = "O vÃdeo atual tem uma versão descrita alternativa, descrição à base de texto."; 19340 var prefDescription2$4 = "O vÃdeo atual tem versão descrita alernativa do vÃdeo."; 19341 var prefDescription3$4 = "O vÃdeo atual tem descrição à base de texto, anunciada pelo leitor de ecrãs."; 19342 var prefIntroDescriptionNone$4 = "O vÃdeo atual não tem descrição de áudio em nenhum formato."; 19343 var prefIntroDescription3$4 = "Utiliza o seguinte formulário para definir as tuas preferências relacionadas com descrição de áudio à base de texto."; 19344 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."; 19345 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)."; 19346 var prefIntroKeyboard2$4 = "Teclas modificadoras (Shift, Alt, and Control) podem ser atribuÃdas em baixo."; 19347 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."; 19348 var prefHeadingKeyboard1$4 = "Teclas modificadoras usadas para atalhos"; 19349 var prefHeadingKeyboard2$4 = "Atalhos de teclado em uso"; 19350 var prefHeadingDescription$4 = "Descrição de áudio"; 19351 var prefHeadingTextDescription$4 = "Descrição de áudio à base de texto"; 19352 var prefAltKey$4 = "Alt"; 19353 var prefCtrlKey$4 = "Control"; 19354 var prefShiftKey$4 = "Shift"; 19355 var prefNoKeyShortcuts$4 = "Disable keyboard shortcuts"; 19356 var escapeKey$4 = "Escape"; 19357 var escapeKeyFunction$4 = "Fecha o diálogo ou menu popup"; 19358 var prefDescPause$4 = "Pausar vÃdeo automaticamente quando a descrição começa"; 19359 var prefDescVisible$4 = "Torna a descrição visÃvel"; 19360 var prefDescVoice$4 = "Voz"; 19361 var prefDescRate$4 = "Spoken Description Rate"; 19362 var prefCaptionRate$4 = "Spoken Caption Rate"; 19363 var prefDescPitch$4 = "Tom"; 19364 var prefDescPitch1$4 = "Muito baixo"; 19365 var prefDescPitch2$4 = "Baixo"; 19366 var prefDescPitch3$4 = "Padrão"; 19367 var prefDescPitch4$4 = "Alto"; 19368 var prefDescPitch5$4 = "Muito Alto"; 19369 var sampleDescriptionText$4 = "Ajusta as preferências para ouvir esta amostra de texto."; 19370 var prefHighlight$4 = "Destacar transcrição enquanto o leito está a reproduzir."; 19371 var prefTabbable$4 = "Transcrição ativada por teclado"; 19372 var prefCaptionsFont$4 = "Tipo de letra"; 19373 var prefCaptionsColor$4 = "Cor de texto"; 19374 var prefCaptionsBGColor$4 = "Fundo"; 19375 var prefCaptionsSize$4 = "Tamanho de letra"; 19376 var prefCaptionsOpacity$4 = "Opacidade"; 19377 var prefCaptionsStyle$4 = "Estilo"; 19378 var serif$4 = "serifada"; 19379 var sans$4 = "sem serifa"; 19380 var cursive$4 = "cursivo"; 19381 var fantasy$4 = "fantasia"; 19382 var monospace$4 = "mono-espaçada"; 19383 var white$4 = "branco"; 19384 var yellow$4 = "amarelo"; 19385 var green$4 = "verde"; 19386 var cyan$4 = "ciano"; 19387 var blue$4 = "azul"; 19388 var magenta$4 = "magenta"; 19389 var red$4 = "vermelho"; 19390 var black$4 = "preto"; 19391 var transparent$4 = "transparente"; 19392 var solid$4 = "sólido"; 19393 var captionsStylePopOn$4 = "Pop-on"; 19394 var captionsStyleRollUp$4 = "Roll-up"; 19395 var prefCaptionsPosition$4 = "Posição"; 19396 var captionsPositionOverlay$4 = "Sobreposição"; 19397 var captionsPositionBelow$4 = "Por baixo do vÃdeo"; 19398 var sampleCaptionText$4 = "Amostra texto de legenda"; 19399 var prefSuccess$4 = "As tuas alterações foram guardadas."; 19400 var prefNoChange$4 = "Não fizeste nenhuma alteração."; 19401 var save$4 = "Guardar"; 19402 var cancel$4 = "Cancelar"; 19403 var dismissButton$4 = "Dismiss"; 19404 var windowButtonLabel$4 = "Opções de janela"; 19405 var windowMove$4 = "Mover"; 19406 var windowMoveLeft$4 = "Window moved left"; 19407 var windowMoveRight$4 = "Window moved right"; 19408 var windowMoveUp$4 = "Window moved up"; 19409 var windowMoveDown$4 = "Window moved down"; 19410 var windowMoveStopped$4 = "Window move stopped"; 19411 var transcriptControls$4 = "Transcript Window Controls"; 19412 var signControls$4 = "Sign Language Window Controls"; 19413 var windowMoveAlert$4 = "Arrasta ou usa as teclas de seta para mover a janela; Enter para parar."; 19414 var windowResize$4 = "Redimencionar"; 19415 var windowResizeHeading$4 = "Redimencionar janela"; 19416 var closeButtonLabel$4 = "Fechar"; 19417 var width$4 = "Largura"; 19418 var height$4 = "Altura"; 19419 var resultsSummary1$4 = "Pesquisaste por:"; 19420 var resultsSummary2$4 = "Encontrado %1 itens correspondentes."; 19421 var resultsSummary3$4 = "Clica no tempo associado a qualquer item para reproduzir o vÃdeo a partir daquele ponto."; 19422 var noResultsFound$4 = "Nenhum resultado encontrado."; 19423 var searchButtonLabel$4 = "Reproduzir a partir de %1"; 19424 var hour$4 = "hora"; 19425 var minute$4 = "minuto"; 19426 var second$4 = "segundo"; 19427 var hours$4 = "horas"; 19428 var minutes$4 = "minutos"; 19429 var seconds$4 = "segundos"; 19430 var vtsHeading$4 = "Video Transcript Sorter"; 19431 var vtsInstructions1$4 = "Use the Video Transcript Sorter to modify text tracks:"; 19432 var vtsInstructions2$4 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 19433 var vtsInstructions3$4 = "Modify content or start/end times (all are directly editable within the table)."; 19434 var vtsInstructions4$4 = "Add new content, such as chapters or descriptions."; 19435 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."; 19436 var vtsSelectLanguage$4 = "Select a language"; 19437 var vtsSave$4 = "Generate new .vtt content"; 19438 var vtsReturn$4 = "Return to Editor"; 19439 var vtsCancel$4 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 19440 var vtsRow$4 = "Row"; 19441 var vtsKind$4 = "Kind"; 19442 var vtsStart$4 = "Start"; 19443 var vtsEnd$4 = "End"; 19444 var vtsContent$4 = "Content"; 19445 var vtsActions$4 = "Actions"; 19446 var vtsNewRow$4 = "A new row %1 has been inserted."; 19447 var vtsDeletedRow$4 = "Row %1 has been deleted."; 19448 var vtsMovedRow$4 = "Row %1 has been moved %2 and is now Row %3."; 19449 var pt = { 19450 playerHeading: playerHeading$4, 19451 audioPlayer: audioPlayer$4, 19452 videoPlayer: videoPlayer$4, 19453 faster: faster$4, 19454 slower: slower$4, 19455 play: play$4, 19456 pause: pause$4, 19457 restart: restart$4, 19458 prevTrack: prevTrack$4, 19459 nextTrack: nextTrack$4, 19460 rewind: rewind$4, 19461 forward: forward$4, 19462 captions: captions$4, 19463 showCaptions: showCaptions$4, 19464 hideCaptions: hideCaptions$4, 19465 captionsOff: captionsOff$4, 19466 showTranscript: showTranscript$4, 19467 hideTranscript: hideTranscript$4, 19468 turnOnDescriptions: turnOnDescriptions$4, 19469 turnOffDescriptions: turnOffDescriptions$4, 19470 chapters: chapters$4, 19471 language: language$4, 19472 sign: sign$4, 19473 showSign: showSign$4, 19474 hideSign: hideSign$4, 19475 seekbarLabel: seekbarLabel$4, 19476 mute: mute$4, 19477 unmute: unmute$4, 19478 volume: volume$4, 19479 volumeUpDown: volumeUpDown$4, 19480 preferences: preferences$4, 19481 enterFullScreen: enterFullScreen$4, 19482 exitFullScreen: exitFullScreen$4, 19483 speed: speed$4, 19484 spacebar: spacebar$4, 19485 transcriptTitle: transcriptTitle$4, 19486 lyricsTitle: lyricsTitle$4, 19487 autoScroll: autoScroll$4, 19488 statusPlaying: statusPlaying$4, 19489 statusPaused: statusPaused$4, 19490 statusStopped: statusStopped$4, 19491 statusBuffering: statusBuffering$4, 19492 statusEnd: statusEnd$4, 19493 selectedTrack: selectedTrack$4, 19494 alertDescribedVersion: alertDescribedVersion$4, 19495 alertNonDescribedVersion: alertNonDescribedVersion$4, 19496 prefMenuCaptions: prefMenuCaptions$4, 19497 prefVoicedCaptions: prefVoicedCaptions$4, 19498 prefMenuDescriptions: prefMenuDescriptions$4, 19499 prefMenuKeyboard: prefMenuKeyboard$4, 19500 prefMenuTranscript: prefMenuTranscript$4, 19501 prefTitleCaptions: prefTitleCaptions$4, 19502 prefTitleDescriptions: prefTitleDescriptions$4, 19503 prefTitleKeyboard: prefTitleKeyboard$4, 19504 prefTitleTranscript: prefTitleTranscript$4, 19505 prefIntroDescription1: prefIntroDescription1$4, 19506 prefDescription1: prefDescription1$4, 19507 prefDescription2: prefDescription2$4, 19508 prefDescription3: prefDescription3$4, 19509 prefIntroDescriptionNone: prefIntroDescriptionNone$4, 19510 prefIntroDescription3: prefIntroDescription3$4, 19511 prefIntroDescription4: prefIntroDescription4$4, 19512 prefIntroKeyboard1: prefIntroKeyboard1$4, 19513 prefIntroKeyboard2: prefIntroKeyboard2$4, 19514 prefIntroKeyboard3: prefIntroKeyboard3$4, 19515 prefHeadingKeyboard1: prefHeadingKeyboard1$4, 19516 prefHeadingKeyboard2: prefHeadingKeyboard2$4, 19517 prefHeadingDescription: prefHeadingDescription$4, 19518 prefHeadingTextDescription: prefHeadingTextDescription$4, 19519 prefAltKey: prefAltKey$4, 19520 prefCtrlKey: prefCtrlKey$4, 19521 prefShiftKey: prefShiftKey$4, 19522 prefNoKeyShortcuts: prefNoKeyShortcuts$4, 19523 escapeKey: escapeKey$4, 19524 escapeKeyFunction: escapeKeyFunction$4, 19525 prefDescPause: prefDescPause$4, 19526 prefDescVisible: prefDescVisible$4, 19527 prefDescVoice: prefDescVoice$4, 19528 prefDescRate: prefDescRate$4, 19529 prefCaptionRate: prefCaptionRate$4, 19530 prefDescPitch: prefDescPitch$4, 19531 prefDescPitch1: prefDescPitch1$4, 19532 prefDescPitch2: prefDescPitch2$4, 19533 prefDescPitch3: prefDescPitch3$4, 19534 prefDescPitch4: prefDescPitch4$4, 19535 prefDescPitch5: prefDescPitch5$4, 19536 sampleDescriptionText: sampleDescriptionText$4, 19537 prefHighlight: prefHighlight$4, 19538 prefTabbable: prefTabbable$4, 19539 prefCaptionsFont: prefCaptionsFont$4, 19540 prefCaptionsColor: prefCaptionsColor$4, 19541 prefCaptionsBGColor: prefCaptionsBGColor$4, 19542 prefCaptionsSize: prefCaptionsSize$4, 19543 prefCaptionsOpacity: prefCaptionsOpacity$4, 19544 prefCaptionsStyle: prefCaptionsStyle$4, 19545 serif: serif$4, 19546 sans: sans$4, 19547 cursive: cursive$4, 19548 fantasy: fantasy$4, 19549 monospace: monospace$4, 19550 white: white$4, 19551 yellow: yellow$4, 19552 green: green$4, 19553 cyan: cyan$4, 19554 blue: blue$4, 19555 magenta: magenta$4, 19556 red: red$4, 19557 black: black$4, 19558 transparent: transparent$4, 19559 solid: solid$4, 19560 captionsStylePopOn: captionsStylePopOn$4, 19561 captionsStyleRollUp: captionsStyleRollUp$4, 19562 prefCaptionsPosition: prefCaptionsPosition$4, 19563 captionsPositionOverlay: captionsPositionOverlay$4, 19564 captionsPositionBelow: captionsPositionBelow$4, 19565 sampleCaptionText: sampleCaptionText$4, 19566 prefSuccess: prefSuccess$4, 19567 prefNoChange: prefNoChange$4, 19568 save: save$4, 19569 cancel: cancel$4, 19570 dismissButton: dismissButton$4, 19571 windowButtonLabel: windowButtonLabel$4, 19572 windowMove: windowMove$4, 19573 windowMoveLeft: windowMoveLeft$4, 19574 windowMoveRight: windowMoveRight$4, 19575 windowMoveUp: windowMoveUp$4, 19576 windowMoveDown: windowMoveDown$4, 19577 windowMoveStopped: windowMoveStopped$4, 19578 transcriptControls: transcriptControls$4, 19579 signControls: signControls$4, 19580 windowMoveAlert: windowMoveAlert$4, 19581 windowResize: windowResize$4, 19582 windowResizeHeading: windowResizeHeading$4, 19583 closeButtonLabel: closeButtonLabel$4, 19584 width: width$4, 19585 height: height$4, 19586 resultsSummary1: resultsSummary1$4, 19587 resultsSummary2: resultsSummary2$4, 19588 resultsSummary3: resultsSummary3$4, 19589 noResultsFound: noResultsFound$4, 19590 searchButtonLabel: searchButtonLabel$4, 19591 hour: hour$4, 19592 minute: minute$4, 19593 second: second$4, 19594 hours: hours$4, 19595 minutes: minutes$4, 19596 seconds: seconds$4, 19597 vtsHeading: vtsHeading$4, 19598 vtsInstructions1: vtsInstructions1$4, 19599 vtsInstructions2: vtsInstructions2$4, 19600 vtsInstructions3: vtsInstructions3$4, 19601 vtsInstructions4: vtsInstructions4$4, 19602 vtsInstructions5: vtsInstructions5$4, 19603 vtsSelectLanguage: vtsSelectLanguage$4, 19604 vtsSave: vtsSave$4, 19605 vtsReturn: vtsReturn$4, 19606 vtsCancel: vtsCancel$4, 19607 vtsRow: vtsRow$4, 19608 vtsKind: vtsKind$4, 19609 vtsStart: vtsStart$4, 19610 vtsEnd: vtsEnd$4, 19611 vtsContent: vtsContent$4, 19612 vtsActions: vtsActions$4, 19613 vtsNewRow: vtsNewRow$4, 19614 vtsDeletedRow: vtsDeletedRow$4, 19615 vtsMovedRow: vtsMovedRow$4 19616 }; 19617 19618 var __author__ = "Slovak translation by Radoslav ÄuraÄ (@rraddatch)"; 19619 var playerHeading$3 = "PrehrávaÄ médiÃ"; 19620 var audioPlayer$3 = "PrehrávaÄ zvuku"; 19621 var videoPlayer$3 = "PrehrávaÄ videa"; 19622 var faster$3 = "RýchlejÅ¡ie"; 19623 var slower$3 = "PomalÅ¡ie"; 19624 var play$3 = "PrehraÅ¥"; 19625 var pause$3 = "PozastaviÅ¥"; 19626 var restart$3 = "PrehraÅ¥ odznova"; 19627 var prevTrack$3 = "Predchádzajúca stopa"; 19628 var nextTrack$3 = "Nasledujúca stopa"; 19629 var rewind$3 = "PretoÄiÅ¥ dozadu"; 19630 var forward$3 = "PretoÄiÅ¥ dopredu"; 19631 var captions$3 = "Skryté titulky"; 19632 var showCaptions$3 = "ZobraziÅ¥ skryté titulky"; 19633 var hideCaptions$3 = "Vypnúť skryté titulky"; 19634 var captionsOff$3 = "Vypnúť skryté titulky"; 19635 var showTranscript$3 = "ZobraziÅ¥ prepis"; 19636 var hideTranscript$3 = "SkryÅ¥ prepis"; 19637 var turnOnDescriptions$3 = "Zapnúť audiokomentár"; 19638 var turnOffDescriptions$3 = "Vypnúť audiokomentár"; 19639 var chapters$3 = "Kapitoly"; 19640 var language$3 = "Jazyk"; 19641 var sign$3 = "Posunkový jazyk"; 19642 var showSign$3 = "ZobraziÅ¥ posunkový jazyk"; 19643 var hideSign$3 = "SkryÅ¥ posunkový jazyk"; 19644 var seekbarLabel$3 = "Äasová os"; 19645 var mute$3 = "StlmiÅ¥"; 19646 var unmute$3 = "ZruÅ¡iÅ¥ stlmenie"; 19647 var volume$3 = "HlasitosÅ¥"; 19648 var volumeUpDown$3 = "HlasitosÅ¥ hore dole"; 19649 var preferences$3 = "Nastavenia"; 19650 var enterFullScreen$3 = "PrejsÅ¥ na celú obrazovku"; 19651 var exitFullScreen$3 = "UkonÄiÅ¥ celú obrazovku"; 19652 var speed$3 = "RýchlosÅ¥"; 19653 var spacebar$3 = "medzernÃk"; 19654 var transcriptTitle$3 = "Prepis"; 19655 var lyricsTitle$3 = "Text piesne"; 19656 var autoScroll$3 = "Automatické posúvanie"; 19657 var statusPlaying$3 = "Prehráva sa"; 19658 var statusPaused$3 = "Pozastavené"; 19659 var statusStopped$3 = "Zastavené"; 19660 var statusBuffering$3 = "NaÄÃtava sa"; 19661 var statusEnd$3 = "Koniec stopy"; 19662 var selectedTrack$3 = "Vybraná stopa"; 19663 var alertDescribedVersion$3 = "PoužÃva sa verzia videa s audiokomentárom"; 19664 var alertNonDescribedVersion$3 = "PoužÃva sa verzia videa bez audiokomentára"; 19665 var prefMenuCaptions$3 = "Skryté titulky"; 19666 var prefVoicedCaptions$3 = "Hovorené skryté titulky"; 19667 var prefMenuDescriptions$3 = "Audiokomentár"; 19668 var prefMenuKeyboard$3 = "Klávesnica"; 19669 var prefMenuTranscript$3 = "Prepis"; 19670 var prefTitleCaptions$3 = "Nastavenia skrytých titulkov"; 19671 var prefTitleDescriptions$3 = "Nastavenia audiokomentára"; 19672 var prefTitleKeyboard$3 = "Nastavenia klávesnice"; 19673 var prefTitleTranscript$3 = "Nastavenia prepisu"; 19674 var prefIntroDescription1$3 = "Tento prehrávaÄ médià podporuje audiokomentár dvoma spôsobmi: "; 19675 var prefDescription1$3 = "Aktuálne video má alternatÃvnu verziu s audiokomentárom a textový audiokomentár oznamovaný ÄÃtaÄkou obrazovky."; 19676 var prefDescription2$3 = "Aktuálne video má textový audiokomentár."; 19677 var prefDescription3$3 = "Aktuálne video má alternatÃvnu verziu s audiokomentárom."; 19678 var prefIntroDescriptionNone$3 = "Aktuálne video nemá audiokomentár v žiadnom formáte."; 19679 var prefIntroDescription3$3 = "Pomocou nasledujúceho formulára nastavte svoje predvoľby týkajúce sa textového audiokomentára."; 19680 var prefIntroDescription4$3 = "Po uloženà nastavenà je možné audiokomentár zapnúť/vypnúť pomocou prÃsluÅ¡ného tlaÄidla."; 19681 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)."; 19682 var prefIntroKeyboard2$3 = "ModifikaÄné klávesy (Shift, Alt a Control) je možné priradiÅ¥ nižšie."; 19683 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."; 19684 var prefHeadingKeyboard1$3 = "ModifikaÄné klávesy použÃvané pre skratky"; 19685 var prefHeadingKeyboard2$3 = "Aktuálne klávesové skratky"; 19686 var prefHeadingDescription$3 = "Audiokomentár"; 19687 var prefHeadingTextDescription$3 = "Textový audiokomentár"; 19688 var prefAltKey$3 = "Alt"; 19689 var prefCtrlKey$3 = "Control"; 19690 var prefShiftKey$3 = "Shift"; 19691 var prefNoKeyShortcuts$3 = "ZakázaÅ¥ klávesové skratky"; 19692 var escapeKey$3 = "Escape"; 19693 var escapeKeyFunction$3 = "ZatvoriÅ¥ aktuálny dialóg alebo vyskakovacie menu"; 19694 var prefDescPause$3 = "Automaticky pozastaviÅ¥ video, keÄ sa spustà audiokomentár"; 19695 var prefDescVisible$3 = "ZobraziÅ¥ audiokomentár"; 19696 var prefDescVoice$3 = "Hlas"; 19697 var prefDescRate$3 = "RýchlosÅ¥ hovoreného audiokomentára"; 19698 var prefCaptionRate$3 = "RýchlosÅ¥ hovorených skrytých titulkov"; 19699 var prefDescPitch$3 = "Výška tónu"; 19700 var prefDescPitch1$3 = "Veľmi nÃzka"; 19701 var prefDescPitch2$3 = "NÃzka"; 19702 var prefDescPitch3$3 = "Predvolená"; 19703 var prefDescPitch4$3 = "Vysoká"; 19704 var prefDescPitch5$3 = "Veľmi vysoká"; 19705 var sampleDescriptionText$3 = "Upravte nastavenia, aby ste poÄuli tento vzorový text."; 19706 var prefHighlight$3 = "ZvýrazniÅ¥ prepis poÄas prehrávania médiÃ"; 19707 var prefTabbable$3 = "PovoliÅ¥ ovládanie prepisu klávesnicou"; 19708 var prefCaptionsFont$3 = "PÃsmo"; 19709 var prefCaptionsColor$3 = "Farba textu"; 19710 var prefCaptionsBGColor$3 = "Pozadie"; 19711 var prefCaptionsSize$3 = "VeľkosÅ¥ pÃsma"; 19712 var prefCaptionsOpacity$3 = "NepriehľadnosÅ¥"; 19713 var prefCaptionsStyle$3 = "Å týl"; 19714 var serif$3 = "pätkové (serif)"; 19715 var sans$3 = "bezpätkové (sans-serif)"; 19716 var cursive$3 = "kurzÃva"; 19717 var fantasy$3 = "ozdobné"; 19718 var monospace$3 = "stála Å¡Ãrka (monospace)"; 19719 var white$3 = "biela"; 19720 var yellow$3 = "žltá"; 19721 var green$3 = "zelená"; 19722 var cyan$3 = "azúrová"; 19723 var blue$3 = "modrá"; 19724 var magenta$3 = "fialová"; 19725 var red$3 = "Äervená"; 19726 var black$3 = "Äierna"; 19727 var transparent$3 = "priehľadné"; 19728 var solid$3 = "nepriehľadné"; 19729 var captionsStylePopOn$3 = "Pop-on"; 19730 var captionsStyleRollUp$3 = "Roll-up"; 19731 var prefCaptionsPosition$3 = "PozÃcia"; 19732 var captionsPositionOverlay$3 = "Prekrytie"; 19733 var captionsPositionBelow$3 = "Pod videom"; 19734 var sampleCaptionText$3 = "Vzorový text skrytých titulkov"; 19735 var prefSuccess$3 = "VaÅ¡e zmeny boli uložené."; 19736 var prefNoChange$3 = "Neurobili ste žiadne zmeny."; 19737 var save$3 = "UložiÅ¥"; 19738 var cancel$3 = "ZruÅ¡iÅ¥"; 19739 var dismissButton$3 = "ZatvoriÅ¥"; 19740 var windowButtonLabel$3 = "Možnosti okna"; 19741 var windowMove$3 = "Presunúť"; 19742 var windowMoveLeft$3 = "Okno sa presunulo doľava"; 19743 var windowMoveRight$3 = "Okno sa presunulo doprava"; 19744 var windowMoveUp$3 = "Okno sa presunulo hore"; 19745 var windowMoveDown$3 = "Okno sa presunulo dole"; 19746 var windowMoveStopped$3 = "Presúvanie okna zastavené"; 19747 var transcriptControls$3 = "Ovládacie prvky okna prepisu"; 19748 var signControls$3 = "Ovládacie prvky okna posunkového jazyka"; 19749 var windowMoveAlert$3 = "Potiahnite alebo použite Å¡Ãpky na presun okna; stlaÄenÃm Enter zastavte"; 19750 var windowResize$3 = "ZmeniÅ¥ veľkosÅ¥"; 19751 var windowResizeHeading$3 = "ZmeniÅ¥ veľkosÅ¥ okna"; 19752 var closeButtonLabel$3 = "ZatvoriÅ¥"; 19753 var width$3 = "Å Ãrka"; 19754 var height$3 = "Výška"; 19755 var resultsSummary1$3 = "Hľadali ste:"; 19756 var resultsSummary2$3 = "Nájdených %1 zodpovedajúcich položiek."; 19757 var resultsSummary3$3 = "KliknutÃm na Äas priradený k ľubovoľnej položke prehráte video od daného bodu."; 19758 var noResultsFound$3 = "NenaÅ¡li sa žiadne výsledky."; 19759 var searchButtonLabel$3 = "PrehraÅ¥ od %1"; 19760 var hour$3 = "hodina"; 19761 var minute$3 = "minúta"; 19762 var second$3 = "sekunda"; 19763 var hours$3 = "hodiny"; 19764 var minutes$3 = "minúty"; 19765 var seconds$3 = "sekundy"; 19766 var vtsHeading$3 = "TriediÄ prepisu videa"; 19767 var vtsInstructions1$3 = "Použite TriediÄ prepisu videa na úpravu textových stôp:"; 19768 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."; 19769 var vtsInstructions3$3 = "Upravte obsah alebo Äasy zaÄiatku/konca (vÅ¡etky sú priamo upraviteľné v tabuľke)."; 19770 var vtsInstructions4$3 = "Pridajte nový obsah, naprÃklad kapitoly alebo audiokomentáre."; 19771 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."; 19772 var vtsSelectLanguage$3 = "Vyberte jazyk"; 19773 var vtsSave$3 = "VygenerovaÅ¥ nový obsah .vtt"; 19774 var vtsReturn$3 = "VrátiÅ¥ sa do editora"; 19775 var vtsCancel$3 = "Ukladanie sa ruÅ¡Ã. VÅ¡etky vykonané úpravy boli v tabuľke VTS obnovené."; 19776 var vtsRow$3 = "Riadok"; 19777 var vtsKind$3 = "Typ"; 19778 var vtsStart$3 = "ZaÄiatok"; 19779 var vtsEnd$3 = "Koniec"; 19780 var vtsContent$3 = "Obsah"; 19781 var vtsActions$3 = "Akcie"; 19782 var vtsNewRow$3 = "Bol vložený nový riadok %1."; 19783 var vtsDeletedRow$3 = "Riadok %1 bol odstránený."; 19784 var vtsMovedRow$3 = "Riadok %1 bol presunutý %2 a teraz je riadkom %3."; 19785 var sk = { 19786 __author__: __author__, 19787 playerHeading: playerHeading$3, 19788 audioPlayer: audioPlayer$3, 19789 videoPlayer: videoPlayer$3, 19790 faster: faster$3, 19791 slower: slower$3, 19792 play: play$3, 19793 pause: pause$3, 19794 restart: restart$3, 19795 prevTrack: prevTrack$3, 19796 nextTrack: nextTrack$3, 19797 rewind: rewind$3, 19798 forward: forward$3, 19799 captions: captions$3, 19800 showCaptions: showCaptions$3, 19801 hideCaptions: hideCaptions$3, 19802 captionsOff: captionsOff$3, 19803 showTranscript: showTranscript$3, 19804 hideTranscript: hideTranscript$3, 19805 turnOnDescriptions: turnOnDescriptions$3, 19806 turnOffDescriptions: turnOffDescriptions$3, 19807 chapters: chapters$3, 19808 language: language$3, 19809 sign: sign$3, 19810 showSign: showSign$3, 19811 hideSign: hideSign$3, 19812 seekbarLabel: seekbarLabel$3, 19813 mute: mute$3, 19814 unmute: unmute$3, 19815 volume: volume$3, 19816 volumeUpDown: volumeUpDown$3, 19817 preferences: preferences$3, 19818 enterFullScreen: enterFullScreen$3, 19819 exitFullScreen: exitFullScreen$3, 19820 speed: speed$3, 19821 spacebar: spacebar$3, 19822 transcriptTitle: transcriptTitle$3, 19823 lyricsTitle: lyricsTitle$3, 19824 autoScroll: autoScroll$3, 19825 statusPlaying: statusPlaying$3, 19826 statusPaused: statusPaused$3, 19827 statusStopped: statusStopped$3, 19828 statusBuffering: statusBuffering$3, 19829 statusEnd: statusEnd$3, 19830 selectedTrack: selectedTrack$3, 19831 alertDescribedVersion: alertDescribedVersion$3, 19832 alertNonDescribedVersion: alertNonDescribedVersion$3, 19833 prefMenuCaptions: prefMenuCaptions$3, 19834 prefVoicedCaptions: prefVoicedCaptions$3, 19835 prefMenuDescriptions: prefMenuDescriptions$3, 19836 prefMenuKeyboard: prefMenuKeyboard$3, 19837 prefMenuTranscript: prefMenuTranscript$3, 19838 prefTitleCaptions: prefTitleCaptions$3, 19839 prefTitleDescriptions: prefTitleDescriptions$3, 19840 prefTitleKeyboard: prefTitleKeyboard$3, 19841 prefTitleTranscript: prefTitleTranscript$3, 19842 prefIntroDescription1: prefIntroDescription1$3, 19843 prefDescription1: prefDescription1$3, 19844 prefDescription2: prefDescription2$3, 19845 prefDescription3: prefDescription3$3, 19846 prefIntroDescriptionNone: prefIntroDescriptionNone$3, 19847 prefIntroDescription3: prefIntroDescription3$3, 19848 prefIntroDescription4: prefIntroDescription4$3, 19849 prefIntroKeyboard1: prefIntroKeyboard1$3, 19850 prefIntroKeyboard2: prefIntroKeyboard2$3, 19851 prefIntroKeyboard3: prefIntroKeyboard3$3, 19852 prefHeadingKeyboard1: prefHeadingKeyboard1$3, 19853 prefHeadingKeyboard2: prefHeadingKeyboard2$3, 19854 prefHeadingDescription: prefHeadingDescription$3, 19855 prefHeadingTextDescription: prefHeadingTextDescription$3, 19856 prefAltKey: prefAltKey$3, 19857 prefCtrlKey: prefCtrlKey$3, 19858 prefShiftKey: prefShiftKey$3, 19859 prefNoKeyShortcuts: prefNoKeyShortcuts$3, 19860 escapeKey: escapeKey$3, 19861 escapeKeyFunction: escapeKeyFunction$3, 19862 prefDescPause: prefDescPause$3, 19863 prefDescVisible: prefDescVisible$3, 19864 prefDescVoice: prefDescVoice$3, 19865 prefDescRate: prefDescRate$3, 19866 prefCaptionRate: prefCaptionRate$3, 19867 prefDescPitch: prefDescPitch$3, 19868 prefDescPitch1: prefDescPitch1$3, 19869 prefDescPitch2: prefDescPitch2$3, 19870 prefDescPitch3: prefDescPitch3$3, 19871 prefDescPitch4: prefDescPitch4$3, 19872 prefDescPitch5: prefDescPitch5$3, 19873 sampleDescriptionText: sampleDescriptionText$3, 19874 prefHighlight: prefHighlight$3, 19875 prefTabbable: prefTabbable$3, 19876 prefCaptionsFont: prefCaptionsFont$3, 19877 prefCaptionsColor: prefCaptionsColor$3, 19878 prefCaptionsBGColor: prefCaptionsBGColor$3, 19879 prefCaptionsSize: prefCaptionsSize$3, 19880 prefCaptionsOpacity: prefCaptionsOpacity$3, 19881 prefCaptionsStyle: prefCaptionsStyle$3, 19882 serif: serif$3, 19883 sans: sans$3, 19884 cursive: cursive$3, 19885 fantasy: fantasy$3, 19886 monospace: monospace$3, 19887 white: white$3, 19888 yellow: yellow$3, 19889 green: green$3, 19890 cyan: cyan$3, 19891 blue: blue$3, 19892 magenta: magenta$3, 19893 red: red$3, 19894 black: black$3, 19895 transparent: transparent$3, 19896 solid: solid$3, 19897 captionsStylePopOn: captionsStylePopOn$3, 19898 captionsStyleRollUp: captionsStyleRollUp$3, 19899 prefCaptionsPosition: prefCaptionsPosition$3, 19900 captionsPositionOverlay: captionsPositionOverlay$3, 19901 captionsPositionBelow: captionsPositionBelow$3, 19902 sampleCaptionText: sampleCaptionText$3, 19903 prefSuccess: prefSuccess$3, 19904 prefNoChange: prefNoChange$3, 19905 save: save$3, 19906 cancel: cancel$3, 19907 dismissButton: dismissButton$3, 19908 windowButtonLabel: windowButtonLabel$3, 19909 windowMove: windowMove$3, 19910 windowMoveLeft: windowMoveLeft$3, 19911 windowMoveRight: windowMoveRight$3, 19912 windowMoveUp: windowMoveUp$3, 19913 windowMoveDown: windowMoveDown$3, 19914 windowMoveStopped: windowMoveStopped$3, 19915 transcriptControls: transcriptControls$3, 19916 signControls: signControls$3, 19917 windowMoveAlert: windowMoveAlert$3, 19918 windowResize: windowResize$3, 19919 windowResizeHeading: windowResizeHeading$3, 19920 closeButtonLabel: closeButtonLabel$3, 19921 width: width$3, 19922 height: height$3, 19923 resultsSummary1: resultsSummary1$3, 19924 resultsSummary2: resultsSummary2$3, 19925 resultsSummary3: resultsSummary3$3, 19926 noResultsFound: noResultsFound$3, 19927 searchButtonLabel: searchButtonLabel$3, 19928 hour: hour$3, 19929 minute: minute$3, 19930 second: second$3, 19931 hours: hours$3, 19932 minutes: minutes$3, 19933 seconds: seconds$3, 19934 vtsHeading: vtsHeading$3, 19935 vtsInstructions1: vtsInstructions1$3, 19936 vtsInstructions2: vtsInstructions2$3, 19937 vtsInstructions3: vtsInstructions3$3, 19938 vtsInstructions4: vtsInstructions4$3, 19939 vtsInstructions5: vtsInstructions5$3, 19940 vtsSelectLanguage: vtsSelectLanguage$3, 19941 vtsSave: vtsSave$3, 19942 vtsReturn: vtsReturn$3, 19943 vtsCancel: vtsCancel$3, 19944 vtsRow: vtsRow$3, 19945 vtsKind: vtsKind$3, 19946 vtsStart: vtsStart$3, 19947 vtsEnd: vtsEnd$3, 19948 vtsContent: vtsContent$3, 19949 vtsActions: vtsActions$3, 19950 vtsNewRow: vtsNewRow$3, 19951 vtsDeletedRow: vtsDeletedRow$3, 19952 vtsMovedRow: vtsMovedRow$3 19953 }; 19954 19955 var playerHeading$2 = "Mediaspelare"; 19956 var audioPlayer$2 = "Audio player"; 19957 var videoPlayer$2 = "Video player"; 19958 var faster$2 = "Snabbare"; 19959 var slower$2 = "LÃ¥ngsammare"; 19960 var play$2 = "Spela upp"; 19961 var pause$2 = "Pausa"; 19962 var restart$2 = "Starta om"; 19963 var prevTrack$2 = "FöregÃ¥ende spÃ¥r"; 19964 var nextTrack$2 = "Nästa spÃ¥r"; 19965 var rewind$2 = "Spola tillbaka"; 19966 var forward$2 = "Spola framÃ¥t"; 19967 var captions$2 = "Undertexter"; 19968 var showCaptions$2 = "Visa undertexter"; 19969 var hideCaptions$2 = "Göm undertexter"; 19970 var captionsOff$2 = "Stäng av undertexter"; 19971 var showTranscript$2 = "Visa transkript"; 19972 var hideTranscript$2 = "Göm transkript"; 19973 var turnOnDescriptions$2 = "Sätt pÃ¥ syntolkning"; 19974 var turnOffDescriptions$2 = "Stäng av syntolkning"; 19975 var chapters$2 = "Kapitel"; 19976 var language$2 = "SprÃ¥k"; 19977 var sign$2 = "TeckensprÃ¥k"; 19978 var showSign$2 = "Visa teckensprÃ¥k"; 19979 var hideSign$2 = "Göm teckensprÃ¥k"; 19980 var seekbarLabel$2 = "tidslinje"; 19981 var mute$2 = "SlÃ¥ av ljud"; 19982 var unmute$2 = "SlÃ¥ pÃ¥ ljud"; 19983 var volume$2 = "Volym"; 19984 var volumeUpDown$2 = "Volym up ner"; 19985 var preferences$2 = "Preferenser"; 19986 var enterFullScreen$2 = "Visa i fullskärmsläge"; 19987 var exitFullScreen$2 = "GÃ¥ ur fullskärmsläge"; 19988 var speed$2 = "Hastighet"; 19989 var spacebar$2 = "mellanslag"; 19990 var transcriptTitle$2 = "Transkript"; 19991 var lyricsTitle$2 = "Lyrik"; 19992 var autoScroll$2 = "Auto-scrolla"; 19993 var statusPlaying$2 = "Spelar upp"; 19994 var statusPaused$2 = "Pausad"; 19995 var statusStopped$2 = "Stoppad"; 19996 var statusBuffering$2 = "Buffrar"; 19997 var statusEnd$2 = "Slut pÃ¥ spÃ¥ret"; 19998 var selectedTrack$2 = "Valt spÃ¥r"; 19999 var alertDescribedVersion$2 = "Använder syntolkad version av denna video"; 20000 var alertNonDescribedVersion$2 = "Använder ej syntolkad version av denna video"; 20001 var prefMenuCaptions$2 = "Undertexter"; 20002 var prefVoicedCaptions$2 = "Spoken Captions"; 20003 var prefMenuDescriptions$2 = "Syntolkning"; 20004 var prefMenuKeyboard$2 = "Tangentbord"; 20005 var prefMenuTranscript$2 = "Transkript"; 20006 var prefTitleCaptions$2 = "Undertextprefenser"; 20007 var prefTitleDescriptions$2 = "Syntolkningspreferenser"; 20008 var prefTitleKeyboard$2 = "Tangentbordspreferenser"; 20009 var prefTitleTranscript$2 = "Transkriptpreferenser"; 20010 var prefIntroDescription1$2 = "Denna mediaspelare stöder syntolkning pÃ¥ tvÃ¥ sätt: "; 20011 var prefDescription1$2 = "Följande video har en alternativ syntolkad version, textbaserad syntolkning."; 20012 var prefDescription2$2 = "Följande video har alternativ syntolkad version av videon."; 20013 var prefDescription3$2 = "Följande video har textbaserad syntolkning, uppläst av skärmläsare."; 20014 var prefIntroDescriptionNone$2 = "Nuvarande video har ingen syntolkning i nÃ¥got format."; 20015 var prefIntroDescription3$2 = "Använd följande formulär för att ställa in dina prefenser gällande textbaserad syntolkning."; 20016 var prefIntroDescription4$2 = "Efter du sparar dina prefenser, kan syntolkning växlas av och pÃ¥ med syntolkningsknappen."; 20017 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)."; 20018 var prefIntroKeyboard2$2 = "Metatangenter (Shift, Alt, och Ctrl) kan tilldelas nedan."; 20019 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."; 20020 var prefHeadingKeyboard1$2 = "Metatangenter använda till genvägar"; 20021 var prefHeadingKeyboard2$2 = "Nuvarande tangentbordsgenvägar"; 20022 var prefHeadingDescription$2 = "Syntolkning"; 20023 var prefHeadingTextDescription$2 = "Textbaserad syntolkning"; 20024 var prefAltKey$2 = "Alt"; 20025 var prefCtrlKey$2 = "Ctrl"; 20026 var prefShiftKey$2 = "Shift"; 20027 var prefNoKeyShortcuts$2 = "Disable keyboard shortcuts"; 20028 var escapeKey$2 = "Escape"; 20029 var escapeKeyFunction$2 = "Stäng nuvarande dialog eller popupmeny"; 20030 var prefDescPause$2 = "Pausa automatiskt video när syntolkning startar"; 20031 var prefDescVisible$2 = "Synliggör syntolkning"; 20032 var prefDescVoice$2 = "Röst"; 20033 var prefDescRate$2 = "Spoken Description Rate"; 20034 var prefCaptionRate$2 = "Spoken Caption Rate"; 20035 var prefDescPitch$2 = "Tonhöjd"; 20036 var prefDescPitch1$2 = "Mycket lÃ¥gt"; 20037 var prefDescPitch2$2 = "LÃ¥g"; 20038 var prefDescPitch3$2 = "Normal"; 20039 var prefDescPitch4$2 = "Högt"; 20040 var prefDescPitch5$2 = "Mycket högt"; 20041 var sampleDescriptionText$2 = "Justera inställningarna för att höra denna exempeltext."; 20042 var prefHighlight$2 = "Markera det som läses upp i transkriptet"; 20043 var prefTabbable$2 = "Tangentbordsaktiverat transkript"; 20044 var prefCaptionsFont$2 = "Typsnitt"; 20045 var prefCaptionsColor$2 = "Textfärg"; 20046 var prefCaptionsBGColor$2 = "Bakgrund"; 20047 var prefCaptionsSize$2 = "Textstorlek"; 20048 var prefCaptionsOpacity$2 = "Ogenomskinlighet"; 20049 var prefCaptionsStyle$2 = "Stil"; 20050 var serif$2 = "serif"; 20051 var sans$2 = "sans-serif"; 20052 var cursive$2 = "kursiv"; 20053 var fantasy$2 = "fantasi"; 20054 var monospace$2 = "monospace"; 20055 var white$2 = "vit"; 20056 var yellow$2 = "gul"; 20057 var green$2 = "grön"; 20058 var cyan$2 = "ljusblÃ¥"; 20059 var blue$2 = "blÃ¥"; 20060 var magenta$2 = "lila"; 20061 var red$2 = "röd"; 20062 var black$2 = "svart"; 20063 var transparent$2 = "genomskinligt"; 20064 var solid$2 = "massiv"; 20065 var captionsStylePopOn$2 = "Dyker upp"; 20066 var captionsStyleRollUp$2 = "Rullar upp"; 20067 var prefCaptionsPosition$2 = "Position"; 20068 var captionsPositionOverlay$2 = "Täck över videon"; 20069 var captionsPositionBelow$2 = "Under videon"; 20070 var sampleCaptionText$2 = "Exempel pÃ¥ undertext"; 20071 var prefSuccess$2 = "Dina ändringar har blivit sparade."; 20072 var prefNoChange$2 = "Du gjorde inga ändringar."; 20073 var save$2 = "Spara"; 20074 var cancel$2 = "Avbryt"; 20075 var dismissButton$2 = "Dismiss"; 20076 var windowButtonLabel$2 = "Fönsteralternativ"; 20077 var windowMove$2 = "Flytta"; 20078 var windowMoveLeft$2 = "Window moved left"; 20079 var windowMoveRight$2 = "Window moved right"; 20080 var windowMoveUp$2 = "Window moved up"; 20081 var windowMoveDown$2 = "Window moved down"; 20082 var windowMoveStopped$2 = "Window move stopped"; 20083 var transcriptControls$2 = "Transcript Window Controls"; 20084 var signControls$2 = "Sign Language Window Controls"; 20085 var windowMoveAlert$2 = "Drag eller använd piltangenter för att flytta fönstret; Enter för att sluta"; 20086 var windowResize$2 = "Ãndra storlek"; 20087 var windowResizeHeading$2 = "Ãndra fönsterstorlek"; 20088 var closeButtonLabel$2 = "Stäng"; 20089 var width$2 = "Bredd"; 20090 var height$2 = "Höjd"; 20091 var resultsSummary1$2 = "Du sökte efter:"; 20092 var resultsSummary2$2 = "Hittade %1 träffar."; 20093 var resultsSummary3$2 = "Klicka pÃ¥ tiden för en punkt för att spela video frÃ¥n den tiden."; 20094 var noResultsFound$2 = "Inga träffar hittade."; 20095 var searchButtonLabel$2 = "Spela frÃ¥n %1"; 20096 var hour$2 = "timme"; 20097 var minute$2 = "minut"; 20098 var second$2 = "sekund"; 20099 var hours$2 = "timmar"; 20100 var minutes$2 = "minuter"; 20101 var seconds$2 = "sekunder"; 20102 var vtsHeading$2 = "Video Transcript Sorter"; 20103 var vtsInstructions1$2 = "Use the Video Transcript Sorter to modify text tracks:"; 20104 var vtsInstructions2$2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20105 var vtsInstructions3$2 = "Modify content or start/end times (all are directly editable within the table)."; 20106 var vtsInstructions4$2 = "Add new content, such as chapters or descriptions."; 20107 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."; 20108 var vtsSelectLanguage$2 = "Select a language"; 20109 var vtsSave$2 = "Generate new .vtt content"; 20110 var vtsReturn$2 = "Return to Editor"; 20111 var vtsCancel$2 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20112 var vtsRow$2 = "Row"; 20113 var vtsKind$2 = "Kind"; 20114 var vtsStart$2 = "Start"; 20115 var vtsEnd$2 = "End"; 20116 var vtsContent$2 = "Content"; 20117 var vtsActions$2 = "Actions"; 20118 var vtsNewRow$2 = "A new row %1 has been inserted."; 20119 var vtsDeletedRow$2 = "Row %1 has been deleted."; 20120 var vtsMovedRow$2 = "Row %1 has been moved %2 and is now Row %3."; 20121 var sv = { 20122 playerHeading: playerHeading$2, 20123 audioPlayer: audioPlayer$2, 20124 videoPlayer: videoPlayer$2, 20125 faster: faster$2, 20126 slower: slower$2, 20127 play: play$2, 20128 pause: pause$2, 20129 restart: restart$2, 20130 prevTrack: prevTrack$2, 20131 nextTrack: nextTrack$2, 20132 rewind: rewind$2, 20133 forward: forward$2, 20134 captions: captions$2, 20135 showCaptions: showCaptions$2, 20136 hideCaptions: hideCaptions$2, 20137 captionsOff: captionsOff$2, 20138 showTranscript: showTranscript$2, 20139 hideTranscript: hideTranscript$2, 20140 turnOnDescriptions: turnOnDescriptions$2, 20141 turnOffDescriptions: turnOffDescriptions$2, 20142 chapters: chapters$2, 20143 language: language$2, 20144 sign: sign$2, 20145 showSign: showSign$2, 20146 hideSign: hideSign$2, 20147 seekbarLabel: seekbarLabel$2, 20148 mute: mute$2, 20149 unmute: unmute$2, 20150 volume: volume$2, 20151 volumeUpDown: volumeUpDown$2, 20152 preferences: preferences$2, 20153 enterFullScreen: enterFullScreen$2, 20154 exitFullScreen: exitFullScreen$2, 20155 speed: speed$2, 20156 spacebar: spacebar$2, 20157 transcriptTitle: transcriptTitle$2, 20158 lyricsTitle: lyricsTitle$2, 20159 autoScroll: autoScroll$2, 20160 statusPlaying: statusPlaying$2, 20161 statusPaused: statusPaused$2, 20162 statusStopped: statusStopped$2, 20163 statusBuffering: statusBuffering$2, 20164 statusEnd: statusEnd$2, 20165 selectedTrack: selectedTrack$2, 20166 alertDescribedVersion: alertDescribedVersion$2, 20167 alertNonDescribedVersion: alertNonDescribedVersion$2, 20168 prefMenuCaptions: prefMenuCaptions$2, 20169 prefVoicedCaptions: prefVoicedCaptions$2, 20170 prefMenuDescriptions: prefMenuDescriptions$2, 20171 prefMenuKeyboard: prefMenuKeyboard$2, 20172 prefMenuTranscript: prefMenuTranscript$2, 20173 prefTitleCaptions: prefTitleCaptions$2, 20174 prefTitleDescriptions: prefTitleDescriptions$2, 20175 prefTitleKeyboard: prefTitleKeyboard$2, 20176 prefTitleTranscript: prefTitleTranscript$2, 20177 prefIntroDescription1: prefIntroDescription1$2, 20178 prefDescription1: prefDescription1$2, 20179 prefDescription2: prefDescription2$2, 20180 prefDescription3: prefDescription3$2, 20181 prefIntroDescriptionNone: prefIntroDescriptionNone$2, 20182 prefIntroDescription3: prefIntroDescription3$2, 20183 prefIntroDescription4: prefIntroDescription4$2, 20184 prefIntroKeyboard1: prefIntroKeyboard1$2, 20185 prefIntroKeyboard2: prefIntroKeyboard2$2, 20186 prefIntroKeyboard3: prefIntroKeyboard3$2, 20187 prefHeadingKeyboard1: prefHeadingKeyboard1$2, 20188 prefHeadingKeyboard2: prefHeadingKeyboard2$2, 20189 prefHeadingDescription: prefHeadingDescription$2, 20190 prefHeadingTextDescription: prefHeadingTextDescription$2, 20191 prefAltKey: prefAltKey$2, 20192 prefCtrlKey: prefCtrlKey$2, 20193 prefShiftKey: prefShiftKey$2, 20194 prefNoKeyShortcuts: prefNoKeyShortcuts$2, 20195 escapeKey: escapeKey$2, 20196 escapeKeyFunction: escapeKeyFunction$2, 20197 prefDescPause: prefDescPause$2, 20198 prefDescVisible: prefDescVisible$2, 20199 prefDescVoice: prefDescVoice$2, 20200 prefDescRate: prefDescRate$2, 20201 prefCaptionRate: prefCaptionRate$2, 20202 prefDescPitch: prefDescPitch$2, 20203 prefDescPitch1: prefDescPitch1$2, 20204 prefDescPitch2: prefDescPitch2$2, 20205 prefDescPitch3: prefDescPitch3$2, 20206 prefDescPitch4: prefDescPitch4$2, 20207 prefDescPitch5: prefDescPitch5$2, 20208 sampleDescriptionText: sampleDescriptionText$2, 20209 prefHighlight: prefHighlight$2, 20210 prefTabbable: prefTabbable$2, 20211 prefCaptionsFont: prefCaptionsFont$2, 20212 prefCaptionsColor: prefCaptionsColor$2, 20213 prefCaptionsBGColor: prefCaptionsBGColor$2, 20214 prefCaptionsSize: prefCaptionsSize$2, 20215 prefCaptionsOpacity: prefCaptionsOpacity$2, 20216 prefCaptionsStyle: prefCaptionsStyle$2, 20217 serif: serif$2, 20218 sans: sans$2, 20219 cursive: cursive$2, 20220 fantasy: fantasy$2, 20221 monospace: monospace$2, 20222 white: white$2, 20223 yellow: yellow$2, 20224 green: green$2, 20225 cyan: cyan$2, 20226 blue: blue$2, 20227 magenta: magenta$2, 20228 red: red$2, 20229 black: black$2, 20230 transparent: transparent$2, 20231 solid: solid$2, 20232 captionsStylePopOn: captionsStylePopOn$2, 20233 captionsStyleRollUp: captionsStyleRollUp$2, 20234 prefCaptionsPosition: prefCaptionsPosition$2, 20235 captionsPositionOverlay: captionsPositionOverlay$2, 20236 captionsPositionBelow: captionsPositionBelow$2, 20237 sampleCaptionText: sampleCaptionText$2, 20238 prefSuccess: prefSuccess$2, 20239 prefNoChange: prefNoChange$2, 20240 save: save$2, 20241 cancel: cancel$2, 20242 dismissButton: dismissButton$2, 20243 windowButtonLabel: windowButtonLabel$2, 20244 windowMove: windowMove$2, 20245 windowMoveLeft: windowMoveLeft$2, 20246 windowMoveRight: windowMoveRight$2, 20247 windowMoveUp: windowMoveUp$2, 20248 windowMoveDown: windowMoveDown$2, 20249 windowMoveStopped: windowMoveStopped$2, 20250 transcriptControls: transcriptControls$2, 20251 signControls: signControls$2, 20252 windowMoveAlert: windowMoveAlert$2, 20253 windowResize: windowResize$2, 20254 windowResizeHeading: windowResizeHeading$2, 20255 closeButtonLabel: closeButtonLabel$2, 20256 width: width$2, 20257 height: height$2, 20258 resultsSummary1: resultsSummary1$2, 20259 resultsSummary2: resultsSummary2$2, 20260 resultsSummary3: resultsSummary3$2, 20261 noResultsFound: noResultsFound$2, 20262 searchButtonLabel: searchButtonLabel$2, 20263 hour: hour$2, 20264 minute: minute$2, 20265 second: second$2, 20266 hours: hours$2, 20267 minutes: minutes$2, 20268 seconds: seconds$2, 20269 vtsHeading: vtsHeading$2, 20270 vtsInstructions1: vtsInstructions1$2, 20271 vtsInstructions2: vtsInstructions2$2, 20272 vtsInstructions3: vtsInstructions3$2, 20273 vtsInstructions4: vtsInstructions4$2, 20274 vtsInstructions5: vtsInstructions5$2, 20275 vtsSelectLanguage: vtsSelectLanguage$2, 20276 vtsSave: vtsSave$2, 20277 vtsReturn: vtsReturn$2, 20278 vtsCancel: vtsCancel$2, 20279 vtsRow: vtsRow$2, 20280 vtsKind: vtsKind$2, 20281 vtsStart: vtsStart$2, 20282 vtsEnd: vtsEnd$2, 20283 vtsContent: vtsContent$2, 20284 vtsActions: vtsActions$2, 20285 vtsNewRow: vtsNewRow$2, 20286 vtsDeletedRow: vtsDeletedRow$2, 20287 vtsMovedRow: vtsMovedRow$2 20288 }; 20289 20290 var playerHeading$1 = "Medya Oynatıcı"; 20291 var audioPlayer$1 = "Audio player"; 20292 var videoPlayer$1 = "Video player"; 20293 var faster$1 = "Hızlandır"; 20294 var slower$1 = "YavaÅlat"; 20295 var play$1 = "Oynat"; 20296 var pause$1 = "Duraklat"; 20297 var restart$1 = "Yeniden Oynat"; 20298 var prevTrack$1 = "Ãnceki Parça"; 20299 var nextTrack$1 = "Gelecek Parça"; 20300 var rewind$1 = "Geri Sar"; 20301 var forward$1 = "İleri Sar"; 20302 var captions$1 = "Altyazılar"; 20303 var showCaptions$1 = "Altyazıları Göster"; 20304 var hideCaptions$1 = "Altyazıları Gizle"; 20305 var captionsOff$1 = "Altyazıkarı Kapat"; 20306 var showTranscript$1 = "Belgeyi Göster"; 20307 var hideTranscript$1 = "HBelgeyi Gizle"; 20308 var turnOnDescriptions$1 = "Açıklamaları Aç"; 20309 var turnOffDescriptions$1 = "Açıklamaları Kapa"; 20310 var chapters$1 = "Bölümler"; 20311 var language$1 = "Dil"; 20312 var sign$1 = "İÅaret Dili"; 20313 var showSign$1 = "İÅaret Dilini Göster"; 20314 var hideSign$1 = "İÅaret Dilini Gizle"; 20315 var seekbarLabel$1 = "Zaman Cetveli"; 20316 var mute$1 = "Sessize Al"; 20317 var unmute$1 = "Sesi Aç"; 20318 var volume$1 = "Ses Ayarı"; 20319 var volumeUpDown$1 = "Sesi yükselt veya alçalt"; 20320 var preferences$1 = "Seçenekler"; 20321 var enterFullScreen$1 = "Tam Ekranı EtkinleÅtir"; 20322 var exitFullScreen$1 = "Tam Ekranı Kapa"; 20323 var speed$1 = "Hız"; 20324 var spacebar$1 = "boÅluk tuÅu"; 20325 var transcriptTitle$1 = "Belge"; 20326 var lyricsTitle$1 = "Sözler"; 20327 var autoScroll$1 = "Otomatik Kaydırma"; 20328 var statusPlaying$1 = "Yürütülüyor"; 20329 var statusPaused$1 = "Duraklat"; 20330 var statusStopped$1 = "Durdur"; 20331 var statusBuffering$1 = "ArabelleÄe Alınıyor"; 20332 var statusEnd$1 = "Parça sonlandı"; 20333 var selectedTrack$1 = "Seçilen Parça"; 20334 var alertDescribedVersion$1 = "Tanımlı video versiyonu için ses kullanılıyor"; 20335 var alertNonDescribedVersion$1 = "TanımlanmamıŠvideo versiyonu için kullanılıyor"; 20336 var prefMenuCaptions$1 = "Altyazılar"; 20337 var prefVoicedCaptions$1 = "Spoken Captions"; 20338 var prefMenuDescriptions$1 = "Açıklamalar"; 20339 var prefMenuKeyboard$1 = "Klavye"; 20340 var prefMenuTranscript$1 = "Belgeler"; 20341 var prefTitleCaptions$1 = "Altyazı Seçenekleri"; 20342 var prefTitleDescriptions$1 = "Ses Açıklama Ayarları"; 20343 var prefTitleKeyboard$1 = "Klavye Tercihleri"; 20344 var prefTitleTranscript$1 = "Belge Tercihleri"; 20345 var prefIntroDescription1$1 = "Bu video oynatıcı iki farklı ses açıklaması destekler: "; 20346 var prefDescription1$1 = "Åu anki video'da mevcut alternatif tanımlı versiyon, metin tabanlı açıklama."; 20347 var prefDescription2$1 = "Åu anki video'da mevcut Video versiyonu için tanımlı alternatifler."; 20348 var prefDescription3$1 = "Åu anki video'da mevcut metin tabanlı açıklama, ekran okuyucusu tarafından duyurulan."; 20349 var prefIntroDescriptionNone$1 = "Åu anki video'da her iki format için de ses açıklaması mevcut deÄil."; 20350 var prefIntroDescription3$1 = "Ses açıklamalarını tercihlerinle iliÅkilendirmek için belirtilen biçimi kullan."; 20351 var prefIntroDescription4$1 = "Tercihlerini kaydettikten sonra, ses açıklamasını Açıklama butonu aracılıÄıyla açıp kapatabilirsin."; 20352 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)."; 20353 var prefIntroKeyboard2$1 = "Niteleme TuÅları (Shift, Alt, ve Control) aÅaÄınan atanabilir."; 20354 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."; 20355 var prefHeadingKeyboard1$1 = "Niteleme TuÅları kısayollar için kullanılır"; 20356 var prefHeadingKeyboard2$1 = "Åu anki klavye kısayolları"; 20357 var prefHeadingDescription$1 = "Ses açıklaması"; 20358 var prefHeadingTextDescription$1 = "Metin tabanlı ses açıklaması"; 20359 var prefAltKey$1 = "Alt"; 20360 var prefCtrlKey$1 = "Control"; 20361 var prefShiftKey$1 = "Shift"; 20362 var prefNoKeyShortcuts$1 = "Disable keyboard shortcuts"; 20363 var escapeKey$1 = "Escape"; 20364 var escapeKeyFunction$1 = "Åu anki diyalogu veya açılır menüyü kapa"; 20365 var prefDescPause$1 = "Açıklama baÅladıÄında video'yu otomatik olarak durdur"; 20366 var prefDescVisible$1 = "Açıklamayı görünür yap"; 20367 var prefDescVoice$1 = "Voice"; 20368 var prefDescRate$1 = "Spoken Description Rate"; 20369 var prefCaptionRate$1 = "Spoken Caption Rate"; 20370 var prefDescPitch$1 = "Pitch"; 20371 var prefDescPitch1$1 = "Very low"; 20372 var prefDescPitch2$1 = "Low"; 20373 var prefDescPitch3$1 = "Default"; 20374 var prefDescPitch4$1 = "High"; 20375 var prefDescPitch5$1 = "Very high"; 20376 var sampleDescriptionText$1 = "Adjust settings to hear this sample text."; 20377 var prefHighlight$1 = "Medya'daki belgeyi öne çıkar"; 20378 var prefTabbable$1 = "Klavye ile aktif edilen belge"; 20379 var prefCaptionsFont$1 = "Yazı Tipi"; 20380 var prefCaptionsColor$1 = "Metin Rengi"; 20381 var prefCaptionsBGColor$1 = "Arkaplan"; 20382 var prefCaptionsSize$1 = "Yazı Boyutu"; 20383 var prefCaptionsOpacity$1 = "Opasite"; 20384 var prefCaptionsStyle$1 = "Stil"; 20385 var serif$1 = "serif"; 20386 var sans$1 = "sans-serif"; 20387 var cursive$1 = "el yazısı"; 20388 var fantasy$1 = "fantezi"; 20389 var monospace$1 = "tek hacimli"; 20390 var white$1 = "beyaz"; 20391 var yellow$1 = "sarı"; 20392 var green$1 = "yeÅik"; 20393 var cyan$1 = "açık mavi"; 20394 var blue$1 = "mavi"; 20395 var magenta$1 = "mor"; 20396 var red$1 = "kırmızı"; 20397 var black$1 = "siyah"; 20398 var transparent$1 = "transparan"; 20399 var solid$1 = "düz renk"; 20400 var captionsStylePopOn$1 = "Pop-on"; 20401 var captionsStyleRollUp$1 = "Roll-up"; 20402 var prefCaptionsPosition$1 = "Pozisyon"; 20403 var captionsPositionOverlay$1 = "Kaplama"; 20404 var captionsPositionBelow$1 = "Video'nun altında"; 20405 var sampleCaptionText$1 = "Ãrnek altyaı metni"; 20406 var prefSuccess$1 = "DeÄiÅikliklerin kaydedildi."; 20407 var prefNoChange$1 = "Hiçbir deÄiÅiklik yapmadın."; 20408 var save$1 = "Kaydet"; 20409 var cancel$1 = "İptal Et"; 20410 var dismissButton$1 = "Dismiss"; 20411 var windowButtonLabel$1 = "Pencere Seçenekleri"; 20412 var windowMove$1 = "Hareket"; 20413 var windowMoveLeft$1 = "Window moved left"; 20414 var windowMoveRight$1 = "Window moved right"; 20415 var windowMoveUp$1 = "Window moved up"; 20416 var windowMoveDown$1 = "Window moved down"; 20417 var windowMoveStopped$1 = "Window move stopped"; 20418 var transcriptControls$1 = "Transcript Window Controls"; 20419 var signControls$1 = "Sign Language Window Controls"; 20420 var windowMoveAlert$1 = "Pencereyi hareket ettirmek için mouse'unu sürü veya ok tuÅlarını kullan; Durdurmak için Enter'a bas"; 20421 var windowResize$1 = "Yeniden Boyutlandır"; 20422 var windowResizeHeading$1 = "Pencereyi yeniden Boyutlandır"; 20423 var closeButtonLabel$1 = "Kapat"; 20424 var width$1 = "En"; 20425 var height$1 = "Boy"; 20426 var resultsSummary1$1 = "You searched for:"; 20427 var resultsSummary2$1 = "Found %1 matching items."; 20428 var resultsSummary3$1 = "Click the time associated with any item to play the video from that point."; 20429 var noResultsFound$1 = "No results found."; 20430 var searchButtonLabel$1 = "Play at %1"; 20431 var hour$1 = "hour"; 20432 var minute$1 = "minute"; 20433 var second$1 = "second"; 20434 var hours$1 = "hours"; 20435 var minutes$1 = "minutes"; 20436 var seconds$1 = "seconds"; 20437 var vtsHeading$1 = "Video Transcript Sorter"; 20438 var vtsInstructions1$1 = "Use the Video Transcript Sorter to modify text tracks:"; 20439 var vtsInstructions2$1 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20440 var vtsInstructions3$1 = "Modify content or start/end times (all are directly editable within the table)."; 20441 var vtsInstructions4$1 = "Add new content, such as chapters or descriptions."; 20442 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."; 20443 var vtsSelectLanguage$1 = "Select a language"; 20444 var vtsSave$1 = "Generate new .vtt content"; 20445 var vtsReturn$1 = "Return to Editor"; 20446 var vtsCancel$1 = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20447 var vtsRow$1 = "Row"; 20448 var vtsKind$1 = "Kind"; 20449 var vtsStart$1 = "Start"; 20450 var vtsEnd$1 = "End"; 20451 var vtsContent$1 = "Content"; 20452 var vtsActions$1 = "Actions"; 20453 var vtsNewRow$1 = "A new row %1 has been inserted."; 20454 var vtsDeletedRow$1 = "Row %1 has been deleted."; 20455 var vtsMovedRow$1 = "Row %1 has been moved %2 and is now Row %3."; 20456 var tr = { 20457 playerHeading: playerHeading$1, 20458 audioPlayer: audioPlayer$1, 20459 videoPlayer: videoPlayer$1, 20460 faster: faster$1, 20461 slower: slower$1, 20462 play: play$1, 20463 pause: pause$1, 20464 restart: restart$1, 20465 prevTrack: prevTrack$1, 20466 nextTrack: nextTrack$1, 20467 rewind: rewind$1, 20468 forward: forward$1, 20469 captions: captions$1, 20470 showCaptions: showCaptions$1, 20471 hideCaptions: hideCaptions$1, 20472 captionsOff: captionsOff$1, 20473 showTranscript: showTranscript$1, 20474 hideTranscript: hideTranscript$1, 20475 turnOnDescriptions: turnOnDescriptions$1, 20476 turnOffDescriptions: turnOffDescriptions$1, 20477 chapters: chapters$1, 20478 language: language$1, 20479 sign: sign$1, 20480 showSign: showSign$1, 20481 hideSign: hideSign$1, 20482 seekbarLabel: seekbarLabel$1, 20483 mute: mute$1, 20484 unmute: unmute$1, 20485 volume: volume$1, 20486 volumeUpDown: volumeUpDown$1, 20487 preferences: preferences$1, 20488 enterFullScreen: enterFullScreen$1, 20489 exitFullScreen: exitFullScreen$1, 20490 speed: speed$1, 20491 spacebar: spacebar$1, 20492 transcriptTitle: transcriptTitle$1, 20493 lyricsTitle: lyricsTitle$1, 20494 autoScroll: autoScroll$1, 20495 statusPlaying: statusPlaying$1, 20496 statusPaused: statusPaused$1, 20497 statusStopped: statusStopped$1, 20498 statusBuffering: statusBuffering$1, 20499 statusEnd: statusEnd$1, 20500 selectedTrack: selectedTrack$1, 20501 alertDescribedVersion: alertDescribedVersion$1, 20502 alertNonDescribedVersion: alertNonDescribedVersion$1, 20503 prefMenuCaptions: prefMenuCaptions$1, 20504 prefVoicedCaptions: prefVoicedCaptions$1, 20505 prefMenuDescriptions: prefMenuDescriptions$1, 20506 prefMenuKeyboard: prefMenuKeyboard$1, 20507 prefMenuTranscript: prefMenuTranscript$1, 20508 prefTitleCaptions: prefTitleCaptions$1, 20509 prefTitleDescriptions: prefTitleDescriptions$1, 20510 prefTitleKeyboard: prefTitleKeyboard$1, 20511 prefTitleTranscript: prefTitleTranscript$1, 20512 prefIntroDescription1: prefIntroDescription1$1, 20513 prefDescription1: prefDescription1$1, 20514 prefDescription2: prefDescription2$1, 20515 prefDescription3: prefDescription3$1, 20516 prefIntroDescriptionNone: prefIntroDescriptionNone$1, 20517 prefIntroDescription3: prefIntroDescription3$1, 20518 prefIntroDescription4: prefIntroDescription4$1, 20519 prefIntroKeyboard1: prefIntroKeyboard1$1, 20520 prefIntroKeyboard2: prefIntroKeyboard2$1, 20521 prefIntroKeyboard3: prefIntroKeyboard3$1, 20522 prefHeadingKeyboard1: prefHeadingKeyboard1$1, 20523 prefHeadingKeyboard2: prefHeadingKeyboard2$1, 20524 prefHeadingDescription: prefHeadingDescription$1, 20525 prefHeadingTextDescription: prefHeadingTextDescription$1, 20526 prefAltKey: prefAltKey$1, 20527 prefCtrlKey: prefCtrlKey$1, 20528 prefShiftKey: prefShiftKey$1, 20529 prefNoKeyShortcuts: prefNoKeyShortcuts$1, 20530 escapeKey: escapeKey$1, 20531 escapeKeyFunction: escapeKeyFunction$1, 20532 prefDescPause: prefDescPause$1, 20533 prefDescVisible: prefDescVisible$1, 20534 prefDescVoice: prefDescVoice$1, 20535 prefDescRate: prefDescRate$1, 20536 prefCaptionRate: prefCaptionRate$1, 20537 prefDescPitch: prefDescPitch$1, 20538 prefDescPitch1: prefDescPitch1$1, 20539 prefDescPitch2: prefDescPitch2$1, 20540 prefDescPitch3: prefDescPitch3$1, 20541 prefDescPitch4: prefDescPitch4$1, 20542 prefDescPitch5: prefDescPitch5$1, 20543 sampleDescriptionText: sampleDescriptionText$1, 20544 prefHighlight: prefHighlight$1, 20545 prefTabbable: prefTabbable$1, 20546 prefCaptionsFont: prefCaptionsFont$1, 20547 prefCaptionsColor: prefCaptionsColor$1, 20548 prefCaptionsBGColor: prefCaptionsBGColor$1, 20549 prefCaptionsSize: prefCaptionsSize$1, 20550 prefCaptionsOpacity: prefCaptionsOpacity$1, 20551 prefCaptionsStyle: prefCaptionsStyle$1, 20552 serif: serif$1, 20553 sans: sans$1, 20554 cursive: cursive$1, 20555 fantasy: fantasy$1, 20556 monospace: monospace$1, 20557 white: white$1, 20558 yellow: yellow$1, 20559 green: green$1, 20560 cyan: cyan$1, 20561 blue: blue$1, 20562 magenta: magenta$1, 20563 red: red$1, 20564 black: black$1, 20565 transparent: transparent$1, 20566 solid: solid$1, 20567 captionsStylePopOn: captionsStylePopOn$1, 20568 captionsStyleRollUp: captionsStyleRollUp$1, 20569 prefCaptionsPosition: prefCaptionsPosition$1, 20570 captionsPositionOverlay: captionsPositionOverlay$1, 20571 captionsPositionBelow: captionsPositionBelow$1, 20572 sampleCaptionText: sampleCaptionText$1, 20573 prefSuccess: prefSuccess$1, 20574 prefNoChange: prefNoChange$1, 20575 save: save$1, 20576 cancel: cancel$1, 20577 dismissButton: dismissButton$1, 20578 windowButtonLabel: windowButtonLabel$1, 20579 windowMove: windowMove$1, 20580 windowMoveLeft: windowMoveLeft$1, 20581 windowMoveRight: windowMoveRight$1, 20582 windowMoveUp: windowMoveUp$1, 20583 windowMoveDown: windowMoveDown$1, 20584 windowMoveStopped: windowMoveStopped$1, 20585 transcriptControls: transcriptControls$1, 20586 signControls: signControls$1, 20587 windowMoveAlert: windowMoveAlert$1, 20588 windowResize: windowResize$1, 20589 windowResizeHeading: windowResizeHeading$1, 20590 closeButtonLabel: closeButtonLabel$1, 20591 width: width$1, 20592 height: height$1, 20593 resultsSummary1: resultsSummary1$1, 20594 resultsSummary2: resultsSummary2$1, 20595 resultsSummary3: resultsSummary3$1, 20596 noResultsFound: noResultsFound$1, 20597 searchButtonLabel: searchButtonLabel$1, 20598 hour: hour$1, 20599 minute: minute$1, 20600 second: second$1, 20601 hours: hours$1, 20602 minutes: minutes$1, 20603 seconds: seconds$1, 20604 vtsHeading: vtsHeading$1, 20605 vtsInstructions1: vtsInstructions1$1, 20606 vtsInstructions2: vtsInstructions2$1, 20607 vtsInstructions3: vtsInstructions3$1, 20608 vtsInstructions4: vtsInstructions4$1, 20609 vtsInstructions5: vtsInstructions5$1, 20610 vtsSelectLanguage: vtsSelectLanguage$1, 20611 vtsSave: vtsSave$1, 20612 vtsReturn: vtsReturn$1, 20613 vtsCancel: vtsCancel$1, 20614 vtsRow: vtsRow$1, 20615 vtsKind: vtsKind$1, 20616 vtsStart: vtsStart$1, 20617 vtsEnd: vtsEnd$1, 20618 vtsContent: vtsContent$1, 20619 vtsActions: vtsActions$1, 20620 vtsNewRow: vtsNewRow$1, 20621 vtsDeletedRow: vtsDeletedRow$1, 20622 vtsMovedRow: vtsMovedRow$1 20623 }; 20624 20625 var playerHeading = "åªé«ææ¾å¨"; 20626 var audioPlayer = "Audio player"; 20627 var videoPlayer = "Video player"; 20628 var faster = "å å¿«"; 20629 var slower = "æ¸æ ¢"; 20630 var play = "ææ¾"; 20631 var pause = "æ«å"; 20632 var restart = "å¾é éå§"; 20633 var prevTrack = "åä¸è»"; 20634 var nextTrack = "å¾ä¸è»"; 20635 var rewind = "åå"; 20636 var forward = "å¿«è½"; 20637 var captions = "åå¹"; 20638 var showCaptions = "顯示åå¹"; 20639 var hideCaptions = "é±èåå¹"; 20640 var captionsOff = "åå¹éé"; 20641 var showTranscript = "顯示éå稿"; 20642 var hideTranscript = "é±èéå稿"; 20643 var turnOnDescriptions = "éåå£è¿°å½±å"; 20644 var turnOffDescriptions = "ééå£è¿°å½±å"; 20645 var chapters = "ç« ç¯"; 20646 var language = "èªè¨"; 20647 var sign = "æèª"; 20648 var showSign = "顯示æèª"; 20649 var hideSign = "é±èæèª"; 20650 var seekbarLabel = "æé軸"; 20651 var mute = "éé³"; 20652 var unmute = "åæ¶éé³"; 20653 var volume = "é³é"; 20654 var volumeUpDown = "é³é墿¸"; 20655 var preferences = "å好è¨å®"; 20656 var enterFullScreen = "é²å ¥å ¨è¢å¹æ¨¡å¼"; 20657 var exitFullScreen = "é¢éå ¨è¢å¹æ¨¡å¼"; 20658 var speed = "ææ¾é度"; 20659 var spacebar = "空ç½éµ"; 20660 var transcriptTitle = "éå稿"; 20661 var lyricsTitle = "æè©"; 20662 var autoScroll = "èªåæ²å"; 20663 var statusPlaying = "æ£å¨ææ¾"; 20664 var statusPaused = "å·²æ«å"; 20665 var statusStopped = "已忢"; 20666 var statusBuffering = "æ£å¨ç·©è¡"; 20667 var statusEnd = "è»æ®µçµæ"; 20668 var selectedTrack = "å·²é¸è»æ®µ"; 20669 var alertDescribedVersion = "æ£å¨ä½¿ç¨æ¬å½±ççå£è¿°å½±åçæ¬"; 20670 var alertNonDescribedVersion = "æ£å¨ä½¿ç¨æ¬å½±ççéå£è¿°å½±åçæ¬"; 20671 var prefMenuCaptions = "åå¹"; 20672 var prefVoicedCaptions = "Spoken Captions"; 20673 var prefMenuDescriptions = "å£è¿°å½±å"; 20674 var prefMenuKeyboard = "éµç¤"; 20675 var prefMenuTranscript = "éå稿"; 20676 var prefTitleCaptions = "åå¹å好è¨å®"; 20677 var prefTitleDescriptions = "å£è¿°å½±åå好è¨å®"; 20678 var prefTitleKeyboard = "éµç¤å好è¨å®"; 20679 var prefTitleTranscript = "éå稿å好è¨å®"; 20680 var prefIntroDescription1 = "æ¬åªé«ææ¾å¨æ¯æ´å ©ç¨®æ¯æ´å£è¿°å½±åæ ¼å¼ï¼"; 20681 var prefDescription1 = "ç®åçå½±çæ é製å£è¿°å½±åçæ¿ä»£çæ¬ æåå¼å£è¿°å½±å"; 20682 var prefDescription2 = "ç®åçå½±çæ å½±ççå£è¿°å½±åæ¿ä»£çæ¬"; 20683 var prefDescription3 = "ç®åçå½±çæ æåå¼å£è¿°å½±åï¼éæé è¢å¹å ±è®è»é«"; 20684 var prefIntroDescriptionNone = "ç®åçå½±çå ©ç¨®æ ¼å¼é½æ²æä¾ã"; 20685 var prefIntroDescription3 = "è«ä½¿ç¨ä¸å表å®è¨å®å£è¿°å½±åçç¸éå好ã"; 20686 var prefIntroDescription4 = "å²åè¨å®å¾ï¼å£è¿°å½±ååè½å¯ä»¥ç±å£è¿°å½±åæéåæééã"; 20687 var prefIntroKeyboard1 = "ä¸è«æ¨èº«èç¶²é ä½èï¼é½å¯ä»¥éç¨ä¸åéµç¤å¿«é鵿ä½é é¢ä¸çéååªé«ææ¾å¨ï¼å¿«é鵿¸ å®è«è¦åºä¸ï¼ã"; 20688 var prefIntroKeyboard2 = "以ä¸é½å¯ä»¥ææ´¾çµåéµï¼ShiftãAltãCtrlï¼ã"; 20689 var prefIntroKeyboard3 = "è«æ³¨æï¼æäºå¿«ééµçµåå¯è½æè·æ¨ççè¦½å¨æå ¶ä»æç¨ç¨å¼ç¸è¡çªãè«èªè¡å¤å å試ï¼ç¢ºèªåªäºæéµçµåå¯ä»¥ä½¿ç¨ã"; 20690 var prefHeadingKeyboard1 = "ç¨æ¼å¿«ééµççµåéµ"; 20691 var prefHeadingKeyboard2 = "ç®åçå¿«ééµ"; 20692 var prefHeadingDescription = "å£è¿°å½±å"; 20693 var prefHeadingTextDescription = "æåå¼å£è¿°å½±å"; 20694 var prefAltKey = "Alt"; 20695 var prefCtrlKey = "Ctrl"; 20696 var prefShiftKey = "Shift"; 20697 var prefNoKeyShortcuts = "Disable keyboard shortcuts"; 20698 var escapeKey = "Esc"; 20699 var escapeKeyFunction = "ééç®åçå°è©±è¦çªæå½åºå¼é¸å®"; 20700 var prefDescPause = "éå§å£è¿°æèªåæ«åå½±ç"; 20701 var prefDescVisible = "åæä»¥è¦è¦ºæ¹å¼åç¾å£è¿°å½±å"; 20702 var prefDescVoice = "èªé³"; 20703 var prefDescRate = "Spoken Description Rate"; 20704 var prefCaptionRate = "Spoken Caption Rate"; 20705 var prefDescPitch = "é³èª¿"; 20706 var prefDescPitch1 = "é叏使²"; 20707 var prefDescPitch2 = "使²"; 20708 var prefDescPitch3 = "é è¨"; 20709 var prefDescPitch4 = "é«äº¢"; 20710 var prefDescPitch5 = "é常é«äº¢"; 20711 var sampleDescriptionText = "調æ´è¨å®ä»¥èè½é段ç¯ä¾æåã"; 20712 var prefHighlight = "ææ¾åªé«å §å®¹æå¸é¡¯ç¸å°æçéå稿段è½"; 20713 var prefTabbable = "åç¨éµç¤çéå稿"; 20714 var prefCaptionsFont = "åé«è®å"; 20715 var prefCaptionsColor = "æåè²å½©"; 20716 var prefCaptionsBGColor = "èæ¯"; 20717 var prefCaptionsSize = "æå尺寸"; 20718 var prefCaptionsOpacity = "ä¸éæåº¦"; 20719 var prefCaptionsStyle = "å忍£å¼"; 20720 var serif = "襯ç·åå"; 20721 var sans = "ç¡è¥¯ç·åå"; 20722 var cursive = "æå¯«åå"; 20723 var fantasy = "è¯éºåå"; 20724 var monospace = "ç寬åå"; 20725 var white = "ç½è²"; 20726 var yellow = "é»è²"; 20727 var green = "ç¶ è²"; 20728 var cyan = "éè²"; 20729 var blue = "èè²"; 20730 var magenta = "æ´ç´ è²"; 20731 var red = "ç´ è²"; 20732 var black = "é»è²"; 20733 var transparent = "éæ"; 20734 var solid = "ä¸éæ"; 20735 var captionsStylePopOn = "å½åº"; 20736 var captionsStyleRollUp = "å¾ä¸æ²å"; 20737 var prefCaptionsPosition = "ä½ç½®"; 20738 var captionsPositionOverlay = "è¦è"; 20739 var captionsPositionBelow = "å¨å½±çä¹ä¸"; 20740 var sampleCaptionText = "åå¹ç¯ä¾"; 20741 var prefSuccess = "å·²å²åæ¨æåºçè®åã"; 20742 var prefNoChange = "æ¨ä¸¦æªæåºä»»ä½è®åã"; 20743 var save = "å²å"; 20744 var cancel = "åæ¶"; 20745 var dismissButton = "Dismiss"; 20746 var windowButtonLabel = "è¦çªé¸é "; 20747 var windowMove = "ç§»å"; 20748 var windowMoveLeft = "Window moved left"; 20749 var windowMoveRight = "Window moved right"; 20750 var windowMoveUp = "Window moved up"; 20751 var windowMoveDown = "Window moved down"; 20752 var windowMoveStopped = "Window move stopped"; 20753 var transcriptControls = "Transcript Window Controls"; 20754 var signControls = "Sign Language Window Controls"; 20755 var windowMoveAlert = "ææ³è¦çªæä»¥æ¹åéµç§»åè¦çªï¼ç¢ºå®å¾æ Enter"; 20756 var windowResize = "è®æ´å¤§å°"; 20757 var windowResizeHeading = "è®æ´è¦çªå¤§å°"; 20758 var closeButtonLabel = "é"; 20759 var width = "寬度"; 20760 var height = "é«åº¦"; 20761 var resultsSummary1 = "You searched for:"; 20762 var resultsSummary2 = "Found %1 matching items."; 20763 var resultsSummary3 = "Click the time associated with any item to play the video from that point."; 20764 var noResultsFound = "No results found."; 20765 var searchButtonLabel = "Play at %1"; 20766 var hour = "hour"; 20767 var minute = "minute"; 20768 var second = "second"; 20769 var hours = "hours"; 20770 var minutes = "minutes"; 20771 var seconds = "seconds"; 20772 var vtsHeading = "Video Transcript Sorter"; 20773 var vtsInstructions1 = "Use the Video Transcript Sorter to modify text tracks:"; 20774 var vtsInstructions2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript."; 20775 var vtsInstructions3 = "Modify content or start/end times (all are directly editable within the table)."; 20776 var vtsInstructions4 = "Add new content, such as chapters or descriptions."; 20777 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."; 20778 var vtsSelectLanguage = "Select a language"; 20779 var vtsSave = "Generate new .vtt content"; 20780 var vtsReturn = "Return to Editor"; 20781 var vtsCancel = "Cancelling saving. Any edits you made have been restored in the VTS table."; 20782 var vtsRow = "Row"; 20783 var vtsKind = "Kind"; 20784 var vtsStart = "Start"; 20785 var vtsEnd = "End"; 20786 var vtsContent = "Content"; 20787 var vtsActions = "Actions"; 20788 var vtsNewRow = "A new row %1 has been inserted."; 20789 var vtsDeletedRow = "Row %1 has been deleted."; 20790 var vtsMovedRow = "Row %1 has been moved %2 and is now Row %3."; 20791 var zh_tw = { 20792 playerHeading: playerHeading, 20793 audioPlayer: audioPlayer, 20794 videoPlayer: videoPlayer, 20795 faster: faster, 20796 slower: slower, 20797 play: play, 20798 pause: pause, 20799 restart: restart, 20800 prevTrack: prevTrack, 20801 nextTrack: nextTrack, 20802 rewind: rewind, 20803 forward: forward, 20804 captions: captions, 20805 showCaptions: showCaptions, 20806 hideCaptions: hideCaptions, 20807 captionsOff: captionsOff, 20808 showTranscript: showTranscript, 20809 hideTranscript: hideTranscript, 20810 turnOnDescriptions: turnOnDescriptions, 20811 turnOffDescriptions: turnOffDescriptions, 20812 chapters: chapters, 20813 language: language, 20814 sign: sign, 20815 showSign: showSign, 20816 hideSign: hideSign, 20817 seekbarLabel: seekbarLabel, 20818 mute: mute, 20819 unmute: unmute, 20820 volume: volume, 20821 volumeUpDown: volumeUpDown, 20822 preferences: preferences, 20823 enterFullScreen: enterFullScreen, 20824 exitFullScreen: exitFullScreen, 20825 speed: speed, 20826 spacebar: spacebar, 20827 transcriptTitle: transcriptTitle, 20828 lyricsTitle: lyricsTitle, 20829 autoScroll: autoScroll, 20830 statusPlaying: statusPlaying, 20831 statusPaused: statusPaused, 20832 statusStopped: statusStopped, 20833 statusBuffering: statusBuffering, 20834 statusEnd: statusEnd, 20835 selectedTrack: selectedTrack, 20836 alertDescribedVersion: alertDescribedVersion, 20837 alertNonDescribedVersion: alertNonDescribedVersion, 20838 prefMenuCaptions: prefMenuCaptions, 20839 prefVoicedCaptions: prefVoicedCaptions, 20840 prefMenuDescriptions: prefMenuDescriptions, 20841 prefMenuKeyboard: prefMenuKeyboard, 20842 prefMenuTranscript: prefMenuTranscript, 20843 prefTitleCaptions: prefTitleCaptions, 20844 prefTitleDescriptions: prefTitleDescriptions, 20845 prefTitleKeyboard: prefTitleKeyboard, 20846 prefTitleTranscript: prefTitleTranscript, 20847 prefIntroDescription1: prefIntroDescription1, 20848 prefDescription1: prefDescription1, 20849 prefDescription2: prefDescription2, 20850 prefDescription3: prefDescription3, 20851 prefIntroDescriptionNone: prefIntroDescriptionNone, 20852 prefIntroDescription3: prefIntroDescription3, 20853 prefIntroDescription4: prefIntroDescription4, 20854 prefIntroKeyboard1: prefIntroKeyboard1, 20855 prefIntroKeyboard2: prefIntroKeyboard2, 20856 prefIntroKeyboard3: prefIntroKeyboard3, 20857 prefHeadingKeyboard1: prefHeadingKeyboard1, 20858 prefHeadingKeyboard2: prefHeadingKeyboard2, 20859 prefHeadingDescription: prefHeadingDescription, 20860 prefHeadingTextDescription: prefHeadingTextDescription, 20861 prefAltKey: prefAltKey, 20862 prefCtrlKey: prefCtrlKey, 20863 prefShiftKey: prefShiftKey, 20864 prefNoKeyShortcuts: prefNoKeyShortcuts, 20865 escapeKey: escapeKey, 20866 escapeKeyFunction: escapeKeyFunction, 20867 prefDescPause: prefDescPause, 20868 prefDescVisible: prefDescVisible, 20869 prefDescVoice: prefDescVoice, 20870 prefDescRate: prefDescRate, 20871 prefCaptionRate: prefCaptionRate, 20872 prefDescPitch: prefDescPitch, 20873 prefDescPitch1: prefDescPitch1, 20874 prefDescPitch2: prefDescPitch2, 20875 prefDescPitch3: prefDescPitch3, 20876 prefDescPitch4: prefDescPitch4, 20877 prefDescPitch5: prefDescPitch5, 20878 sampleDescriptionText: sampleDescriptionText, 20879 prefHighlight: prefHighlight, 20880 prefTabbable: prefTabbable, 20881 prefCaptionsFont: prefCaptionsFont, 20882 prefCaptionsColor: prefCaptionsColor, 20883 prefCaptionsBGColor: prefCaptionsBGColor, 20884 prefCaptionsSize: prefCaptionsSize, 20885 prefCaptionsOpacity: prefCaptionsOpacity, 20886 prefCaptionsStyle: prefCaptionsStyle, 20887 serif: serif, 20888 sans: sans, 20889 cursive: cursive, 20890 fantasy: fantasy, 20891 monospace: monospace, 20892 white: white, 20893 yellow: yellow, 20894 green: green, 20895 cyan: cyan, 20896 blue: blue, 20897 magenta: magenta, 20898 red: red, 20899 black: black, 20900 transparent: transparent, 20901 solid: solid, 20902 captionsStylePopOn: captionsStylePopOn, 20903 captionsStyleRollUp: captionsStyleRollUp, 20904 prefCaptionsPosition: prefCaptionsPosition, 20905 captionsPositionOverlay: captionsPositionOverlay, 20906 captionsPositionBelow: captionsPositionBelow, 20907 sampleCaptionText: sampleCaptionText, 20908 prefSuccess: prefSuccess, 20909 prefNoChange: prefNoChange, 20910 save: save, 20911 cancel: cancel, 20912 dismissButton: dismissButton, 20913 windowButtonLabel: windowButtonLabel, 20914 windowMove: windowMove, 20915 windowMoveLeft: windowMoveLeft, 20916 windowMoveRight: windowMoveRight, 20917 windowMoveUp: windowMoveUp, 20918 windowMoveDown: windowMoveDown, 20919 windowMoveStopped: windowMoveStopped, 20920 transcriptControls: transcriptControls, 20921 signControls: signControls, 20922 windowMoveAlert: windowMoveAlert, 20923 windowResize: windowResize, 20924 windowResizeHeading: windowResizeHeading, 20925 closeButtonLabel: closeButtonLabel, 20926 width: width, 20927 height: height, 20928 resultsSummary1: resultsSummary1, 20929 resultsSummary2: resultsSummary2, 20930 resultsSummary3: resultsSummary3, 20931 noResultsFound: noResultsFound, 20932 searchButtonLabel: searchButtonLabel, 20933 hour: hour, 20934 minute: minute, 20935 second: second, 20936 hours: hours, 20937 minutes: minutes, 20938 seconds: seconds, 20939 vtsHeading: vtsHeading, 20940 vtsInstructions1: vtsInstructions1, 20941 vtsInstructions2: vtsInstructions2, 20942 vtsInstructions3: vtsInstructions3, 20943 vtsInstructions4: vtsInstructions4, 20944 vtsInstructions5: vtsInstructions5, 20945 vtsSelectLanguage: vtsSelectLanguage, 20946 vtsSave: vtsSave, 20947 vtsReturn: vtsReturn, 20948 vtsCancel: vtsCancel, 20949 vtsRow: vtsRow, 20950 vtsKind: vtsKind, 20951 vtsStart: vtsStart, 20952 vtsEnd: vtsEnd, 20953 vtsContent: vtsContent, 20954 vtsActions: vtsActions, 20955 vtsNewRow: vtsNewRow, 20956 vtsDeletedRow: vtsDeletedRow, 20957 vtsMovedRow: vtsMovedRow 20958 }; 20959 20960 const moduleFromTag = { 20961 ca, 20962 cs, 20963 da, 20964 de, 20965 en, 20966 es, 20967 fr, 20968 he, 20969 id, 20970 it, 20971 ja, 20972 ms, 20973 nb, 20974 nl, 20975 pl, 20976 pt, 20977 'pt-BR': pt_br, 20978 sk, 20979 sv, 20980 tr, 20981 'zh-TW': zh_tw, 20982 }; 20983 20984 function addTranslationFunctions(AblePlayer) { 20985 AblePlayer.prototype.getSupportedLangs = function() { 20986 // returns an array of languages for which AblePlayer has translation tables 20987 var langs = { 20988 'ca' : 'Catalan', 20989 'cs' : 'Czech', 20990 'da' : 'Danish', 20991 'de' : 'German', 20992 'en' : 'English', 20993 'es' : 'Spanish', 20994 'fr' : 'French', 20995 'he' : 'Hebrew', 20996 'id' : 'Indonesian', 20997 'it' : 'Italian', 20998 'ja' : 'Japanese', 20999 'ms' : 'Malay', 21000 'nb' : 'Norwegian BokmÃ¥l', 21001 'nl' : 'Dutch', 21002 'pl' : 'Polish', 21003 'pt' : 'Portuguese', 21004 'pt-BR' : 'Brazilian Portuguese', 21005 'sk' : 'Slovak', 21006 'sv' : 'Swedish', 21007 'tr' : 'Turkish', 21008 'zh-TW' : 'Chinese (Taiwan)' 21009 }; 21010 21011 return langs; 21012 }; 21013 21014 /** 21015 * Fetch a translated string if it exists. 21016 * 21017 * @param {string} key JSON key to locate translated string. 21018 * @param {string} fallback Default language if no translation found. 21019 * @param {Array} args Ordered array of arguments to replace in string. 21020 * @returns 21021 */ 21022 AblePlayer.prototype.translate = function( key, fallback, args = Array() ) { 21023 let translation = ''; 21024 if ( this.tt[ key ] ) { 21025 translation = this.tt[ key ]; 21026 } else { 21027 translation = fallback; 21028 } 21029 if ( args.length > 0 ) { 21030 args.forEach( ( val, index ) => { 21031 let ref = index + 1; 21032 translation = translation.replace( '%' + ref, val ); 21033 }); 21034 } 21035 21036 return translation; 21037 }; 21038 21039 AblePlayer.prototype.getTranslationText = function() { 21040 21041 // determine language, then get labels and prompts from corresponding translation var 21042 var thisObj, supportedLangs, docLang, similarLangFound; 21043 thisObj = this; 21044 21045 supportedLangs = this.getSupportedLangs(); // returns an array 21046 21047 if (this.lang) { // a data-lang attribute is included on the media element 21048 this.lang; 21049 if ( ! Object.hasOwn( supportedLangs,this.lang ) ) { 21050 // the specified language code is not in the index 21051 if ( this.lang.indexOf('-') == 2 ) { 21052 // this is a localized lang attribute (e.g., fr-CA) 21053 // try the parent language, given the first two characters 21054 // if parent lang is supported. Use that, else null. 21055 this.lang = ( Object.hasOwn(supportedLangs,this.lang.substring(0,2)) !== -1 ) ? this.lang.substring(0,2) : null; 21056 } else { 21057 // this is not a localized language. 21058 // but maybe there's a similar localized language supported 21059 // that has the same parent? 21060 similarLangFound = false; 21061 for ( const [key,value] of Object.entries(supportedLangs) ) { 21062 if ( key.substring(0,2) == this.lang ) { 21063 this.lang = key; 21064 similarLangFound = true; 21065 } 21066 } 21067 if ( !similarLangFound ) { 21068 // language requested via data-lang is not supported 21069 this.lang = null; 21070 } 21071 } 21072 } 21073 } 21074 21075 if (!this.lang) { 21076 // try the language of the web page, if specified 21077 if ($('body').attr('lang')) { 21078 docLang = $('body').attr('lang').toLowerCase(); 21079 } else if ($('html').attr('lang')) { 21080 docLang = $('html').attr('lang').toLowerCase(); 21081 } else { 21082 docLang = null; 21083 } 21084 if (docLang) { 21085 if ( Object.hasOwn( supportedLangs,docLang ) ) { 21086 // the document language is supported 21087 this.lang = docLang; 21088 } else { 21089 // the document language is not supported 21090 if (docLang.indexOf('-') == 2) { 21091 // this is a localized lang attribute (e.g., fr-CA) 21092 // try the parent language, given the first two characters 21093 if ( Object.hasOwn(supportedLangs,docLang.substring(0,2)) ) { 21094 // the parent language is supported. use that. 21095 this.lang = docLang.substring(0,2); 21096 } 21097 } 21098 } 21099 } 21100 } 21101 21102 if (!this.lang) { 21103 // No supported language has been specified by any means 21104 // Fallback to English 21105 this.lang = 'en'; 21106 } 21107 21108 if (!this.searchLang) { 21109 this.searchLang = this.lang; 21110 } 21111 const ttModule = moduleFromTag[this.lang]; 21112 if (!ttModule) { 21113 thisObj.tt = {}; 21114 thisObj.translationFiles = false; 21115 } else { 21116 thisObj.tt = ttModule; 21117 thisObj.translationFiles = true; 21118 } 21119 }; 21120 21121 AblePlayer.prototype.getSampleDescriptionText = function() { 21122 if ( ! this.translationFiles ) { 21123 this.sampleText = []; 21124 let translation = { 'lang':'en', 'text': this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) }; 21125 this.sampleText.push(translation); 21126 } else { 21127 // Create an array of sample description text in all languages 21128 // This needs to be readily available for testing different voices 21129 // in the Description Preferences dialog 21130 var supportedLangs, thisText, translation; 21131 21132 supportedLangs = this.getSupportedLangs(); 21133 21134 this.sampleText = []; 21135 for ( const [tag,name] of Object.entries(supportedLangs) ) { 21136 const ttModule = moduleFromTag[tag]; 21137 thisText = ttModule.sampleDescriptionText; 21138 translation = {'lang':name, 'text': thisText}; 21139 this.sampleText.push(translation); 21140 } 21141 } 21142 }; 21143 21144 } 21145 21146 /* global Vimeo */ 21147 21148 function addVimeoFunctions(AblePlayer) { 21149 21150 AblePlayer.prototype.initVimeoPlayer = function () { 21151 21152 var thisObj, deferred, promise, containerId, vimeoId, options; 21153 thisObj = this; 21154 21155 deferred = new this.defer(); 21156 promise = deferred.promise(); 21157 21158 containerId = this.mediaId + '_vimeo'; 21159 21160 // add container to which Vimeo player iframe will be appended 21161 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 21162 21163 // if a described version is available && user prefers description 21164 // init player using the described version 21165 vimeoId = (this.vimeoDescId && this.prefDesc) ? this.vimeoDescId : this.vimeoId; 21166 21167 this.activeVimeoId = vimeoId; 21168 21169 // Notes re. Vimeo Embed Options: 21170 // If a video is owned by a user with a paid Plus, PRO, or Business account, 21171 // setting the "controls" option to "false" will hide the default controls, without hiding captions. 21172 // This is a new option from Vimeo; previously used "background:true" to hide the controller, 21173 // but that had unwanted side effects: 21174 // - In addition to hiding the controls, it also hides captions 21175 // - It automatically autoplays (initializing the player with autoplay:false does not override this) 21176 // - It automatically loops (but this can be overridden by initializing the player with loop:false) 21177 // - It automatically sets volume to 0 (not sure if this can be overridden, since no longer using the background option) 21178 21179 if (this.playerWidth) { 21180 if (this.vimeoUrlHasParams) { 21181 // use url param, not id 21182 options = { 21183 url: vimeoId, 21184 width: this.playerWidth, 21185 controls: false 21186 }; 21187 } else { 21188 options = { 21189 id: vimeoId, 21190 width: this.playerWidth, 21191 controls: false 21192 }; 21193 } 21194 } else { 21195 // initialize without width & set width later 21196 if (this.vimeoUrlHasParams) { 21197 options = { 21198 url: vimeoId, 21199 controls: false 21200 }; 21201 } else { 21202 options = { 21203 id: vimeoId, 21204 controls: false 21205 }; 21206 } 21207 } 21208 21209 this.vimeoPlayer = new Vimeo.Player(containerId, options); 21210 21211 this.vimeoPlayer.ready().then(function() { 21212 // add tabindex -1 on iframe so vimeo frame cannot be focused on 21213 $('#'+containerId).children('iframe').attr({ 21214 'tabindex': '-1', 21215 'aria-hidden': true 21216 }); 21217 21218 // get video's intrinsic size and initiate player dimensions 21219 thisObj.vimeoPlayer.getVideoWidth().then(function(width) { 21220 if (width) { 21221 // also get height 21222 thisObj.vimeoPlayer.getVideoHeight().then(function(height) { 21223 if (height) { 21224 thisObj.resizePlayer(width,height); 21225 } 21226 }); 21227 } 21228 }).catch(function(error) { 21229 // an error occurred getting height or width 21230 // TODO: Test this to see how gracefully it organically recovers 21231 }); 21232 21233 if (!thisObj.hasPlaylist) { 21234 // remove the media element, since Vimeo replaces that with its own element in an iframe 21235 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 21236 thisObj.$media.remove(); 21237 21238 // define variables that will impact player setup 21239 21240 // vimeoSupportsPlaybackRateChange 21241 // changing playbackRate is only supported if the video is hosted on a Pro or Business account 21242 // unfortunately there is no direct way to query for that information. 21243 // this.vimeoPlayer.getPlaybackRate() returns a value, regardless of account type 21244 // This is a hack: 21245 // Attempt to change the playbackRate. If it results in an error, assume changing playbackRate is not supported. 21246 // Supported playbackRate values are 0.5 to 2. 21247 thisObj.vimeoPlaybackRate = 1; 21248 thisObj.vimeoPlayer.setPlaybackRate(thisObj.vimeoPlaybackRate).then(function(playbackRate) { 21249 // playback rate was set 21250 thisObj.vimeoSupportsPlaybackRateChange = true; 21251 }).catch(function(error) { 21252 thisObj.vimeoSupportsPlaybackRateChange = false; 21253 }); 21254 deferred.resolve(); 21255 } 21256 }); 21257 return promise; 21258 }; 21259 21260 AblePlayer.prototype.getVimeoPaused = function () { 21261 21262 var deferred, promise; 21263 deferred = new this.defer(); 21264 promise = deferred.promise(); 21265 21266 this.vimeoPlayer.getPaused().then(function (paused) { 21267 // paused is Boolean 21268 deferred.resolve(paused); 21269 }); 21270 21271 return promise; 21272 }; 21273 21274 AblePlayer.prototype.getVimeoEnded = function () { 21275 21276 var deferred, promise; 21277 deferred = new this.defer(); 21278 promise = deferred.promise(); 21279 21280 this.vimeoPlayer.getEnded().then(function (ended) { 21281 // ended is Boolean 21282 deferred.resolve(ended); 21283 }); 21284 21285 return promise; 21286 }; 21287 21288 AblePlayer.prototype.getVimeoState = function () { 21289 21290 var deferred, promise, promises, gettingPausedPromise, gettingEndedPromise; 21291 21292 deferred = new this.defer(); 21293 promise = deferred.promise(); 21294 promises = []; 21295 21296 gettingPausedPromise = this.vimeoPlayer.getPaused(); 21297 gettingEndedPromise = this.vimeoPlayer.getEnded(); 21298 21299 promises.push(gettingPausedPromise); 21300 promises.push(gettingEndedPromise); 21301 21302 gettingPausedPromise.then(function (paused) { 21303 deferred.resolve(paused); 21304 }); 21305 gettingEndedPromise.then(function (ended) { 21306 deferred.resolve(ended); 21307 }); 21308 $.when.apply($, promises).then(function () { 21309 deferred.resolve(); 21310 }); 21311 return promise; 21312 }; 21313 21314 AblePlayer.prototype.getVimeoCaptionTracks = function () { 21315 21316 // get data via Vimeo Player API, and push data to this.captions 21317 // Note: Vimeo doesn't expose the caption cues themselves 21318 // so this.captions will only include metadata about caption tracks; not cues 21319 var deferred = new this.defer(); 21320 var promise = deferred.promise(); 21321 21322 var thisObj, i, isDefaultTrack; 21323 21324 thisObj = this; 21325 21326 this.vimeoPlayer.getTextTracks().then(function(tracks) { 21327 21328 // each Vimeo track includes the following: 21329 // label (local name of the language) 21330 // language (2-character code) 21331 // kind (captions or subtitles, as declared by video owner) 21332 // mode ('disabled' or 'showing') 21333 21334 if (tracks.length) { 21335 21336 // create a new button for each caption track 21337 for (i=0; i<tracks.length; i++) { 21338 21339 thisObj.hasCaptions = true; 21340 if (thisObj.prefCaptions === 1) { 21341 thisObj.captionsOn = true; 21342 } else { 21343 thisObj.captionsOn = false; 21344 } 21345 // assign the default track based on language of the player 21346 if (tracks[i]['language'] === thisObj.lang) { 21347 isDefaultTrack = true; 21348 } else { 21349 isDefaultTrack = false; 21350 } 21351 thisObj.tracks.push({ 21352 'kind': tracks[i]['kind'], 21353 'language': tracks[i]['language'], 21354 'label': tracks[i]['label'], 21355 'def': isDefaultTrack 21356 }); 21357 } 21358 thisObj.captions = thisObj.tracks; 21359 thisObj.hasCaptions = true; 21360 21361 // setupPopups again with new captions array, replacing original 21362 thisObj.setupPopups('captions'); 21363 deferred.resolve(); 21364 } else { 21365 thisObj.hasCaptions = false; 21366 thisObj.usingVimeoCaptions = false; 21367 deferred.resolve(); 21368 } 21369 }); 21370 21371 return promise; 21372 }; 21373 21374 AblePlayer.prototype.getVimeoPosterUrl = function (vimeoId) { 21375 const thisObj = this; 21376 21377 // Vimeo Oembed only returns a 640px width image. Hope at some point there's an alternative. 21378 var url = 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/' + vimeoId, imageUrl = ''; 21379 fetch( url ).then( response => { 21380 21381 return response.json(); 21382 }) 21383 .then( json => { 21384 imageUrl = json.thumbnail_url; 21385 }) 21386 .catch( error => { 21387 if (thisObj.debug) ; 21388 }); 21389 21390 return imageUrl; 21391 }; 21392 21393 AblePlayer.prototype.getVimeoId = function (url) { 21394 21395 // return a Vimeo ID, extracted from a full Vimeo URL 21396 // Supported URL patterns are anything containing 'vimeo.com' 21397 // and ending with a '/' followed by the ID. 21398 // (Vimeo IDs do not have predicatable lengths) 21399 21400 // Update: If URL contains parameters, return the full url 21401 // This will need to be passed to the Vimeo Player API 21402 // as a url parameter, not as an id parameter 21403 this.vimeoUrlHasParams = false; 21404 21405 let urlObject; 21406 if (typeof url === 'number') { 21407 // this is likely already a vimeo ID 21408 return url; 21409 } else { 21410 urlObject = new URL(url); 21411 } 21412 if ( 'vimeo.com' === urlObject.hostname || 'player.vimeo.com' === urlObject.hostname ) { 21413 // this is a full Vimeo URL 21414 if ( '' !== urlObject.search ) { 21415 // URL contains parameters 21416 this.vimeoUrlHasParams = true; 21417 return url; 21418 } else { 21419 if ( 'player.vimeo.com' === urlObject.hostname ) { 21420 return urlObject.pathname.replace( '/video/', '' ); 21421 } else { 21422 return urlObject.pathname.replace( '/', '' ); 21423 } 21424 } 21425 } else { 21426 return url; 21427 } 21428 }; 21429 21430 } 21431 21432 function addVolumeFunctions(AblePlayer) { 21433 21434 AblePlayer.prototype.addVolumeSlider = function($div) { 21435 21436 // Prior to v4.4.64, we were using a custom-build vertical volunme slider 21437 // Changed to input type="range" because it's standard and gaining more widespread support 21438 // including screen reader support 21439 // TODO: Improve presentation of vertical slider. That requires some CSS finesse. 21440 21441 var thisObj, volumeSliderId, volumeHelpId, volumePct, volumeLabel, volumeHeight; 21442 21443 thisObj = this; 21444 21445 // define a few variables 21446 volumeSliderId = this.mediaId + '-volume-slider'; 21447 volumeHelpId = this.mediaId + '-volume-help'; 21448 21449 this.$volumeSlider = $('<div>',{ 21450 'id': volumeSliderId, 21451 'class': 'able-volume-slider', 21452 'aria-hidden': 'true' 21453 }).hide(); 21454 this.$volumeSliderTooltip = $('<div>',{ 21455 'class': 'able-tooltip', 21456 'role': 'tooltip' 21457 }).hide(); 21458 this.$volumeRange = $('<input>',{ 21459 'type': 'range', 21460 'min': '0', 21461 'max': '10', 21462 'step': '1', 21463 'orient': 'vertical', // non-standard, but required for Firefox 21464 'aria-label': this.translate( 'volumeUpDown', 'Volume up down' ), 21465 'value': this.volume 21466 }); 21467 volumePct = parseInt(thisObj.volume) / 10 * 100; 21468 this.$volumeHelp = $('<div>',{ 21469 'id': volumeHelpId, 21470 'class': 'able-volume-help', 21471 'aria-live': 'polite' 21472 }).text(volumePct + '%'); 21473 volumeLabel = this.$volumeButton.attr( 'aria-label' ); 21474 this.$volumeButton.attr( 'aria-label', volumeLabel + ' ' + volumePct + '%'); 21475 this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp); 21476 volumeHeight = this.$volumeButton.parents( '.able-control-row' )[0]; 21477 this.$volumeSlider.css( 'bottom', volumeHeight.offsetHeight ); 21478 21479 $div.append(this.$volumeSlider); 21480 21481 // add event listeners 21482 this.$volumeRange.on('change',function (e) { 21483 thisObj.handleVolumeChange($(this).val()); 21484 }); 21485 21486 this.$volumeRange.on('input',function (e) { 21487 thisObj.handleVolumeChange($(this).val()); 21488 }); 21489 21490 this.$volumeRange.on('keydown',function (e) { 21491 21492 if (e.key === 'Escape' || e.key === 'Tab' || e.key === 'Enter') { 21493 // close popup 21494 if (thisObj.$volumeSlider.is(':visible')) { 21495 thisObj.closingVolume = true; // stopgap 21496 thisObj.hideVolumePopup(); 21497 } else { 21498 if (!thisObj.closingVolume) { 21499 thisObj.showVolumePopup(); 21500 } 21501 } 21502 } else { 21503 return; 21504 } 21505 }); 21506 }; 21507 21508 AblePlayer.prototype.refreshVolumeHelp = function(volume) { 21509 21510 // make adjustments based on current volume 21511 var volumePct; 21512 volumePct = (volume/10) * 100; 21513 21514 // Update help text 21515 if (this.$volumeHelp) { 21516 this.$volumeHelp.text(volumePct + '%'); 21517 } 21518 21519 // Update the default value of the volume slider input field 21520 // This doesn't seem to be necessary; browsers remember the previous setting during a session 21521 // but this is a fallback in case they don't 21522 this.$volumeRange.attr('value',volume); 21523 }; 21524 21525 AblePlayer.prototype.refreshVolumeButton = function(volume) { 21526 21527 var volumeName, volumePct, volumeLabel; 21528 21529 volumeName = this.getVolumeName(volume); 21530 volumePct = (volume/10) * 100; 21531 volumeLabel = this.translate( 'volume', 'Volume' ) + ' ' + volumePct + '%'; 21532 21533 this.getIcon( this.$volumeButton, 'volume-' + volumeName ); 21534 this.$volumeButton.attr( 'aria-label', volumeLabel ); 21535 }; 21536 21537 AblePlayer.prototype.handleVolumeButtonClick = function() { 21538 21539 if (this.$volumeSlider.is(':visible')) { 21540 this.hideVolumePopup(); 21541 } else { 21542 this.showVolumePopup(); 21543 } 21544 }; 21545 21546 AblePlayer.prototype.handleVolumeKeystroke = function(volume) { 21547 // keyboard shortcuts for changing volume 21548 if (this.isMuted() && volume > 0) { 21549 this.setMute(false); 21550 } else if (volume === 0) { 21551 this.setMute(true); 21552 } else { 21553 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 21554 this.refreshVolumeHelp(volume); 21555 this.refreshVolumeButton(volume); 21556 } 21557 }; 21558 21559 21560 AblePlayer.prototype.handleVolumeChange = function(volume) { 21561 21562 // handle volume change using the volume input slider 21563 21564 if (this.isMuted() && volume > 0) { 21565 this.setMute(false); 21566 } else if (volume === 0) { 21567 this.setMute(true); 21568 } else { 21569 this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js) 21570 this.refreshVolumeHelp(volume); 21571 this.refreshVolumeButton(volume); 21572 } 21573 }; 21574 21575 AblePlayer.prototype.handleMute = function() { 21576 21577 if (this.isMuted()) { 21578 this.setMute(false); 21579 } else { 21580 this.setMute(true); 21581 } 21582 }; 21583 21584 AblePlayer.prototype.showVolumePopup = function() { 21585 21586 this.closePopups(); 21587 this.$tooltipDiv.hide(); 21588 this.$volumeSlider.show().attr('aria-hidden','false'); 21589 this.$volumeButton.attr('aria-expanded','true'); 21590 this.$volumeButton.focus(); // for screen reader expanded state to be read 21591 this.waitThenFocus(this.$volumeRange); 21592 }; 21593 21594 AblePlayer.prototype.hideVolumePopup = function() { 21595 21596 var thisObj = this; 21597 21598 this.$volumeSlider.hide().attr('aria-hidden','true'); 21599 this.$volumeButton.attr('aria-expanded','false').focus(); 21600 // wait a second before resetting stopgap var 21601 // otherwise the keypress used to close volume popup will trigger the volume button 21602 setTimeout(function() { 21603 thisObj.closingVolume = false; 21604 }, 1000); 21605 }; 21606 21607 AblePlayer.prototype.isMuted = function () { 21608 21609 if (this.player === 'html5') { 21610 return this.media.muted; 21611 } else if (this.player === 'youtube') { 21612 return this.youTubePlayer.isMuted(); 21613 } 21614 }; 21615 21616 AblePlayer.prototype.setMute = function(mute) { 21617 21618 // mute is either true (muting) or false (unmuting) 21619 if (mute) { 21620 // save current volume so it can be restored after unmute 21621 this.lastVolume = this.volume; 21622 this.volume = 0; 21623 } else { // restore to previous volume 21624 if (typeof this.lastVolume !== 'undefined') { 21625 this.volume = this.lastVolume; 21626 } 21627 } 21628 21629 if (this.player === 'html5') { 21630 this.media.muted = mute; 21631 } else if (this.player === 'youtube') { 21632 if (mute) { 21633 this.youTubePlayer.mute(); 21634 } else { 21635 this.youTubePlayer.unMute(); 21636 } 21637 } 21638 this.setVolume(this.volume); 21639 this.refreshVolumeHelp(this.volume); 21640 this.refreshVolumeButton(this.volume); 21641 }; 21642 21643 AblePlayer.prototype.setVolume = function (volume) { 21644 21645 // volume is 1 to 10 21646 // convert as needed depending on player 21647 21648 var newVolume; 21649 this.syncSignVideo( {'volume' : 0 } ); 21650 if (this.player === 'html5') { 21651 // volume is 0 to 1 21652 newVolume = volume / 10; 21653 this.media.volume = newVolume; 21654 } else if (this.player === 'youtube') { 21655 // volume is 0 to 100 21656 newVolume = volume * 10; 21657 this.youTubePlayer.setVolume(newVolume); 21658 this.volume = volume; 21659 } else if (this.player === 'vimeo') { 21660 // volume is 0 to 1 21661 newVolume = volume / 10; 21662 this.vimeoPlayer.setVolume(newVolume).then(function() { 21663 // setVolume finished. 21664 // successful completion also fires a 'volumechange' event (see event.js) 21665 }); 21666 } 21667 this.lastVolume = volume; 21668 }; 21669 21670 AblePlayer.prototype.getVolume = function (volume) { 21671 21672 // return volume using common audio control scale 1 to 10 21673 if (this.player === 'html5') { 21674 // uses 0 to 1 scale 21675 return this.media.volume * 10; 21676 } else if (this.player === 'youtube') { 21677 // uses 0 to 100 scale 21678 if (this.youTubePlayerReady) { 21679 return this.youTubePlayer.getVolume() / 10; 21680 } 21681 } 21682 if (this.player === 'vimeo') { 21683 // uses 0 to 1 scale 21684 // this.vimeoPlayer.getVolume() takes too long to resolve with a value 21685 // Just use variable that's already been defined (should be the same value anyway) 21686 return this.volume; 21687 } 21688 }; 21689 21690 AblePlayer.prototype.getVolumeName = function (volume) { 21691 21692 // returns 'mute','soft','medium', or 'loud' depending on volume level 21693 if (volume == 0) { 21694 return 'mute'; 21695 } else if (volume == 10) { 21696 return 'loud'; 21697 } else if (volume < 5) { 21698 return 'soft'; 21699 } else { 21700 return 'medium'; 21701 } 21702 }; 21703 21704 } 21705 21706 /* Video Transcript Sorter (VTS) 21707 * Used to synchronize time stamps from WebVTT resources 21708 * so they appear in the proper sequence within an auto-generated interactive transcript 21709 */ 21710 21711 21712 function addVtsFunctions(AblePlayer) { 21713 AblePlayer.prototype.injectVTS = function() { 21714 21715 var thisObj, $heading, $instructions, $p1, $p2, $ul, $li1, $li2, $li3, 21716 $fieldset, $legend, i, $radioDiv, radioId, $label, $radio, $saveButton, $savedTable; 21717 21718 thisObj = this; 21719 21720 if ( null !== document.getElementById( 'able-vts' ) ) { 21721 // Are they qualifying tracks? 21722 if (this.vtsTracks.length) { 21723 // Build an array of unique languages 21724 this.langs = []; 21725 this.getAllLangs(this.vtsTracks); 21726 21727 // Set the default VTS language 21728 this.vtsLang = this.lang; 21729 21730 // Inject a heading 21731 let heading = this.translate( 'vtsHeading', 'Video Transcript Sorter' ); 21732 $heading = $('<h2>').text( heading ); // TODO: intelligently assign proper heading level 21733 $('#able-vts').append($heading); 21734 21735 // Inject an empty div for writing messages 21736 this.$vtsAlert = $('<div>',{ 21737 'id': 'able-vts-alert', 21738 'aria-live': 'polite', 21739 'aria-atomic': 'true' 21740 }); 21741 $('#able-vts').append(this.$vtsAlert); 21742 21743 // Inject instructions (TODO: Localize) 21744 $instructions = $('<div>',{ 21745 'id': 'able-vts-instructions' 21746 }); 21747 $p1 = $('<p>').text( this.translate( 'vtsInstructions1', 'Use the Video Transcript Sorter to modify text tracks:' ) ); 21748 $ul = $('<ul>'); 21749 $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.' ) ); 21750 $li2 = $('<li>').text( this.translate( 'vtsInstructions3', 'Modify content or start/end times (all are directly editable within the table).' ) ); 21751 $li3 = $('<li>').text( this.translate( 'vtsInstructions4', 'Add new content, such as chapters or descriptions.' ) ); 21752 $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.' ) ); 21753 $ul.append($li1,$li2,$li3); 21754 $instructions.append($p1,$ul,$p2); 21755 $('#able-vts').append($instructions); 21756 21757 // Inject a fieldset with radio buttons for each language 21758 $fieldset = $('<fieldset>'); 21759 $legend = $('<legend>').text( this.translate( 'vtsSelectLanguage', 'Select a language' ) ); 21760 $fieldset.append($legend); 21761 let $fieldWrapper = $( '<div class="vts-lang-selector"></div>' ); 21762 for (i in this.langs) { 21763 radioId = 'vts-lang-radio-' + this.langs[i]; 21764 $radioDiv = $('<div>',{ 21765 // uncomment the following if label is native name 21766 // 'lang': this.langs[i] 21767 }); 21768 $radio = $('<input>', { 21769 'type': 'radio', 21770 'name': 'vts-lang', 21771 'id': radioId, 21772 'value': this.langs[i] 21773 }).on('click',function() { 21774 thisObj.vtsLang = $(this).val(); 21775 thisObj.showVtsAlert('Loading ' + thisObj.getLanguageName(thisObj.vtsLang) + ' tracks'); 21776 thisObj.injectVtsTable('update',thisObj.vtsLang); 21777 }); 21778 if (this.langs[i] == this.lang) { 21779 // this is the default language. 21780 $radio.prop('checked',true); 21781 } 21782 $label = $('<label>', { 21783 'for': radioId 21784 // Two options for label: 21785 // getLanguageName() - with second parameter "local" would return native name, otherwise returns English; 21786 // TODO: if using this be sure to add lang attr to <div> (see above) 21787 }).text(this.getLanguageName(this.langs[i])); 21788 $radioDiv.append($radio,$label); 21789 $fieldWrapper.append($radioDiv); 21790 } 21791 $fieldset.append( $fieldWrapper ); 21792 $('#able-vts').append($fieldset); 21793 let vtsSave = this.translate( 'vtsSave', 'Generate new .vtt content' ); 21794 // Inject a button to generate new files. 21795 $saveButton = $('<button>',{ 21796 'type': 'button', 21797 'id': 'able-vts-save', 21798 'value': 'save' 21799 }).text( vtsSave ); 21800 $('#able-vts').append($saveButton); 21801 21802 // Inject a table with one row for each cue in the default language 21803 this.injectVtsTable('add',this.vtsLang); 21804 21805 // TODO: Add drag/drop functionality for mousers 21806 // Add event listeners for contenteditable cells 21807 var kindOptions, beforeEditing, editedCell, editedContent; 21808 kindOptions = ['captions','chapters','descriptions','subtitles']; 21809 $('td[contenteditable="true"]').on('focus',function() { 21810 beforeEditing = $(this).text(); 21811 }).on('blur',function() { 21812 if (beforeEditing != $(this).text()) { 21813 editedCell = $(this).index(); 21814 editedContent = $(this).text(); 21815 if (editedCell === 1) { 21816 // do some simple spelling auto-correct 21817 if ($.inArray(editedContent,kindOptions) === -1) { 21818 // whatever user typed is not a valid kind 21819 // assume they correctly typed the first character 21820 if (editedContent.substring(0,1) === 's') { 21821 $(this).text('subtitles'); 21822 } else if (editedContent.substring(0,1) === 'd') { 21823 $(this).text('descriptions'); 21824 } else if (editedContent.substring(0,2) === 'ch') { 21825 $(this).text('chapters'); 21826 } else { 21827 // whatever else they types, assume 'captions' 21828 $(this).text('captions'); 21829 } 21830 } 21831 } else if (editedCell === 2 || editedCell === 3) { 21832 // start or end time 21833 // ensure proper formatting (with 3 decimal places) 21834 $(this).text(thisObj.formatTimestamp(editedContent)); 21835 } 21836 } 21837 }).on('keydown',function(e) { 21838 // don't allow keystrokes to trigger Able Player (or other) functions 21839 // while user is editing 21840 e.stopPropagation(); 21841 }); 21842 21843 // handle click on the Save button 21844 $('#able-vts-save').on('click',function(e) { 21845 e.stopPropagation(); 21846 if ($(this).attr('value') == 'save') { 21847 // replace table with WebVTT output in textarea fields (for copying/pasting) 21848 $(this).attr('value','cancel').text( thisObj.translate( 'vtsReturn', 'Return to Editor' ) ); 21849 $savedTable = $('#able-vts table'); 21850 $('#able-vts-instructions').hide(); 21851 $('#able-vts > fieldset').hide(); 21852 $('#able-vts table').remove(); 21853 $('#able-vts-icon-credit').remove(); 21854 thisObj.parseVtsOutput($savedTable); 21855 } else { 21856 // cancel saving, and restore the table using edited content 21857 $(this).attr('value','save').text( vtsSave ); 21858 $('#able-vts-output').remove(); 21859 $('#able-vts-instructions').show(); 21860 $('#able-vts > fieldset').show(); 21861 $('#able-vts').append($savedTable); 21862 $('#able-vts').append(thisObj.getIconCredit()); 21863 thisObj.showVtsAlert( thisObj.translate( 'vtsCancel', 'Cancelling saving. Any edits you made have been restored in the VTS table.' ) ); 21864 } 21865 }); 21866 } 21867 } 21868 }; 21869 21870 AblePlayer.prototype.setupVtsTracks = function(kind, lang, trackDesc, label, src, contents) { 21871 21872 // TODO: Add support for trackDesc 21873 // (to destinguish between tracks for the decribed vs non-described versions) 21874 var srcFile, vtsCues; 21875 21876 srcFile = this.getFilenameFromPath(src); 21877 vtsCues = this.parseVtsTracks(contents); 21878 21879 this.vtsTracks.push({ 21880 'kind': kind, 21881 'language': lang, 21882 'label': label, 21883 'srcFile': srcFile, 21884 'cues': vtsCues 21885 }); 21886 }; 21887 21888 AblePlayer.prototype.getFilenameFromPath = function(path) { 21889 21890 var lastSlash; 21891 lastSlash = path.lastIndexOf('/'); 21892 // fix slashes. 21893 return (lastSlash === -1) ? path : path.substring(lastSlash+1); 21894 }; 21895 21896 AblePlayer.prototype.getFilenameFromTracks = function(kind,lang) { 21897 21898 for (var i=0; i<this.vtsTracks.length; i++) { 21899 if (this.vtsTracks[i].kind === kind && this.vtsTracks[i].language === lang) { 21900 // this is a matching track 21901 // srcFile has already been converted to filename from path before saving to vtsTracks 21902 return this.vtsTracks[i].srcFile; 21903 } 21904 } 21905 // no matching track found 21906 return false; 21907 }; 21908 21909 AblePlayer.prototype.parseVtsTracks = function(contents) { 21910 21911 var rows, timeParts, cues, i, j, thisRow, nextRow, content, blankRow; 21912 rows = contents.split("\n"); 21913 cues = []; 21914 i = 0; 21915 while (i < rows.length) { 21916 thisRow = rows[i]; 21917 if (thisRow.indexOf(' --> ') !== -1) { 21918 // this is probably a time row 21919 timeParts = thisRow.trim().split(' '); 21920 if (this.isValidTimestamp(timeParts[0]) && this.isValidTimestamp(timeParts[2])) { 21921 // both timestamps are valid. This is definitely a time row 21922 content = ''; 21923 j = i+1; 21924 blankRow = false; 21925 while (j < rows.length && !blankRow) { 21926 nextRow = rows[j].trim(); 21927 if (nextRow.length > 0) { 21928 if (content.length > 0) { 21929 // add back the EOL between rows of content 21930 content += "\n" + nextRow; 21931 } else { 21932 // this is the first row of content. No need for an EOL 21933 content += nextRow; 21934 } 21935 } else { 21936 blankRow = true; 21937 } 21938 j++; 21939 } 21940 cues.push({ 21941 'start': timeParts[0], 21942 'end': timeParts[2], 21943 'content': content 21944 }); 21945 i = j; //skip ahead 21946 } 21947 } else { 21948 i++; 21949 } 21950 } 21951 return cues; 21952 }; 21953 21954 AblePlayer.prototype.isValidTimestamp = function(timestamp) { 21955 21956 // return true if timestamp contains only numbers or expected punctuation 21957 return (/^[0-9:,.]*$/.test(timestamp)) ? true : false; 21958 }; 21959 21960 AblePlayer.prototype.formatTimestamp = function(timestamp) { 21961 21962 // timestamp is a string in the form "HH:MM:SS.xxx" 21963 // Take some simple steps to ensure edited timestamp values still adhere to expected format 21964 21965 var firstPart, lastPart; 21966 21967 firstPart = timestamp.substring(0,timestamp.lastIndexOf('.')+1); 21968 lastPart = timestamp.substring(timestamp.lastIndexOf('.')+1); 21969 21970 // TODO: Be sure each component within firstPart has only exactly two digits 21971 // Probably can't justify doing this automatically 21972 // If users enters '5' for minutes, that could be either '05' or '50' 21973 // This should trigger an error and prompt the user to correct the value before proceeding 21974 21975 // Be sure lastPart has exactly three digits 21976 if (lastPart.length > 3) { 21977 // chop off any extra digits 21978 lastPart = lastPart.substring(0,3); 21979 } else if (lastPart.length < 3) { 21980 // add trailing zeros 21981 while (lastPart.length < 3) { 21982 lastPart += '0'; 21983 } 21984 } 21985 return firstPart + lastPart; 21986 }; 21987 21988 21989 AblePlayer.prototype.injectVtsTable = function(action,lang) { 21990 21991 // action is either 'add' (for a new table) or 'update' (if user has selected a new lang) 21992 21993 var $table, $thead, headers, i, $tr, $th, $td, rows, rowNum, rowId; 21994 21995 if (action === 'update') { 21996 // remove existing table 21997 $('#able-vts table').remove(); 21998 $('#able-vts-icon-credit').remove(); 21999 } 22000 22001 $table = $('<table>',{ 22002 'lang': lang 22003 }); 22004 $thead = $( '<thead>' ); 22005 $tr = $( '<tr>' ); 22006 headers = [ 22007 this.translate( 'vtsRow', 'Row' ), 22008 this.translate( 'vtsKind', 'Kind' ), 22009 this.translate( 'vtsStart', 'Start' ), 22010 this.translate( 'vtsEnd', 'End' ), 22011 this.translate( 'vtsContent', 'Content' ), 22012 this.translate( 'vtsActions', 'Actions' ) 22013 ]; 22014 for (i=0; i < headers.length; i++) { 22015 $th = $('<th>', { 22016 'scope': 'col' 22017 }).text(headers[i]); 22018 if (headers[i] === 'Actions') { 22019 $th.addClass('actions'); 22020 } 22021 $tr.append($th); 22022 } 22023 $thead.append($tr); 22024 $table.append($thead); 22025 22026 // Get all rows (sorted by start time), and inject them into table 22027 rows = this.getAllRows(lang); 22028 for (i=0; i < rows.length; i++) { 22029 rowNum = i + 1; 22030 rowId = 'able-vts-row-' + rowNum; 22031 $tr = $('<tr>',{ 22032 'id': rowId, 22033 'class': 'kind-' + rows[i].kind 22034 }); 22035 // Row # 22036 $td = $('<td>').text(rowNum); 22037 $tr.append($td); 22038 22039 // Kind 22040 $td = $('<td>',{ 22041 'contenteditable': 'true' 22042 }).text(rows[i].kind); 22043 $tr.append($td); 22044 22045 // Start 22046 $td = $('<td>',{ 22047 'contenteditable': 'true' 22048 }).text(rows[i].start); 22049 $tr.append($td); 22050 22051 // End 22052 $td = $('<td>',{ 22053 'contenteditable': 'true' 22054 }).text(rows[i].end); 22055 $tr.append($td); 22056 22057 // Content 22058 $td = $('<td>',{ 22059 'contenteditable': 'true' 22060 }).text(rows[i].content); // TODO: Preserve tags 22061 $tr.append($td); 22062 22063 // Actions 22064 $td = this.addVtsActionButtons(rowNum,rows.length); 22065 $tr.append($td); 22066 22067 $table.append($tr); 22068 } 22069 $('#able-vts').append($table); 22070 22071 // Add credit for action button SVG icons 22072 $('#able-vts').append(this.getIconCredit()); 22073 22074 }; 22075 22076 AblePlayer.prototype.addVtsActionButtons = function(rowNum,numRows) { 22077 22078 // rowNum is the number of the current table row (starting with 1) 22079 // numRows is the total number of rows (excluding the header row) 22080 // TODO: Position buttons so they're vertically aligned, even if missing an Up or Down button 22081 var thisObj, $td, buttons, i, button, $button, $svg, $g, pathString, pathString2, $path, $path2; 22082 thisObj = this; 22083 $td = $('<td>'); 22084 buttons = ['up','down','insert','delete']; 22085 22086 for (i=0; i < buttons.length; i++) { 22087 button = buttons[i]; 22088 if (button === 'up') { 22089 if (rowNum > 1) { 22090 $button = $('<button>',{ 22091 'id': 'able-vts-button-up-' + rowNum, 22092 'title': 'Move up', 22093 'aria-label': 'Move Row ' + rowNum + ' up' 22094 }).on('click', function(el) { 22095 thisObj.onClickVtsActionButton(el.currentTarget); 22096 }); 22097 $svg = $('<svg>',{ 22098 'focusable': 'false', 22099 'aria-hidden': 'true', 22100 'x': '0px', 22101 'y': '0px', 22102 'width': '254.296px', 22103 'height': '254.296px', 22104 'viewBox': '0 0 254.296 254.296', 22105 'style': 'enable-background:new 0 0 254.296 254.296' 22106 }); 22107 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' 22108 + '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' 22109 + 'l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z'; 22110 $path = $('<path>',{ 22111 'd': pathString 22112 }); 22113 $g = $('<g>').append($path); 22114 $svg.append($g); 22115 $button.append($svg); 22116 // Refresh button in the DOM in order for browser to process & display the SVG 22117 $button.html($button.html()); 22118 $td.append($button); 22119 } 22120 } else if (button === 'down') { 22121 if (rowNum < numRows) { 22122 $button = $('<button>',{ 22123 'id': 'able-vts-button-down-' + rowNum, 22124 'title': 'Move down', 22125 'aria-label': 'Move Row ' + rowNum + ' down' 22126 }).on('click', function(el) { 22127 thisObj.onClickVtsActionButton(el.currentTarget); 22128 }); 22129 $svg = $('<svg>',{ 22130 'focusable': 'false', 22131 'aria-hidden': 'true', 22132 'x': '0px', 22133 'y': '0px', 22134 'width': '292.362px', 22135 'height': '292.362px', 22136 'viewBox': '0 0 292.362 292.362', 22137 'style': 'enable-background:new 0 0 292.362 292.362' 22138 }); 22139 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' 22140 + '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' 22141 + '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'; 22142 $path = $('<path>',{ 22143 'd': pathString 22144 }); 22145 $g = $('<g>').append($path); 22146 $svg.append($g); 22147 $button.append($svg); 22148 // Refresh button in the DOM in order for browser to process & display the SVG 22149 $button.html($button.html()); 22150 $td.append($button); 22151 } 22152 } else if (button === 'insert') { 22153 // Add Insert button to all rows 22154 $button = $('<button>',{ 22155 'id': 'able-vts-button-insert-' + rowNum, 22156 'title': 'Insert row below', 22157 'aria-label': 'Insert row before Row ' + rowNum 22158 }).on('click', function(el) { 22159 thisObj.onClickVtsActionButton(el.currentTarget); 22160 }); 22161 $svg = $('<svg>',{ 22162 'focusable': 'false', 22163 'aria-hidden': 'true', 22164 'x': '0px', 22165 'y': '0px', 22166 'width': '401.994px', 22167 'height': '401.994px', 22168 'viewBox': '0 0 401.994 401.994', 22169 'style': 'enable-background:new 0 0 401.994 401.994' 22170 }); 22171 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' 22172 + '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' 22173 + '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' 22174 + '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' 22175 + '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' 22176 + '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'; 22177 $path = $('<path>',{ 22178 'd': pathString 22179 }); 22180 $g = $('<g>').append($path); 22181 $svg.append($g); 22182 $button.append($svg); 22183 // Refresh button in the DOM in order for browser to process & display the SVG 22184 $button.html($button.html()); 22185 $td.append($button); 22186 } else if (button === 'delete') { 22187 // Add Delete button to all rows 22188 $button = $('<button>',{ 22189 'id': 'able-vts-button-delete-' + rowNum, 22190 'title': 'Delete row ', 22191 'aria-label': 'Delete Row ' + rowNum 22192 }).on('click', function(el) { 22193 thisObj.onClickVtsActionButton(el.currentTarget); 22194 }); 22195 $svg = $('<svg>',{ 22196 'focusable': 'false', 22197 'aria-hidden': 'true', 22198 'x': '0px', 22199 'y': '0px', 22200 'width': '508.52px', 22201 'height': '508.52px', 22202 'viewBox': '0 0 508.52 508.52', 22203 'style': 'enable-background:new 0 0 508.52 508.52' 22204 }); 22205 pathString = 'M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347' 22206 + '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' 22207 + 'C429.063,46.021,414.825,31.782,397.281,31.782z'; 22208 $path = $('<path>',{ 22209 'd': pathString 22210 }); 22211 pathString2 = 'M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042' 22212 + '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' 22213 + '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' 22214 + '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' 22215 + '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' 22216 + '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' 22217 + 'c-8.772,0-15.891-7.151-15.891-15.891V174.804z'; 22218 $path2 = $('<path>',{ 22219 'd': pathString2 22220 }); 22221 22222 $g = $('<g>').append($path,$path2); 22223 $svg.append($g); 22224 $button.append($svg); 22225 // Refresh button in the DOM in order for browser to process & display the SVG 22226 $button.html($button.html()); 22227 $td.append($button); 22228 } 22229 } 22230 return $td; 22231 }; 22232 22233 AblePlayer.prototype.updateVtsActionButtons = function($buttons,nextRowNum) { 22234 22235 // TODO: Add some filters to this function to add or delete 'Up' and 'Down' buttons 22236 // if row is moved to/from the first/last rows 22237 var i, $thisButton, id, label, newId, newLabel; 22238 for (i=0; i < $buttons.length; i++) { 22239 $thisButton = $buttons.eq(i); 22240 id = $thisButton.attr('id'); 22241 label = $thisButton.attr('aria-label'); 22242 // replace the integer (id) within each of the above strings 22243 newId = id.replace(/[0-9]+/g, nextRowNum); 22244 newLabel = label.replace(/[0-9]+/g, nextRowNum); 22245 $thisButton.attr('id',newId); 22246 $thisButton.attr('aria-label',newLabel); 22247 } 22248 }; 22249 22250 AblePlayer.prototype.getIconCredit = function() { 22251 22252 var credit 22253 = 'Action buttons made by <a target="_blank" rel="noreferrer" href="https://www.elegantthemes.com">Elegant Themes</a>' 22254 + ' from <a target="_blank" rel="noreferrer" href="https://www.flaticon.com">flaticon</a>' 22255 + ' 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>' 22256 ; 22257 return '<div id="able-vts-icon-credit">' + credit + '</div>'; 22258 }; 22259 22260 AblePlayer.prototype.getAllLangs = function(tracks) { 22261 22262 // update this.langs with any unique languages found in tracks 22263 var i; 22264 for (i in tracks) { 22265 if (Object.hasOwn(tracks[i], 'language')) { 22266 if ($.inArray(tracks[i].language,this.langs) === -1) { 22267 // this language is not already in the langs array. Add it. 22268 this.langs[this.langs.length] = tracks[i].language; 22269 } 22270 } 22271 } 22272 }; 22273 22274 AblePlayer.prototype.getAllRows = function(lang) { 22275 22276 // returns an array of data to be displayed in VTS table 22277 // includes all cues for tracks of any type with matching lang 22278 // cues are sorted by start time 22279 var i, track, c, cues; 22280 cues = []; 22281 for (i=0; i < this.vtsTracks.length; i++) { 22282 track = this.vtsTracks[i]; 22283 if (track.language == lang) { 22284 // this track matches the language. Add its cues to array 22285 for (c in track.cues) { 22286 cues.push({ 22287 'kind': track.kind, 22288 'lang': lang, 22289 'id': track.cues[c].id, 22290 'start': track.cues[c].start, 22291 'end': track.cues[c].end, 22292 'content': track.cues[c].content 22293 }); 22294 } 22295 } 22296 } 22297 // Now sort cues by start time 22298 cues.sort(function(a,b) { 22299 return a.start > b.start ? 1 : -1; 22300 }); 22301 return cues; 22302 }; 22303 22304 22305 AblePlayer.prototype.onClickVtsActionButton = function(el) { 22306 22307 // handle click on up, down, insert, or delete button 22308 var idParts, action, rowNum; 22309 idParts = $(el).attr('id').split('-'); 22310 action = idParts[3]; 22311 rowNum = idParts[4]; 22312 if (action == 'up') { 22313 // move the row up 22314 this.moveRow(rowNum,'up'); 22315 } else if (action == 'down') { 22316 // move the row down 22317 this.moveRow(rowNum,'down'); 22318 } else if (action == 'insert') { 22319 // insert a row below 22320 this.insertRow(rowNum); 22321 } else if (action == 'delete') { 22322 // delete the row 22323 this.deleteRow(rowNum); 22324 } 22325 }; 22326 22327 AblePlayer.prototype.insertRow = function(rowNum) { 22328 22329 // Insert empty row below rowNum 22330 var $table, $rows, numRows, newRowNum, newRowId, $tr, $td, $select, 22331 options, i, $option, newKind, newClass, $parentRow, nextRowNum, $buttons; 22332 22333 $table = $('#able-vts table'); 22334 $rows = $table.find('tr'); 22335 22336 numRows = $rows.length - 1; // exclude header row 22337 22338 newRowNum = parseInt(rowNum) + 1; 22339 newRowId = 'able-vts-row-' + newRowNum; 22340 22341 // Create an empty row 22342 $tr = $('<tr>',{ 22343 'id': newRowId 22344 }); 22345 22346 // Row # 22347 $td = $('<td>').text(newRowNum); 22348 $tr.append($td); 22349 22350 // Kind (add a select field for chosing a kind) 22351 newKind = null; 22352 $select = $('<select>',{ 22353 'id': 'able-vts-kind-' + newRowNum, 22354 'aria-label': 'What kind of track is this?', 22355 'placeholder': 'Select a kind' 22356 }).on('change',function() { 22357 newKind = $(this).val(); 22358 newClass = 'kind-' + newKind; 22359 $parentRow = $(this).closest('tr'); 22360 // replace the select field with the chosen value as text 22361 $(this).parent().text(newKind); 22362 // add a class to the parent row 22363 $parentRow.addClass(newClass); 22364 }); 22365 options = ['','captions','chapters','descriptions','subtitles']; 22366 for (i=0; i<options.length; i++) { 22367 $option = $('<option>',{ 22368 'value': options[i] 22369 }).text(options[i]); 22370 $select.append($option); 22371 } 22372 $td = $('<td>').append($select); 22373 $tr.append($td); 22374 22375 // Start 22376 $td = $('<td>',{ 22377 'contenteditable': 'true' 22378 }); // TODO; Intelligently assign a new start time (see getAdjustedTimes()) 22379 $tr.append($td); 22380 22381 // End 22382 $td = $('<td>',{ 22383 'contenteditable': 'true' 22384 }); // TODO; Intelligently assign a new end time (see getAdjustedTimes()) 22385 $tr.append($td); 22386 22387 // Content 22388 $td = $('<td>',{ 22389 'contenteditable': 'true' 22390 }); 22391 $tr.append($td); 22392 22393 // Actions 22394 $td = this.addVtsActionButtons(newRowNum,numRows); 22395 $tr.append($td); 22396 22397 // Now insert the new row 22398 $table.find('tr').eq(rowNum).after($tr); 22399 22400 // Update row.id, Row # cell, & action items for all rows after the inserted one 22401 for (i=newRowNum; i <= numRows; i++) { 22402 nextRowNum = i + 1; 22403 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 22404 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 22405 $buttons = $rows.eq(i).find('button'); 22406 this.updateVtsActionButtons($buttons,nextRowNum); 22407 } 22408 22409 // Auto-adjust times 22410 this.adjustTimes(newRowNum); 22411 22412 // Announce the insertion 22413 let newAlert = this.translate( 'vtsNewRow', 'A new row %1 has been inserted.', [ newRowNum ] ); 22414 this.showVtsAlert( newAlert ); 22415 22416 // Place focus in new select field 22417 $select.trigger('focus'); 22418 22419 }; 22420 22421 AblePlayer.prototype.deleteRow = function(rowNum) { 22422 22423 var $table, $rows, numRows, i, nextRowNum, $buttons; 22424 22425 $table = $('#able-vts table'); 22426 $table[0].deleteRow(rowNum); 22427 $rows = $table.find('tr'); // this does not include the deleted row 22428 numRows = $rows.length - 1; // exclude header row 22429 22430 // Update row.id, Row # cell, & action buttons for all rows after the deleted one 22431 for (i=rowNum; i <= numRows; i++) { 22432 nextRowNum = i; 22433 $rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id 22434 $rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td 22435 $buttons = $rows.eq(i).find('button'); 22436 this.updateVtsActionButtons($buttons,nextRowNum); 22437 } 22438 22439 // Announce the deletion 22440 let newAlert = this.translate( 'vtsDeletedRow', 'Row %1 has been deleted.', [ rowNum ] ); 22441 this.showVtsAlert( newAlert ); 22442 22443 }; 22444 22445 AblePlayer.prototype.moveRow = function(rowNum,direction) { 22446 22447 // swap two rows 22448 var $thisRow, otherRowNum, $otherRow, msg; 22449 22450 $thisRow = $('#able-vts table').find('tr').eq(rowNum); 22451 if (direction == 'up') { 22452 otherRowNum = parseInt(rowNum) - 1; 22453 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 22454 $otherRow.before($thisRow); 22455 } else if (direction == 'down') { 22456 otherRowNum = parseInt(rowNum) + 1; 22457 $otherRow = $('#able-vts table').find('tr').eq(otherRowNum); 22458 $otherRow.after($thisRow); 22459 } 22460 // Update row.id, Row # cell, & action buttons for the two swapped rows 22461 $thisRow.attr('id','able-vts-row-' + otherRowNum); 22462 $thisRow.find('td').eq(0).text(otherRowNum); 22463 this.updateVtsActionButtons($thisRow.find('button'),otherRowNum); 22464 $otherRow.attr('id','able-vts-row-' + rowNum); 22465 $otherRow.find('td').eq(0).text(rowNum); 22466 this.updateVtsActionButtons($otherRow.find('button'),rowNum); 22467 22468 // auto-adjust times 22469 this.adjustTimes(otherRowNum); 22470 22471 // Announce the move 22472 msg = this.translate( 'vtsMovedRow', 'Row %1 has been moved %2 and is now Row %3.', [ rowNum, direction, otherRowNum ] ); 22473 this.showVtsAlert(msg); 22474 }; 22475 22476 AblePlayer.prototype.adjustTimes = function(rowNum) { 22477 22478 // Adjusts start and end times of the current, previous, and next rows in VTS table 22479 // after a move or insert 22480 // NOTE: Fully automating this process would be extraordinarily complicated 22481 // The goal here is simply to make subtle tweaks to ensure rows appear 22482 // in the new order within the Able Player transcript 22483 // Additional tweaking will likely be required by the user 22484 22485 // HISTORY: Originally set minDuration to 2 seconds for captions and .500 for descriptions 22486 // However, this can results in significant changes to existing caption timing, 22487 // with not-so-positive results. 22488 // As of 3.1.15, setting minDuration to .001 for all track kinds 22489 // Users will have to make further adjustments manually if needed 22490 22491 // TODO: Add WebVTT validation on save, since tweaking times is risky 22492 22493 var minDuration, $rows, prevRowNum, nextRowNum, $row, $prevRow, $nextRow, 22494 kind, prevKind, nextKind, 22495 start, prevStart, nextStart, 22496 end, prevEnd, nextEnd; 22497 22498 // Define minimum duration (in seconds) for each kind of track 22499 minDuration = []; 22500 minDuration['captions'] = .001; 22501 minDuration['descriptions'] = .001; 22502 minDuration['chapters'] = .001; 22503 22504 // refresh rows object 22505 $rows = $('#able-vts table').find('tr'); 22506 22507 // Get kind, start, and end from current row 22508 $row = $rows.eq(rowNum); 22509 // row has a class that starts with "kind-" 22510 // Extract kind from the class name 22511 kind = ($row.is('[class^="kind-"]')) ? this.getKindFromClass($row.attr('class')) : 'captions'; 22512 22513 start = this.getSecondsFromColonTime($row.find('td').eq(2).text()); 22514 end = this.getSecondsFromColonTime($row.find('td').eq(3).text()); 22515 22516 // Get kind, start, and end from previous row 22517 if (rowNum > 1) { 22518 // this is not the first row. Include the previous row 22519 prevRowNum = rowNum - 1; 22520 $prevRow = $rows.eq(prevRowNum); 22521 // row has a class that starts with "kind-" 22522 // Extract kind from the class name 22523 prevKind = ($prevRow.is('[class^="kind-"]')) ? this.getKindFromClass($prevRow.attr('class')) : null; 22524 prevStart = this.getSecondsFromColonTime($prevRow.find('td').eq(2).text()); 22525 prevEnd = this.getSecondsFromColonTime($prevRow.find('td').eq(3).text()); 22526 } else { 22527 // this is the first row 22528 $prevRow = null; 22529 prevKind = null; 22530 prevStart = null; 22531 prevEnd = null; 22532 } 22533 22534 // Get kind, start, and end from next row 22535 if (rowNum < ($rows.length - 1)) { 22536 // this is not the last row. Include the next row 22537 nextRowNum = rowNum + 1; 22538 $nextRow = $rows.eq(nextRowNum); 22539 // row has a class that starts with "kind-" 22540 // Extract kind from the class name 22541 nextKind = ($nextRow.is('[class^="kind-"]')) ? this.getKindFromClass($nextRow.attr('class')) : null; 22542 nextStart = this.getSecondsFromColonTime($nextRow.find('td').eq(2).text()); 22543 nextEnd = this.getSecondsFromColonTime($nextRow.find('td').eq(3).text()); 22544 } else { 22545 // this is the last row 22546 $nextRow = null; 22547 nextKind = null; 22548 nextStart = null; 22549 nextEnd = null; 22550 } 22551 22552 if (isNaN(start)) { 22553 if (prevKind == null) { 22554 // The previous row was probably inserted, and user has not yet selected a kind 22555 // automatically set it to captions 22556 prevKind = 'captions'; 22557 $prevRow.attr('class','kind-captions'); 22558 $prevRow.find('td').eq(1).html('captions'); 22559 } 22560 // Current row has no start time (i.e., it's an inserted row) 22561 if (prevKind === 'captions') { 22562 // start the new row immediately after the captions end 22563 start = (parseFloat(prevEnd) + .001).toFixed(3); 22564 // end the new row immediately before the next row starts 22565 end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3); 22566 } else if (prevKind === 'chapters') { 22567 // start the new row immediately after the chapter start (not end) 22568 start = (parseFloat(prevStart) + .001).toFixed(3); 22569 // end the new row immediately before the next row starts 22570 end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3); 22571 } else if (prevKind === 'descriptions') { 22572 // start the new row minDuration['descriptions'] after the description starts 22573 // this will theoretically allow at least a small cushion for the description to be read 22574 start = (parseFloat(prevStart) + minDuration['descriptions']).toFixed(3); 22575 end = (parseFloat(start) + minDuration['descriptions']).toFixed(3); 22576 } 22577 } else { 22578 // current row has a start time (i.e., an existing row has been moved)) 22579 if (prevStart) { 22580 // this is not the first row. 22581 if (prevStart < start) { 22582 if (start < nextStart) ; else { 22583 // nextStart needs to be incremented 22584 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 22585 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 22586 // TODO: Ensure nextEnd does not exceed the following start (nextNextStart) 22587 // Or... maybe this is getting too complicated and should be left up to the user 22588 } 22589 } else { 22590 // start needs to be incremented 22591 start = (parseFloat(prevStart) + minDuration[prevKind]).toFixed(3); 22592 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 22593 } 22594 } else { 22595 // this is the first row 22596 if (start < nextStart) ; else { 22597 // nextStart needs to be incremented 22598 nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3); 22599 nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3); 22600 } 22601 } 22602 } 22603 22604 // check to be sure there is sufficient duration between new start & end times 22605 if (end - start < minDuration[kind]) { 22606 // duration is too short. Change end time 22607 end = (parseFloat(start) + minDuration[kind]).toFixed(3); 22608 if (nextStart) { 22609 // this is not the last row 22610 // increase start time of next row 22611 nextStart = (parseFloat(end) + .001).toFixed(3); 22612 } 22613 } 22614 22615 // Update all affected start/end times 22616 $row.find('td').eq(2).text(this.formatSecondsAsColonTime(start,true)); 22617 $row.find('td').eq(3).text(this.formatSecondsAsColonTime(end,true)); 22618 if ($prevRow) { 22619 $prevRow.find('td').eq(2).text(this.formatSecondsAsColonTime(prevStart,true)); 22620 $prevRow.find('td').eq(3).text(this.formatSecondsAsColonTime(prevEnd,true)); 22621 } 22622 if ($nextRow) { 22623 $nextRow.find('td').eq(2).text(this.formatSecondsAsColonTime(nextStart,true)); 22624 $nextRow.find('td').eq(3).text(this.formatSecondsAsColonTime(nextEnd,true)); 22625 } 22626 }; 22627 22628 AblePlayer.prototype.getKindFromClass = function(myclass) { 22629 22630 // This function is called when a class with prefix "kind-" is found in the class attribute 22631 22632 var kindStart, kindEnd; 22633 22634 kindStart = myclass.indexOf('kind-')+5; 22635 kindEnd = myclass.indexOf(' ',kindStart); 22636 if (kindEnd == -1) { 22637 // no spaces found, "kind-" must be the only myclass 22638 return myclass.substring(kindStart); 22639 } else { 22640 // kind-* is one of multiple classes 22641 // the following will find it regardless of position of "kind-*" within the class string 22642 return myclass.substring(kindStart,kindEnd); 22643 } 22644 }; 22645 22646 AblePlayer.prototype.showVtsAlert = function(message) { 22647 22648 // this is distinct from greater Able Player showAlert() 22649 // because it's positioning needs are unique 22650 // For now, alertDiv is fixed at top left of screen 22651 // but could ultimately be modified to appear near the point of action in the VTS table 22652 const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); 22653 this.$vtsAlert.text(message).show(); 22654 delay(10000).then(() => { 22655 this.$vtsAlert.text(message).hide(); 22656 }); 22657 }; 22658 22659 AblePlayer.prototype.parseVtsOutput = function($table) { 22660 22661 // parse table into arrays, then into WebVTT content, for each kind 22662 // Display the WebVTT content in textarea fields for users to copy and paste 22663 var lang, i, kinds, kind, vtt, $rows, start, end, content, $output; 22664 22665 lang = $table.attr('lang'); 22666 kinds = ['captions','chapters','descriptions','subtitles']; 22667 vtt = {}; 22668 for (i=0; i < kinds.length; i++) { 22669 kind = kinds[i]; 22670 vtt[kind] = 'WEBVTT' + "\n\n"; 22671 } 22672 $rows = $table.find('tr'); 22673 if ($rows.length > 0) { 22674 for (i=0; i < $rows.length; i++) { 22675 kind = $rows.eq(i).find('td').eq(1).text(); 22676 if ($.inArray(kind,kinds) !== -1) { 22677 start = $rows.eq(i).find('td').eq(2).text(); 22678 end = $rows.eq(i).find('td').eq(3).text(); 22679 content = $rows.eq(i).find('td').eq(4)[0].innerText; 22680 if (start !== undefined && end !== undefined) { 22681 vtt[kind] += start + ' --> ' + end + "\n"; 22682 if (content !== 'undefined') { 22683 vtt[kind] += content; 22684 } 22685 vtt[kind] += "\n\n"; 22686 } 22687 } 22688 } 22689 } 22690 $output = $('<div>',{ 22691 'id': 'able-vts-output' 22692 }); 22693 $('#able-vts').append($output); 22694 for (i=0; i < kinds.length; i++) { 22695 kind = kinds[i]; 22696 if (vtt[kind].length > 8) { 22697 // some content has been added 22698 this.showWebVttOutput(kind,vtt[kind],lang); 22699 } 22700 } 22701 }; 22702 22703 AblePlayer.prototype.showWebVttOutput = function(kind,vttString,lang) { 22704 22705 var $heading, filename, $p, pText, $textarea; 22706 22707 $heading = $('<h3>').text( this.capitalizeFirstLetter( kind ) ); 22708 filename = this.getFilenameFromTracks(kind,lang); 22709 pText = 'If you made changes, copy/paste the following content '; 22710 if (filename) { 22711 pText += 'to replace the original content of your ' + this.getLanguageName(lang) + ' '; 22712 pText += '<em>' + kind + '</em> WebVTT file (<strong>' + filename + '</strong>).'; 22713 } else { 22714 pText += 'into a new ' + this.getLanguageName(lang) + ' <em>' + kind + '</em> WebVTT file.'; 22715 } 22716 $p = $('<p>',{ 22717 'class': 'able-vts-output-instructions' 22718 }).html(pText); 22719 $textarea = $('<textarea>').text(vttString); 22720 $('#able-vts-output').append($heading,$p,$textarea); 22721 }; 22722 22723 } 22724 22725 function addWebvttFunctions(AblePlayer) { 22726 // See section 4.1 of dev.w3.org/html5/webvtt for format details. 22727 AblePlayer.prototype.parseWebVTT = function(data) { 22728 22729 let srcFile = data.src; 22730 let text = data.text; 22731 // Normalize line ends to \n. 22732 text = text.replace(/(\r\n|\n|\r)/g,'\n'); 22733 22734 var parserState = { 22735 src: srcFile, 22736 text: text, 22737 error: null, 22738 metadata: {}, 22739 cues: [], 22740 line: 1, 22741 column: 1 22742 }; 22743 22744 try { 22745 act(parserState, parseFileBody); 22746 } 22747 catch (err) { 22748 var errString = 'Invalid WebVTT file: ' + parserState.src + '\n'; 22749 errString += 'Line: ' + parserState.line + ', '; 22750 errString += 'Column: ' + parserState.column + '\n'; 22751 errString += err; 22752 if (console.warn) { 22753 console.warn(errString); 22754 } 22755 } 22756 return parserState; 22757 }; 22758 22759 function actList(state, list) { 22760 var results = []; 22761 for (var ii = 0; ii < list.length; ii++) { 22762 results.push(act(state, list[ii])); 22763 } 22764 return results; 22765 } 22766 22767 // Applies the action and checks for errors. 22768 function act(state, action) { 22769 var val = action(state); 22770 if (state.error !== null) { 22771 throw state.error; 22772 } 22773 return val; 22774 } 22775 22776 function updatePosition(state, cutText) { 22777 for (var ii = 0; ii < cutText.length; ii++) { 22778 if (cutText[ii] === '\n') { 22779 state.column = 1; 22780 state.line += 1; 22781 } else { 22782 state.column += 1; 22783 } 22784 } 22785 } 22786 22787 function cut(state, length) { 22788 var returnText = state.text.substring(0, length); 22789 updatePosition(state, returnText); 22790 state.text = state.text.substring(length); 22791 return returnText; 22792 } 22793 22794 function cutLine(state) { 22795 var nextEOL = state.text.indexOf('\n'); 22796 var returnText; 22797 if (nextEOL === -1) { 22798 returnText = state.text; 22799 updatePosition(state, returnText); 22800 state.text = ''; 22801 } else { 22802 returnText = state.text.substring(0, nextEOL); 22803 updatePosition(state, returnText + '\n'); 22804 state.text = state.text.substring(nextEOL + 1); 22805 } 22806 return returnText; 22807 } 22808 22809 function peekLine(state) { 22810 var nextEOL = state.text.indexOf('\n'); 22811 return (nextEOL === -1) ? state.text : state.text.substring(0, nextEOL); 22812 } 22813 22814 function parseFileBody(state) { 22815 actList(state, [ 22816 eatOptionalBOM, 22817 eatSignature]); 22818 var c = state.text[0]; 22819 if (c === ' ' || c === '\t' || c === '\n') { 22820 actList(state, [ 22821 eatUntilEOLInclusive, 22822 parseMetadataHeaders, 22823 eatAtLeast1EmptyLines, 22824 parseCuesAndComments]); 22825 } else { 22826 state.error = "WEBVTT signature not followed by whitespace."; 22827 } 22828 } 22829 22830 // Parses all metadata headers until a cue is discovered. 22831 function parseMetadataHeaders(state) { 22832 while (true) { 22833 var nextLine = peekLine(state); 22834 if (nextLine.indexOf('-->') !== -1) { 22835 return; 22836 } else if (nextLine.length === 0) { 22837 return; 22838 } else { 22839 var keyValue = act(state, getMetadataKeyValue); 22840 state.metadata[keyValue[0]] = keyValue[1]; 22841 act(state, eatUntilEOLInclusive); 22842 } 22843 } 22844 } 22845 22846 function nextSpaceOrNewline(s) { 22847 var possible = []; 22848 var spaceIndex = s.indexOf(' '); 22849 if (spaceIndex >= 0) { 22850 possible.push(spaceIndex); 22851 } 22852 var tabIndex = s.indexOf('\t'); 22853 if (tabIndex >= 0) { 22854 possible.push(tabIndex); 22855 } 22856 var lineIndex = s.indexOf('\n'); 22857 if (lineIndex >= 0) { 22858 possible.push(lineIndex); 22859 } 22860 22861 return Math.min.apply(null, possible); 22862 } 22863 22864 function getMetadataKeyValue(state) { 22865 var next = state.text.indexOf('\n'); 22866 var pair = cut(state, next); 22867 var colon = pair.indexOf(':'); 22868 if (colon === -1) { 22869 state.error = 'Missing colon.'; 22870 return; 22871 } else { 22872 var pairName = pair.substring(0, colon); 22873 var pairValue = pair.substring(colon + 1); 22874 return [pairName, pairValue]; 22875 } 22876 } 22877 22878 function getSettingsKeyValue(state) { 22879 var next = nextSpaceOrNewline(state.text); 22880 var pair = cut(state, next); 22881 var colon = pair.indexOf(':'); 22882 if (colon === -1) { 22883 state.error = 'Missing colon.'; 22884 return; 22885 } else { 22886 var pairName = pair.substring(0, colon); 22887 var pairValue = pair.substring(colon + 1); 22888 return [pairName, pairValue]; 22889 } 22890 } 22891 22892 function parseCuesAndComments(state) { 22893 while (true) { 22894 var nextLine = peekLine(state); 22895 // If NOTE is not on a line all its own, it must be followed by a space or tab. 22896 if (nextLine.indexOf('NOTE') === 0 && ((nextLine.length === 4) || (nextLine[4] === ' ') || (nextLine[4] === '\t'))) { 22897 actList(state, [eatComment, eatEmptyLines]); 22898 } else if (nextLine.trim().length === 0 && state.text.length > 0) { 22899 act(state, eatEmptyLines); 22900 } else if (nextLine.trim().length > 0) { 22901 act(state, parseCue); 22902 } else { 22903 // Everythings parsed! 22904 return; 22905 } 22906 } 22907 } 22908 22909 function parseCue(state) { 22910 22911 var nextLine = peekLine(state); 22912 var cueId; 22913 var errString; 22914 22915 if(nextLine.indexOf('-->') === -1) { 22916 cueId = cutLine(state); 22917 nextLine = peekLine(state); 22918 if(nextLine.indexOf('-->') === -1) { 22919 errString = 'Invalid WebVTT file: ' + state.src + '\n'; 22920 errString += 'Line: ' + state.line + ', '; 22921 errString += 'Column: ' + state.column + '\n'; 22922 errString += 'Expected cue timing for cueId \''+cueId+'\' but found: ' + nextLine + '\n'; 22923 if (console.warn) { 22924 console.warn(errString); 22925 } 22926 return; // Return leaving line for parseCuesAndComments to handle 22927 } 22928 } 22929 22930 var cueTimings = actList(state, [getTiming, 22931 eatAtLeast1SpacesOrTabs, 22932 eatArrow, 22933 eatAtLeast1SpacesOrTabs, 22934 getTiming]); 22935 22936 var startTime = cueTimings[0]; 22937 var endTime = cueTimings[4]; 22938 if (startTime >= endTime) { 22939 state.error = 'Start time is not sooner than end time.'; 22940 return; 22941 } 22942 22943 act(state, eatSpacesOrTabs); 22944 var cueSettings = act(state, getCueSettings); 22945 // Cut the newline. 22946 cut(state, 1); 22947 var components = act(state, getCuePayload); 22948 22949 if (typeof cueId === 'undefined') { 22950 cueId = state.cues.length + 1; 22951 } 22952 state.cues.push({ 22953 id: cueId, 22954 start: startTime, 22955 end: endTime, 22956 settings: cueSettings, 22957 components: components 22958 }); 22959 } 22960 22961 function getCueSettings(state) { 22962 var cueSettings = {}; 22963 while (state.text.length > 0 && state.text[0] !== '\n') { 22964 var keyValue = act(state, getSettingsKeyValue); 22965 cueSettings[keyValue[0]] = keyValue[1]; 22966 act(state, eatSpacesOrTabs); 22967 } 22968 return cueSettings; 22969 } 22970 22971 function getCuePayload(state) { 22972 // Parser based on instructions in draft. 22973 var result = {type: 'internal', tagName: '', value: '', classes: [], annotation: '', parent: null, children: [], language: ''}; 22974 var current = result; 22975 var languageStack = []; 22976 while (state.text.length > 0) { 22977 var nextLine = peekLine(state); 22978 if (nextLine.indexOf('-->') !== -1 || /^\s+$/.test(nextLine)) { 22979 break; // Handle empty cues 22980 } 22981 // Have to separately detect double-lines ending cue due to our non-standard parsing. 22982 // TODO: Redo outer algorithm to conform to W3 spec? 22983 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 22984 cut(state, 2); 22985 break; 22986 } 22987 22988 var token = getCueToken(state); 22989 // We'll use the tokens themselves as objects where possible. 22990 if (token.type === 'string') { 22991 current.children.push(token); 22992 } else if (token.type === 'startTag') { 22993 token.type = token.tagName; 22994 // Define token.parent; added by Terrill to fix bug end 'endTag' loop 22995 token.parent = current; 22996 if ($.inArray(token.tagName, ['i', 'b', 'u', 'ruby']) !== -1) { 22997 if (languageStack.length > 0) { 22998 current.language = languageStack[languageStack.length - 1]; 22999 } 23000 current.children.push(token); 23001 current = token; 23002 } else if (token.tagName === 'rt' && current.tagName === 'ruby') { 23003 if (languageStack.length > 0) { 23004 current.language = languageStack[languageStack.length - 1]; 23005 } 23006 current.children.push(token); 23007 current = token; 23008 } else if (token.tagName === 'c') { 23009 token.value = token.annotation; 23010 if (languageStack.length > 0) { 23011 current.language = languageStack[languageStack.length - 1]; 23012 } 23013 current.children.push(token); 23014 current = token; 23015 } else if (token.tagName === 'v') { 23016 token.value = token.annotation; 23017 if (languageStack.length > 0) { 23018 current.language = languageStack[languageStack.length - 1]; 23019 } 23020 current.children.push(token); 23021 current = token; 23022 } else if (token.tagName === 'lang') { 23023 languageStack.push(token.annotation); 23024 if (languageStack.length > 0) { 23025 current.language = languageStack[languageStack.length - 1]; 23026 } 23027 current.children.push(token); 23028 current = token; 23029 } 23030 } else if (token.type === 'endTag') { 23031 if (token.tagName === current.type && $.inArray(token.tagName, ['c', 'i', 'b', 'u', 'ruby', 'rt', 'v']) !== -1) { 23032 // NOTE from Terrill: This was resulting in an error because current.parent was undefined 23033 // Fixed (I think) by assigning current token to token.parent in 'startTag' loop 23034 current = current.parent; 23035 } else if (token.tagName === 'lang' && current.type === 'lang') { 23036 current = current.parent; 23037 languageStack.pop(); 23038 } else if (token.tagName === 'ruby' && current.type === 'rt') { 23039 current = current.parent.parent; 23040 } 23041 } else if (token.type === 'timestampTag') { 23042 var tempState = { 23043 text: token.value, 23044 error: null, 23045 metadata: {}, 23046 cues: [], 23047 line: 1, 23048 column: 1 23049 }; 23050 try { 23051 var timing = act(tempState, getTiming); 23052 if (tempState.text.length === 0) { 23053 token.value = timing; 23054 current.push(token); 23055 } 23056 } 23057 catch (e) { 23058 // Errors are suppressed here, not sure why - VP 2026-03-02 23059 } 23060 } 23061 } 23062 return result; 23063 } 23064 23065 // Gets a single cue token; uses the method in the w3 specification. 23066 function getCueToken(state) { 23067 var tokenState = 'data'; 23068 var result = []; 23069 var buffer = ''; 23070 var token = {type: '', tagName: '', value: '', classes: [], annotation: '', children: []}; 23071 23072 while (true) { 23073 var c; 23074 // Double newlines indicate end of token. 23075 if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') { 23076 c = '\u0004'; 23077 } else if (state.text.length > 0) { 23078 c = state.text[0]; 23079 } else { 23080 // End of file. 23081 c = '\u0004'; 23082 } 23083 if (tokenState === 'data') { 23084 if (c === '&') { 23085 buffer = '&'; 23086 tokenState = 'escape'; 23087 } else if (c === '<') { 23088 if (result.length === 0) { 23089 tokenState = 'tag'; 23090 } else { 23091 token.type = 'string'; 23092 token.value = result.join(''); 23093 return token; 23094 } 23095 } else if (c === '\u0004') { 23096 return {type: 'string', value: result.join('')}; 23097 } else { 23098 result.push(c); 23099 } 23100 } else if (tokenState === 'escape') { 23101 if (c === '&') { 23102 result.push(buffer); 23103 buffer = '&'; 23104 } else if (c.match(/[0-9a-z]/)) { 23105 buffer += c; 23106 } else if (c === ';') { 23107 if (buffer === '&') { 23108 result.push('&'); 23109 } else if (buffer === '<') { 23110 result.push('<'); 23111 } else if (buffer === '>') { 23112 result.push('>'); 23113 } else if (buffer === '&lrm') { 23114 result.push('\u200e'); 23115 } else if (buffer === '&rlm') { 23116 result.push('\u200f'); 23117 } else if (buffer === ' ') { 23118 result.push('\u00a0'); 23119 } else { 23120 result.push(buffer); 23121 result.push(';'); 23122 } 23123 tokenState = 'data'; 23124 } else if (c === '<' || c === '\u0004') { 23125 result.push(buffer); 23126 token.type = 'string'; 23127 token.value = result.join(''); 23128 return token; 23129 } else if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { // Handle unescaped & chars as strings 23130 result.push(buffer); 23131 token.type = 'string'; 23132 token.value = result.join(''); 23133 return token; 23134 } else { 23135 result.push(buffer); 23136 tokenState = 'data'; 23137 } 23138 } else if (tokenState === 'tag') { 23139 if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { 23140 tokenState = 'startTagAnnotation'; 23141 } else if (c === '.') { 23142 tokenState = 'startTagClass'; 23143 } else if (c === '/') { 23144 tokenState = 'endTag'; 23145 } else if (c.match('[0-9]')) { 23146 tokenState = 'timestampTag'; 23147 result.push(c); 23148 } else if (c === '>') { 23149 cut(state, 1); 23150 break; 23151 } else if (c === '\u0004') { 23152 token.tagName = ''; 23153 token.type = 'startTag'; 23154 return token; 23155 } else { 23156 result.push(c); 23157 tokenState = 'startTag'; 23158 } 23159 } else if (tokenState === 'startTag') { 23160 if (c === '\t' || c === '\u000c' || c === ' ') { 23161 tokenState = 'startTagAnnotation'; 23162 } else if (c === '\n') { 23163 buffer = c; 23164 tokenState = 'startTagAnnotation'; 23165 } else if (c === '.') { 23166 tokenState = 'startTagClass'; 23167 } else if (c === '>') { 23168 cut(state, 1); 23169 token.tagName = result.join(''); 23170 token.type = 'startTag'; 23171 return token; 23172 } else if (c === '\u0004') { 23173 token.tagName = result.join(''); 23174 token.type = 'startTag'; 23175 return token; 23176 } else { 23177 result.push(c); 23178 } 23179 } else if (tokenState === 'startTagClass') { 23180 if (c === '\t' || c === '\u000c' || c === ' ') { 23181 token.classes.push(buffer); 23182 buffer = ''; 23183 tokenState = 'startTagAnnotation'; 23184 } else if (c === '\n') { 23185 token.classes.push(buffer); 23186 buffer = c; 23187 tokenState = 'startTagAnnotation'; 23188 } else if (c === '.') { 23189 token.classes.push(buffer); 23190 buffer = ""; 23191 } else if (c === '>') { 23192 cut(state, 1); 23193 token.classes.push(buffer); 23194 token.type = 'startTag'; 23195 token.tagName = result.join(''); 23196 return token; 23197 } else if (c === '\u0004') { 23198 token.classes.push(buffer); 23199 token.type = 'startTag'; 23200 token.tagName = result.join(''); 23201 return token; 23202 } else { 23203 buffer += 'c'; 23204 } 23205 } else if (tokenState === 'startTagAnnotation') { 23206 if (c === '>') { 23207 cut(state, 1); 23208 buffer = buffer.trim().replace(/ +/, ' '); 23209 token.type = 'startTag'; 23210 token.tagName = result.join(''); 23211 token.annotation = buffer; 23212 return token; 23213 } else if (c === '\u0004') { 23214 buffer = buffer.trim().replace(/ +/, ' '); 23215 token.type = 'startTag'; 23216 token.tagName = result.join(''); 23217 token.annotation = buffer; 23218 return token; 23219 } else { 23220 buffer += c; 23221 } 23222 } else if (tokenState === 'endTag') { 23223 if (c === '>') { 23224 cut(state, 1); 23225 token.type = 'endTag'; 23226 token.tagName = result.join(''); 23227 return token; 23228 } else if (c === '\u0004') { 23229 token.type = 'endTag'; 23230 token.tagName = result.join(''); 23231 return token; 23232 } else { 23233 result.push(c); 23234 } 23235 } else if (tokenState === 'timestampTag') { 23236 if (c === '>') { 23237 cut(state, 1); 23238 token.type = 'timestampTag'; 23239 token.name = result.join(''); 23240 return token; 23241 } else if (c === '\u0004') { 23242 token.type = 'timestampTag'; 23243 token.name = result.join(''); 23244 return token; 23245 } else { 23246 result.push(c); 23247 } 23248 } else { 23249 throw 'Unknown tokenState ' + tokenState; 23250 } 23251 23252 cut(state, 1); 23253 } 23254 } 23255 23256 function eatComment(state) { 23257 // Cut the NOTE line. 23258 var noteLine = cutLine(state); 23259 if (noteLine.indexOf('-->') !== -1) { 23260 state.error = 'Invalid syntax: --> in NOTE line.'; 23261 return; 23262 } 23263 while (true) { 23264 var nextLine = peekLine(state); 23265 if ( nextLine.trim().length === 0) { 23266 // End of comment. 23267 return; 23268 } else if (nextLine.indexOf('-->') !== -1) { 23269 state.error = 'Invalid syntax: --> in comment.'; 23270 return; 23271 } else { 23272 cutLine(state); 23273 } 23274 } 23275 } 23276 23277 // Initial byte order mark. 23278 function eatOptionalBOM(state) { 23279 if (state.text[0] === '\ufeff') { 23280 cut(state, 1); 23281 } 23282 23283 } 23284 23285 // "WEBVTT" string. 23286 function eatSignature(state) { 23287 if (state.text.substring(0,6) === 'WEBVTT') { 23288 cut(state, 6); 23289 } else { 23290 state.error = 'Invalid signature.'; 23291 } 23292 } 23293 23294 function eatArrow(state) { 23295 if (state.text.length < 3 || state.text.substring(0,3) !== '-->') { 23296 state.error = 'Missing -->'; 23297 } else { 23298 cut(state, 3); 23299 } 23300 } 23301 23302 function eatSpacesOrTabs(state) { 23303 while (state.text[0] === '\t' || state.text[0] === ' ') { 23304 cut(state, 1); 23305 } 23306 } 23307 23308 function eatAtLeast1SpacesOrTabs(state) { 23309 var numEaten = 0; 23310 while (state.text[0] === '\t' || state.text[0] === ' ') { 23311 cut(state, 1); 23312 numEaten += 1; 23313 } 23314 if (numEaten === 0) { 23315 state.error = 'Missing space.'; 23316 } 23317 } 23318 23319 function eatUntilEOLInclusive(state) { 23320 var nextEOL = state.text.indexOf('\n'); 23321 if (nextEOL === -1) { 23322 state.error = 'Missing EOL.'; 23323 } else { 23324 cut(state, nextEOL + 1); 23325 } 23326 } 23327 23328 function eatEmptyLines(state) { 23329 while (state.text.length > 0) { 23330 var nextLine = peekLine(state); 23331 if ( nextLine.trim().length === 0) { 23332 cutLine(state); 23333 } else { 23334 break; 23335 } 23336 } 23337 } 23338 23339 // Eats empty lines, but throws an error if there's not at least one. 23340 function eatAtLeast1EmptyLines(state) { 23341 var linesEaten = 0; 23342 while (state.text.length > 0) { 23343 var nextLine = peekLine(state); 23344 if ( nextLine.trim().length === 0) { 23345 cutLine(state); 23346 linesEaten += 1; 23347 } else { 23348 break; 23349 } 23350 } 23351 if (linesEaten === 0) { 23352 state.error = 'Missing empty line.'; 23353 } 23354 } 23355 23356 function getTiming(state) { 23357 var nextSpace = nextSpaceOrNewline(state.text); 23358 if (nextSpace === -1) { 23359 state.error('Missing timing.'); 23360 return; 23361 } 23362 var timestamp = cut(state, nextSpace); 23363 23364 // The spec requires exactly 2 characters for minutes and seconds, and 2+ for hours, 23365 // 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 23366 var results = /((\d+):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp); 23367 23368 if (!results) { 23369 state.error = 'Unable to parse timestamp'; 23370 return; 23371 } 23372 var time = 0; 23373 var hours = results[2]; 23374 var minutes = results[4]; 23375 23376 if (minutes) { 23377 if (parseInt(minutes, 10) > 59) { 23378 state.error = 'Invalid minute range'; 23379 return; 23380 } 23381 if (hours) { 23382 time += 3600 * parseInt(hours, 10); 23383 } 23384 time += 60 * parseInt(minutes, 10); 23385 var seconds = results[5]; 23386 if (parseInt(seconds, 10) > 59) { 23387 state.error = 'Invalid second range'; 23388 return; 23389 } 23390 23391 time += parseInt(seconds, 10); 23392 time += parseInt(results[6], 10) / 1000; 23393 } else { 23394 time += parseInt(results[7], 10); 23395 time += parseInt(results[8], 10) / 1000; 23396 } 23397 23398 return time; 23399 } 23400 } 23401 23402 /* global YT */ 23403 23404 function addYoutubeFunctions(AblePlayer) { 23405 23406 AblePlayer.prototype.initYouTubePlayer = function () { 23407 23408 var thisObj, deferred, promise, youTubeId; 23409 thisObj = this; 23410 deferred = new this.defer(); 23411 promise = deferred.promise(); 23412 23413 this.youTubePlayerReady = false; 23414 23415 // if a described version is available && user prefers desription 23416 // init player using the described version 23417 youTubeId = (this.youTubeDescId && this.prefDesc) ? this.youTubeDescId : this.youTubeId; 23418 23419 this.activeYouTubeId = youTubeId; 23420 if (AblePlayer.youTubeIframeAPIReady) { 23421 // Script already loaded and ready. 23422 thisObj.finalizeYoutubeInit().then(function() { 23423 deferred.resolve(); 23424 }); 23425 } else { 23426 // Has another player already started loading the script? If so, abort... 23427 if (!AblePlayer.loadingYouTubeIframeAPI) { 23428 thisObj.getScript('https://www.youtube.com/iframe_api', function () { 23429 }); 23430 } 23431 23432 // Otherwise, keeping waiting for script load event... 23433 $('body').on('youTubeIframeAPIReady', function () { 23434 thisObj.finalizeYoutubeInit().then(function() { 23435 deferred.resolve(); 23436 }); 23437 }); 23438 } 23439 return promise; 23440 }; 23441 23442 AblePlayer.prototype.finalizeYoutubeInit = function () { 23443 23444 // This is called once we're sure the Youtube iFrame API is loaded -- see above 23445 var deferred, promise, thisObj, containerId, ccLoadPolicy, autoplay; 23446 23447 deferred = new this.defer(); 23448 promise = deferred.promise(); 23449 thisObj = this; 23450 containerId = this.mediaId + '_youtube'; 23451 23452 this.$mediaContainer.prepend($('<div>').attr('id', containerId)); 23453 23454 // cc_load_policy: 23455 // 0 - show captions depending on user's preference on YouTube 23456 // 1 - show captions by default, even if the user has turned them off 23457 // IMPORTANT: This *must* be set to 1 or some browsers 23458 // fail to load any text tracks (observed in Chrome, not in Firefox) 23459 ccLoadPolicy = 1; 23460 autoplay = (this.okToPlay) ? 1 : 0; 23461 23462 // Documentation https://developers.google.com/youtube/player_parameters 23463 23464 if (typeof this.captionLang == 'undefined') { 23465 // init using the default player lang 23466 this.captionLang = this.lang; 23467 } 23468 this.youTubePlayer = new YT.Player(containerId, { 23469 videoId: this.activeYouTubeId, 23470 host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com', 23471 playerVars: { 23472 autoplay: autoplay, 23473 cc_lang_pref: this.captionLang, // set the caption language 23474 cc_load_policy: ccLoadPolicy, 23475 controls: 0, // no controls, using our own 23476 disableKb: 1, // disable keyboard shortcuts, using our own 23477 enablejsapi: 1, 23478 hl: this.lang, // set the UI language to match Able Player 23479 iv_load_policy: 3, // do not show video annotations 23480 origin: window.location.origin, 23481 playsinline: this.playsInline, 23482 rel: 0, // when video ends, show only related videos from same channel (1 shows any) 23483 start: this.startTime 23484 }, 23485 events: { 23486 onReady: function () { 23487 thisObj.youTubePlayerReady = true; 23488 if (!thisObj.playerWidth || !thisObj.playerHeight) { 23489 thisObj.getYouTubeDimensions(); 23490 } 23491 if (thisObj.playerWidth && thisObj.playerHeight) { 23492 thisObj.youTubePlayer.setSize(thisObj.playerWidth,thisObj.playerHeight); 23493 } 23494 if (thisObj.swappingSrc) { 23495 // swap is now complete 23496 thisObj.swappingSrc = false; 23497 thisObj.restoreFocus(); 23498 thisObj.cueingPlaylistItem = false; 23499 if (thisObj.playing || thisObj.okToPlay) { 23500 // resume playing 23501 thisObj.playMedia(); 23502 } 23503 } 23504 if (thisObj.userClickedPlaylist) { 23505 thisObj.userClickedPlaylist = false; // reset 23506 } 23507 if (thisObj.recreatingPlayer) { 23508 thisObj.recreatingPlayer = false; // reset 23509 } 23510 deferred.resolve(); 23511 }, 23512 onError: function (e) { 23513 deferred.reject(); 23514 }, 23515 onStateChange: function (e) { 23516 thisObj.getPlayerState().then(function(playerState) { 23517 // values of playerState: 'playing','paused','buffering','ended' 23518 if (playerState === 'playing') { 23519 if (thisObj.hasSignLanguage && thisObj.signVideo) { 23520 thisObj.signVideo.play(true); 23521 } 23522 thisObj.playing = true; 23523 thisObj.startedPlaying = true; 23524 thisObj.paused = false; 23525 } else if (playerState == 'ended') { 23526 thisObj.onMediaComplete(); 23527 } else { 23528 thisObj.playing = false; 23529 thisObj.paused = true; 23530 } 23531 if (thisObj.stoppingYouTube && playerState === 'paused') { 23532 if (thisObj.hasSignLanguage && thisObj.signVideo) { 23533 thisObj.signVideo.pause(true); 23534 } 23535 if (typeof thisObj.$posterImg !== 'undefined') { 23536 thisObj.$posterImg.show(); 23537 } 23538 thisObj.stoppingYouTube = false; 23539 thisObj.seeking = false; 23540 thisObj.playing = false; 23541 thisObj.paused = true; 23542 } 23543 }); 23544 // If caption tracks are hosted locally, but are also available on YouTube, 23545 // we need to turn them off on YouTube or there will be redundant captions 23546 // This is the most reliable event on which to unload the caption module 23547 if (thisObj.player === 'youtube' && !thisObj.usingYouTubeCaptions) { 23548 if (thisObj.youTubePlayer.getOptions('captions')) { 23549 thisObj.youTubePlayer.unloadModule('captions'); 23550 } 23551 } 23552 }, 23553 onApiChange: function() { 23554 // getDuration() can be fetched during API change event. 23555 thisObj.duration = thisObj.youTubePlayer.getDuration(); 23556 }, 23557 onPlaybackQualityChange: function () { 23558 // do something 23559 }, 23560 } 23561 }); 23562 if (!this.hasPlaylist) { 23563 // remove the media element, since YouTube replaces that with its own element in an iframe 23564 // this is handled differently for playlists. See buildplayer.js > cuePlaylistItem() 23565 this.$media.remove(); 23566 } 23567 return promise; 23568 }; 23569 23570 AblePlayer.prototype.getYouTubeDimensions = function () { 23571 23572 // The YouTube iframe API does not have a getSize() of equivalent method 23573 // so, need to get dimensions from YouTube's iframe 23574 var $iframe, width, height; 23575 23576 $iframe = this.$ableWrapper.find('iframe'); 23577 if (typeof $iframe !== 'undefined') { 23578 if ($iframe.prop('width')) { 23579 width = $iframe.prop('width'); 23580 if ($iframe.prop('height')) { 23581 height = $iframe.prop('height'); 23582 this.resizePlayer(width,height); 23583 } 23584 } 23585 } 23586 }; 23587 23588 /** 23589 * Get data from the YouTube iFrame API. Pushes data into `this.tracks` and `this.captions`. 23590 * Initiates play to trigger loading the captions module, then stops and collects data. 23591 * 23592 * @returns {Promise} promise 23593 */ 23594 AblePlayer.prototype.getYouTubeCaptionTracks = function () { 23595 23596 var deferred = new this.defer(); 23597 var promise = deferred.promise(); 23598 var thisObj, ytTracks, i, trackLang, trackLabel, isDefaultTrack, apiTriggered = false; 23599 23600 thisObj = this; 23601 if (!this.youTubePlayer.getOption('captions','tracklist') ) { 23602 // no tracks were found, probably because the captions module hasn't loaded 23603 // play video briefly (required to load the captions module) 23604 // and after the apiChange event is triggered, try again to retrieve tracks 23605 this.youTubePlayer.addEventListener('onApiChange',function() { 23606 apiTriggered = true; 23607 // getDuration() also requires video to play briefly 23608 // so, let's set that while we're here 23609 thisObj.duration = thisObj.youTubePlayer.getDuration(); 23610 23611 if (thisObj.loadingYouTubeCaptions) { 23612 // loadingYouTubeCaptions is a stopgap in case onApiChange is called more than once 23613 ytTracks = thisObj.youTubePlayer.getOption('captions','tracklist'); 23614 if ( ! thisObj.okToPlay ) { 23615 // Don't stopVideo() - that cancels loading, just pause. 23616 // No need to seekTo(0) - the time passed isn't noticeable to the user 23617 thisObj.youTubePlayer.pauseVideo(); 23618 } 23619 if (ytTracks && ytTracks.length) { 23620 // Step through ytTracks and add them to global tracks array 23621 // Note: Unlike YouTube Data API, the IFrame Player API only returns 23622 // tracks that are published, and does NOT include ASR captions 23623 // So, no additional filtering is required 23624 for (i=0; i < ytTracks.length; i++) { 23625 trackLang = ytTracks[i].languageCode; 23626 trackLabel = ytTracks[i].languageName; // displayName and languageName seem to always have the same value 23627 isDefaultTrack = false; 23628 if (typeof thisObj.captionLang !== 'undefined' && (trackLang === thisObj.captionLang) ) { 23629 isDefaultTrack = true; 23630 } else if (typeof thisObj.lang !== 'undefined') { 23631 if (trackLang === thisObj.lang) { 23632 isDefaultTrack = true; 23633 } 23634 } 23635 thisObj.tracks.push({ 23636 'kind': 'captions', 23637 'language': trackLang, 23638 'label': trackLabel, 23639 'def': isDefaultTrack 23640 }); 23641 thisObj.captions.push({ 23642 'language': trackLang, 23643 'label': trackLabel, 23644 'def': isDefaultTrack, 23645 'cues': null 23646 }); 23647 } 23648 thisObj.hasCaptions = true; 23649 // setupPopups again with new captions array, replacing original 23650 thisObj.setupPopups('captions'); 23651 } else { 23652 // there are no YouTube captions 23653 thisObj.usingYouTubeCaptions = false; 23654 thisObj.hasCaptions = false; 23655 } 23656 thisObj.loadingYouTubeCaptions = false; 23657 if (thisObj.okToPlay) { 23658 thisObj.youTubePlayer.playVideo(); 23659 } 23660 } 23661 if (thisObj.captionLangPending) { 23662 // user selected a new caption language prior to playback starting 23663 // set it now 23664 thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLangPending}); 23665 thisObj.captionLangPending = null; 23666 } 23667 if (typeof thisObj.prefCaptionsSize !== 'undefined') { 23668 // set the default caption size 23669 // this doesn't work until the captions module is loaded 23670 thisObj.youTubePlayer.setOption('captions','fontSize',thisObj.translatePrefs('size',thisObj.prefCaptionsSize,'youtube')); 23671 } 23672 deferred.resolve(); 23673 }); 23674 // Trigger the above event listener by briefly playing the video 23675 this.loadingYouTubeCaptions = true; 23676 this.youTubePlayer.playVideo(); 23677 // If onApiChange has not been triggered, the captions module is not loading. 23678 setTimeout(() => { 23679 if ( ! apiTriggered ) { 23680 setTimeout(() => { 23681 // If a second passes without loading captions, assume there are none. 23682 thisObj.youTubePlayer.pauseVideo(); 23683 deferred.resolve(); 23684 }, 500); 23685 } 23686 },500); 23687 } 23688 return promise; 23689 }; 23690 23691 AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) { 23692 23693 // return a URL for retrieving a YouTube poster image 23694 // supported values of width: 120, 320, 480, 640, 1280, 1920. 23695 var url = 'https://img.youtube.com/vi/' + youTubeId; 23696 if (width == '120') { 23697 // default (small) thumbnail, 120 x 90 23698 return url + '/default.jpg'; 23699 } else if (width == '320') { 23700 // medium quality thumbnail, 320 x 180 23701 return url + '/mqdefault.jpg'; 23702 } else if (width == '480') { 23703 // high quality thumbnail, 480 x 360 23704 return url + '/hqdefault.jpg'; 23705 } else if (width == '640') { 23706 // standard definition poster image, 640 x 480 23707 return url + '/sddefault.jpg'; 23708 } else if (width == '1280') { 23709 // standard definition poster image, 640 x 480 23710 return url + '/hq720.jpg'; 23711 } else if ( width == '1920' ) { 23712 // standard definition poster image, 640 x 480 23713 return url + '/maxresdefault.jpg'; 23714 } 23715 return false; 23716 }; 23717 23718 AblePlayer.prototype.getYouTubeId = function (url) { 23719 23720 // return a YouTube ID, extracted from a full YouTube URL 23721 // Supported URL patterns: 23722 // http|s://youtu.be/xxx 23723 // http|s://www.youtube.com/watch?v=xxx 23724 // http|s://www.youtube.com/embed/xxx 23725 23726 // in all supported patterns, the id is the last 11 characters 23727 var idStartPos, id; 23728 23729 if (url.indexOf('youtu') !== -1) { 23730 // this is a full Youtube URL 23731 url = url.trim(); 23732 idStartPos = url.length - 11; 23733 id = url.substring(idStartPos); 23734 return id; 23735 } else { 23736 return url; 23737 } 23738 }; 23739 23740 } 23741 23742 // Order copied over from 2026-02-27 Gruntfile.js, in case it matters 23743 addInitializeFunctions(AblePlayer); 23744 addPreferenceFunctions(AblePlayer); 23745 addWebvttFunctions(AblePlayer); 23746 addBuildplayerFunctions(AblePlayer); 23747 addTrackFunctions(AblePlayer); 23748 addYoutubeFunctions(AblePlayer); 23749 addVolumeFunctions(AblePlayer); 23750 addMiscFunctions(AblePlayer); 23751 addDescriptionFunctions(AblePlayer); 23752 addBrowserFunctions(AblePlayer); 23753 addControlFunctions(AblePlayer); 23754 addCaptionFunctions(AblePlayer); 23755 addChaptersFunctions(AblePlayer); 23756 addMetadataFunctions(AblePlayer); 23757 addTranscriptFunctions(AblePlayer); 23758 addSearchFunctions(AblePlayer); 23759 addEventFunctions(AblePlayer); 23760 addDragdropFunctions(AblePlayer); 23761 addSignFunctions(AblePlayer); 23762 addLangsFunctions(AblePlayer); 23763 addTranslationFunctions(AblePlayer); 23764 addVtsFunctions(AblePlayer); 23765 addVimeoFunctions(AblePlayer); 23766 23767 AblePlayer.ablePlayerSetupWindow(); 23768 23769 return AblePlayer; 23770 23771})); 23772//# sourceMappingURL=ableplayer.dist.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.