PageSourceSearch

https://herrenchiemsee.de/ableplayer/build/ableplayer.dist.js

js herrenchiemsee.de collected 2026-10-02 04:49:31 UTC 915,182 bytes, 23,772 lines download raw bytes

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(/--&gt;/g, "-->");
2214      var timestampTagReplaced = arrowReplaced.replace(/&lt;([\d:.]+)&gt;/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 === '&amp') {
23108  						result.push('&');
23109  					} else if (buffer === '&lt') {
23110  						result.push('<');
23111  					} else if (buffer === '&gt') {
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 === '&nbsp') {
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.