PageSourceSearch

https://www.continualengine.com/wp-content/plugins/ableplayer/node…/build/ableplayer.js?ver=2.4.0-8006

js continualengine.com collected 2026-10-02 04:51:28 UTC 918,632 bytes, 23,853 lines download raw bytes

vendor: 918,632 bytes, lines 1-23853
1/*! ableplayer V5.0.0 - with DOMPurify included. Console logs enabled, for development */
2
3(function (global, factory) {
4  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
5  typeof define === 'function' && define.amd ? define(['jquery'], factory) :
6  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AblePlayer = factory(global.jQuery));
7})(this, (function ($) { 'use strict';
8
9  /*! @license DOMPurify 3.4.0 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.0/LICENSE */
10
11  const {
12    entries,
13    setPrototypeOf,
14    isFrozen,
15    getPrototypeOf,
16    getOwnPropertyDescriptor
17  } = Object;
18  let {
19    freeze,
20    seal,
21    create
22  } = Object; // eslint-disable-line import/no-mutable-exports
23  let {
24    apply,
25    construct
26  } = typeof Reflect !== 'undefined' && Reflect;
27  if (!freeze) {
28    freeze = function freeze(x) {
29      return x;
30    };
31  }
32  if (!seal) {
33    seal = function seal(x) {
34      return x;
35    };
36  }
37  if (!apply) {
38    apply = function apply(func, thisArg) {
39      for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
40        args[_key - 2] = arguments[_key];
41      }
42      return func.apply(thisArg, args);
43    };
44  }
45  if (!construct) {
46    construct = function construct(Func) {
47      for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
48        args[_key2 - 1] = arguments[_key2];
49      }
50      return new Func(...args);
51    };
52  }
53  const arrayForEach = unapply(Array.prototype.forEach);
54  const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
55  const arrayPop = unapply(Array.prototype.pop);
56  const arrayPush = unapply(Array.prototype.push);
57  const arraySplice = unapply(Array.prototype.splice);
58  const stringToLowerCase = unapply(String.prototype.toLowerCase);
59  const stringToString = unapply(String.prototype.toString);
60  const stringMatch = unapply(String.prototype.match);
61  const stringReplace = unapply(String.prototype.replace);
62  const stringIndexOf = unapply(String.prototype.indexOf);
63  const stringTrim = unapply(String.prototype.trim);
64  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
65  const regExpTest = unapply(RegExp.prototype.test);
66  const typeErrorCreate = unconstruct(TypeError);
67  /**
68   * Creates a new function that calls the given function with a specified thisArg and arguments.
69   *
70   * @param func - The function to be wrapped and called.
71   * @returns A new function that calls the given function with a specified thisArg and arguments.
72   */
73  function unapply(func) {
74    return function (thisArg) {
75      if (thisArg instanceof RegExp) {
76        thisArg.lastIndex = 0;
77      }
78      for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
79        args[_key3 - 1] = arguments[_key3];
80      }
81      return apply(func, thisArg, args);
82    };
83  }
84  /**
85   * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
86   *
87   * @param func - The constructor function to be wrapped and called.
88   * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
89   */
90  function unconstruct(Func) {
91    return function () {
92      for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
93        args[_key4] = arguments[_key4];
94      }
95      return construct(Func, args);
96    };
97  }
98  /**
99   * Add properties to a lookup table
100   *
101   * @param set - The set to which elements will be added.
102   * @param array - The array containing elements to be added to the set.
103   * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
104   * @returns The modified set with added elements.
105   */
106  function addToSet(set, array) {
107    let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
108    if (setPrototypeOf) {
109      // Make 'in' and truthy checks like Boolean(set.constructor)
110      // independent of any properties defined on Object.prototype.
111      // Prevent prototype setters from intercepting set as a this value.
112      setPrototypeOf(set, null);
113    }
114    let l = array.length;
115    while (l--) {
116      let element = array[l];
117      if (typeof element === 'string') {
118        const lcElement = transformCaseFunc(element);
119        if (lcElement !== element) {
120          // Config presets (e.g. tags.js, attrs.js) are immutable.
121          if (!isFrozen(array)) {
122            array[l] = lcElement;
123          }
124          element = lcElement;
125        }
126      }
127      set[element] = true;
128    }
129    return set;
130  }
131  /**
132   * Clean up an array to harden against CSPP
133   *
134   * @param array - The array to be cleaned.
135   * @returns The cleaned version of the array
136   */
137  function cleanArray(array) {
138    for (let index = 0; index < array.length; index++) {
139      const isPropertyExist = objectHasOwnProperty(array, index);
140      if (!isPropertyExist) {
141        array[index] = null;
142      }
143    }
144    return array;
145  }
146  /**
147   * Shallow clone an object
148   *
149   * @param object - The object to be cloned.
150   * @returns A new object that copies the original.
151   */
152  function clone(object) {
153    const newObject = create(null);
154    for (const [property, value] of entries(object)) {
155      const isPropertyExist = objectHasOwnProperty(object, property);
156      if (isPropertyExist) {
157        if (Array.isArray(value)) {
158          newObject[property] = cleanArray(value);
159        } else if (value && typeof value === 'object' && value.constructor === Object) {
160          newObject[property] = clone(value);
161        } else {
162          newObject[property] = value;
163        }
164      }
165    }
166    return newObject;
167  }
168  /**
169   * This method automatically checks if the prop is function or getter and behaves accordingly.
170   *
171   * @param object - The object to look up the getter function in its prototype chain.
172   * @param prop - The property name for which to find the getter function.
173   * @returns The getter function found in the prototype chain or a fallback function.
174   */
175  function lookupGetter(object, prop) {
176    while (object !== null) {
177      const desc = getOwnPropertyDescriptor(object, prop);
178      if (desc) {
179        if (desc.get) {
180          return unapply(desc.get);
181        }
182        if (typeof desc.value === 'function') {
183          return unapply(desc.value);
184        }
185      }
186      object = getPrototypeOf(object);
187    }
188    function fallbackValue() {
189      return null;
190    }
191    return fallbackValue;
192  }
193
194  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
195  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
196  const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
197  // List of SVG elements that are disallowed by default.
198  // We still need to know them so that we can do namespace
199  // checks properly in case one wants to add them to
200  // allow-list.
201  const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
202  const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
203  // Similarly to SVG, we want to know all MathML elements,
204  // even those that we disallow by default.
205  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
206  const text = freeze(['#text']);
207
208  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
209  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
210  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
211  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
212
213  // eslint-disable-next-line unicorn/better-regex
214  const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
215  const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
216  const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
217  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
218  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
219  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
220  );
221  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
222  const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
223  );
224  const DOCTYPE_NAME = seal(/^html$/i);
225  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
226
227  var EXPRESSIONS = /*#__PURE__*/Object.freeze({
228    __proto__: null,
229    ARIA_ATTR: ARIA_ATTR,
230    ATTR_WHITESPACE: ATTR_WHITESPACE,
231    CUSTOM_ELEMENT: CUSTOM_ELEMENT,
232    DATA_ATTR: DATA_ATTR,
233    DOCTYPE_NAME: DOCTYPE_NAME,
234    ERB_EXPR: ERB_EXPR,
235    IS_ALLOWED_URI: IS_ALLOWED_URI,
236    IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
237    MUSTACHE_EXPR: MUSTACHE_EXPR,
238    TMPLIT_EXPR: TMPLIT_EXPR
239  });
240
241  /* eslint-disable @typescript-eslint/indent */
242  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
243  const NODE_TYPE = {
244    element: 1,
245    text: 3,
246    // Deprecated
247    progressingInstruction: 7,
248    comment: 8,
249    document: 9};
250  const getGlobal = function getGlobal() {
251    return typeof window === 'undefined' ? null : window;
252  };
253  /**
254   * Creates a no-op policy for internal use only.
255   * Don't export this function outside this module!
256   * @param trustedTypes The policy factory.
257   * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
258   * @return The policy created (or null, if Trusted Types
259   * are not supported or creating the policy failed).
260   */
261  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
262    if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
263      return null;
264    }
265    // Allow the callers to control the unique policy name
266    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
267    // Policy creation with duplicate names throws in Trusted Types.
268    let suffix = null;
269    const ATTR_NAME = 'data-tt-policy-suffix';
270    if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
271      suffix = purifyHostElement.getAttribute(ATTR_NAME);
272    }
273    const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
274    try {
275      return trustedTypes.createPolicy(policyName, {
276        createHTML(html) {
277          return html;
278        },
279        createScriptURL(scriptUrl) {
280          return scriptUrl;
281        }
282      });
283    } catch (_) {
284      // Policy creation failed (most likely another DOMPurify script has
285      // already run). Skip creating the policy, as this will only cause errors
286      // if TT are enforced.
287      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
288      return null;
289    }
290  };
291  const _createHooksMap = function _createHooksMap() {
292    return {
293      afterSanitizeAttributes: [],
294      afterSanitizeElements: [],
295      afterSanitizeShadowDOM: [],
296      beforeSanitizeAttributes: [],
297      beforeSanitizeElements: [],
298      beforeSanitizeShadowDOM: [],
299      uponSanitizeAttribute: [],
300      uponSanitizeElement: [],
301      uponSanitizeShadowNode: []
302    };
303  };
304  function createDOMPurify() {
305    let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
306    const DOMPurify = root => createDOMPurify(root);
307    DOMPurify.version = '3.4.0';
308    DOMPurify.removed = [];
309    if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
310      // Not running in a browser, provide a factory function
311      // so that you can pass your own Window
312      DOMPurify.isSupported = false;
313      return DOMPurify;
314    }
315    let {
316      document
317    } = window;
318    const originalDocument = document;
319    const currentScript = originalDocument.currentScript;
320    const {
321      DocumentFragment,
322      HTMLTemplateElement,
323      Node,
324      Element,
325      NodeFilter,
326      NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
327      HTMLFormElement,
328      DOMParser,
329      trustedTypes
330    } = window;
331    const ElementPrototype = Element.prototype;
332    const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
333    const remove = lookupGetter(ElementPrototype, 'remove');
334    const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
335    const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
336    const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
337    // As per issue #47, the web-components registry is inherited by a
338    // new document created via createHTMLDocument. As per the spec
339    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
340    // a new empty registry is used when creating a template contents owner
341    // document, so we use that as our parent document to ensure nothing
342    // is inherited.
343    if (typeof HTMLTemplateElement === 'function') {
344      const template = document.createElement('template');
345      if (template.content && template.content.ownerDocument) {
346        document = template.content.ownerDocument;
347      }
348    }
349    let trustedTypesPolicy;
350    let emptyHTML = '';
351    const {
352      implementation,
353      createNodeIterator,
354      createDocumentFragment,
355      getElementsByTagName
356    } = document;
357    const {
358      importNode
359    } = originalDocument;
360    let hooks = _createHooksMap();
361    /**
362     * Expose whether this browser supports running the full DOMPurify.
363     */
364    DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
365    const {
366      MUSTACHE_EXPR,
367      ERB_EXPR,
368      TMPLIT_EXPR,
369      DATA_ATTR,
370      ARIA_ATTR,
371      IS_SCRIPT_OR_DATA,
372      ATTR_WHITESPACE,
373      CUSTOM_ELEMENT
374    } = EXPRESSIONS;
375    let {
376      IS_ALLOWED_URI: IS_ALLOWED_URI$1
377    } = EXPRESSIONS;
378    /**
379     * We consider the elements and attributes below to be safe. Ideally
380     * don't add any new ones but feel free to remove unwanted ones.
381     */
382    /* allowed element names */
383    let ALLOWED_TAGS = null;
384    const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
385    /* Allowed attribute names */
386    let ALLOWED_ATTR = null;
387    const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
388    /*
389     * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
390     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
391     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
392     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
393     */
394    let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
395      tagNameCheck: {
396        writable: true,
397        configurable: false,
398        enumerable: true,
399        value: null
400      },
401      attributeNameCheck: {
402        writable: true,
403        configurable: false,
404        enumerable: true,
405        value: null
406      },
407      allowCustomizedBuiltInElements: {
408        writable: true,
409        configurable: false,
410        enumerable: true,
411        value: false
412      }
413    }));
414    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
415    let FORBID_TAGS = null;
416    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
417    let FORBID_ATTR = null;
418    /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */
419    const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, {
420      tagCheck: {
421        writable: true,
422        configurable: false,
423        enumerable: true,
424        value: null
425      },
426      attributeCheck: {
427        writable: true,
428        configurable: false,
429        enumerable: true,
430        value: null
431      }
432    }));
433    /* Decide if ARIA attributes are okay */
434    let ALLOW_ARIA_ATTR = true;
435    /* Decide if custom data attributes are okay */
436    let ALLOW_DATA_ATTR = true;
437    /* Decide if unknown protocols are okay */
438    let ALLOW_UNKNOWN_PROTOCOLS = false;
439    /* Decide if self-closing tags in attributes are allowed.
440     * Usually removed due to a mXSS issue in jQuery 3.0 */
441    let ALLOW_SELF_CLOSE_IN_ATTR = true;
442    /* Output should be safe for common template engines.
443     * This means, DOMPurify removes data attributes, mustaches and ERB
444     */
445    let SAFE_FOR_TEMPLATES = false;
446    /* Output should be safe even for XML used within HTML and alike.
447     * This means, DOMPurify removes comments when containing risky content.
448     */
449    let SAFE_FOR_XML = true;
450    /* Decide if document with <html>... should be returned */
451    let WHOLE_DOCUMENT = false;
452    /* Track whether config is already set on this instance of DOMPurify. */
453    let SET_CONFIG = false;
454    /* Decide if all elements (e.g. style, script) must be children of
455     * document.body. By default, browsers might move them to document.head */
456    let FORCE_BODY = false;
457    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
458     * string (or a TrustedHTML object if Trusted Types are supported).
459     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
460     */
461    let RETURN_DOM = false;
462    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
463     * string  (or a TrustedHTML object if Trusted Types are supported) */
464    let RETURN_DOM_FRAGMENT = false;
465    /* Try to return a Trusted Type object instead of a string, return a string in
466     * case Trusted Types are not supported  */
467    let RETURN_TRUSTED_TYPE = false;
468    /* Output should be free from DOM clobbering attacks?
469     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
470     */
471    let SANITIZE_DOM = true;
472    /* Achieve full DOM Clobbering protection by isolating the namespace of named
473     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
474     *
475     * HTML/DOM spec rules that enable DOM Clobbering:
476     *   - Named Access on Window (§7.3.3)
477     *   - DOM Tree Accessors (§3.1.5)
478     *   - Form Element Parent-Child Relations (§4.10.3)
479     *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
480     *   - HTMLCollection (§4.2.10.2)
481     *
482     * Namespace isolation is implemented by prefixing `id` and `name` attributes
483     * with a constant string, i.e., `user-content-`
484     */
485    let SANITIZE_NAMED_PROPS = false;
486    const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
487    /* Keep element content when removing element? */
488    let KEEP_CONTENT = true;
489    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
490     * of importing it into a new Document and returning a sanitized copy */
491    let IN_PLACE = false;
492    /* Allow usage of profiles like html, svg and mathMl */
493    let USE_PROFILES = {};
494    /* Tags to ignore content of when KEEP_CONTENT is true */
495    let FORBID_CONTENTS = null;
496    const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
497    /* Tags that are safe for data: URIs */
498    let DATA_URI_TAGS = null;
499    const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
500    /* Attributes safe for values like "javascript:" */
501    let URI_SAFE_ATTRIBUTES = null;
502    const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
503    const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
504    const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
505    const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
506    /* Document namespace */
507    let NAMESPACE = HTML_NAMESPACE;
508    let IS_EMPTY_INPUT = false;
509    /* Allowed XHTML+XML namespaces */
510    let ALLOWED_NAMESPACES = null;
511    const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
512    let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
513    let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
514    // Certain elements are allowed in both SVG and HTML
515    // namespace. We need to specify them explicitly
516    // so that they don't get erroneously deleted from
517    // HTML namespace.
518    const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
519    /* Parsing of strict XHTML documents */
520    let PARSER_MEDIA_TYPE = null;
521    const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
522    const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
523    let transformCaseFunc = null;
524    /* Keep a reference to config to pass to hooks */
525    let CONFIG = null;
526    /* Ideally, do not touch anything below this line */
527    /* ______________________________________________ */
528    const formElement = document.createElement('form');
529    const isRegexOrFunction = function isRegexOrFunction(testValue) {
530      return testValue instanceof RegExp || testValue instanceof Function;
531    };
532    /**
533     * _parseConfig
534     *
535     * @param cfg optional config literal
536     */
537    // eslint-disable-next-line complexity
538    const _parseConfig = function _parseConfig() {
539      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
540      if (CONFIG && CONFIG === cfg) {
541        return;
542      }
543      /* Shield configuration object from tampering */
544      if (!cfg || typeof cfg !== 'object') {
545        cfg = {};
546      }
547      /* Shield configuration object from prototype pollution */
548      cfg = clone(cfg);
549      PARSER_MEDIA_TYPE =
550      // eslint-disable-next-line unicorn/prefer-includes
551      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
552      // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
553      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
554      /* Set configuration parameters */
555      ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
556      ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
557      ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
558      URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
559      DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
560      FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
561      FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
562      FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
563      USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
564      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
565      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
566      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
567      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
568      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
569      SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
570      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
571      RETURN_DOM = cfg.RETURN_DOM || false; // Default false
572      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
573      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
574      FORCE_BODY = cfg.FORCE_BODY || false; // Default false
575      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
576      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
577      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
578      IN_PLACE = cfg.IN_PLACE || false; // Default false
579      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
580      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
581      MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
582      HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
583      CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || create(null);
584      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
585        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
586      }
587      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
588        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
589      }
590      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
591        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
592      }
593      if (SAFE_FOR_TEMPLATES) {
594        ALLOW_DATA_ATTR = false;
595      }
596      if (RETURN_DOM_FRAGMENT) {
597        RETURN_DOM = true;
598      }
599      /* Parse profile info */
600      if (USE_PROFILES) {
601        ALLOWED_TAGS = addToSet({}, text);
602        ALLOWED_ATTR = create(null);
603        if (USE_PROFILES.html === true) {
604          addToSet(ALLOWED_TAGS, html$1);
605          addToSet(ALLOWED_ATTR, html);
606        }
607        if (USE_PROFILES.svg === true) {
608          addToSet(ALLOWED_TAGS, svg$1);
609          addToSet(ALLOWED_ATTR, svg);
610          addToSet(ALLOWED_ATTR, xml);
611        }
612        if (USE_PROFILES.svgFilters === true) {
613          addToSet(ALLOWED_TAGS, svgFilters);
614          addToSet(ALLOWED_ATTR, svg);
615          addToSet(ALLOWED_ATTR, xml);
616        }
617        if (USE_PROFILES.mathMl === true) {
618          addToSet(ALLOWED_TAGS, mathMl$1);
619          addToSet(ALLOWED_ATTR, mathMl);
620          addToSet(ALLOWED_ATTR, xml);
621        }
622      }
623      /* Always reset function-based ADD_TAGS / ADD_ATTR checks to prevent
624       * leaking across calls when switching from function to array config */
625      EXTRA_ELEMENT_HANDLING.tagCheck = null;
626      EXTRA_ELEMENT_HANDLING.attributeCheck = null;
627      /* Merge configuration parameters */
628      if (cfg.ADD_TAGS) {
629        if (typeof cfg.ADD_TAGS === 'function') {
630          EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
631        } else {
632          if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
633            ALLOWED_TAGS = clone(ALLOWED_TAGS);
634          }
635          addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
636        }
637      }
638      if (cfg.ADD_ATTR) {
639        if (typeof cfg.ADD_ATTR === 'function') {
640          EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
641        } else {
642          if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
643            ALLOWED_ATTR = clone(ALLOWED_ATTR);
644          }
645          addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
646        }
647      }
648      if (cfg.ADD_URI_SAFE_ATTR) {
649        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
650      }
651      if (cfg.FORBID_CONTENTS) {
652        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
653          FORBID_CONTENTS = clone(FORBID_CONTENTS);
654        }
655        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
656      }
657      if (cfg.ADD_FORBID_CONTENTS) {
658        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
659          FORBID_CONTENTS = clone(FORBID_CONTENTS);
660        }
661        addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
662      }
663      /* Add #text in case KEEP_CONTENT is set to true */
664      if (KEEP_CONTENT) {
665        ALLOWED_TAGS['#text'] = true;
666      }
667      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
668      if (WHOLE_DOCUMENT) {
669        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
670      }
671      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
672      if (ALLOWED_TAGS.table) {
673        addToSet(ALLOWED_TAGS, ['tbody']);
674        delete FORBID_TAGS.tbody;
675      }
676      if (cfg.TRUSTED_TYPES_POLICY) {
677        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
678          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
679        }
680        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
681          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
682        }
683        // Overwrite existing TrustedTypes policy.
684        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
685        // Sign local variables required by `sanitize`.
686        emptyHTML = trustedTypesPolicy.createHTML('');
687      } else {
688        // Uninitialized policy, attempt to initialize the internal dompurify policy.
689        if (trustedTypesPolicy === undefined) {
690          trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
691        }
692        // If creating the internal policy succeeded sign internal variables.
693        if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
694          emptyHTML = trustedTypesPolicy.createHTML('');
695        }
696      }
697      // Prevent further manipulation of configuration.
698      // Not available in IE8, Safari 5, etc.
699      if (freeze) {
700        freeze(cfg);
701      }
702      CONFIG = cfg;
703    };
704    /* Keep track of all possible SVG and MathML tags
705     * so that we can perform the namespace checks
706     * correctly. */
707    const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
708    const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
709    /**
710     * @param element a DOM element whose namespace is being checked
711     * @returns Return false if the element has a
712     *  namespace that a spec-compliant parser would never
713     *  return. Return true otherwise.
714     */
715    const _checkValidNamespace = function _checkValidNamespace(element) {
716      let parent = getParentNode(element);
717      // In JSDOM, if we're inside shadow DOM, then parentNode
718      // can be null. We just simulate parent in this case.
719      if (!parent || !parent.tagName) {
720        parent = {
721          namespaceURI: NAMESPACE,
722          tagName: 'template'
723        };
724      }
725      const tagName = stringToLowerCase(element.tagName);
726      const parentTagName = stringToLowerCase(parent.tagName);
727      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
728        return false;
729      }
730      if (element.namespaceURI === SVG_NAMESPACE) {
731        // The only way to switch from HTML namespace to SVG
732        // is via <svg>. If it happens via any other tag, then
733        // it should be killed.
734        if (parent.namespaceURI === HTML_NAMESPACE) {
735          return tagName === 'svg';
736        }
737        // The only way to switch from MathML to SVG is via`
738        // svg if parent is either <annotation-xml> or MathML
739        // text integration points.
740        if (parent.namespaceURI === MATHML_NAMESPACE) {
741          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
742        }
743        // We only allow elements that are defined in SVG
744        // spec. All others are disallowed in SVG namespace.
745        return Boolean(ALL_SVG_TAGS[tagName]);
746      }
747      if (element.namespaceURI === MATHML_NAMESPACE) {
748        // The only way to switch from HTML namespace to MathML
749        // is via <math>. If it happens via any other tag, then
750        // it should be killed.
751        if (parent.namespaceURI === HTML_NAMESPACE) {
752          return tagName === 'math';
753        }
754        // The only way to switch from SVG to MathML is via
755        // <math> and HTML integration points
756        if (parent.namespaceURI === SVG_NAMESPACE) {
757          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
758        }
759        // We only allow elements that are defined in MathML
760        // spec. All others are disallowed in MathML namespace.
761        return Boolean(ALL_MATHML_TAGS[tagName]);
762      }
763      if (element.namespaceURI === HTML_NAMESPACE) {
764        // The only way to switch from SVG to HTML is via
765        // HTML integration points, and from MathML to HTML
766        // is via MathML text integration points
767        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
768          return false;
769        }
770        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
771          return false;
772        }
773        // We disallow tags that are specific for MathML
774        // or SVG and should never appear in HTML namespace
775        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
776      }
777      // For XHTML and XML documents that support custom namespaces
778      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
779        return true;
780      }
781      // The code should never reach this place (this means
782      // that the element somehow got namespace that is not
783      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
784      // Return false just in case.
785      return false;
786    };
787    /**
788     * _forceRemove
789     *
790     * @param node a DOM node
791     */
792    const _forceRemove = function _forceRemove(node) {
793      arrayPush(DOMPurify.removed, {
794        element: node
795      });
796      try {
797        // eslint-disable-next-line unicorn/prefer-dom-node-remove
798        getParentNode(node).removeChild(node);
799      } catch (_) {
800        remove(node);
801      }
802    };
803    /**
804     * _removeAttribute
805     *
806     * @param name an Attribute name
807     * @param element a DOM node
808     */
809    const _removeAttribute = function _removeAttribute(name, element) {
810      try {
811        arrayPush(DOMPurify.removed, {
812          attribute: element.getAttributeNode(name),
813          from: element
814        });
815      } catch (_) {
816        arrayPush(DOMPurify.removed, {
817          attribute: null,
818          from: element
819        });
820      }
821      element.removeAttribute(name);
822      // We void attribute values for unremovable "is" attributes
823      if (name === 'is') {
824        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
825          try {
826            _forceRemove(element);
827          } catch (_) {}
828        } else {
829          try {
830            element.setAttribute(name, '');
831          } catch (_) {}
832        }
833      }
834    };
835    /**
836     * _initDocument
837     *
838     * @param dirty - a string of dirty markup
839     * @return a DOM, filled with the dirty markup
840     */
841    const _initDocument = function _initDocument(dirty) {
842      /* Create a HTML document */
843      let doc = null;
844      let leadingWhitespace = null;
845      if (FORCE_BODY) {
846        dirty = '<remove></remove>' + dirty;
847      } else {
848        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
849        const matches = stringMatch(dirty, /^[\r\n\t ]+/);
850        leadingWhitespace = matches && matches[0];
851      }
852      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
853        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
854        dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
855      }
856      const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
857      /*
858       * Use the DOMParser API by default, fallback later if needs be
859       * DOMParser not work for svg when has multiple root element.
860       */
861      if (NAMESPACE === HTML_NAMESPACE) {
862        try {
863          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
864        } catch (_) {}
865      }
866      /* Use createHTMLDocument in case DOMParser is not available */
867      if (!doc || !doc.documentElement) {
868        doc = implementation.createDocument(NAMESPACE, 'template', null);
869        try {
870          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
871        } catch (_) {
872          // Syntax error if dirtyPayload is invalid xml
873        }
874      }
875      const body = doc.body || doc.documentElement;
876      if (dirty && leadingWhitespace) {
877        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
878      }
879      /* Work on whole document or just its body */
880      if (NAMESPACE === HTML_NAMESPACE) {
881        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
882      }
883      return WHOLE_DOCUMENT ? doc.documentElement : body;
884    };
885    /**
886     * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
887     *
888     * @param root The root element or node to start traversing on.
889     * @return The created NodeIterator
890     */
891    const _createNodeIterator = function _createNodeIterator(root) {
892      return createNodeIterator.call(root.ownerDocument || root, root,
893      // eslint-disable-next-line no-bitwise
894      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
895    };
896    /**
897     * _isClobbered
898     *
899     * @param element element to check for clobbering attacks
900     * @return true if clobbered, false if safe
901     */
902    const _isClobbered = function _isClobbered(element) {
903      return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
904    };
905    /**
906     * Checks whether the given object is a DOM node.
907     *
908     * @param value object to check whether it's a DOM node
909     * @return true is object is a DOM node
910     */
911    const _isNode = function _isNode(value) {
912      return typeof Node === 'function' && value instanceof Node;
913    };
914    function _executeHooks(hooks, currentNode, data) {
915      arrayForEach(hooks, hook => {
916        hook.call(DOMPurify, currentNode, data, CONFIG);
917      });
918    }
919    /**
920     * _sanitizeElements
921     *
922     * @protect nodeName
923     * @protect textContent
924     * @protect removeChild
925     * @param currentNode to check for permission to exist
926     * @return true if node was killed, false if left alive
927     */
928    const _sanitizeElements = function _sanitizeElements(currentNode) {
929      let content = null;
930      /* Execute a hook if present */
931      _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
932      /* Check if element is clobbered or can clobber */
933      if (_isClobbered(currentNode)) {
934        _forceRemove(currentNode);
935        return true;
936      }
937      /* Now let's check the element's type and name */
938      const tagName = transformCaseFunc(currentNode.nodeName);
939      /* Execute a hook if present */
940      _executeHooks(hooks.uponSanitizeElement, currentNode, {
941        tagName,
942        allowedTags: ALLOWED_TAGS
943      });
944      /* Detect mXSS attempts abusing namespace confusion */
945      if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
946        _forceRemove(currentNode);
947        return true;
948      }
949      /* Remove risky CSS construction leading to mXSS */
950      if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
951        _forceRemove(currentNode);
952        return true;
953      }
954      /* Remove any occurrence of processing instructions */
955      if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
956        _forceRemove(currentNode);
957        return true;
958      }
959      /* Remove any kind of possibly harmful comments */
960      if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
961        _forceRemove(currentNode);
962        return true;
963      }
964      /* Remove element if anything forbids its presence */
965      if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
966        /* Check if we have a custom element to handle */
967        if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
968          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
969            return false;
970          }
971          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
972            return false;
973          }
974        }
975        /* Keep content except for bad-listed elements */
976        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
977          const parentNode = getParentNode(currentNode) || currentNode.parentNode;
978          const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
979          if (childNodes && parentNode) {
980            const childCount = childNodes.length;
981            for (let i = childCount - 1; i >= 0; --i) {
982              const childClone = cloneNode(childNodes[i], true);
983              childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
984              parentNode.insertBefore(childClone, getNextSibling(currentNode));
985            }
986          }
987        }
988        _forceRemove(currentNode);
989        return true;
990      }
991      /* Check whether element has a valid namespace */
992      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
993        _forceRemove(currentNode);
994        return true;
995      }
996      /* Make sure that older browsers don't get fallback-tag mXSS */
997      if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
998        _forceRemove(currentNode);
999        return true;
1000      }
1001      /* Sanitize element content to be template-safe */
1002      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
1003        /* Get the element's text content */
1004        content = currentNode.textContent;
1005        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1006          content = stringReplace(content, expr, ' ');
1007        });
1008        if (currentNode.textContent !== content) {
1009          arrayPush(DOMPurify.removed, {
1010            element: currentNode.cloneNode()
1011          });
1012          currentNode.textContent = content;
1013        }
1014      }
1015      /* Execute a hook if present */
1016      _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1017      return false;
1018    };
1019    /**
1020     * _isValidAttribute
1021     *
1022     * @param lcTag Lowercase tag name of containing element.
1023     * @param lcName Lowercase attribute name.
1024     * @param value Attribute value.
1025     * @return Returns true if `value` is valid, otherwise false.
1026     */
1027    // eslint-disable-next-line complexity
1028    const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1029      /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
1030      if (FORBID_ATTR[lcName]) {
1031        return false;
1032      }
1033      /* Make sure attribute cannot clobber */
1034      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1035        return false;
1036      }
1037      /* Allow valid data-* attributes: At least one character after "-"
1038          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1039          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1040          We don't need to check the value; it's always URI safe. */
1041      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1042        if (
1043        // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1044        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1045        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1046        _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) ||
1047        // Alternative, second condition checks if it's an `is`-attribute, AND
1048        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1049        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
1050          return false;
1051        }
1052        /* Check value is safe. First, is attr inert? If so, is safe */
1053      } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
1054        return false;
1055      } else ;
1056      return true;
1057    };
1058    /**
1059     * _isBasicCustomElement
1060     * checks if at least one dash is included in tagName, and it's not the first char
1061     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1062     *
1063     * @param tagName name of the tag of the node to sanitize
1064     * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
1065     */
1066    const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
1067      return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
1068    };
1069    /**
1070     * _sanitizeAttributes
1071     *
1072     * @protect attributes
1073     * @protect nodeName
1074     * @protect removeAttribute
1075     * @protect setAttribute
1076     *
1077     * @param currentNode to sanitize
1078     */
1079    const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1080      /* Execute a hook if present */
1081      _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
1082      const {
1083        attributes
1084      } = currentNode;
1085      /* Check if we have attributes; if not we might have a text node */
1086      if (!attributes || _isClobbered(currentNode)) {
1087        return;
1088      }
1089      const hookEvent = {
1090        attrName: '',
1091        attrValue: '',
1092        keepAttr: true,
1093        allowedAttributes: ALLOWED_ATTR,
1094        forceKeepAttr: undefined
1095      };
1096      let l = attributes.length;
1097      /* Go backwards over all attributes; safely remove bad ones */
1098      while (l--) {
1099        const attr = attributes[l];
1100        const {
1101          name,
1102          namespaceURI,
1103          value: attrValue
1104        } = attr;
1105        const lcName = transformCaseFunc(name);
1106        const initValue = attrValue;
1107        let value = name === 'value' ? initValue : stringTrim(initValue);
1108        /* Execute a hook if present */
1109        hookEvent.attrName = lcName;
1110        hookEvent.attrValue = value;
1111        hookEvent.keepAttr = true;
1112        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1113        _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
1114        value = hookEvent.attrValue;
1115        /* Full DOM Clobbering protection via namespace isolation,
1116         * Prefix id and name attributes with `user-content-`
1117         */
1118        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1119          // Remove the attribute with this value
1120          _removeAttribute(name, currentNode);
1121          // Prefix the value and later re-create the attribute with the sanitized value
1122          value = SANITIZE_NAMED_PROPS_PREFIX + value;
1123        }
1124        /* Work around a security issue with comments inside attributes */
1125        if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
1126          _removeAttribute(name, currentNode);
1127          continue;
1128        }
1129        /* Make sure we cannot easily use animated hrefs, even if animations are allowed */
1130        if (lcName === 'attributename' && stringMatch(value, 'href')) {
1131          _removeAttribute(name, currentNode);
1132          continue;
1133        }
1134        /* Did the hooks approve of the attribute? */
1135        if (hookEvent.forceKeepAttr) {
1136          continue;
1137        }
1138        /* Did the hooks approve of the attribute? */
1139        if (!hookEvent.keepAttr) {
1140          _removeAttribute(name, currentNode);
1141          continue;
1142        }
1143        /* Work around a security issue in jQuery 3.0 */
1144        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1145          _removeAttribute(name, currentNode);
1146          continue;
1147        }
1148        /* Sanitize attribute content to be template-safe */
1149        if (SAFE_FOR_TEMPLATES) {
1150          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1151            value = stringReplace(value, expr, ' ');
1152          });
1153        }
1154        /* Is `value` valid for this attribute? */
1155        const lcTag = transformCaseFunc(currentNode.nodeName);
1156        if (!_isValidAttribute(lcTag, lcName, value)) {
1157          _removeAttribute(name, currentNode);
1158          continue;
1159        }
1160        /* Handle attributes that require Trusted Types */
1161        if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1162          if (namespaceURI) ; else {
1163            switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1164              case 'TrustedHTML':
1165                {
1166                  value = trustedTypesPolicy.createHTML(value);
1167                  break;
1168                }
1169              case 'TrustedScriptURL':
1170                {
1171                  value = trustedTypesPolicy.createScriptURL(value);
1172                  break;
1173                }
1174            }
1175          }
1176        }
1177        /* Handle invalid data-* attribute set by try-catching it */
1178        if (value !== initValue) {
1179          try {
1180            if (namespaceURI) {
1181              currentNode.setAttributeNS(namespaceURI, name, value);
1182            } else {
1183              /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1184              currentNode.setAttribute(name, value);
1185            }
1186            if (_isClobbered(currentNode)) {
1187              _forceRemove(currentNode);
1188            } else {
1189              arrayPop(DOMPurify.removed);
1190            }
1191          } catch (_) {
1192            _removeAttribute(name, currentNode);
1193          }
1194        }
1195      }
1196      /* Execute a hook if present */
1197      _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
1198    };
1199    /**
1200     * _sanitizeShadowDOM
1201     *
1202     * @param fragment to iterate over recursively
1203     */
1204    const _sanitizeShadowDOM2 = function _sanitizeShadowDOM(fragment) {
1205      let shadowNode = null;
1206      const shadowIterator = _createNodeIterator(fragment);
1207      /* Execute a hook if present */
1208      _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
1209      while (shadowNode = shadowIterator.nextNode()) {
1210        /* Execute a hook if present */
1211        _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
1212        /* Sanitize tags and elements */
1213        _sanitizeElements(shadowNode);
1214        /* Check attributes next */
1215        _sanitizeAttributes(shadowNode);
1216        /* Deep shadow DOM detected */
1217        if (shadowNode.content instanceof DocumentFragment) {
1218          _sanitizeShadowDOM2(shadowNode.content);
1219        }
1220      }
1221      /* Execute a hook if present */
1222      _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
1223    };
1224    // eslint-disable-next-line complexity
1225    DOMPurify.sanitize = function (dirty) {
1226      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1227      let body = null;
1228      let importedNode = null;
1229      let currentNode = null;
1230      let returnNode = null;
1231      /* Make sure we have a string to sanitize.
1232        DO NOT return early, as this will return the wrong type if
1233        the user has requested a DOM object rather than a string */
1234      IS_EMPTY_INPUT = !dirty;
1235      if (IS_EMPTY_INPUT) {
1236        dirty = '<!-->';
1237      }
1238      /* Stringify, in case dirty is an object */
1239      if (typeof dirty !== 'string' && !_isNode(dirty)) {
1240        if (typeof dirty.toString === 'function') {
1241          dirty = dirty.toString();
1242          if (typeof dirty !== 'string') {
1243            throw typeErrorCreate('dirty is not a string, aborting');
1244          }
1245        } else {
1246          throw typeErrorCreate('toString is not a function');
1247        }
1248      }
1249      /* Return dirty HTML if DOMPurify cannot run */
1250      if (!DOMPurify.isSupported) {
1251        return dirty;
1252      }
1253      /* Assign config vars */
1254      if (!SET_CONFIG) {
1255        _parseConfig(cfg);
1256      }
1257      /* Clean up removed elements */
1258      DOMPurify.removed = [];
1259      /* Check if dirty is correctly typed for IN_PLACE */
1260      if (typeof dirty === 'string') {
1261        IN_PLACE = false;
1262      }
1263      if (IN_PLACE) {
1264        /* Do some early pre-sanitization to avoid unsafe root nodes */
1265        if (dirty.nodeName) {
1266          const tagName = transformCaseFunc(dirty.nodeName);
1267          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1268            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1269          }
1270        }
1271      } else if (dirty instanceof Node) {
1272        /* If dirty is a DOM element, append to an empty document to avoid
1273           elements being stripped by the parser */
1274        body = _initDocument('<!---->');
1275        importedNode = body.ownerDocument.importNode(dirty, true);
1276        if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
1277          /* Node is already a body, use as is */
1278          body = importedNode;
1279        } else if (importedNode.nodeName === 'HTML') {
1280          body = importedNode;
1281        } else {
1282          // eslint-disable-next-line unicorn/prefer-dom-node-append
1283          body.appendChild(importedNode);
1284        }
1285      } else {
1286        /* Exit directly if we have nothing to do */
1287        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1288        // eslint-disable-next-line unicorn/prefer-includes
1289        dirty.indexOf('<') === -1) {
1290          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1291        }
1292        /* Initialize the document to work on */
1293        body = _initDocument(dirty);
1294        /* Check we have a DOM node from the data */
1295        if (!body) {
1296          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1297        }
1298      }
1299      /* Remove first element node (ours) if FORCE_BODY is set */
1300      if (body && FORCE_BODY) {
1301        _forceRemove(body.firstChild);
1302      }
1303      /* Get node iterator */
1304      const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
1305      /* Now start iterating over the created document */
1306      while (currentNode = nodeIterator.nextNode()) {
1307        /* Sanitize tags and elements */
1308        _sanitizeElements(currentNode);
1309        /* Check attributes next */
1310        _sanitizeAttributes(currentNode);
1311        /* Shadow DOM detected, sanitize it */
1312        if (currentNode.content instanceof DocumentFragment) {
1313          _sanitizeShadowDOM2(currentNode.content);
1314        }
1315      }
1316      /* If we sanitized `dirty` in-place, return it. */
1317      if (IN_PLACE) {
1318        return dirty;
1319      }
1320      /* Return sanitized string or DOM */
1321      if (RETURN_DOM) {
1322        if (SAFE_FOR_TEMPLATES) {
1323          body.normalize();
1324          let html = body.innerHTML;
1325          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1326            html = stringReplace(html, expr, ' ');
1327          });
1328          body.innerHTML = html;
1329        }
1330        if (RETURN_DOM_FRAGMENT) {
1331          returnNode = createDocumentFragment.call(body.ownerDocument);
1332          while (body.firstChild) {
1333            // eslint-disable-next-line unicorn/prefer-dom-node-append
1334            returnNode.appendChild(body.firstChild);
1335          }
1336        } else {
1337          returnNode = body;
1338        }
1339        if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
1340          /*
1341            AdoptNode() is not used because internal state is not reset
1342            (e.g. the past names map of a HTMLFormElement), this is safe
1343            in theory but we would rather not risk another attack vector.
1344            The state that is cloned by importNode() is explicitly defined
1345            by the specs.
1346          */
1347          returnNode = importNode.call(originalDocument, returnNode, true);
1348        }
1349        return returnNode;
1350      }
1351      let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1352      /* Serialize doctype if allowed */
1353      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1354        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1355      }
1356      /* Sanitize final string template-safe */
1357      if (SAFE_FOR_TEMPLATES) {
1358        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
1359          serializedHTML = stringReplace(serializedHTML, expr, ' ');
1360        });
1361      }
1362      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1363    };
1364    DOMPurify.setConfig = function () {
1365      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1366      _parseConfig(cfg);
1367      SET_CONFIG = true;
1368    };
1369    DOMPurify.clearConfig = function () {
1370      CONFIG = null;
1371      SET_CONFIG = false;
1372    };
1373    DOMPurify.isValidAttribute = function (tag, attr, value) {
1374      /* Initialize shared config vars if necessary. */
1375      if (!CONFIG) {
1376        _parseConfig({});
1377      }
1378      const lcTag = transformCaseFunc(tag);
1379      const lcName = transformCaseFunc(attr);
1380      return _isValidAttribute(lcTag, lcName, value);
1381    };
1382    DOMPurify.addHook = function (entryPoint, hookFunction) {
1383      if (typeof hookFunction !== 'function') {
1384        return;
1385      }
1386      arrayPush(hooks[entryPoint], hookFunction);
1387    };
1388    DOMPurify.removeHook = function (entryPoint, hookFunction) {
1389      if (hookFunction !== undefined) {
1390        const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
1391        return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
1392      }
1393      return arrayPop(hooks[entryPoint]);
1394    };
1395    DOMPurify.removeHooks = function (entryPoint) {
1396      hooks[entryPoint] = [];
1397    };
1398    DOMPurify.removeAllHooks = function () {
1399      hooks = _createHooksMap();
1400    };
1401    return DOMPurify;
1402  }
1403  var purify = createDOMPurify();
1404
1405  /*jslint node: true, browser: true, white: true, indent: 2, unparam: true, plusplus: true */
1406
1407
1408  // maintain an array of Able Player instances for use globally (e.g., for keeping prefs in sync)
1409  // 5.0.0: this is now a Set to make it easier to create and destroy players
1410  const ablePlayerInstances = new Set();
1411
1412  /**
1413   * Performs one-time setup on `window`.
1414   *
1415   * Does nothing if `window` is not available, for example in SSR.
1416   */
1417  function ablePlayerSetupWindow() {
1418  	if (typeof window === 'undefined') {
1419  		console.log("`window` is undefined. Skipping one-time Able Player `window` setup.");
1420  		return;
1421  	}
1422  	$(function () {
1423  		if (typeof purify === 'undefined') {
1424  			console.warn('Required dependency DOMPurify not available. Please use the full Able Player bundle which has DOMPurify built in. Or, keep using this bundle, and include DOMPurify separately.');
1425  		}
1426
1427  		$('video, audio').each(function (index, element) {
1428  			if ($(element).data('able-player') !== undefined) {
1429  				new AblePlayer($(this),$(element));
1430  			}
1431  		});
1432  	});
1433
1434  	// YouTube player support; pass ready event to jQuery so we can catch in player.
1435  	window.onYouTubeIframeAPIReady = function() {
1436  		AblePlayer.youTubeIframeAPIReady = true;
1437  		$('body').trigger('youTubeIframeAPIReady', []);
1438  	};
1439
1440  	// If there is only one player on the page, dispatch global keydown events to it
1441  	// Otherwise, keydowwn events are handled locally (see event.js > handleEventListeners())
1442  	$(window).on('keydown',function(e) {
1443  		if (AblePlayer.hasSingleInstance()) {
1444  			const singleInstance = AblePlayer.getSingleInstance();
1445  			singleInstance.onPlayerKeyPress(e);
1446  		}
1447  	});
1448  }
1449
1450  // Outdented for a simpler diff during module conversion
1451  	/**
1452  	 * Construct the AblePlayer object.
1453  	 *
1454  	 * Able Player needs `window` to instantiate, so, skip the constructor if
1455  	 * you are running outside the browser (for example, SSR).
1456  	 *
1457  	 * @param object media jQuery selector or element identifying the media.
1458  	 */
1459  	function AblePlayer(media) {
1460
1461  		if (typeof window === 'undefined') {
1462  			console.warn("`window` is undefined. Able Player needs `window` to instantiate. Skip constructing Able Player if you are running outside a browser (for example, SSR).");
1463  			return;
1464  		}
1465
1466  		var thisObj = this;
1467
1468  		this.media = media;
1469
1470  		if ($(media).length === 0) {
1471  			this.provideFallback();
1472  			return;
1473  		}
1474
1475  		// Default variables assignment
1476  		// The following variables CAN be overridden with HTML attributes
1477
1478  		// autoplay (Boolean; if present always resolves to true, regardless of value)
1479  		if ($(media).attr('autoplay') !== undefined) {
1480  			this.autoplay = true; // this value remains constant
1481  			this.okToPlay = true; // this value can change dynamically
1482  		} else {
1483  			this.autoplay = false;
1484  			this.okToPlay = false;
1485  		}
1486
1487  		// loop (Boolean; if present always resolves to true, regardless of value)
1488  		this.loop = ($(media).attr('loop') !== undefined) ? true : false;
1489
1490  		// playsinline (Boolean; if present always resolves to true, regardless of value)
1491  		this.playsInline = ($(media).attr('playsinline') !== undefined) ? '1' : '0';
1492
1493  		// poster (Boolean, indicating whether media element has a poster attribute)
1494  		this.hasPoster = ( $(media).attr('poster') || $(media).data('poster') ) ? true : false;
1495
1496  		this.audioPoster = $(media).data('poster');
1497  		this.audioPosterAlt = $(media).data('poster-alt' );
1498
1499  		// get height and width attributes, if present
1500  		// and add them to variables
1501  		// Not currently used, but might be useful for resizing player
1502  		this.width = $(media).attr('width') ?? 0;
1503  		this.height = $(media).attr('height') ?? 0;
1504
1505  		// start-time
1506  		var startTime = $(media).data('start-time');
1507  		var isNumeric = ( typeof startTime === 'number' || ( typeof startTime === 'string' && startTime.trim() !== '' && ! isNaN(startTime) && isFinite( Number(startTime) ) ) ) ? true : false;
1508  		this.startTime =  ( startTime !== undefined && isNumeric ) ? startTime : 0;
1509
1510  		// debug
1511  		this.debug = ($(media).data('debug') !== undefined && $(media).data('debug') !== false) ? true : false;
1512
1513  		// Volume
1514  		// Range is 0 to 10. Best not to crank it to avoid overpowering screen readers
1515  		this.defaultVolume = 7;
1516  		if ($(media).data('volume') !== undefined && $(media).data('volume') !== "") {
1517  			var volume = $(media).data('volume');
1518  			if (volume >= 0 && volume <= 10) {
1519  				this.defaultVolume = volume;
1520  			}
1521  		}
1522  		this.volume = this.defaultVolume;
1523
1524  		// Optional Buttons
1525  		// Buttons are added to the player controller if relevant media is present
1526  		// However, in some applications it might be undesirable to show buttons
1527  		// (e.g., if chapters or transcripts are provided in an external container)
1528
1529  		if ($(media).data('use-chapters-button') !== undefined && $(media).data('use-chapters-button') === false) {
1530  			this.useChaptersButton = false;
1531  		} else {
1532  			this.useChaptersButton = true;
1533  		}
1534
1535  		// Control whether text descriptions are read aloud
1536  		// set to "false" if the sole purpose of the WebVTT descriptions file
1537  		// is to integrate text description into the transcript
1538  		// set to "true" to write description text to a div
1539  		// This variable does *not* control the method by which description is read.
1540  		// For that, see below (this.descMethod)
1541  		if ($(media).data('descriptions-audible') !== undefined && $(media).data('descriptions-audible') === false) {
1542  			this.readDescriptionsAloud = false;
1543  		} else if ($(media).data('description-audible') !== undefined && $(media).data('description-audible') === false) {
1544  			// support both singular and plural spelling of attribute
1545  			this.readDescriptionsAloud = false;
1546  		} else {
1547  			this.readDescriptionsAloud = true;
1548  		}
1549
1550  		// setting initial this.descVoices to an empty array
1551  		// to be populated later by getBrowserVoices
1552  		this.descVoices = [];
1553
1554  		// Method by which text descriptions are read
1555  		// valid values of data-desc-reader are:
1556  		// 'brower' (default) - text-based audio description is handled by the browser, if supported
1557  		// 'screenreader' - text-based audio description is always handled by screen readers
1558  		// The latter may be preferable by owners of websites in languages that are not well supported
1559  		// by the Web Speech API
1560  		this.descReader = ($(media).data('desc-reader') == 'screenreader') ? 'screenreader' : 'browser';
1561
1562  		// Default state of captions and descriptions
1563  		// This setting is overridden by user preferences, if they exist
1564  		// values for data-state-captions and data-state-descriptions are 'on' or 'off'
1565  		this.defaultStateCaptions = ($(media).data('state-captions') == 'off') ? 0 : 1;
1566  		this.defaultStateDescriptions = ($(media).data('state-descriptions') == 'on') ? 1 : 0;
1567
1568  		// Default setting for prefDescPause
1569  		// Extended description (i.e., pausing during description) is on by default
1570  		// but this settings give website owners control over that
1571  		// since they know the nature of their videos, and whether pausing is necessary
1572  		// This setting is overridden by user preferences, if they exist
1573  		this.defaultDescPause = ($(media).data('desc-pause-default') == 'off') ? 0 : 1;
1574
1575  		// Headings
1576  		// By default, an off-screen heading is automatically added to the top of the media player
1577  		// It is intelligently assigned a heading level based on context, via misc.js > getNextHeadingLevel()
1578  		// Authors can override this behavior by manually assigning a heading level using data-heading-level
1579  		// Accepted values are 1-6, or 0 which indicates "no heading"
1580  		// (i.e., author has already hard-coded a heading before the media player; Able Player doesn't need to do this)
1581  		if ($(media).data('heading-level') !== undefined && $(media).data('heading-level') !== "") {
1582  			var headingLevel = $(media).data('heading-level');
1583  			if (/^[0-6]*$/.test(headingLevel)) { // must be a valid HTML heading level 1-6; or 0
1584  				this.playerHeadingLevel = headingLevel;
1585  			}
1586  		}
1587
1588  		// Transcripts
1589  		// There are three types of interactive transcripts.
1590  		// In descending of order of precedence (in case there are conflicting tags), they are:
1591  		// 1. "manual" - A manually coded external transcript (requires data-transcript-src)
1592  		// 2. "external" - Automatically generated, written to an external div (requires data-transcript-div & a valid target element)
1593  		// 3. "popup" - Automatically generated, written to a draggable, resizable popup window that can be toggled on/off with a button
1594  		// If data-include-transcript="false", there is no "popup" transcript
1595  		var transcriptDivLocation = $(media).data('transcript-div');
1596  		if ( transcriptDivLocation !== undefined && transcriptDivLocation !== "" && null !== document.getElementById( transcriptDivLocation ) ) {
1597  			this.transcriptDivLocation = transcriptDivLocation;
1598  		} else {
1599  			this.transcriptDivLocation = null;
1600  		}
1601  		var includeTranscript = $(media).data('include-transcript');
1602  		this.hideTranscriptButton = ( includeTranscript !== undefined && includeTranscript === false) ? true : false;
1603
1604  		this.transcriptType = null;
1605  		if ($(media).data('transcript-src') !== undefined) {
1606  			this.transcriptSrc = $(media).data('transcript-src');
1607  			if (this.transcriptSrcHasRequiredParts()) {
1608  				this.transcriptType = 'manual';
1609  			} else {
1610  				console.log('ERROR: Able Player transcript is missing required parts');
1611  			}
1612  		} else if ($(media).find('track[kind="captions"],track[kind="subtitles"],track:not([kind])').length > 0) {
1613  			// required tracks are present. COULD automatically generate a transcript
1614  			this.transcriptType = (this.transcriptDivLocation) ? 'external' : 'popup';
1615  		}
1616
1617  		// In "Lyrics Mode", line breaks in WebVTT caption files are supported in the transcript
1618  		// If false (default), line breaks are are removed from transcripts for a more seamless reading experience
1619  		// If true, line breaks are preserved, so content can be presented karaoke-style, or as lines in a poem
1620  		this.lyricsMode = ($(media).data('lyrics-mode') !== undefined && $(media).data('lyrics-mode') !== false) ? true : false;
1621
1622  		// Set Transcript Title if defined explicitly. See transcript.js.
1623  		if ($(media).data('transcript-title') !== undefined && $(media).data('transcript-title') !== "") {
1624  			this.transcriptTitle = $(media).data('transcript-title');
1625  		}
1626
1627  		// Sign Language
1628  		// sign language can be a modal (default) or assigned to a div on the page.
1629  		var signDivLocation = $(media).data('sign-div');
1630  		if ( signDivLocation !== undefined && signDivLocation !== "" && null !== document.getElementById( signDivLocation ) ) {
1631  			this.$signDivLocation = $( '#' + signDivLocation );
1632  		} else {
1633  			this.$signDivLocation = null;
1634  		}
1635
1636  		// Captions
1637  		// data-captions-position can be used to set the default captions position
1638  		// this is only the default, and can be overridden by user preferences
1639  		// valid values of data-captions-position are 'below' and 'overlay'
1640  		this.defaultCaptionsPosition = ($(media).data('captions-position') === 'overlay') ? 'overlay' : 'below';
1641
1642  		// Chapters
1643  		var chaptersDiv = $(media).data('chapters-div');
1644  		if ( chaptersDiv !== undefined && chaptersDiv !== "") {
1645  			this.chaptersDivLocation = chaptersDiv;
1646  		}
1647
1648  		if ($(media).data('chapters-title') !== undefined) {
1649  			// NOTE: empty string is valid; results in no title being displayed
1650  			this.chaptersTitle = $(media).data('chapters-title');
1651  		}
1652
1653  		var defaultChapter = $(media).data('chapters-default');
1654  		this.defaultChapter = ( defaultChapter !== undefined && defaultChapter !== "") ? defaultChapter : null;
1655
1656  		// Slower/Faster buttons
1657  		// valid values of data-speed-icons are 'animals' (default) and 'arrows'
1658  		// 'animals' uses turtle and rabbit; 'arrows' uses up/down arrows
1659  		this.speedIcons = ($(media).data('speed-icons') === 'arrows') ? 'arrows' : 'animals';
1660
1661  		// Seekbar
1662  		// valid values of data-seekbar-scope are 'chapter' and 'video'; will also accept 'chapters'
1663  		var seekbarScope = $(media).data('seekbar-scope');
1664  		this.seekbarScope = ( seekbarScope === 'chapter' || seekbarScope === 'chapters') ? 'chapter' : 'video';
1665
1666  		// YouTube
1667  		var youTubeId = $(media).data('youtube-id');
1668  		if ( youTubeId !== undefined && youTubeId !== "") {
1669  			this.youTubeId = this.getYouTubeId(youTubeId);
1670  			if ( ! this.hasPoster ) {
1671  				let poster = this.getYouTubePosterUrl(this.youTubeId,'640');
1672  				$(media).attr( 'poster', poster );
1673  			}
1674  		}
1675
1676  		var youTubeDescId = $(media).data('youtube-desc-id');
1677  		if ( youTubeDescId !== undefined && youTubeDescId !== "") {
1678  			this.youTubeDescId = this.getYouTubeId(youTubeDescId);
1679  		}
1680
1681  		var youTubeSignId = $(media).data('youtube-sign-src');
1682  		if ( youTubeSignId !== undefined && youTubeSignId !== "") {
1683  			this.youTubeSignId = this.getYouTubeId(youTubeSignId);
1684  		}
1685
1686  		var youTubeNoCookie = $(media).data('youtube-nocookie');
1687  		this.youTubeNoCookie = (youTubeNoCookie !== undefined && youTubeNoCookie) ? true : false;
1688
1689  		// Vimeo
1690  		var vimeoId = $(media).data('vimeo-id');
1691  		if ( vimeoId !== undefined && vimeoId !== "") {
1692  			this.vimeoId = this.getVimeoId(vimeoId);
1693  			if ( ! this.hasPoster ) {
1694  				let poster = thisObj.getVimeoPosterUrl(this.vimeoId,'1200');
1695  				$(media).attr( 'poster', poster );
1696  			}
1697  		}
1698  		var vimeoDescId = $(media).data('vimeo-desc-id');
1699  		if ( vimeoDescId !== undefined && vimeoDescId !== "") {
1700  			this.vimeoDescId = this.getVimeoId(vimeoDescId);
1701  		}
1702
1703  		// Skin
1704  		// valid values of data-skin are:
1705  		// '2020' (default as of 4.6), all buttons in one row beneath a full-width seekbar
1706  		// 'legacy', two rows of controls; seekbar positioned in available space within top row
1707  		this.skin = ($(media).data('skin') == 'legacy') ? 'legacy' : '2020';
1708
1709  		// Size
1710  		// width of Able Player is determined using the following order of precedence:
1711  		// 1. data-width attribute
1712  		// 2. width attribute (for video or audio, although it is not valid HTML for audio)
1713  		// 3. Intrinsic size from video (video only, determined later)
1714  		if ($(media).data('width') !== undefined) {
1715  			this.playerWidth = parseInt($(media).data('width'));
1716  		} else if ($(media)[0].getAttribute('width')) {
1717  			// NOTE: jQuery attr() returns null for all invalid HTML attributes
1718  			// (e.g., width on <audio>)
1719  			// but it can be acessed via JavaScript getAttribute()
1720  			this.playerWidth = parseInt($(media)[0].getAttribute('width'));
1721  		} else {
1722  			this.playerWidth = null;
1723  		}
1724
1725  		var allowFullScreen = $(media).data('allow-fullscreen');
1726  		this.allowFullscreen = (allowFullScreen !== undefined && allowFullScreen === false) ? false : true;
1727
1728  		// Define other variables that are used in fullscreen program flow
1729  		this.clickedFullscreenButton = false;
1730  		this.restoringAfterFullscreen = false;
1731
1732  		// Seek interval
1733  		// Number of seconds to seek forward or back with Rewind & Forward buttons
1734  		// Unless specified with data-seek-interval, the default value is re-calculated in initialize.js > setSeekInterval();
1735  		// Calculation attempts to intelligently assign a reasonable interval based on media length
1736  		this.defaultSeekInterval = 10;
1737  		this.useFixedSeekInterval = false; // will change to true if media has valid data-seek-interval attribute
1738  		if ($(media).data('seek-interval') !== undefined && $(media).data('seek-interval') !== "") {
1739  			var seekInterval = $(media).data('seek-interval');
1740  			if (/^[1-9][0-9]*$/.test(seekInterval)) { // must be a whole number greater than 0
1741  				this.seekInterval = seekInterval;
1742  				this.useFixedSeekInterval = true; // do not override with calculuation
1743  			}
1744  		}
1745
1746  		// Now Playing
1747  		// Shows "Now Playing:" plus the title of the current track above player
1748  		// Only used if there is a playlist
1749  		var showNowPlaying = $(media).data('show-now-playing');
1750  		this.showNowPlaying = (showNowPlaying !== undefined && showNowPlaying === false) ? false : true;
1751
1752  		// Fallback
1753  		// The data-test-fallback attribute can be used to test the fallback solution in any browser
1754  		var testFallback = $(media).data('test-fallback');
1755  		if ( testFallback !== undefined && testFallback !== false) {
1756  			// 1: build error; 2: browser doesn't support media.
1757  			this.testFallback = ( testFallback == '2' ) ? 2 : 1;
1758  		} else {
1759  			this.testFallback = false;
1760  		}
1761
1762  		// Language
1763  		// Player language is determined given the following precedence:
1764  		// 1. The value of data-lang on the media element, if provided and a matching translation file is available
1765  		// 2. Lang attribute on <html> or <body>, if a matching translation file is available
1766  		// 3. English
1767  		// Final calculation occurs in translation.js > getTranslationText()
1768  		var lang = $(media).data('lang');
1769  		this.lang = ( lang !== undefined && lang !== "") ? lang.toLowerCase() : null;
1770
1771  		// Metadata Tracks
1772  		var metaType = $(media).data('meta-type');
1773  		if ( metaType !== undefined && metaType !== "") {
1774  			this.metaType = metaType;
1775  		}
1776  		var metaDiv = $(media).data('meta-div');
1777  		if ( metaDiv !== undefined && metaDiv !== "") {
1778  			this.metaDiv = metaDiv;
1779  		}
1780
1781  		// Search
1782  		// conducting a search requires an external div in which to write the results
1783  		var searchDiv = $(media).data('search-div');
1784  		if ( searchDiv !== undefined && searchDiv !== "") {
1785
1786  			this.searchDiv = searchDiv;
1787
1788  			// Search term (optional; could be assigned later in a JavaScript application)
1789  			var searchString = $(media).data('search');
1790  			if ( searchString !== undefined && searchString !== "") {
1791  				this.searchString = searchString;
1792  			}
1793
1794  			// Search Language
1795  			var searchLang = $(media).data('search-lang');
1796  			this.searchLang = ( searchLang !== undefined && searchLang !== "") ? searchLang : null;
1797
1798  			// Search option: Ignore capitalization in search terms
1799  			var searchIgnoreCaps = $(media).data('search-ignore-caps');
1800  			this.searchIgnoreCaps = ( searchIgnoreCaps !== undefined && searchIgnoreCaps !== false) ? true : false;
1801  		}
1802
1803  		// Hide controls when video starts playing
1804  		// They will reappear again when user presses a key or moves the mouse
1805  		// As of v4.0, controls are hidden automatically on playback in fullscreen mode
1806  		if ($(media).data('hide-controls') !== undefined && $(media).data('hide-controls') !== false) {
1807  			this.hideControls = true;
1808  			this.hideControlsOriginal = true; // a copy of hideControls, since the former may change if user enters full screen mode
1809  		} else {
1810  			this.hideControls = false;
1811  			this.hideControlsOriginal = false;
1812  		}
1813
1814  		// Steno mode
1815  		// Enable support for Able Player keyboard shortcuts in textaarea fields
1816  		// so users can control the player while transcribing
1817  		if ($(media).data('steno-mode') !== undefined && $(media).data('steno-mode') !== false) {
1818  			this.stenoMode = true;
1819  			// Add support for stenography in an iframe via data-steno-iframe-id
1820  			if ($(media).data('steno-iframe-id') !== undefined && $(media).data('steno-iframe-id') !== "") {
1821  				this.stenoFrameId = $(media).data('steno-iframe-id');
1822  				this.$stenoFrame = $('#' + this.stenoFrameId);
1823  				if (!(this.$stenoFrame.length)) {
1824  					// iframe not found
1825  					this.stenoFrameId = null;
1826  					this.$stenoFrame = null;
1827  				}
1828  			} else {
1829  				this.stenoFrameId = null;
1830  				this.$stenoFrame = null;
1831  			}
1832  		} else {
1833  			this.stenoMode = false;
1834  			this.stenoFrameId = null;
1835  			this.$stenoFrame = null;
1836  		}
1837
1838  		// Define built-in variables that CANNOT be overridden with HTML attributes
1839  		this.setDefaults();
1840
1841  		////////////////////////////////////////
1842  		// End assignment of default variables
1843  		////////////////////////////////////////
1844
1845  		this.ableIndex = AblePlayer.nextIndex;
1846  		AblePlayer.nextIndex += 1;
1847
1848  		this.title = $(media).attr('title');
1849
1850  		// populate translation object with localized versions of all labels and prompts
1851  		this.tt = {};
1852  		try {
1853  			this.getTranslationText();
1854  			this.setup();
1855  		} catch (e) {
1856  			console.warn('Error setting up translations:', e);
1857  			this.provideFallback();
1858  		}
1859
1860  		ablePlayerInstances.add(this);
1861  	}
1862  	// Index to increment every time new player is created.
1863  	// 5.0.0: this is now only used to generate unique IDs. Otherwise use hasSingleInstance.
1864  	AblePlayer.nextIndex = 0;
1865
1866  	AblePlayer.prototype.setup = function() {
1867
1868  		var thisObj = this;
1869  		this.initializing = true; // will remain true until entire sequence of function calls is complete
1870
1871  		this.reinitialize().then(function () {
1872  			if (!thisObj.player) {
1873  				// No player for this media, show last-line fallback.
1874  				thisObj.provideFallback();
1875  			} else {
1876  				thisObj.setupInstance().then(function () {
1877  					thisObj.setupInstancePlaylist();
1878  					if (thisObj.hasPlaylist) ; else {
1879  						thisObj.recreatePlayer().then(function() {
1880  							thisObj.initializing = false;
1881  							thisObj.playerCreated = true; // remains true until browser is refreshed
1882  						});
1883  					}
1884  				});
1885  			}
1886  		});
1887  	};
1888
1889  	/**
1890  	 * Removes this player from the global instance list.
1891  	 *
1892  	 * You probably want to call this during/after removing a player from the
1893  	 * DOM. This avoids memory leaks, and allows the event handling to have the
1894  	 * correct count of how many players are actually on the page.
1895  	 */
1896  	AblePlayer.prototype.dispose = function () {
1897  		AblePlayer.ablePlayerInstances.delete(this);
1898
1899  		// Look for various dialogs tied to this instance. Elements associated
1900  		// with these are appended to the body, and they need to be
1901  		// `.remove()`d here.
1902  		const dialogs = [
1903  			this.captionPrefsDialog,
1904  			this.descPrefsDialog,
1905  			this.keyboardPrefsDialog,
1906  			this.transcriptPrefsDialog,
1907  			this.transcriptResizeDialog,
1908  			this.signResizeDialog,
1909  		];
1910
1911  		for (const dialog of dialogs) {
1912  			if (!dialog) {
1913  				continue;
1914  			}
1915  			if (dialog.modal) {
1916  				dialog.modal.remove();
1917  			}
1918  			if (dialog.overlay) {
1919  				dialog.overlay.remove();
1920  			}
1921  		}
1922  	};
1923
1924  	AblePlayer.getActiveDOMElement = function () {
1925  		var activeElement = document.activeElement;
1926
1927  		// For shadow DOMs we need to keep digging down through the DOMs
1928  		while (activeElement.shadowRoot && activeElement.shadowRoot.activeElement) {
1929  			activeElement = activeElement.shadowRoot.activeElement;
1930  		}
1931
1932  		return activeElement;
1933  	};
1934
1935  	AblePlayer.localGetElementById = function(element, id) {
1936  		if (element.getRootNode) {
1937  			// Use getRootNode() and querySelector() where supported (for shadow DOM support)
1938  			return $(element.getRootNode().querySelector('#' + id));
1939  		} else {
1940  			// If getRootNode is not supported it should be safe to use document.getElementById (since there is no shadow DOM support)
1941  			return $(document.getElementById(id));
1942  		}
1943  	};
1944
1945  	AblePlayer.ablePlayerSetupWindow = ablePlayerSetupWindow;
1946
1947  	AblePlayer.youTubeIframeAPIReady = false;
1948  	AblePlayer.loadingYouTubeIframeAPI = false;
1949
1950  	AblePlayer.ablePlayerInstances = ablePlayerInstances;
1951
1952  	AblePlayer.hasSingleInstance = () => AblePlayer.ablePlayerInstances.size === 1;
1953
1954  	AblePlayer.getSingleInstance = () => {
1955  		// If there are actually more instances, this returns the first one
1956  		for (const instance of AblePlayer.ablePlayerInstances) {
1957  			return instance;
1958  		}
1959  	};
1960
1961  function addBrowserFunctions(AblePlayer) {
1962
1963  	AblePlayer.prototype.isIOS = function(version) {
1964
1965  		// return true if this is iOS
1966  		// if version is provided check for a particular version
1967
1968  		var userAgent, iOS;
1969
1970  		userAgent = navigator.userAgent.toLowerCase();
1971  		iOS = /ipad|iphone|ipod/.exec(userAgent);
1972  		if (iOS) {
1973  			if (typeof version !== 'undefined') {
1974  				if (userAgent.indexOf('os ' + version) !== -1) {
1975  					// this is the target version of iOS
1976  					return true;
1977  				} else {
1978  					return false;
1979  				}
1980  			} else {
1981  				// no version was specified
1982  				return true;
1983  			}
1984  		} else {
1985  			// this is not iOS
1986  			return false;
1987  		}
1988  	};
1989
1990  	AblePlayer.prototype.browserSupportsVolume = function() {
1991
1992  		// To test whether the browser supports changing the volume,
1993  		// create a new audio element and try setting the volume to something other than 1.
1994  		// Then, retrieve the current setting to see if it preserved it.
1995  		// This doesn't work in iOS by design: https://developer.apple.com/documentation/avfoundation/avplayer/volume
1996
1997  		var audio, testVolume;
1998
1999  		if (this.isIOS()) {
2000  			return false;
2001  		}
2002
2003  		testVolume = 0.9;  // any value between 0.1 and 0.9
2004  		audio = new Audio();
2005  		audio.volume = testVolume;
2006
2007  		return ( audio.volume === testVolume );
2008  	};
2009
2010  	AblePlayer.prototype.nativeFullscreenSupported = function () {
2011
2012  		return document.fullscreenEnabled || document.webkitFullscreenEnabled;
2013  	};
2014
2015  }
2016
2017  /**
2018   * @file validate.js
2019   * @description This file contains the code to validate the VTT data.
2020   */
2021
2022  /** PRE-SANITIZED FUNCTIONS
2023   * Some of the VTT attributes need to be transformed before being sanitized by DOMPurify.
2024   * @namespace preProcessing
2025   */
2026  var preProcessing = {
2027    /**
2028     * Transforms tags with class names separated by dots into tags with a class attribute containing space-separated class names.
2029     * @memberof preProcessing
2030     * @param {string} vttContent - The content of the VTT.
2031     * @returns {string} - The VTT content with processed tags.
2032     */
2033    transformCSSClasses: function (vttContent) {
2034  	// This function should only be passed one cue at a time.
2035  	// Throw an error if the string checked is more than 1000 characters.
2036  	if ( vttContent.length > 1000 ) {
2037  		throw new Error( "Input too long" );
2038  	}
2039      return vttContent.replace(
2040        /<(v|c|b|i|u|lang|ruby)\.([\w.]+)([^>]*)>/g,
2041        function (_, tag, cssClasses, otherAttrs) {
2042          var classAttr = cssClasses.replace(/\./g, " ");
2043          return `<${tag} class="${classAttr}"${otherAttrs}>`;
2044        }
2045      );
2046    },
2047
2048    /**
2049     * Transforms <lang> tags by adding a lang attribute with the language code.
2050     * @memberof preProcessing
2051     * @param {string} content - The content with processed CSS classes.
2052     * @returns {string} - The content with <lang> tags transformed.
2053     */
2054    transformLangTags: function (content) {
2055      return content.replace(
2056        /<lang\s+([\w-]+)([^>]*)>/g,
2057        function (_, langCode, otherAttrs) {
2058          return '<lang lang="' + langCode + '"' + otherAttrs + ">";
2059        }
2060      );
2061    },
2062
2063    /**
2064     * Transforms <v> tags by extracting any non-attribute text as a `title` attribute,
2065     * retains existing attributes (except class), and preserves the class attribute if present.
2066     * Example: <v John class="foo" data-x="y"> becomes <v title="John" data-x="y" class="foo">
2067     *
2068     * @function
2069     * @memberof preProcessing
2070     * @param {string} content - The string content containing <v> tags to process.
2071     * @returns {string} The content with <v> tags transformed to include a title attribute and preserved attributes.
2072     */
2073    transformVTags: function (content) {
2074  	return content.replace(/<v\s+([^>]*?)>/g, function (_, tagAttributes) {
2075  		var classMatch = tagAttributes.match(/class="([^"]*)"/);
2076  		var titleMatch = tagAttributes.match(/title="([^"]*)"/);
2077  		var classAttr = classMatch ? classMatch[0] : "";
2078  		var titleAttr = titleMatch ? titleMatch[0] : "";
2079  		var otherAttributes = tagAttributes
2080  			.replace(/class="[^"]*"/, "")
2081  			.replace(/title="[^"]*"/, "")
2082  			.trim()
2083  			.split(/\s+/);
2084
2085  		var attributes = [];
2086  		var titleParts = [];
2087
2088  		// Iterate over each token of the tag content
2089  		otherAttributes.forEach(function (token) {
2090  			if (token.indexOf("=") !== -1) {
2091  			attributes.push(token);
2092  			} else {
2093  			titleParts.push(token);
2094  			}
2095  		});
2096
2097  		var title = ( titleParts ) ? titleParts.join(" ") : false;
2098  		var newTag = "<v";
2099
2100  		if ( title && ! titleAttr ) {
2101  			newTag += ' title="' + title + '"';
2102  		}
2103
2104  		if ( titleAttr && ! title ) {
2105  			newTag += " " + titleAttr;
2106  		}
2107
2108  		if (attributes.length > 0) {
2109  			newTag += " " + attributes.join(" ");
2110  		}
2111
2112  		if (classAttr) {
2113  			newTag += " " + classAttr;
2114  		}
2115
2116  		newTag += ">";
2117
2118  		return newTag;
2119        });
2120      },
2121  };
2122
2123  /** POST-SANITIZED FUNCTIONS
2124   * After sanitizing the VTT data, some tags need to be transformed back to their original form.
2125   * @namespace postProcessing
2126   */
2127  var postProcessing = {
2128    /**
2129     * Post-processes <c> tags by converting class attributes to dot-separated class names.
2130     * @memberof postProcessing
2131     * @param {string} vttContent - The VTT content to be processed.
2132     * @returns {string} - The VTT content with processed <c> tags.
2133     */
2134    postprocessCTag: function (vttContent) {
2135      return vttContent.replace(
2136        /<c class="([\w\s]+)">/g,
2137        function (_, classNames) {
2138          var classes = classNames.replace(/ /g, ".");
2139          return "<c." + classes + ">";
2140        }
2141      );
2142    },
2143
2144    /**
2145     * * Post-processes <v> tags by converting class attributes, no matter where found in the attribute order, to dot-separated class names.
2146     * For example, <v class="foo bar" title="John"> becomes <v.foo.bar title="John">.
2147     * Removes the class attribute and appends other attributes after the class names.
2148     *
2149     * @function
2150     * @memberof postProcessing
2151     * @param {string} vttContent - The VTT content to be processed.
2152     * @returns {string} - The VTT content with processed <v> tags.
2153     */
2154    postprocessVTag: function (vttContent) {
2155      return vttContent.replace(
2156        /<v([^>]*)class="([\w\s]+)"([^>]*)>/g,
2157        function (_, beforeClass, classNames, afterClass) {
2158          var classes = classNames.trim().split(/\s+/).join(".");
2159          // Rebuild the tag: <v.{classes}{other attributes}>
2160          // Remove class="..." from attributes
2161          var attrs = (beforeClass + afterClass)
2162            .replace(/\s*class="[\w\s]+"/, "")
2163            .trim();
2164          return "<v." + classes + (attrs ? " " + attrs : "") + ">";
2165        }
2166      );
2167    },
2168
2169    /**
2170     * Post-processes <lang> tags by removing the lang attribute and placing the language code directly in the tag.
2171     * @memberof postProcessing
2172     * @param {string} vttContent - The VTT content to be processed.
2173     * @returns {string} - The VTT content with processed <lang> tags.
2174     */
2175    postprocessLangTag: function (vttContent) {
2176      return vttContent.replace(
2177        /<lang lang="([\w-]+)"([^>]*)>/g,
2178        function (_, langCode, otherAttrs) {
2179          return "<lang " + langCode + otherAttrs + ">";
2180        }
2181      );
2182    },
2183  };
2184
2185  /**
2186   * Preprocesses, sanitizes and post-processes VTT content as well as other utility functions.
2187   * @namespace validate
2188   */
2189  var validate = {
2190    /**
2191     * Sets up the VTT content before sanitizing by transforming tags.
2192     * This way DOM purify will process the tags correctly.
2193     * @memberof validate
2194     * @param {string} vttContent - The original content of the VTT.
2195     * @returns {string} - The VTT content for the next and final step of preprocessing.
2196     */
2197    preProcessVttContent: function (vttContent) {
2198      var processedCSS = preProcessing.transformCSSClasses(vttContent);
2199      var processedLang = preProcessing.transformLangTags(processedCSS);
2200      var processedVTags = preProcessing.transformVTags(processedLang);
2201      return processedVTags;
2202    },
2203
2204    /**
2205     * Post-processes the sanitized VTT data by converting class attributes to dot-separated class names and other transformations.
2206     * @memberof validate
2207     * @param {string} sanitizedVttContent - The sanitized VTT content to be post-processed.
2208     * @param {string} originalVttContent - The original VTT content before sanitization.
2209     * @returns {string} - The post-processed VTT content.
2210     */
2211    postProcessVttContent: function (sanitizedVttContent, originalVttContent) {
2212      var processedCTags = postProcessing.postprocessCTag(sanitizedVttContent);
2213      var processedVTags = postProcessing.postprocessVTag(processedCTags);
2214      var processedLangTags = postProcessing.postprocessLangTag(processedVTags);
2215
2216      var arrowReplaced = processedLangTags.replace(/--&gt;/g, "-->");
2217      var timestampTagReplaced = arrowReplaced.replace(/&lt;([\d:.]+)&gt;/g, '<$1>');
2218
2219      var finalContent = timestampTagReplaced.replace(
2220        /<\/v>/g,
2221        function (match, offset) {
2222          return originalVttContent.indexOf(match, offset) !== -1 ? match : "";
2223        }
2224      );
2225
2226      return finalContent;
2227    },
2228
2229    /**
2230     * Sanitizes the VTT data by removing unwanted tags and attributes, and then post-processes it.
2231     * @memberof validate
2232     * @param {string} vttContent - The VTT content to be sanitized and post-processed.
2233     * @returns {string} - The fully processed VTT content.
2234     */
2235    sanitizeVttContent: function (vttContent) {
2236      if (vttContent === null || vttContent === undefined) {
2237        return "";
2238      }
2239      var preSanitizedVttContent = validate.preProcessVttContent(vttContent);
2240
2241      var config = {
2242        ALLOWED_TAGS: ["b", "i", "u", "v", "c", "lang", "ruby", "rt", "rp"],
2243        ALLOWED_ATTR: ["title", "class", "lang"],
2244        KEEP_CONTENT: true,
2245      };
2246
2247      var sanitizedVttContent = purify.sanitize(
2248        preSanitizedVttContent,
2249        config
2250      );
2251
2252      return validate.postProcessVttContent(sanitizedVttContent, vttContent);
2253    },
2254    // Utility validation functions
2255    isProtocolSafe: function (url) {
2256      //creates a new URL object for analysis to check if the protocol is http or https
2257      //returns true if there is a match false otherwise
2258      try {
2259        const parsedUrl = new URL(url, window.location.origin); // Resolve relative URLs
2260        return ["http:", "https:"].includes(parsedUrl.protocol); // Allow only HTTP and HTTPS
2261      } catch (e) {
2262        return false; // Invalid URL
2263      }
2264    },
2265  };
2266
2267  // Events:
2268  	// - startTracking(event, position)
2269  	// - tracking(event, position)
2270  	// - stopTracking(event, position)
2271
2272  	function AccessibleSlider(div, max, bigInterval, label) {
2273
2274  		// div is the host element around which the slider will be built
2275  		// max is the high end of the slider scale
2276  		// bigInterval is the number of steps supported by page up/page down (set to 0 if not supported)
2277  		// (smallInterval, defined as nextStep below, is always set to 1) - this is the interval supported by arrow keys
2278  		// label is used within an aria-label attribute to identify the slider to screen reader users
2279
2280  		var thisObj, coords;
2281
2282  		thisObj = this;
2283
2284  		// Initialize some variables.
2285  		this.position = 0; // Note: position does not change while tracking.
2286  		this.tracking = false;
2287  		this.trackDevice = null; // 'mouse' or 'keyboard'
2288  		this.keyTrackPosition = 0;
2289  		this.lastTrackPosition = 0;
2290  		this.nextStep = 1;
2291  		this.inertiaCount = 0;
2292
2293  		this.seekbarDiv = $(div);
2294
2295  		// Add divs for tracking amount of media loaded and played
2296  		this.loadedDiv = $('<div></div>');
2297  		this.playedDiv = $('<div></div>');
2298
2299  		// Add a seekhead
2300  		this.seekHead = $('<div>',{
2301  			'aria-orientation': 'horizontal',
2302  			'class': 'able-seekbar-head'
2303  		});
2304
2305  		this.seekHead.attr('tabindex', '0');
2306
2307  		// Since head is focusable, it gets the aria roles/titles.
2308  		this.seekHead.attr({
2309  			'role': 'slider',
2310  			'aria-label': label,
2311  			'aria-valuemin': 0,
2312  			'aria-valuemax': max
2313  		});
2314
2315  		this.timeTooltipTimeoutId = null;
2316  		this.overTooltip = false;
2317  		this.timeTooltip = $('<div>');
2318  		this.seekbarDiv.append(this.timeTooltip);
2319
2320  		this.timeTooltip.attr('role', 'tooltip');
2321  		this.timeTooltip.addClass('able-tooltip');
2322  		this.timeTooltip.on('mouseenter focus', function(){
2323  			thisObj.overTooltip = true;
2324  			clearInterval(thisObj.timeTooltipTimeoutId);
2325  		});
2326  		this.timeTooltip.on('mouseleave blur', function(){
2327  			thisObj.overTooltip = false;
2328  			$(this).hide();
2329  		});
2330  		this.timeTooltip.hide();
2331
2332  		this.seekbarDiv.append(this.loadedDiv);
2333  		this.seekbarDiv.append(this.playedDiv);
2334  		this.seekbarDiv.append(this.seekHead);
2335  		this.seekbarDiv.wrap('<div></div>');
2336  		this.wrapperDiv = this.seekbarDiv.parent();
2337
2338  		if (this.skin === 'legacy') {
2339  			this.wrapperDiv.width( 100 );
2340  			this.loadedDiv.width(0);
2341  		}
2342  		this.wrapperDiv.addClass('able-seekbar-wrapper');
2343  		this.loadedDiv.addClass('able-seekbar-loaded');
2344  		this.playedDiv.width(0);
2345  		this.playedDiv.addClass('able-seekbar-played');
2346
2347  		// Set a default duration. User can call this dynamically if duration changes.
2348  		this.setDuration(max);
2349
2350  		// handle seekHead events
2351  		this.seekHead.on('mouseenter mouseleave mousemove mousedown mouseup focus blur touchstart touchmove touchend', function (e) {
2352
2353  			coords = thisObj.pointerEventToXY(e);
2354
2355  			if (e.type === 'mouseenter' || e.type === 'focus') {
2356  				thisObj.overHead = true;
2357  			} else if (e.type === 'mouseleave' || e.type === 'blur') {
2358  				thisObj.overHead = false;
2359  				if (!thisObj.overBody && thisObj.tracking && thisObj.trackDevice === 'mouse') {
2360  					thisObj.stopTracking(thisObj.pageXToPosition(coords.x));
2361  				}
2362  			} else if (e.type === 'mousemove' || e.type === 'touchmove') {
2363  				if (thisObj.tracking && thisObj.trackDevice === 'mouse') {
2364  					thisObj.trackHeadAtPageX(coords.x);
2365  				}
2366  			} else if (e.type === 'mousedown' || e.type === 'touchstart') {
2367  				thisObj.startTracking('mouse', thisObj.pageXToPosition(thisObj.seekHead.offset() + (thisObj.seekHead.width() / 2)));
2368  				if (!thisObj.seekbarDiv.is(':focus')) {
2369  					thisObj.seekbarDiv.focus();
2370  				}
2371  				e.preventDefault();
2372  			} else if (e.type === 'mouseup' || e.type === 'touchend') {
2373  				if (thisObj.tracking && thisObj.trackDevice === 'mouse') {
2374  					thisObj.stopTracking(thisObj.pageXToPosition(coords.x));
2375  				}
2376  			}
2377  			if (e.type !== 'mousemove' && e.type !== 'mousedown' && e.type !== 'mouseup' && e.type !== 'touchstart' && e.type !== 'touchend') {
2378  				thisObj.refreshTooltip();
2379  			}
2380  		});
2381
2382  		// handle seekbarDiv events
2383  		this.seekbarDiv.on(
2384  			'mouseenter mouseleave mousemove mousedown mouseup keydown keyup touchstart touchmove touchend', function (e) {
2385
2386  			// Don't trigger move on right click.
2387  			if ( e.button == 2 && e.type == 'mousedown' ) {
2388  				return;
2389  			}
2390  			coords = thisObj.pointerEventToXY(e);
2391  			let keyPressed = e.key;
2392
2393  			if (e.type === 'mouseenter') {
2394  				thisObj.overBody = true;
2395  				thisObj.overBodyMousePos = {
2396  					x: coords.x,
2397  					y: coords.y
2398  				};
2399  			} else if (e.type === 'mouseleave') {
2400  				thisObj.overBody = false;
2401  				thisObj.overBodyMousePos = null;
2402  				if (!thisObj.overHead && thisObj.tracking && thisObj.trackDevice === 'mouse') {
2403  					thisObj.stopTracking(thisObj.pageXToPosition(coords.x));
2404  				}
2405  			} else if (e.type === 'mousemove' || e.type === 'touchmove') {
2406  				thisObj.overBodyMousePos = {
2407  					x: coords.x,
2408  					y: coords.y
2409  				};
2410  				if (thisObj.tracking && thisObj.trackDevice === 'mouse') {
2411  					thisObj.trackHeadAtPageX(coords.x);
2412  				}
2413  			} else if (e.type === 'mousedown' || e.type === 'touchstart') {
2414  				thisObj.startTracking('mouse', thisObj.pageXToPosition(coords.x));
2415  				thisObj.trackHeadAtPageX(coords.x);
2416  				if (!thisObj.seekHead.is(':focus')) {
2417  					thisObj.seekHead.focus();
2418  				}
2419  				e.preventDefault();
2420  			} else if (e.type === 'mouseup' || e.type === 'touchend') {
2421  				if (thisObj.tracking && thisObj.trackDevice === 'mouse') {
2422  					thisObj.stopTracking(thisObj.pageXToPosition(coords.x));
2423  				}
2424  			} else if (e.type === 'keydown') {
2425  				if (e.key === 'Home') {
2426  					thisObj.trackImmediatelyTo(0);
2427  				} else if (e.key === 'End') {
2428  					thisObj.trackImmediatelyTo(thisObj.duration);
2429  				} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
2430  					thisObj.arrowKeyDown(-1);
2431  				} else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
2432  					thisObj.arrowKeyDown(1);
2433  				} else if (e.key === 'PageUp' && bigInterval > 0) {
2434  					thisObj.arrowKeyDown(bigInterval);
2435  				} else if (e.key === 'PageDown' && bigInterval > 0) {
2436  					thisObj.arrowKeyDown(-bigInterval);
2437  				} else {
2438  					return;
2439  				}
2440  				e.preventDefault();
2441  			} else if (e.type === 'keyup') {
2442  				if ( keyPressed === e.key ) {
2443  					if (thisObj.tracking && thisObj.trackDevice === 'keyboard') {
2444  						thisObj.stopTracking(thisObj.keyTrackPosition);
2445  					}
2446  					e.preventDefault();
2447  				}
2448  			}
2449  			if (!thisObj.overTooltip && e.type !== 'mouseup' && e.type !== 'keydown' && e.type !== 'keydown') {
2450  				thisObj.refreshTooltip();
2451  			}
2452  		});
2453  	}
2454
2455  	AccessibleSlider.prototype.arrowKeyDown = function (multiplier) {
2456  		if (this.tracking && this.trackDevice === 'keyboard') {
2457  			this.keyTrackPosition = this.boundPos(this.keyTrackPosition + (this.nextStep * multiplier));
2458  			this.inertiaCount += 1;
2459  			if (this.inertiaCount === 20) {
2460  				this.inertiaCount = 0;
2461  				this.nextStep *= 2;
2462  			}
2463  			this.trackHeadAtPosition(this.keyTrackPosition);
2464  		} else {
2465  			this.nextStep = 1;
2466  			this.inertiaCount = 0;
2467  			this.keyTrackPosition = this.boundPos(this.position + (this.nextStep * multiplier));
2468  			this.startTracking('keyboard', this.keyTrackPosition);
2469  			this.trackHeadAtPosition(this.keyTrackPosition);
2470  		}
2471  	};
2472
2473  	AccessibleSlider.prototype.pageXToPosition = function (pageX) {
2474  		var offset = pageX - this.seekbarDiv.offset().left;
2475  		var position = this.duration * (offset / this.seekbarDiv.width());
2476  		return this.boundPos(position);
2477  	};
2478
2479  	AccessibleSlider.prototype.boundPos = function (position) {
2480  		return Math.max(0, Math.min(position, this.duration));
2481  	};
2482
2483  	AccessibleSlider.prototype.setDuration = function (duration) {
2484  		if (duration !== this.duration) {
2485  			this.duration = duration;
2486  			this.resetHeadLocation();
2487  			this.seekHead.attr('aria-valuemax', duration);
2488  		}
2489  	};
2490
2491  	// Set width of the legacy seekbar.
2492  	AccessibleSlider.prototype.setWidth = function (width) {
2493  		this.wrapperDiv.width(width);
2494  		this.resizeDivs();
2495  		this.resetHeadLocation();
2496  	};
2497
2498  	AccessibleSlider.prototype.getWidth = function () {
2499  		return this.wrapperDiv.width();
2500  	};
2501
2502  	AccessibleSlider.prototype.resizeDivs = function () {
2503  		this.playedDiv.width( 100 * (this.position / this.duration) + '%' );
2504  		this.loadedDiv.width( 100 * this.buffered + '%' );
2505  	};
2506
2507  	// Stops tracking, sets the head location to the current position.
2508  	AccessibleSlider.prototype.resetHeadLocation = function () {
2509  		var ratio = this.position / this.duration;
2510  		var center = this.seekbarDiv.width() * ratio;
2511  		this.seekHead.css('left', center - (this.seekHead.width() / 2));
2512
2513  		if (this.tracking) {
2514  			this.stopTracking(this.position);
2515  		}
2516  	};
2517
2518  	AccessibleSlider.prototype.setPosition = function (position, updateLive) {
2519  		this.position = position;
2520  		this.resetHeadLocation();
2521  		if (this.overHead) {
2522  			this.refreshTooltip();
2523  		}
2524  		this.resizeDivs();
2525  		this.updateAriaValues(position, updateLive);
2526  	};
2527
2528  	// TODO: Native HTML5 can have several buffered segments, and this actually happens quite often. Change this to display them all.
2529  	AccessibleSlider.prototype.setBuffered = function (ratio) {
2530  		if (!isNaN(ratio)) {
2531  			this.buffered = ratio;
2532  			this.redrawDivs;
2533  		}
2534  	};
2535
2536  	AccessibleSlider.prototype.startTracking = function (device, position) {
2537  		if (!this.tracking) {
2538  			this.trackDevice = device;
2539  			this.tracking = true;
2540  			this.seekbarDiv.trigger('startTracking', [position]);
2541  		}
2542  	};
2543
2544  	AccessibleSlider.prototype.stopTracking = function (position) {
2545  		this.trackDevice = null;
2546  		this.tracking = false;
2547  		this.seekbarDiv.trigger('stopTracking', [position]);
2548  		this.setPosition(position, true);
2549  	};
2550
2551  	AccessibleSlider.prototype.trackHeadAtPageX = function (pageX) {
2552  		var position = this.pageXToPosition(pageX);
2553  		var newLeft = pageX - this.seekbarDiv.offset().left - (this.seekHead.width() / 2);
2554  		newLeft = Math.max(0, Math.min(newLeft, this.seekbarDiv.width() - this.seekHead.width()));
2555  		this.lastTrackPosition = position;
2556  		this.seekHead.css('left', newLeft);
2557  		this.reportTrackAtPosition(position);
2558  	};
2559
2560  	AccessibleSlider.prototype.trackHeadAtPosition = function (position) {
2561  		var ratio = position / this.duration;
2562  		var center = this.seekbarDiv.width() * ratio;
2563  		this.lastTrackPosition = position;
2564  		this.seekHead.css('left', center - (this.seekHead.width() / 2));
2565  		this.reportTrackAtPosition(position);
2566  	};
2567
2568  	AccessibleSlider.prototype.reportTrackAtPosition = function (position) {
2569  		this.seekbarDiv.trigger('tracking', [position]);
2570  		this.updateAriaValues(position, true);
2571  	};
2572
2573  	AccessibleSlider.prototype.updateAriaValues = function (position, updateLive) {
2574  		// TODO: Localize, move to another function.
2575  		var pHours = Math.floor(position / 3600);
2576  		var pMinutes = Math.floor((position % 3600) / 60);
2577  		var pSeconds = Math.floor(position % 60);
2578
2579  		var pHourWord = pHours === 1 ? 'hour' : 'hours';
2580  		var pMinuteWord = pMinutes === 1 ? 'minute' : 'minutes';
2581  		var pSecondWord = pSeconds === 1 ? 'second' : 'seconds';
2582
2583  		var descriptionText;
2584  		if (pHours > 0) {
2585  			descriptionText = pHours +
2586  				' ' + pHourWord +
2587  				', ' + pMinutes +
2588  				' ' + pMinuteWord +
2589  				', ' + pSeconds +
2590  				' ' + pSecondWord;
2591  		} else if (pMinutes > 0) {
2592  			descriptionText	 = pMinutes +
2593  				' ' + pMinuteWord +
2594  				', ' + pSeconds +
2595  				' ' + pSecondWord;
2596  		} else {
2597  			descriptionText = pSeconds + ' ' + pSecondWord;
2598  		}
2599
2600  		/* Comment to stop live region from generating or being used. */
2601  		if (!this.liveAriaRegion) {
2602  			this.liveAriaRegion = $('<span>', {
2603  				'class': 'able-offscreen',
2604  				'aria-live': 'polite'
2605  			});
2606  			this.wrapperDiv.append(this.liveAriaRegion);
2607  		}
2608  		if (updateLive && (this.liveAriaRegion.text() !== descriptionText)) {
2609  			this.liveAriaRegion.text(descriptionText);
2610  		}
2611
2612  		// Uncomment the following lines to use aria values instead of separate live region.
2613  		this.seekHead.attr('aria-valuetext', descriptionText);
2614  		this.seekHead.attr('aria-valuenow', Math.floor(position).toString());
2615  	};
2616
2617  	AccessibleSlider.prototype.trackImmediatelyTo = function (position) {
2618  		this.startTracking('keyboard', position);
2619  		this.trackHeadAtPosition(position);
2620  		this.keyTrackPosition = position;
2621  	};
2622
2623  	AccessibleSlider.prototype.refreshTooltip = function () {
2624  		if (this.overHead) {
2625  			this.timeTooltip.show();
2626  			if (this.tracking) {
2627  				this.timeTooltip.text(this.positionToStr(this.lastTrackPosition));
2628  			} else {
2629  				this.timeTooltip.text(this.positionToStr(this.position));
2630  			}
2631  			this.setTooltipPosition(this.seekHead.position().left + (this.seekHead.width() / 2));
2632  		} else if (this.overBody && this.overBodyMousePos) {
2633  			this.timeTooltip.show();
2634  			this.timeTooltip.text(this.positionToStr(this.pageXToPosition(this.overBodyMousePos.x)));
2635  			this.setTooltipPosition(this.overBodyMousePos.x - this.seekbarDiv.offset().left);
2636  		} else {
2637
2638  			clearTimeout(this.timeTooltipTimeoutId);
2639  			var _this = this;
2640  			this.timeTooltipTimeoutId = setTimeout(function() {
2641  				// give user a half second move cursor over tooltip
2642  				_this.timeTooltip.hide();
2643  			}, 500);
2644  		}
2645  	};
2646
2647  	AccessibleSlider.prototype.hideSliderTooltips = function () {
2648  		this.overHead = false;
2649  		this.overBody = false;
2650  		this.timeTooltip.hide();
2651  	};
2652
2653  	AccessibleSlider.prototype.setTooltipPosition = function (x) {
2654  		this.timeTooltip.css({
2655  			left: x - (this.timeTooltip.width() / 2) - 10,
2656  			bottom: this.seekHead.height()
2657  		});
2658  	};
2659
2660  	AccessibleSlider.prototype.positionToStr = function (seconds) {
2661
2662  		// same logic as misc.js > formatSecondsAsColonTime()
2663  		var dHours = Math.floor(seconds / 3600);
2664  		var dMinutes = Math.floor(seconds / 60) % 60;
2665  		var dSeconds = Math.floor(seconds % 60);
2666  		if (dSeconds < 10) {
2667  			dSeconds = '0' + dSeconds;
2668  		}
2669  		if (dHours > 0) {
2670  			if (dMinutes < 10) {
2671  				dMinutes = '0' + dMinutes;
2672  			}
2673  			return dHours + ':' + dMinutes + ':' + dSeconds;
2674  		} else {
2675  			return dMinutes + ':' + dSeconds;
2676  		}
2677  	};
2678
2679  	AccessibleSlider.prototype.pointerEventToXY = function(e) {
2680
2681  		// returns array of coordinates x and y in response to both mouse and touch events
2682  		// for mouse events, this comes from e.pageX and e.pageY
2683  		// for touch events, it's a bit more complicated
2684  		var out = {x:0, y:0};
2685  		if (e.type == 'touchstart' || e.type == 'touchmove' || e.type == 'touchend' || e.type == 'touchcancel') {
2686  			var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
2687  			out.x = touch.pageX;
2688  			out.y = touch.pageY;
2689  		} else if (e.type == 'mousedown' || e.type == 'mouseup' || e.type == 'mousemove' || e.type == 'mouseover'|| e.type=='mouseout' || e.type=='mouseenter' || e.type=='mouseleave') {
2690  			out.x = e.pageX;
2691  			out.y = e.pageY;
2692  		}
2693  		return out;
2694  	};
2695
2696  function addBuildplayerFunctions(AblePlayer) {
2697  	AblePlayer.prototype.injectPlayerCode = function() {
2698
2699  		// create and inject surrounding HTML structure
2700  		// If iOS & video:
2701  		// iOS does not support any of the player's functionality - everything plays in its own player
2702  		// Therefore, AblePlayer is not loaded & all functionality is disabled
2703  		// (this all determined. If this is iOS && video, this function is never called)
2704
2705  		var captionsContainer;
2706  		// Wrappers, from inner to outer:
2707  		// $mediaContainer - contains the original media element
2708  		// $ableDiv - contains the media player and all its objects (e.g., captions, controls, descriptions)
2709  		// $ableWrapper - contains additional widgets (e.g., transcript window, sign window)
2710  		this.$mediaContainer = this.$media.wrap('<div class="able-media-container"></div>').parent();
2711  		this.$ableDiv = this.$mediaContainer.wrap('<div class="able"></div>').parent();
2712  		this.$ableWrapper = this.$ableDiv.wrap('<div class="able-wrapper"></div>').parent();
2713  		this.$ableWrapper.addClass('able-skin-' + this.skin);
2714
2715  		if (this.mediaType === 'video') {
2716  			// youtube adds its own big play button
2717  			// don't show ours *unless* video has a poster attribute
2718  			// (which obstructs the YouTube poster & big play button)
2719  			if (this.player !== 'youtube' || this.hasPoster) {
2720  				this.injectBigPlayButton();
2721  			}
2722  		}
2723
2724  		// add container that captions or description will be appended to
2725  		// Note: new Jquery object must be assigned _after_ wrap, hence the temp captionsContainer variable
2726  		captionsContainer = $('<div>');
2727  		if (this.mediaType === 'video') {
2728  			captionsContainer.addClass('able-vidcap-container');
2729  		} else if (this.mediaType === 'audio') {
2730  			captionsContainer.addClass('able-audcap-container');
2731  			// hide this by default. It will be shown if captions are available
2732  			captionsContainer.addClass('captions-off');
2733  		}
2734
2735  		this.injectPlayerControlArea(); // this may need to be injected after captions???
2736  		this.$captionsContainer = this.$mediaContainer.wrap(captionsContainer).parent();
2737  		this.injectAlert(this.$ableDiv);
2738  		this.injectPlaylist();
2739  		this.injectAudioPoster();
2740  		// Do this last, as it should be prepended to the top of this.$ableDiv
2741  		// after everything else has prepended
2742  		this.injectOffscreenHeading();
2743  	};
2744
2745  	AblePlayer.prototype.injectAudioPoster = function() {
2746  		if ( this.mediaType === 'audio' && this.hasPoster ) {
2747  			const audioPoster = purify.sanitize(this.audioPoster);
2748  			const audioPosterAlt = purify.sanitize(this.audioPosterAlt);
2749  			let audioPosterImg = document.createElement( 'img' );
2750  			audioPosterImg.setAttribute( 'src', audioPoster );
2751  			audioPosterImg.setAttribute( 'alt', audioPosterAlt );
2752  			this.$audioWrapper = this.$playerDiv.wrap( '<div class="able-audio-wrapper">' ).parent();
2753  			this.$audioWrapper.prepend( audioPosterImg );
2754  		}
2755  	};
2756
2757  	AblePlayer.prototype.injectOffscreenHeading = function () {
2758
2759  		// Inject an offscreen heading to the media container.
2760  		// If heading hasn't already been manually defined via data-heading-level,
2761  		// automatically assign a level that is one level deeper than the closest parent heading
2762  		// as determined by getNextHeadingLevel()
2763  		var headingType;
2764  		if (this.playerHeadingLevel == '0') ; else {
2765  			if (typeof this.playerHeadingLevel === 'undefined') {
2766  				this.playerHeadingLevel = this.getNextHeadingLevel(this.$ableDiv); // returns in integer 1-6
2767  			}
2768  			headingType = 'h' + this.playerHeadingLevel.toString();
2769  			this.$headingDiv = $('<' + headingType + '>');
2770  			this.$ableDiv.prepend(this.$headingDiv);
2771  			this.$headingDiv.addClass('able-offscreen');
2772  			this.$headingDiv.text( this.translate( 'playerHeading', 'Media player' ) );
2773  		}
2774  	};
2775
2776  	AblePlayer.prototype.injectBigPlayButton = function () {
2777
2778  		var thisObj = this;
2779
2780  		this.$bigPlayButton = $('<button>', {
2781  			'class': 'able-big-play-button',
2782  			'aria-hidden': false,
2783  			'aria-label': this.translate( 'play', 'Play' ),
2784  			'type': 'button',
2785  			'tabindex': 0
2786  		});
2787
2788  		this.getIcon( this.$bigPlayButton, 'play' );
2789
2790  		this.$bigPlayButton.on( 'click', function () {
2791  			thisObj.handlePlay();
2792  		});
2793
2794  		this.$mediaContainer.append(this.$bigPlayButton);
2795  	};
2796
2797  	AblePlayer.prototype.injectPlayerControlArea = function () {
2798
2799  		this.$playerDiv = $('<div>', {
2800  			'class' : 'able-player',
2801  			'role' : 'region',
2802  			'aria-label' : ( 'audio' === this.mediaType ) ? this.translate( 'audioPlayer', 'audio player' ) : this.translate( 'videoPlayer', 'video player' )
2803  		});
2804  		this.$playerDiv.addClass('able-' + this.mediaType);
2805  		if (this.hasPlaylist && this.showNowPlaying) {
2806  			this.$nowPlayingDiv = $('<div>',{
2807  				'class' : 'able-now-playing',
2808  				'aria-live' : 'assertive',
2809  				'aria-atomic': 'true'
2810  			});
2811  		}
2812  		this.$controllerDiv = $('<div>',{
2813  			'class' : 'able-controller'
2814  		});
2815  		this.$controllerDiv.addClass('able-' + this.iconColor + '-controls');
2816
2817  		this.$statusBarDiv = $('<div>',{
2818  			'class' : 'able-status-bar'
2819  		});
2820  		this.$timer = $('<span>',{
2821  			'class' : 'able-timer'
2822  		});
2823  		this.$elapsedTimeContainer = $('<span>',{
2824  			'class': 'able-elapsedTime',
2825  			text: '0:00'
2826  		});
2827  		this.$durationContainer = $('<span>',{
2828  			'class': 'able-duration'
2829  		});
2830  		this.$durationSeparator = $('<span>',{
2831  			'class': 'able-timer-separator',
2832  			'text': ' / '
2833  		});
2834  		this.$timer.append(this.$elapsedTimeContainer).append(this.$durationSeparator).append(this.$durationContainer);
2835
2836  		this.$speed = $('<span>',{
2837  			'class' : 'able-speed',
2838  			'aria-live' : 'assertive',
2839  			'aria-atomic' : 'true'
2840  		}).text(this.translate( 'speed', 'Speed' ) + ': 1x');
2841
2842  		this.$status = $('<span>',{
2843  			'class' : 'able-status',
2844  			'aria-live' : 'polite'
2845  		});
2846
2847  		// Put everything together.
2848  		this.$statusBarDiv.append(this.$timer, this.$speed, this.$status);
2849  		if (this.showNowPlaying) {
2850  			this.$playerDiv.append(this.$nowPlayingDiv, this.$controllerDiv, this.$statusBarDiv);
2851  		} else {
2852  			this.$playerDiv.append(this.$controllerDiv, this.$statusBarDiv);
2853  		}
2854
2855  		if (this.mediaType === 'video') {
2856  			// the player controls go after the media & captions
2857  			this.$ableDiv.append(this.$playerDiv);
2858  		} else {
2859  			// the player controls go before the media & captions
2860  			this.$ableDiv.prepend(this.$playerDiv);
2861  		}
2862  	};
2863
2864  	AblePlayer.prototype.injectTextDescriptionArea = function () {
2865
2866  		// create a div for writing description text
2867  		this.$descDiv = $('<div>',{
2868  			'class': 'able-descriptions'
2869  		});
2870  		// Add ARIA so description will be announced by screen readers
2871  		// Later (in description.js > showDescription()),
2872  		// if browser supports Web Speech API and this.descMethod === 'browser'
2873  		// these attributes will be removed
2874  		this.$descDiv.attr({
2875  			'aria-live': 'assertive',
2876  			'aria-atomic': 'true'
2877  		});
2878  		// Start off with description hidden.
2879  		// It will be exposed conditionally within description.js > initDescription()
2880  		this.$descDiv.hide();
2881  		this.$ableDiv.append(this.$descDiv);
2882  	};
2883
2884  	AblePlayer.prototype.getDefaultWidth = function(which) {
2885  		let viewportMaxwidth = window.innerWidth;
2886  		// return default width of resizable elements
2887  		// these values are somewhat arbitrary, but seem to result in good usability
2888  		// if users disagree, they can resize (and resposition) them
2889  		if (which === 'transcript') {
2890  			return ( viewportMaxwidth <= 450 ) ? viewportMaxwidth : 450;
2891  		} else if (which === 'sign') {
2892  			return ( viewportMaxwidth <= 400 ) ? viewportMaxwidth : 400;
2893  		}
2894  	};
2895
2896  	/**
2897  	 * Reposition draggable windows when switched into fullscreen.
2898  	 *
2899  	 * @param {string} which 'transcript' or 'sign'.
2900  	 */
2901  	AblePlayer.prototype.rePositionDraggableWindow = function (which) {
2902
2903  		let preferences, $window;
2904  		preferences = this.getPref();
2905  		$window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow;
2906  		console.log( $window );
2907  		if ( which === 'transcript' && $window ) {
2908  			if (typeof preferences.transcript !== 'undefined') {
2909  				this.prevTranscriptPosition = preferences.transcript;
2910  			}
2911  			$window.css({
2912  				'top': 0,
2913  				'left': 0
2914  			});
2915  		} else if ( 'sign' === which && $window ) {
2916  			if (typeof preferences.sign !== 'undefined') {
2917  				this.prevSignPosition = preferences.sign;
2918  			}
2919  			$window.css({
2920  				'top': 0,
2921  				'right': 0,
2922  				'left': 'auto'
2923  			});
2924  		}
2925  	};
2926
2927  	AblePlayer.prototype.positionDraggableWindow = function (which, width) {
2928
2929  		// which is either 'transcript' or 'sign'
2930  		var preferences, preferencePos, $window, windowPos, viewportWidth, windowWidth;
2931
2932  		preferences = this.getPref();
2933  		$window = ( which === 'transcript' ) ? this.$transcriptArea : this.$signWindow;
2934  		if ( ! $window ) {
2935  			return;
2936  		}
2937  		if (which === 'transcript') {
2938  			if (typeof preferences.transcript !== 'undefined') {
2939  				preferencePos = preferences.transcript;
2940  			}
2941  			if ( this.prevTranscriptPosition ) {
2942  				preferencePos = this.prevTranscriptPosition;
2943  				this.prevTranscriptPosition = false;
2944  			}
2945  		} else if (which === 'sign') {
2946  			if (typeof preferences.sign !== 'undefined') {
2947  				preferencePos = preferences.sign;
2948  			}
2949  			if ( this.prevSignPosition ) {
2950  				preferencePos = this.prevSignPosition;
2951  				this.prevSignPosition = false;
2952  			}
2953  		}
2954  		if (typeof preferencePos !== 'undefined' && !($.isEmptyObject(preferencePos))) {
2955  			// position window using stored values from preferences
2956  			$window.css({
2957  				'position': preferencePos['position'],
2958  				'width': preferencePos['width'],
2959  				'z-index': preferencePos['zindex']
2960  			});
2961  			if (preferencePos['position'] === 'absolute') {
2962  				$window.css({
2963  					'top': preferencePos['top'],
2964  					'left': preferencePos['left']
2965  				});
2966  				// Check whether the window is above the top of the viewport.
2967  				let topPosition = $window.offset().top;
2968  				let leftPosition = $window.offset().left;
2969  				viewportWidth = window.innerWidth;
2970  				windowWidth = $window.width();
2971  				if ( topPosition < 0 ) {
2972  					$window.css({
2973  						'top': preferencePos['top'] - topPosition
2974  					});
2975  				}
2976  				// If draggable window is off screen to the left.
2977  				if ( leftPosition < 0 && ! this.restoringAfterFullscreen ) {
2978  					console.log( leftPosition );
2979  					$window.css({
2980  						'left': preferencePos['left'] - leftPosition
2981  					});
2982  				}
2983  				// If draggable window is off screen to the right.
2984  				if ( viewportWidth - leftPosition < 30 ) {
2985  					$window.css({
2986  						'left': viewportWidth - windowWidth
2987  					});
2988  				}
2989  			}
2990  			// since preferences are not page-specific, z-index needs may vary across different pages
2991  			this.updateZIndex(which);
2992  		} else {
2993  			// position window using default values
2994  			windowPos = this.getOptimumPosition(which, width);
2995  			if (typeof width === 'undefined') {
2996  				width = this.getDefaultWidth(which);
2997  			}
2998  			$window.css({
2999  				'position': windowPos[0],
3000  				'width': width,
3001  				'z-index': windowPos[3]
3002  			});
3003  			if (windowPos[0] === 'absolute') {
3004  				$window.css({
3005  					'top': windowPos[1] + 'px',
3006  					'left': windowPos[2] + 'px',
3007  				});
3008  			}
3009  		}
3010  	};
3011
3012  	AblePlayer.prototype.getOptimumPosition = function (targetWindow, targetWidth) {
3013
3014  		// returns optimum position for targetWindow, as an array with the following structure:
3015  		// 0 - CSS position ('absolute' or 'relative')
3016  		// 1 - top
3017  		// 2 - left
3018  		// 3 - zindex (if not default)
3019  		// targetWindow is either 'transcript' or 'sign'
3020  		// if there is room to the right of the player, position element there
3021  		// else if there is room the left of the player, position element there
3022  		// else position element beneath player
3023
3024  		var gap, position, ableWidth, ableOffset, ableLeft, windowWidth, otherWindowWidth;
3025
3026  		if (typeof targetWidth === 'undefined') {
3027  			targetWidth = this.getDefaultWidth(targetWindow);
3028  		}
3029
3030  		gap = 5; // number of pixels to preserve between Able Player objects
3031  		position = []; // position, top, left
3032
3033  		ableWidth = this.$ableDiv.width();
3034  		ableOffset = this.$ableDiv.offset();
3035  		ableLeft = ableOffset.left;
3036  		windowWidth = $(window).width();
3037  		otherWindowWidth = 0; // width of other visiable draggable windows will be added to this
3038
3039  		if (targetWindow === 'transcript') {
3040  			// If placing the transcript window, check position of sign window first.
3041  			if (typeof this.$signWindow !== 'undefined' && (this.$signWindow.is(':visible'))) {
3042  				otherWindowWidth = this.$signWindow.width() + gap;
3043  			}
3044  		} else if (targetWindow === 'sign') {
3045  			// If placing the sign window, check position of transcript window first.
3046  			if (typeof this.$transcriptArea !== 'undefined' && (this.$transcriptArea.is(':visible'))) {
3047  				otherWindowWidth = this.$transcriptArea.width() + gap;
3048  			}
3049  		}
3050  		if (targetWidth < (windowWidth - (ableLeft + ableWidth + gap + otherWindowWidth))) {
3051  			// there's room to the left of $ableDiv
3052  			position[0] = 'absolute';
3053  			position[1] = 0;
3054  			position[2] = ableWidth + otherWindowWidth + gap;
3055  		} else if (targetWidth + gap < ableLeft) {
3056  			// there's room to the right of $ableDiv
3057  			position[0] = 'absolute';
3058  			position[1] = 0;
3059  			position[2] = ableLeft - targetWidth - gap;
3060  		} else {
3061  			// position element below $ableDiv
3062  			position[0] = 'relative';
3063  			// no need to define top, left, or z-index
3064  		}
3065  		return position;
3066  	};
3067
3068  	AblePlayer.prototype.injectAlert = function ($container) {
3069  		// inject two alerts, one visible for all users and one for screen reader users only
3070  		this.$alertBox = $('<div role="alert"></div>');
3071  		this.$alertBox.addClass('able-alert');
3072  		this.$alertBox.hide();
3073
3074  		var $alertText = $( '<span></span>' );
3075  		$alertText.appendTo(this.$alertBox);
3076
3077  		var $alertDismiss = $('<button type="button"></button>' );
3078  		$alertDismiss.attr( 'aria-label', this.translate( 'dismissButton', 'Dismiss' ) );
3079  		$alertDismiss.text( '×' );
3080  		$alertDismiss.appendTo(this.$alertBox);
3081
3082  		$alertDismiss.on( 'click', function(e) {
3083  			$(this).parent('div').hide();
3084  		});
3085
3086  		this.$alertBox.appendTo($container);
3087
3088  		if ( ! this.$srAlertBox ) {
3089  			this.$srAlertBox = $('<div role="alert"></div>');
3090  			this.$srAlertBox.addClass('able-screenreader-alert');
3091  			this.$srAlertBox.appendTo($container);
3092  		}
3093  	};
3094
3095  	AblePlayer.prototype.injectPlaylist = function () {
3096
3097  		if (this.playlistEmbed === true) {
3098  			// move playlist into player, immediately before statusBarDiv
3099  			var playlistClone = this.$playlistDom.clone();
3100  			playlistClone.insertBefore(this.$statusBarDiv);
3101  			// Update to the new playlist copy.
3102  			this.$playlist = playlistClone.find('li');
3103  		}
3104  	};
3105
3106  	AblePlayer.prototype.createPopup = function (which, tracks) {
3107
3108  		// Create popup menu and append to player
3109  		// 'which' parameter is either 'captions', 'chapters', 'prefs', 'transcript-window' or 'sign-window'
3110  		// 'tracks', if provided, is a list of tracks to be used as menu items
3111
3112  		var thisObj, $menu, includeMenuItem, i, $menuItem, prefCat, whichPref, hasDefault, track,
3113  		windowOptions, $thisItem, $prevItem, $nextItem, hasDescription, hasTranscript;
3114
3115  		thisObj = this;
3116
3117  		$menu = $('<ul>',{
3118  			'id': this.mediaId + '-' + which + '-menu',
3119  			'class': 'able-popup',
3120  			'role': 'menu'
3121  		}).hide();
3122
3123  		if (which === 'captions') {
3124  			$menu.addClass('able-popup-captions');
3125  		}
3126
3127  		// Populate menu with menu items
3128  		if (which === 'prefs') {
3129  			if (this.prefCats.length > 1) {
3130  				for (i = 0; i < this.prefCats.length; i++) {
3131  					prefCat = this.prefCats[i];
3132  					hasDescription = ( thisObj.hasDescTracks || thisObj.hasOpenDesc || thisObj.hasClosedDesc ) ? true : false;
3133  					hasTranscript  = ( thisObj.transcriptType === null ) ? false : true;
3134
3135  					// If this player does not have descriptions or transcripts, do not output that option preferences.
3136  					if ( prefCat === 'descriptions' && ! hasDescription || prefCat === 'transcript' && ! hasTranscript ) {
3137  						continue;
3138  					}
3139  					$menuItem = $('<li></li>',{
3140  						'role': 'menuitem',
3141  						'tabindex': '-1'
3142  					});
3143  					if (prefCat === 'captions') {
3144  						$menuItem.text( this.translate( 'prefMenuCaptions', 'Captions' ) );
3145  					} else if (prefCat === 'descriptions') {
3146  						$menuItem.text( this.translate( 'prefMenuDescriptions', 'Descriptions' ) );
3147  					} else if (prefCat === 'keyboard') {
3148  						$menuItem.text( this.translate( 'prefMenuKeyboard', 'Keyboard' ) );
3149  					} else if (prefCat === 'transcript') {
3150  						$menuItem.text( this.translate( 'prefMenuTranscript', 'Transcript' ) );
3151  					}
3152  					$menuItem.on('click',function() {
3153  						whichPref = $(this).text();
3154  						thisObj.showingPrefsDialog = true;
3155  						thisObj.setFullscreen(false);
3156  						if (whichPref === thisObj.translate( 'prefMenuCaptions', 'Captions' ) ) {
3157  							thisObj.captionPrefsDialog.show();
3158  						} else if (whichPref === thisObj.translate( 'prefMenuDescriptions', 'Descriptions' ) ) {
3159  							thisObj.descPrefsDialog.show();
3160  						} else if (whichPref === thisObj.translate( 'prefMenuKeyboard', 'Keyboard' ) ) {
3161  							thisObj.keyboardPrefsDialog.show();
3162  						} else if (whichPref === thisObj.translate( 'prefMenuTranscript', 'Transcript' ) ) {
3163  							thisObj.transcriptPrefsDialog.show();
3164  						}
3165  						thisObj.closePopups();
3166  						thisObj.showingPrefsDialog = false;
3167  					});
3168  					$menu.append($menuItem);
3169  				}
3170  				this.$prefsButton.attr('data-prefs-popup','menu');
3171  			} else if (this.prefCats.length == 1) {
3172  				// only 1 category, so don't create a popup menu.
3173  				// Instead, open dialog directly when user clicks Prefs button
3174  				this.$prefsButton.attr('data-prefs-popup',this.prefCats[0]);
3175  			}
3176  		} else if (which === 'captions' || which === 'chapters') {
3177  			hasDefault = false;
3178  			for (i = 0; i < tracks.length; i++) {
3179  				track = tracks[i];
3180  				if (which === 'captions' && this.player === 'html5' && typeof track.cues === 'undefined') {
3181  					includeMenuItem = false;
3182  				} else {
3183  					includeMenuItem = true;
3184  				}
3185  				if (includeMenuItem) {
3186  					$menuItem = $('<li></li>',{
3187  						'role': 'menuitemradio',
3188  						'tabindex': '-1',
3189  						'lang': track.language
3190  					});
3191  					if (track.def && this.prefCaptions == 1) {
3192  						$menuItem.attr('aria-checked','true');
3193  						hasDefault = true;
3194  					} else {
3195  						$menuItem.attr('aria-checked','false');
3196  					}
3197  					// Get a label using track data
3198  					if (which == 'captions') {
3199  						$menuItem.text(track.label);
3200  						$menuItem.on('click',this.getCaptionClickFunction(track));
3201  					} else if (which == 'chapters') {
3202  						$menuItem.text(this.flattenCueForCaption(track) + ' - ' + this.formatSecondsAsColonTime(track.start));
3203  						$menuItem.on('click',this.getChapterClickFunction(track.start));
3204  					}
3205  					$menu.append($menuItem);
3206  				}
3207  			}
3208  			if (which === 'captions') {
3209  				// add a 'captions off' menu item
3210  				$menuItem = $('<li></li>',{
3211  					'role': 'menuitemradio',
3212  					'tabindex': '-1',
3213  				}).text( this.translate( 'captionsOff', 'Captions off' ) );
3214  				if (this.prefCaptions === 0) {
3215  					$menuItem.attr('aria-checked','true');
3216  					hasDefault = true;
3217  				} else {
3218  					$menuItem.attr('aria-checked','false');
3219  				}
3220  				$menuItem.on('click',this.getCaptionOffFunction());
3221  				$menu.append($menuItem);
3222  			}
3223  		} else if (which === 'transcript-window' || which === 'sign-window') {
3224  			windowOptions = [];
3225  			windowOptions.push({
3226  				'name': 'move',
3227  				'label': this.translate( 'windowMove', 'Move' )
3228  			});
3229  			windowOptions.push({
3230  				'name': 'resize',
3231  				'label': this.translate( 'windowResize', 'Resize' )
3232  			});
3233  			windowOptions.push({
3234  				'name': 'close',
3235  				'label': this.translate( 'closeButtonLabel', 'Close' )
3236  			});
3237  			for (i = 0; i < windowOptions.length; i++) {
3238  				$menuItem = $('<li></li>',{
3239  					'role': 'menuitem',
3240  					'tabindex': '-1',
3241  					'data-choice': windowOptions[i].name
3242  				});
3243  				$menuItem.text(windowOptions[i].label);
3244  				$menuItem.on('click',function(e) {
3245  					e.stopPropagation();
3246  					if (typeof e.button !== 'undefined' && e.button !== 0) {
3247  						// this was a mouse click (if click is triggered by keyboard, e.button is undefined)
3248  						// and the button was not a left click (left click = 0)
3249  						// therefore, ignore this click
3250  						return false;
3251  					}
3252  					if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
3253  						thisObj.windowMenuClickRegistered = true;
3254  						thisObj.handleMenuChoice(which.substring(0, which.indexOf('-')), $(this).attr('data-choice'), e);
3255  					}
3256  				});
3257  				$menu.append($menuItem);
3258  			}
3259  		}
3260  		// assign default item, if there isn't one already
3261  		if (which === 'captions' && !hasDefault) {
3262  			// check the menu item associated with the default language
3263  			// as determined in control.js > syncTrackLanguages()
3264  			if ($menu.find('li[lang=' + this.captionLang + ']')) {
3265  				// a track exists for the default language. Check that item in the menu
3266  				$menu.find('li[lang=' + this.captionLang + ']').attr('aria-checked','true');
3267  			} else {
3268  				// check the last item (captions off)
3269  				$menu.find('li').last().attr('aria-checked','true');
3270  			}
3271  		} else if (which === 'chapters') {
3272  			if ($menu.find('li:contains("' + this.defaultChapter + '")')) {
3273  				$menu.find('li:contains("' + this.defaultChapter + '")').attr('aria-checked','true').addClass('able-focus');
3274  			} else {
3275  				$menu.find('li').first().attr('aria-checked','true').addClass('able-focus');
3276  			}
3277  		}
3278  		// add keyboard handlers for navigating within popups
3279  		$menu.on('keydown',function (e) {
3280
3281  			$thisItem = $(this).find('li:focus');
3282  			if ($thisItem.is(':first-child')) {
3283  				// this is the first item in the menu
3284  				$prevItem = $(this).find('li').last(); // wrap to bottom
3285  				$nextItem = $thisItem.next();
3286  			} else if ($thisItem.is(':last-child')) {
3287  				// this is the last Item
3288  				$prevItem = $thisItem.prev();
3289  				$nextItem = $(this).find('li').first(); // wrap to top
3290  			} else {
3291  				$prevItem = $thisItem.prev();
3292  				$nextItem = $thisItem.next();
3293  			}
3294  			if (e.key === 'Tab') {
3295  				if (e.shiftKey) {
3296  					$thisItem.removeClass('able-focus');
3297  					$prevItem.trigger('focus').addClass('able-focus');
3298  				} else {
3299  					$thisItem.removeClass('able-focus');
3300  					$nextItem.trigger('focus').addClass('able-focus');
3301  				}
3302  			} else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
3303  				$thisItem.removeClass('able-focus');
3304  				$nextItem.trigger('focus').addClass('able-focus');
3305  			} else if (e.key == 'ArrowUp' || e.key === 'ArrowLeft') {
3306  				$thisItem.removeClass('able-focus');
3307  				$prevItem.trigger('focus').addClass('able-focus');
3308  			} else if (e.key === ' ' || e.key === 'Enter') {
3309  				$thisItem.trigger( 'click' );
3310  			} else if (e.key === 'Escape') {
3311  				$thisItem.removeClass('able-focus');
3312  				thisObj.closePopups();
3313  				e.stopPropagation;
3314  			}
3315  			e.preventDefault();
3316  		});
3317  		this.$controllerDiv.append($menu);
3318  		return $menu;
3319  	};
3320
3321  	AblePlayer.prototype.closePopups = function () {
3322
3323  		var thisObj = this;
3324
3325  		if (this.chaptersPopup && this.chaptersPopup.is(':visible')) {
3326  			this.chaptersPopup.hide();
3327  			this.$chaptersButton.attr('aria-expanded','false').trigger('focus');
3328  		}
3329  		if (this.captionsPopup && this.captionsPopup.is(':visible')) {
3330  			this.captionsPopup.hide();
3331  			this.$ccButton.attr('aria-expanded', 'false');
3332  			this.waitThenFocus(this.$ccButton);
3333  		}
3334  		if (this.prefsPopup && this.prefsPopup.is(':visible') && !this.hidingPopup) {
3335  			this.hidingPopup = true; // stopgap to prevent popup from re-opening again on keypress
3336  			this.prefsPopup.hide();
3337  			// restore menu items to their original state
3338  			this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
3339  			this.$prefsButton.attr('aria-expanded', 'false');
3340  			if (!this.showingPrefsDialog) {
3341  				this.waitThenFocus(thisObj.$prefsButton);
3342  			}
3343  			// wait briefly, then reset hidingPopup
3344  			setTimeout(function() {
3345  				thisObj.hidingPopup = false;
3346  			},100);
3347  		}
3348  		if (this.$volumeSlider && this.$volumeSlider.is(':visible')) {
3349  			this.$volumeSlider.hide().attr('aria-hidden','true');
3350  			this.$volumeButton.attr('aria-expanded', 'false').trigger('focus');
3351  		}
3352  		if (this.$transcriptPopup && this.$transcriptPopup.is(':visible')) {
3353  			this.hidingPopup = true;
3354  			this.$transcriptPopup.hide();
3355  			// restore menu items to their original state
3356  			this.$transcriptPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
3357  			this.$transcriptPopupButton.attr('aria-expanded','false').trigger('focus');
3358  			// wait briefly, then reset hidingPopup
3359  			setTimeout(function() {
3360  				thisObj.hidingPopup = false;
3361  			},100);
3362  		}
3363  		if (this.$signPopup && this.$signPopup.is(':visible')) {
3364  			this.$signPopup.hide();
3365  			// restore menu items to their original state
3366  			this.$signPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
3367  			this.$signPopupButton.attr('aria-expanded','false').trigger('focus');
3368  		}
3369  	};
3370
3371  	AblePlayer.prototype.setupPopups = function (which) {
3372
3373  		// Create and fill in the popup menu forms for various controls.
3374  		// parameter 'which' is passed if refreshing content of an existing popup ('captions' or 'chapters')
3375  		// If which is undefined, automatically setup 'captions', 'chapters', and 'prefs' popups
3376  		// However, only setup 'transcript-window' and 'sign-window' popups if passed as value of which
3377  		var popups, i, tracks;
3378
3379  		popups = [];
3380  		if (typeof which === 'undefined') {
3381  			popups.push('prefs');
3382  		}
3383
3384  		if (which === 'captions' || (typeof which === 'undefined')) {
3385  			if (this.captions.length > 0) {
3386  				popups.push('captions');
3387  			}
3388  		}
3389  		if (which === 'chapters' || (typeof which === 'undefined')) {
3390  			if (this.chapters.length > 0 && this.useChaptersButton) {
3391  				popups.push('chapters');
3392  			}
3393  		}
3394  		if (which === 'transcript-window' && this.transcriptType === 'popup') {
3395  			popups.push('transcript-window');
3396  		}
3397  		if (which === 'sign-window' && this.hasSignLanguage) {
3398  			popups.push('sign-window');
3399  		}
3400  		if (popups.length > 0) {
3401  			for (i=0; i<popups.length; i++) {
3402  				var popup = popups[i];
3403  				if (popup == 'prefs') {
3404  					this.prefsPopup = this.createPopup('prefs');
3405  				} else if (popup == 'captions') {
3406  					if (typeof this.captionsPopup === 'undefined' || !this.captionsPopup) {
3407  						this.captionsPopup = this.createPopup('captions',this.captions);
3408  					}
3409  				} else if (popup == 'chapters') {
3410  					if (this.selectedChapters) {
3411  						tracks = this.selectedChapters.cues;
3412  					} else if (this.chapters.length >= 1) {
3413  						tracks = this.chapters[0].cues;
3414  					} else {
3415  						tracks = [];
3416  					}
3417  					if (typeof this.chaptersPopup === 'undefined' || !this.chaptersPopup) {
3418  						this.chaptersPopup = this.createPopup('chapters',tracks);
3419  					}
3420  				} else if (popup == 'transcript-window') {
3421  					return this.createPopup('transcript-window');
3422  				} else if (popup == 'sign-window') {
3423  					return this.createPopup('sign-window');
3424  				}
3425  			}
3426  		}
3427  	};
3428
3429  	AblePlayer.prototype.provideFallback = function() {
3430
3431  		// provide fallback in case of a critical error building the player
3432  		// to test, set data-test-fallback to either of the following values:
3433  		// 1 = emulate failure to build Able Player
3434  		// 2 = emulate browser that doesn't support HTML5 media
3435
3436  		var i, $fallback;
3437
3438  		if (this.usingFallback) {
3439  			// fallback has already been implemented.
3440  			// stopgap to prevent this function from executing twice on the same media element
3441  			return;
3442  		} else {
3443  			this.usingFallback = true;
3444  		}
3445
3446  		if (!this.testFallback) {
3447  			// this is not a test.
3448  			// an actual error has resulted in this function being called.
3449  			// use scenario 1
3450  			this.testFallback = 1;
3451  		}
3452
3453  		if (typeof this.$media === 'undefined') {
3454  			// this function has been called prior to initialize.js > reinitialize()
3455  			// before doing anything, need to create the jQuery media object
3456  			this.$media = $(this.media);
3457  		}
3458
3459  		// get/assign an id for the media element
3460  		if (this.$media.attr('id')) {
3461  			this.mediaId = this.$media.attr('id');
3462  		} else {
3463  			this.mediaId = 'media' + Math.floor(Math.random() * 1000000000).toString();
3464  		}
3465
3466  		// check whether element has nested fallback content
3467  		this.hasFallback = false;
3468  		if (this.$media.children().length) {
3469  			i = 0;
3470  			while (i < this.$media.children().length && !this.hasFallback) {
3471  				if (!(this.$media.children()[i].tagName === 'SOURCE' ||
3472  					this.$media.children()[i].tagName === 'TRACK')) {
3473  					// this element is something other than <source> or <track>
3474  					this.hasFallback = true;
3475  				}
3476  				i++;
3477  			}
3478  		}
3479  		if (!this.hasFallback) {
3480  			// the HTML code does not include any nested fallback content
3481  			// inject our own
3482  			// NOTE: this message is not translated, since fallback may be needed
3483  			// due to an error loading the translation file
3484  			// This will only be needed on very rare occasions, so English is ok.
3485  			$fallback = $('<p>').text('Media player unavailable.');
3486  			this.$media.append($fallback);
3487  		}
3488
3489  		// get height and width attributes, if present
3490  		// and add them to a style attribute
3491  		if (this.$media.attr('width')) {
3492  			this.$media.css('width',this.$media.attr('width') + 'px');
3493  		}
3494  		if (this.$media.attr('height')) {
3495  			this.$media.css('height',this.$media.attr('height') + 'px');
3496  		}
3497  		// Remove data-able-player attribute
3498  		this.$media.removeAttr('data-able-player');
3499
3500  		// Add controls attribute (so browser will add its own controls)
3501  		this.$media.prop('controls',true);
3502
3503  		if (this.testFallback == 2) {
3504
3505  			// emulate browser failure to support HTML5 media by changing the media tag name
3506  			// browsers should display the supported content that's nested inside
3507  			$(this.$media).replaceWith($('<foobar id="foobar-' + this.mediaId + '">'));
3508  			this.$newFallbackElement = $('#foobar-' + this.mediaId);
3509
3510  			// append all children from the original media
3511  			if (this.$media.children().length) {
3512  				i = this.$media.children().length - 1;
3513  				while (i >= 0) {
3514  					this.$newFallbackElement.prepend($(this.$media.children()[i]));
3515  					i--;
3516  				}
3517  			}
3518  			if (!this.hasFallback) {
3519  				// inject our own fallback content, defined above
3520  				this.$newFallbackElement.append($fallback);
3521  			}
3522  		} else {
3523  			console.warn("Able Player encountered a problem, falling back to browser's HTML5 player.");
3524  		}
3525  		return;
3526  	};
3527
3528  	AblePlayer.prototype.calculateControlLayout = function () {
3529
3530  		// Calculates the layout for controls based on media and options.
3531  		// Returns an array with 4 keys (for legacy skin) or 2 keys (for 2020 skin)
3532  		// Keys are the following order:
3533  		// 0 = Top left
3534  		// 1 = Top right
3535  		// 2 = Bottom left (legacy skin only)
3536  		// 3 = Bottom right (legacy skin only)
3537  		// Each key contains an array of control names to put in that section.
3538
3539  		var controlLayout, playbackSupported, numA11yButtons;
3540
3541  		controlLayout = [];
3542  		controlLayout[0] = [];
3543  		controlLayout[1] = [];
3544  		if (this.skin === 'legacy') {
3545  			controlLayout[2] = [];
3546  			controlLayout[3] = [];
3547  		}
3548
3549  		controlLayout[0].push('play');
3550  		controlLayout[0].push('restart');
3551  		controlLayout[0].push('rewind');
3552  		controlLayout[0].push('forward');
3553
3554  		if (this.skin === 'legacy') {
3555  			controlLayout[1].push('seek');
3556  		}
3557
3558  		if (this.hasPlaylist) {
3559  			if (this.skin === 'legacy') {
3560  				controlLayout[0].push('previous');
3561  				controlLayout[0].push('next');
3562  			} else {
3563  				controlLayout[0].push('previous');
3564  				controlLayout[0].push('next');
3565  			}
3566  		}
3567
3568  		if (this.isPlaybackRateSupported()) {
3569  			playbackSupported = true;
3570  			if (this.skin === 'legacy') {
3571  				controlLayout[2].push('slower');
3572  				controlLayout[2].push('faster');
3573  			}
3574  		} else {
3575  			playbackSupported = false;
3576  		}
3577
3578  		numA11yButtons = 0;
3579  		if (this.hasCaptions) {
3580  			numA11yButtons++;
3581  			if (this.skin === 'legacy') {
3582  				controlLayout[2].push('captions');
3583  			} else {
3584  				controlLayout[1].push('captions');
3585  			}
3586  		}
3587  		if (this.hasSignLanguage) {
3588  			numA11yButtons++;
3589  			if (this.skin === 'legacy') {
3590  				controlLayout[2].push('sign');
3591  			} else {
3592  				controlLayout[1].push('sign');
3593  			}
3594  		}
3595  		if (this.mediaType === 'video') {
3596  			if (this.hasOpenDesc || this.hasClosedDesc) {
3597  				numA11yButtons++;
3598  				if (this.skin === 'legacy') {
3599  					controlLayout[2].push('descriptions');
3600  				} else {
3601  					controlLayout[1].push('descriptions');
3602  				}
3603  			}
3604  		}
3605  		if (this.transcriptType !== null && !(this.hideTranscriptButton)) {
3606  			numA11yButtons++;
3607  			if (this.skin === 'legacy') {
3608  				controlLayout[2].push('transcript');
3609  			} else {
3610  				controlLayout[1].push('transcript');
3611  			}
3612  		}
3613  		if (this.hasChapters && this.useChaptersButton) {
3614  			numA11yButtons++;
3615  			if (this.skin === 'legacy') {
3616  				controlLayout[2].push('chapters');
3617  			} else {
3618  				controlLayout[1].push('chapters');
3619  			}
3620  		}
3621
3622  		if (this.skin == '2020' && numA11yButtons > 0) {
3623  			controlLayout[1].push('pipe');
3624  		}
3625
3626  		if (playbackSupported && this.skin === '2020') {
3627  			controlLayout[1].push('faster');
3628  			controlLayout[1].push('slower');
3629  			controlLayout[1].push('pipe');
3630  		}
3631
3632  		if (this.skin === 'legacy') {
3633  			controlLayout[3].push('preferences');
3634  		} else {
3635  			controlLayout[1].push('preferences');
3636  		}
3637
3638  		if (this.mediaType === 'video' && this.allowFullscreen && this.nativeFullscreenSupported() ) {
3639  			if (this.skin === 'legacy') {
3640  				controlLayout[3].push('fullscreen');
3641  			} else {
3642  				controlLayout[1].push('fullscreen');
3643  			}
3644  		}
3645
3646  		if (this.browserSupportsVolume()) {
3647  			this.volumeButton = 'volume-' + this.getVolumeName(this.volume);
3648  			if (this.skin === 'legacy') {
3649  				controlLayout[1].push('volume');
3650  			} else {
3651  				controlLayout[1].push('volume');
3652  			}
3653  		} else {
3654  			this.volume = false;
3655  		}
3656  		return controlLayout;
3657  	};
3658
3659  	AblePlayer.prototype.addControls = function() {
3660
3661  		// determine which controls to show based on several factors:
3662  		// mediaType (audio vs video)
3663  		// availability of tracks (e.g., for closed captions & audio description)
3664  		// browser support (e.g., for sliders and speedButtons)
3665  		// user preferences (???)
3666  		// some controls are aligned on the left, and others on the right
3667
3668  		var thisObj, controlLayout, numSections,
3669  		i, j, controls, $controllerSpan, $sliderDiv, sliderLabel, $pipe, control,
3670  		buttonTitle, $newButton, buttonText, position, buttonHeight,
3671  		buttonWidth, buttonSide, controllerWidth, tooltipId, tooltipY, tooltipX,
3672  		tooltipWidth, tooltipStyle, tooltip, tooltipTimerId, captionLabel, popupMenuId;
3673
3674  		thisObj = this;
3675
3676  		// Initialize the layout into the this.controlLayout variable.
3677  		controlLayout = this.calculateControlLayout();
3678  		numSections = controlLayout.length;
3679
3680  		// add an empty div to serve as a tooltip
3681  		tooltipId = this.mediaId + '-tooltip';
3682  		this.$tooltipDiv = $('<div>',{
3683  			'id': tooltipId,
3684  			'class': 'able-tooltip'
3685  		}).hide();
3686  		this.$controllerDiv.append(this.$tooltipDiv);
3687
3688  		if (this.skin == '2020') {
3689  			// add a full-width seek bar
3690  			$sliderDiv = $('<div class="able-seekbar"></div>');
3691  			sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' );
3692  			this.$controllerDiv.append($sliderDiv);
3693  			this.seekBar = new AccessibleSlider($sliderDiv, this.duration, this.seekInterval, sliderLabel );
3694  		}
3695
3696  		// add a full-width seek bar
3697  		let $controlRow = $('<div class="able-control-row"></div>');
3698  		this.$controllerDiv.append($controlRow);
3699
3700  		for (i = 0; i < numSections; i++) {
3701  			controls = controlLayout[i];
3702  			if ((i % 2) === 0) { // even keys on the left
3703  				$controllerSpan = $('<div>',{
3704  					'class': 'able-left-controls'
3705  				});
3706  			} else { // odd keys on the right
3707  				$controllerSpan = $('<div>',{
3708  					'class': 'able-right-controls'
3709  				});
3710  			}
3711  			$controlRow.append($controllerSpan);
3712
3713  			for (j=0; j<controls.length; j++) {
3714  				control = controls[j];
3715  				if (control === 'seek') {
3716  					$sliderDiv = $('<div class="able-seekbar"></div>');
3717  					sliderLabel = this.mediaType + ' ' + this.translate( 'seekbarLabel', 'timeline' );
3718  					$controllerSpan.append($sliderDiv);
3719  					if (typeof this.duration === 'undefined' || this.duration === 0) {
3720  						// set arbitrary starting duration, and change it when duration is known
3721  						this.duration = 60;
3722  						// also set elapsed to 0
3723  						this.elapsed = 0;
3724  					}
3725  					this.seekBar = new AccessibleSlider( $sliderDiv, this.duration, this.seekInterval, sliderLabel );
3726  				} else if (control === 'pipe') {
3727  					$pipe = $('<span>', {
3728  						'aria-hidden': 'true',
3729  						'class': 'able-pipe',
3730  					});
3731  					$pipe.append('|');
3732  					$controllerSpan.append($pipe);
3733  				} else {
3734  					// this control is a button
3735  					buttonTitle = this.getButtonTitle(control);
3736
3737  					// Buttons consist of a <div role="button"> with an <svg> inside.
3738  					// We add aria-label to the button (but not title)
3739  					// This has been thoroughly tested and works well in all screen reader/browser combinations
3740  					// See https://github.com/ableplayer/ableplayer/issues/81
3741
3742  					// NOTE: Changed from <button> to <div role="button" as of 4.2.18
3743  					// because <button> elements are rendered poorly in high contrast mode
3744  					// in some OS/browser/plugin combinations
3745
3746  					// In 5.0.0, icons are always SVG, so the font & image icon edge cases are removed.
3747  					$newButton = $('<div>',{
3748  						'role': 'button',
3749  						'tabindex': '0',
3750  						'class': 'able-button-handler-' + control
3751  					});
3752
3753  					if (control === 'volume' || control === 'preferences' || control === 'captions') {
3754  						if (control == 'preferences') {
3755  							this.prefCats = this.getPreferencesGroups();
3756  							if (this.prefCats.length > 1) {
3757  								// Prefs button will trigger a menu
3758  								popupMenuId = this.mediaId + '-prefs-menu';
3759  								$newButton.attr({
3760  									'aria-controls': popupMenuId,
3761  									'aria-haspopup': 'menu',
3762  									'aria-expanded': 'false'
3763  								});
3764  							} else if (this.prefCats.length === 1) {
3765  								// Prefs button will trigger a dialog
3766  								$newButton.attr({
3767  									'aria-haspopup': 'dialog'
3768  								});
3769  							}
3770  						} else if (control === 'volume') {
3771  							popupMenuId = this.mediaId + '-volume-slider';
3772  							// volume slider popup is not a menu or a dialog
3773  							// therefore, using aria-expanded rather than aria-haspopup to communicate properties/state
3774  							$newButton.attr({
3775  								'aria-controls': popupMenuId,
3776  								'aria-expanded': 'false'
3777  							});
3778  						} else if (control === 'captions' && this.captions) {
3779  							if (this.captions.length > 1) {
3780  								$newButton.attr('aria-expanded', 'false');
3781  							} else {
3782  								$newButton.attr('aria-pressed', 'false');
3783  							}
3784  						}
3785  					}
3786  					var getControl = control;
3787  					if ( control === 'faster' && this.speedIcons === 'animals' ) {
3788  						getControl = 'rabbit';
3789  					}
3790  					if ( control === 'slower' && this.speedIcons === 'animals' ) {
3791  						getControl = 'turtle';
3792  					}
3793  					if ( control === 'volume' ) {
3794  						this.getIcon( $newButton, this.volumeButton );
3795  					} else {
3796  						if ( 'fullscreen' === getControl ) {
3797  							getControl = ( this.fullscreen ) ? 'fullscreen-collapse' : 'fullscreen-expand';
3798  						}
3799  						this.getIcon( $newButton, getControl );
3800  					}
3801
3802  					this.setText($newButton,buttonTitle);
3803  					// add an event listener that displays a tooltip on mouseenter or focus
3804  					$newButton.on('mouseenter focus',function(e) {
3805
3806  						// when entering a new tooltip, we can forget about hiding the previous tooltip.
3807  						// since the same tooltip div is used, it's location just changes.
3808  						clearTimeout(tooltipTimerId);
3809
3810  						buttonText = $(this).attr('aria-label');
3811  						// get position of this button
3812  						position = $(this).position();
3813  						buttonHeight = $(this).height();
3814  						buttonWidth = $(this).width();
3815  						// position() is expressed using top and left (of button);
3816  						// add right (of button) too, for convenience
3817  						controllerWidth = thisObj.$controllerDiv.width();
3818  						position.right = controllerWidth - position.left - buttonWidth;
3819
3820  						// The following formula positions tooltip below the button
3821  						// which allows the tooltip to be hoverable as per WCAG 2.x SC 1.4.13
3822  						// without obstructing the seekbar
3823  						tooltipY = position.top + buttonHeight + 5;
3824
3825  						if ($(this).parent().hasClass('able-right-controls')) {
3826  							// this control is on the right side
3827  							buttonSide = 'right';
3828  						} else {
3829  							// this control is on the left side
3830  							buttonSide = 'left';
3831  						}
3832  						// populate tooltip, then calculate its width before showing it
3833  						tooltipWidth = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).width();
3834  						// center the tooltip horizontally over the button
3835  						if (buttonSide == 'left') {
3836  							tooltipX = position.left - tooltipWidth/2;
3837  							if (tooltipX < 0) {
3838  								// tooltip would exceed the bounds of the player. Adjust.
3839  								tooltipX = 2;
3840  							}
3841  							tooltipStyle = {
3842  								left: tooltipX + 'px',
3843  								right: '',
3844  								top: tooltipY + 'px'
3845  							};
3846  						} else {
3847  							tooltipX = position.right - tooltipWidth/2;
3848  							if (tooltipX < 0) {
3849  								// tooltip would exceed the bounds of the player. Adjust.
3850  								tooltipX = 2;
3851  							}
3852  							tooltipStyle = {
3853  								left: '',
3854  								right: tooltipX + 'px',
3855  								top: tooltipY + 'px'
3856  							};
3857  						}
3858  						tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(buttonText).css(tooltipStyle);
3859  						thisObj.showTooltip(tooltip);
3860  						$(this).on('mouseleave blur',function() {
3861
3862  							// (keep the tooltip visible if user hovers over it)
3863  							// This causes unwanted side effects if tooltips are positioned above the buttons
3864  							// as the persistent tooltip obstructs the seekbar,
3865  							// blocking users from being able to move a pointer from a button to the seekbar
3866  							// This limitation was addressed in 4.4.49 by moving the tooltip below the buttons
3867
3868  							// clear existing timeout before reassigning variable
3869  							clearTimeout(tooltipTimerId);
3870  							tooltipTimerId = setTimeout(function() {
3871  								// give the user a half second to move cursor to tooltip before removing
3872  								// see https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus#hoverable
3873  								AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
3874  							}, 500);
3875
3876  							thisObj.$tooltipDiv.on('mouseenter focus', function() {
3877  								clearTimeout(tooltipTimerId);
3878  							});
3879
3880  							thisObj.$tooltipDiv.on('mouseleave blur', function() {
3881  								AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
3882  							});
3883
3884  						});
3885  					});
3886
3887  					if (control === 'captions') {
3888  						if (!this.prefCaptions || this.prefCaptions !== 1) {
3889  							// captions are available, but user has them turned off
3890  							if (this.captions.length > 1) {
3891  								captionLabel = this.translate( 'captions', 'Captions' );
3892  							} else {
3893  								captionLabel = this.translate( 'showCaptions', 'Show captions' );
3894  							}
3895  							$newButton.addClass('buttonOff').attr('title',captionLabel);
3896  							$newButton.attr('aria-pressed', 'false');
3897  						}
3898  					} else if (control === 'descriptions') {
3899  						if (!this.prefDesc || this.prefDesc !== 1) {
3900  							// user prefer non-audio described version
3901  							// Therefore, load media without description
3902  							// Description can be toggled on later with this button
3903  							$newButton.addClass('buttonOff').attr( 'title', this.translate( 'turnOnDescriptions', 'Turn on descriptions' ) );
3904  						}
3905  					}
3906
3907  					$controllerSpan.append($newButton);
3908
3909  					// create variables of buttons that are referenced throughout the AblePlayer object
3910  					if (control === 'play') {
3911  						this.$playpauseButton = $newButton;
3912  					} else if (control == 'previous') {
3913  						this.$prevButton = $newButton;
3914  						// if player is being rebuilt because user clicked the Prev button
3915  						// return focus to that (newly built) button
3916  						if (this.buttonWithFocus == 'previous') {
3917  							this.$prevButton.trigger('focus');
3918  							this.buttonWithFocus = null;
3919  						}
3920  					} else if (control == 'next') {
3921  						this.$nextButton = $newButton;
3922  						// if player is being rebuilt because user clicked the Next button
3923  						// return focus to that (newly built) button
3924  						if (this.buttonWithFocus == 'next') {
3925  							this.$nextButton.trigger('focus');
3926  							this.buttonWithFocus = null;
3927  						}
3928  					} else if (control === 'captions') {
3929  						this.$ccButton = $newButton;
3930  					} else if (control === 'sign') {
3931  						this.$signButton = $newButton;
3932  						// gray out sign button if sign language window is not active
3933  						if (!(this.$signWindow.is(':visible'))) {
3934  							this.$signButton.addClass('buttonOff');
3935  						}
3936  					} else if (control === 'descriptions') {
3937  						this.$descButton = $newButton;
3938  						// button will be enabled or disabled in description.js > initDescription()
3939  					} else if (control === 'mute') {
3940  						this.$muteButton = $newButton;
3941  					} else if (control === 'transcript') {
3942  						this.$transcriptButton = $newButton;
3943  						// gray out transcript button if transcript is not active
3944  						if (!(this.$transcriptDiv.is(':visible'))) {
3945  							this.$transcriptButton.addClass('buttonOff').attr( 'title', this.translate( 'showTranscript', 'Show transcript' ) );
3946  						}
3947  					} else if (control === 'fullscreen') {
3948  						this.$fullscreenButton = $newButton;
3949  					} else if (control === 'chapters') {
3950  						this.$chaptersButton = $newButton;
3951  					} else if (control === 'preferences') {
3952  						this.$prefsButton = $newButton;
3953  					} else if (control === 'volume') {
3954  						this.$volumeButton = $newButton;
3955  					}
3956  				}
3957  				if (control === 'volume') {
3958  					// in addition to the volume button, add a hidden slider
3959  					this.addVolumeSlider($controllerSpan);
3960  				}
3961  			}
3962  			if ((i % 2) == 1) {
3963  				this.$controllerDiv.append('<div class="ableplayer-clear"></div>');
3964  			}
3965  		}
3966
3967  		if (typeof this.$captionsDiv !== 'undefined') {
3968  			// stylize captions based on user prefs
3969  			this.stylizeCaptions(this.$captionsDiv);
3970  		}
3971  		if (typeof this.$descDiv !== 'undefined') {
3972  			// stylize descriptions based on user's caption prefs
3973  			this.stylizeCaptions(this.$descDiv);
3974  		}
3975
3976  		// combine left and right controls arrays for future reference
3977  		this.controls = [];
3978  		for (var sec in controlLayout) if (Object.hasOwn(controlLayout, sec)) {
3979  			this.controls = this.controls.concat(controlLayout[sec]);
3980  		}
3981
3982  		// Update state-based display of controls.
3983  		this.refreshControls();
3984  	};
3985
3986  	AblePlayer.prototype.cuePlaylistItem = function(sourceIndex) {
3987
3988  		// Move to a new item in a playlist.
3989  		// NOTE: Swapping source for audio description is handled elsewhere;
3990  		// see description.js > swapDescription()
3991
3992  		var $newItem, prevPlayer, newPlayer, itemTitle, itemLang, nowPlayingSpan;
3993
3994  		var thisObj = this;
3995
3996  		prevPlayer = this.player;
3997
3998  		if (this.initializing) ; else {
3999  			if (this.playerCreated) {
4000  				// remove the old
4001  				this.deletePlayer('playlist');
4002  			}
4003  		}
4004
4005  		// set swappingSrc; needs to be true within recreatePlayer(), called below
4006  		this.swappingSrc = true;
4007
4008  		// if a new playlist item is being requested, and playback has already started,
4009  		// it should be ok to play automatically, regardless of how it was requested
4010  		if (this.startedPlaying) {
4011  			this.okToPlay = true;
4012  		} else {
4013  			this.okToPlay = false;
4014  		}
4015
4016  		// We are no longer loading the previous media source
4017  		// Only now, as a new source is requested, is it safe to reset this var
4018  		// It will be reset to true when media.load() is called
4019  		this.loadingMedia = false;
4020
4021  		// Determine appropriate player to play this media
4022  		$newItem = this.$playlist.eq(sourceIndex);
4023  		this.playlistIndex = sourceIndex;
4024  		if (this.hasAttr($newItem,'data-youtube-id')) {
4025  			this.youTubeId = this.getYouTubeId($newItem.attr('data-youtube-id'));
4026  			if (this.hasAttr($newItem,'data-youtube-desc-id')) {
4027  				this.youTubeDescId = this.getYouTubeId($newItem.attr('data-youtube-desc-id'));
4028  			}
4029  			newPlayer = 'youtube';
4030  		} else if (this.hasAttr($newItem,'data-vimeo-id')) {
4031  			this.vimeoId = this.getVimeoId($newItem.attr('data-vimeo-id'));
4032  			if (this.hasAttr($newItem,'data-vimeo-desc-id')) {
4033  				this.vimeoDescId = this.getVimeoId($newItem.attr('data-vimeo-desc-id'));
4034  			}
4035  			newPlayer = 'vimeo';
4036  		} else {
4037  			newPlayer = 'html5';
4038  		}
4039  		if (newPlayer === 'youtube') {
4040  			if (prevPlayer === 'html5') {
4041  				// pause and hide the previous media
4042  				if (this.playing) {
4043  					this.pauseMedia();
4044  				}
4045  				this.$media.hide();
4046  			}
4047  		} else {
4048  			// the new player is not youtube
4049  			this.youTubeId = false;
4050  			if (prevPlayer === 'youtube') {
4051  				// unhide the media element
4052  				this.$media.show();
4053  			}
4054  		}
4055  		this.player = newPlayer;
4056
4057  		// remove source and track elements from previous playlist item
4058  		this.$media.empty();
4059
4060  		// transfer media attributes from playlist to media element
4061  		if (this.hasAttr($newItem,'data-poster')) {
4062  			this.$media.attr('poster',$newItem.attr('data-poster'));
4063  		}
4064  		if (this.hasAttr($newItem,'data-youtube-desc-id')) {
4065  			this.$media.attr('data-youtube-desc-id',$newItem.attr('data-youtube-desc-id'));
4066  		}
4067  		if (this.youTubeId) {
4068  			this.$media.attr('data-youtube-id',$newItem.attr('data-youtube-id'));
4069  		}
4070
4071  		// add new <source> elements from playlist data
4072  		var $sourceSpans = $newItem.children('span.able-source');
4073  		if ($sourceSpans.length) {
4074  			$sourceSpans.each(function() {
4075  				const $this = $(this);
4076
4077  				// Check if the required data-src attribute exists
4078  				if (thisObj.hasAttr($this, "data-src")) {
4079  					const sanitizedSrc = purify.sanitize($this.attr("data-src"));
4080
4081  					// Validate the protocol of the sanitized URL
4082  					if (validate.isProtocolSafe(sanitizedSrc)) {
4083  						// Create a new <source> element with the sanitized src
4084  						const $newSource = $("<source>", { src: sanitizedSrc });
4085
4086  						// List of optional attributes to sanitize and add
4087  						const optionalAttributes = [
4088  							"data-type",
4089  							"data-desc-src",
4090  							"data-sign-src",
4091  						];
4092
4093  						// Process optional attributes
4094  						optionalAttributes.forEach((attr) => {
4095  							if (thisObj.hasAttr($this, attr)) {
4096  								const attrValue = $this.attr(attr); // Get the attribute value
4097  								const sanitizedValue = purify.sanitize(attrValue); // Sanitize the value
4098
4099  								// If the attribute ends with "-src", validate the protocol
4100  								if (attr.endsWith("-src") && validate.isProtocolSafe(sanitizedValue)) {
4101  									$newSource.attr(attr, sanitizedValue); // Add the sanitized and validated attribute
4102  								} else if (!attr.endsWith("-src")) {
4103  									$newSource.attr(attr, sanitizedValue); // Add sanitized value for non-src attributes
4104  								}
4105  							}
4106               			});
4107
4108  						// Append the new <source> element to the media object
4109  						thisObj.$media.append($newSource);
4110  					}
4111  				}
4112  			});
4113  		}
4114
4115  		// add new <track> elements from playlist data
4116  		var $trackSpans = $newItem.children('span.able-track');
4117  		if ($trackSpans.length) {
4118  			 // for each element in $trackSpans, create a new <track> element
4119  			$trackSpans.each(function() {
4120  				const $this = $(this);
4121  				if (thisObj.hasAttr($this, "data-src") && thisObj.hasAttr($this, "data-kind") && thisObj.hasAttr($this, "data-srclang")) {
4122  					// all required attributes are present
4123  					const sanitizedSrc = purify.sanitize($this.attr("data-src"));
4124  					// Validate the protocol of the sanitized URL
4125  					if (validate.isProtocolSafe(sanitizedSrc)) {
4126  						// Create a new <track> element with the sanitized src
4127  						const $newTrack = $("<track>", {
4128  							src: sanitizedSrc,
4129  							kind: $this.attr("data-kind"),
4130  							srclang: $this.attr("data-srclang"),
4131  						});
4132  						// List of optional attributes to sanitize and add
4133  						const optionalAttributes = [
4134  							"data-label",
4135  							"data-desc",
4136  							"data-default",
4137  						];
4138  						optionalAttributes.forEach((attr) => {
4139  							if (thisObj.hasAttr($this, attr)) {
4140  								$newTrack.attr(attr, purify.sanitize($this.attr(attr)));
4141  							}
4142  						});
4143  						// Append the new <track> element to the media object
4144  						thisObj.$media.append($newTrack);
4145  					}
4146  				}
4147  			});
4148  		}
4149
4150  		itemTitle = purify.sanitize( $newItem.text() );
4151  		if (this.hasAttr($newItem,'lang')) {
4152  			itemLang = $newItem.attr('lang');
4153  		}
4154  		// Update relevant arrays
4155  		this.$sources = this.$media.find('source');
4156
4157  		// recreate player, informed by new attributes and track elements
4158  		if (this.recreatingPlayer) {
4159  			// stopgap to prevent multiple firings of recreatePlayer()
4160  			return;
4161  		}
4162  		this.recreatePlayer().then(function() {
4163
4164  			// update playlist to indicate which item is playing
4165  			thisObj.$playlist.removeClass('able-current')
4166  				.children('button').removeAttr('aria-current');
4167  			thisObj.$playlist.eq(sourceIndex).addClass('able-current')
4168  				.children('button').attr('aria-current','true');
4169
4170  			// update Now Playing div
4171  			if (thisObj.showNowPlaying === true) {
4172  				if (typeof thisObj.$nowPlayingDiv !== 'undefined') {
4173  					nowPlayingSpan = $('<span>');
4174  					if (typeof itemLang !== 'undefined') {
4175  						nowPlayingSpan.attr('lang',itemLang);
4176  					}
4177  					nowPlayingSpan.html('<span>' + thisObj.translate( 'selectedTrack', 'Selected Track' ) + ':</span>' + itemTitle);
4178  					thisObj.$nowPlayingDiv.html(nowPlayingSpan);
4179  				}
4180  			}
4181
4182  			// if thisObj.swappingSrc is true, media will autoplay when ready
4183  			if (thisObj.initializing) { // this is the first track - user hasn't pressed play yet
4184  				thisObj.swappingSrc = false;
4185  			} else {
4186  				if (thisObj.player === 'html5') {
4187  					if (!thisObj.loadingMedia) {
4188  						thisObj.media.load();
4189  						thisObj.loadingMedia = true;
4190  					}
4191  				} else if (thisObj.player === 'youtube') {
4192  					thisObj.okToPlay = true;
4193  				}
4194  			}
4195  			thisObj.initializing = false;
4196  			thisObj.playerCreated = true; // remains true until browser is refreshed
4197  		});
4198  	};
4199
4200  	AblePlayer.prototype.deletePlayer = function(context) {
4201
4202  		// remove player components that need to be rebuilt
4203  		// after swapping media sources that have different durations
4204  		// or explicitly declared data-desc attributes
4205
4206  		// Context is one of the following:
4207  		// playlist - called from cuePlaylistItem()
4208  		// swap-desc-html - called from swapDescription with this.player == 'html'
4209  		// swap-desc-youtube - called from swapDescription with this.player == 'youtube'
4210  		// swap-desc-vimeo -  called from swapDescription with this.player == 'vimeo'
4211
4212  		if (this.player === 'youtube' && this.youTubePlayer) {
4213  			this.youTubePlayer.destroy();
4214  		}
4215
4216  		if (this.player === 'vimeo' && this.vimeoPlayer) {
4217  			this.vimeoPlayer.destroy();
4218  		}
4219
4220  		// Empty elements that will be rebuilt
4221  		this.$controllerDiv.empty();
4222  		// this.$statusBarDiv.empty();
4223  		// this.$timer.empty();
4224  		this.$elapsedTimeContainer.empty().text('0:00'); // span.able-elapsedTime
4225  		this.$durationContainer.empty(); // span.able-duration
4226
4227  		// Remove popup windows and modal dialogs; these too will be rebuilt
4228  		if (this.$signWindow) {
4229  				this.$signWindow.remove();
4230  		}
4231  		if (this.$transcriptArea) {
4232  				this.$transcriptArea.remove();
4233  		}
4234  		$('.able-modal-dialog').remove();
4235
4236  		// Remove caption and description wrappers
4237  		if (this.$captionsWrapper) {
4238  			this.$captionsWrapper.remove();
4239  		}
4240  		if (this.$descDiv) {
4241  			this.$descDiv.remove();
4242  		}
4243
4244  		// reset key variables
4245  		this.hasCaptions = false;
4246  		this.hasChapters = false;
4247  		this.hasDescTracks = false;
4248  		this.hasOpenDesc = false;
4249  		this.hasClosedDesc = false;
4250
4251  		this.captionsPopup = null;
4252  		this.chaptersPopup = null;
4253  		this.transcriptType = null;
4254
4255  		this.playerDeleted = true; // will reset to false in recreatePlayer()
4256  	};
4257
4258  	AblePlayer.prototype.getButtonTitle = function(control) {
4259
4260  		if (control === 'playpause') {
4261  			return this.translate( 'play', 'Play' );
4262  		} else if (control === 'play') {
4263  			return this.translate( 'play', 'Play' );
4264  		} else if (control === 'pause') {
4265  			return this.translate( 'pause', 'Pause' );
4266  		} else if (control === 'restart') {
4267  			return this.translate( 'restart', 'Restart' );
4268  		} else if (control === 'previous') {
4269  			return this.translate( 'prevTrack', 'Previous track' );
4270  		} else if (control === 'next') {
4271  			return this.translate( 'nextTrack', 'Next track' );
4272  		} else if (control === 'rewind') {
4273  			return this.translate( 'rewind', 'Rewind' );
4274  		} else if (control === 'forward') {
4275  			return this.translate( 'forward', 'Forward' );
4276  		} else if (control === 'captions') {
4277  			if (this.captions.length > 1) {
4278  				return this.translate( 'captions', 'Captions' );
4279  			} else {
4280  				return (this.captionsOn) ? this.translate( 'hideCaptions', 'Hide captions' ) : this.translate( 'showCaptions', 'Show captions' );
4281  			}
4282  		} else if (control === 'descriptions') {
4283  			return (this.descOn) ? this.translate( 'turnOffDescriptions', 'Turn off descriptions' ) : this.translate( 'turnOnDescriptions', 'Turn on descriptions' );
4284  		} else if (control === 'transcript') {
4285  			return (this.$transcriptDiv.is(':visible')) ? this.translate( 'hideTranscript', 'Hide transcript' ) : this.translate( 'showTranscript', 'Show transcript' );
4286  		} else if (control === 'chapters') {
4287  			return this.translate( 'chapters', 'Chapters' );
4288  		} else if (control === 'sign') {
4289  			return this.translate( 'sign', 'Sign language' );
4290  		} else if (control === 'volume') {
4291  			return this.translate( 'volume', 'Volume' );
4292  		} else if (control === 'faster') {
4293  			return this.translate( 'faster', 'Faster' );
4294  		} else if (control === 'slower') {
4295  			return this.translate( 'slower', 'Slower' );
4296  		} else if (control === 'preferences') {
4297  			return this.translate( 'preferences', 'Preferences' );
4298  		} else if (control === 'fullscreen') {
4299  			return ( !this.fullscreen ) ? this.translate( 'enterFullScreen', 'Enter full screen' ) : this.translate( 'exitFullScreen', 'Exit full screen' );
4300  		} else {
4301  			// there should be no other controls, but just in case:
4302  			// return the name of the control with first letter in upper case
4303  			// ultimately will need to get a translated label from this.tt
4304  			if (this.debug) {
4305  				console.log('Found an untranslated label: ' + control);
4306  			}
4307  			return this.capitalizeFirstLetter( control );
4308  		}
4309  	};
4310  }
4311
4312  function addCaptionFunctions(AblePlayer) {
4313    AblePlayer.prototype.updateCaption = function (time) {
4314      if (
4315        !this.usingYouTubeCaptions &&
4316        !this.usingVimeoCaptions &&
4317        typeof this.$captionsWrapper !== "undefined"
4318      ) {
4319        if (this.captionsOn) {
4320          this.$captionsWrapper.show();
4321          if (typeof time !== "undefined") {
4322            this.showCaptions(time);
4323          }
4324        } else if (this.$captionsWrapper) {
4325          this.$captionsWrapper.hide();
4326          this.prefCaptions = 0;
4327        }
4328      }
4329    };
4330
4331    AblePlayer.prototype.updateCaptionsMenu = function (lang) {
4332      // uncheck all previous menu items
4333      this.captionsPopup.find("li").attr("aria-checked", "false");
4334      if (typeof lang === "undefined") {
4335        // check the last menu item (captions off)
4336        this.captionsPopup.find("li").last().attr("aria-checked", "true");
4337      } else {
4338        // check the newly selected lang
4339        this.captionsPopup
4340          .find("li[lang=" + lang + "]")
4341          .attr("aria-checked", "true");
4342      }
4343    };
4344
4345    AblePlayer.prototype.getCaptionClickFunction = function (track) {
4346      // Returns the function used when a caption is clicked in the captions menu.
4347      // Not called if user clicks "Captions off". Instead, that triggers getCaptionOffFunction()
4348
4349      var thisObj = this;
4350      return function () {
4351        thisObj.selectedCaptions = track;
4352        thisObj.captionLang = track.language;
4353        thisObj.currentCaption = -1;
4354        if (thisObj.usingYouTubeCaptions) {
4355          if (thisObj.captionsOn) {
4356            // Two things must be true in order for setOption() to work:
4357            // The YouTube caption module must be loaded
4358            // and the video must have started playing
4359            if (
4360              thisObj.youTubePlayer.getOptions("captions") &&
4361              thisObj.startedPlaying
4362            ) {
4363              thisObj.youTubePlayer.setOption("captions", "track", {
4364                languageCode: thisObj.captionLang,
4365              });
4366            } else {
4367              // the two conditions were not met
4368              // try again to set the language after onApiChange event is triggered
4369              // meanwhile, the following variable will hold the value
4370              thisObj.captionLangPending = thisObj.captionLang;
4371            }
4372          } else {
4373            if (thisObj.youTubePlayer.getOptions("captions")) {
4374              thisObj.youTubePlayer.setOption("captions", "track", {
4375                languageCode: thisObj.captionLang,
4376              });
4377            } else {
4378              thisObj.youTubePlayer.loadModule("captions");
4379              thisObj.captionLangPending = thisObj.captionLang;
4380            }
4381          }
4382        } else if (thisObj.usingVimeoCaptions) {
4383          thisObj.vimeoPlayer
4384            .enableTextTrack(thisObj.captionLang)
4385            .catch(function (error) {
4386              switch (error.name) {
4387                case 'InvalidTrackLanguageError':
4388                  // There is no text track for the specified language
4389                  console.log(`No Vimeo text track is available in the specified language (${thisObj.captionLang})`);
4390                  break;
4391                case 'InvalidTrackError':
4392                  // There is no such text track
4393                  console.log('No Vimeo text track is available');
4394                  break;
4395                default:
4396                  // some other error occurred
4397                  console.log('Error enabling Vimeo text track');
4398                  break;
4399              }
4400            });
4401        } else {
4402          // using local track elements for captions/subtitles
4403          thisObj.syncTrackLanguages("captions", thisObj.captionLang);
4404          if (!thisObj.swappingSrc) {
4405            thisObj.updateCaption(thisObj.elapsed);
4406            thisObj.showDescription(thisObj.elapsed);
4407          }
4408        }
4409        thisObj.captionsOn = true;
4410        // stopgap to prevent spacebar in Firefox from reopening popup
4411        // immediately after closing it (used in handleCaptionToggle())
4412        thisObj.hidingPopup = true;
4413        thisObj.captionsPopup.hide();
4414        thisObj.$ccButton.attr("aria-expanded", "false");
4415        if (thisObj.mediaType === "audio") {
4416          thisObj.$captionsContainer.removeClass("captions-off");
4417        }
4418        // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called
4419        // e.g., if user triggered button with Enter or mouse click, not spacebar
4420        setTimeout(function () {
4421          thisObj.hidingPopup = false;
4422        }, 100);
4423        thisObj.updateCaptionsMenu(thisObj.captionLang);
4424        thisObj.waitThenFocus(thisObj.$ccButton);
4425
4426        // save preference to cookie
4427        thisObj.prefCaptions = 1;
4428        thisObj.updatePreferences("prefCaptions");
4429        thisObj.refreshControls("captions");
4430      };
4431    };
4432
4433    // Returns the function used when the "Captions Off" button is clicked in the captions tooltip.
4434    AblePlayer.prototype.getCaptionOffFunction = function () {
4435      var thisObj = this;
4436      return function () {
4437        if (thisObj.player == "youtube") {
4438          thisObj.youTubePlayer.unloadModule("captions");
4439        } else if (thisObj.usingVimeoCaptions) {
4440          thisObj.vimeoPlayer.disableTextTrack();
4441        }
4442        thisObj.captionsOn = false;
4443        thisObj.currentCaption = -1;
4444
4445        if (thisObj.mediaType === "audio") {
4446          thisObj.$captionsContainer.addClass("captions-off");
4447        }
4448
4449        // stopgap to prevent spacebar in Firefox from reopening popup
4450        // immediately after closing it (used in handleCaptionToggle())
4451        thisObj.hidingPopup = true;
4452        thisObj.captionsPopup.hide();
4453        thisObj.$ccButton.attr("aria-expanded", "false");
4454        // Ensure stopgap gets cancelled if handleCaptionToggle() isn't called
4455        // e.g., if user triggered button with Enter or mouse click, not spacebar
4456        setTimeout(function () {
4457          thisObj.hidingPopup = false;
4458        }, 100);
4459        thisObj.updateCaptionsMenu();
4460        thisObj.waitThenFocus(thisObj.$ccButton);
4461
4462        // save preference to cookie
4463        thisObj.prefCaptions = 0;
4464        thisObj.updatePreferences("prefCaptions");
4465        if (!this.swappingSrc) {
4466          thisObj.refreshControls("captions");
4467          thisObj.updateCaption();
4468        }
4469      };
4470    };
4471
4472    AblePlayer.prototype.showCaptions = function (now) {
4473      var c, thisCaption, nextCaption, captionText, announceText, announcement, availableTime, rate, cueLength, estimatedTime;
4474      var cues;
4475      if (null !== this.selectedCaptions.cues && this.selectedCaptions.cues.length) {
4476        cues = this.selectedCaptions.cues;
4477      } else if (this.captions.length >= 1) {
4478        cues = this.captions[0].cues;
4479      } else {
4480        cues = [];
4481      }
4482      for (c = 0; c < cues.length; c++) {
4483        if (cues[c].start <= now && cues[c].end > now) {
4484          thisCaption = c;
4485  		nextCaption = cues[ c + 1 ];
4486          break;
4487        }
4488      }
4489
4490      if (typeof thisCaption !== "undefined") {
4491        if (this.currentCaption !== thisCaption) {
4492          // it's time to load the new caption into the container div
4493          captionText = this.flattenCueForCaption(cues[thisCaption]).replace( /\n/g, "<br>" );
4494  		// If preference enabled to voice captions, send to synthesizer.
4495  		if ( this.speechEnabled && this.prefCaptionsSpeak == 1 ) {
4496  			announceText = new DOMParser().parseFromString( captionText, 'text/html' );
4497  			announcement = announceText.body.textContent || '';
4498  			availableTime = ( thisCaption ) ? nextCaption.start - cues[thisCaption].start : 0;
4499  			rate = false;
4500  			if ( availableTime ) {
4501  				cueLength = announcement.trim().split(/\W+/).length;
4502  				estimatedTime = Math.round( ( ( cueLength ) / 135 ) * 60 );
4503  				rate = ( estimatedTime / availableTime );
4504  			}
4505  			// use browser's built-in speech synthesis
4506  			this.announceText( 'caption', announcement, rate );
4507  		}
4508          this.$captionsDiv.html(captionText);
4509          this.currentCaption = thisCaption;
4510          if (captionText.length === 0) {
4511            // hide captionsDiv; otherwise background-color is visible due to padding
4512            this.$captionsDiv.css("display", "none");
4513          } else {
4514            this.$captionsDiv.css("display", "inline-block");
4515          }
4516        }
4517      } else {
4518        this.$captionsDiv.html("").css("display", "none");
4519        this.currentCaption = -1;
4520      }
4521    };
4522
4523    AblePlayer.prototype.flattenCueForCaption = function (cue) {
4524      // Takes a cue and returns the caption text to display
4525      // Also used for chapters
4526
4527      // Support for 'i' and 'b' tags added in 2.3.66
4528      // TODO: Add support for 'c' (class) and 'ruby'
4529
4530      // c (class): <c.myClass1.myClass2>Some text</c>
4531      // Classes can be used to modify other tags too (e.g., <v.loud>)
4532      // If <c> tag, should be rendered as a <span>
4533
4534      // ruby: http://www.w3schools.com/tags/tag_ruby.asp
4535
4536      // WebVTT also supports 'u' (underline)
4537      // I see no reason to support that in Able Player.
4538      // If it's available authors are likely to use it incorrectly
4539      // where <i> or <b> should be used instead
4540      // Here are the rare use cases where an underline is appropriate on the web:
4541      // http://html5doctor.com/u-element/
4542
4543      var result = [];
4544
4545      var flattenComponent = function (component) {
4546        var result = [],
4547          ii;
4548        if (component.type === "string") {
4549          result.push(component.value);
4550        } else if (component.type === "v") {
4551          result.push("(" + component.value + ")");
4552          for (ii = 0; ii < component.children.length; ii++) {
4553            result.push(flattenComponent(component.children[ii]));
4554          }
4555        } else if (component.type === "i") {
4556          result.push("<em>");
4557          for (ii = 0; ii < component.children.length; ii++) {
4558            result.push(flattenComponent(component.children[ii]));
4559          }
4560          result.push("</em>");
4561        } else if (component.type === "b") {
4562          result.push("<strong>");
4563          for (ii = 0; ii < component.children.length; ii++) {
4564            result.push(flattenComponent(component.children[ii]));
4565          }
4566          result.push("</strong>");
4567        } else {
4568          for (ii = 0; ii < component.children.length; ii++) {
4569            result.push(flattenComponent(component.children[ii]));
4570          }
4571        }
4572        return result.join("");
4573      };
4574
4575      if (typeof cue.components !== "undefined") {
4576        for (var ii = 0; ii < cue.components.children.length; ii++) {
4577          result.push(flattenComponent(cue.components.children[ii]));
4578        }
4579      }
4580      return result.join("");
4581    };
4582
4583    AblePlayer.prototype.getCaptionsOptions = function (pref) {
4584      var options = [];
4585
4586      switch (pref) {
4587        case "prefCaptionsFont":
4588          options[0] = ["serif", this.translate( 'serif', 'serif' )];
4589          options[1] = ["sans-serif", this.translate( 'sans', 'sans-serif' )];
4590          options[2] = ["cursive", this.translate( 'cursive', 'cursive' )];
4591          options[3] = ["fantasy", this.translate( 'fantasy', 'fantasy' )];
4592          options[4] = ["monospace", this.translate( 'monospace', 'monospace' )];
4593          break;
4594
4595        case "prefCaptionsColor":
4596        case "prefCaptionsBGColor":
4597          // HTML color values must be in English
4598          options[0] = ["white", this.translate( 'white', 'white' )];
4599          options[1] = ["yellow", this.translate( 'yellow', 'yellow' )];
4600          options[2] = ["green", this.translate( 'green', 'green' )];
4601          options[3] = ["cyan", this.translate( 'cyan', 'cyan' )];
4602          options[4] = ["blue", this.translate( 'blue', 'blue' )];
4603          options[5] = ["magenta", this.translate( 'magenta', 'magenta' )];
4604          options[6] = ["red", this.translate( 'red', 'red' )];
4605          options[7] = ["black", this.translate( 'black', 'black' )];
4606          break;
4607
4608        case "prefCaptionsSize":
4609          options[0] = "75%";
4610          options[1] = "100%";
4611          options[2] = "125%";
4612          options[3] = "150%";
4613          options[4] = "200%";
4614          break;
4615
4616        case "prefCaptionsOpacity":
4617          options[0] = "0%";
4618          options[1] = "25%";
4619          options[2] = "50%";
4620          options[3] = "75%";
4621          options[4] = "100%";
4622          break;
4623
4624        case "prefCaptionsStyle":
4625          options[0] = this.translate( 'captionsStylePopOn', 'Pop-on' );
4626          options[1] = this.translate( 'captionsStyleRollUp', 'Roll-up' );
4627          break;
4628
4629        case "prefCaptionsPosition":
4630          options[0] = "overlay";
4631          options[1] = "below";
4632          break;
4633
4634        case "prefCaptionsSpeak":
4635          options[0] = ["0", this.translate( 'off', 'Off' ) ];
4636          options[1] = ["1", this.translate( 'on', 'On' ) ];
4637          break;
4638
4639        case "prefCaptionsVoice":
4640  		options[0] = null; // set later.
4641          break;
4642
4643        case "prefCaptionsPitch":
4644  		options[0] = null; // set later.
4645          break;
4646
4647        case "prefCaptionsRate":
4648  		options[0] = null; // set later.
4649          break;
4650
4651        case "prefCaptionsVolume":
4652  		options[0] = null; // set later.
4653          break;
4654      }
4655
4656      return options;
4657    };
4658
4659    AblePlayer.prototype.translatePrefs = function (pref, value, outputFormat) {
4660      // translate current value of pref to a value supported by outputformat
4661      if (outputFormat == "youtube") {
4662        if (pref === "size") {
4663          // YouTube font sizes are a range from -1 to 3 (0 = default)
4664          switch (value) {
4665            case "75%":
4666              return -1;
4667            case "100%":
4668              return 0;
4669            case "125%":
4670              return 1;
4671            case "150%":
4672              return 2;
4673            case "200%":
4674              return 3;
4675          }
4676        }
4677      }
4678      return false;
4679    };
4680
4681    AblePlayer.prototype.stylizeCaptions = function ($element, pref) {
4682      // $element is the jQuery element containing the captions
4683      // this function handles stylizing of the sample caption text in the Prefs dialog
4684      // plus the actual production captions
4685      // TODO: consider applying the same user prefs to visible text-based description
4686      var property, newValue, opacity;
4687
4688      if (typeof $element !== "undefined") {
4689        if (pref == "prefCaptionsPosition") {
4690          this.positionCaptions();
4691        } else if (typeof pref !== "undefined") {
4692          // just change the one property that user just changed
4693          if (pref === "prefCaptionsFont") {
4694            property = "font-family";
4695          } else if (pref === "prefCaptionsSize") {
4696            property = "font-size";
4697          } else if (pref === "prefCaptionsColor") {
4698            property = "color";
4699          } else if (pref === "prefCaptionsBGColor") {
4700            property = "background-color";
4701          } else if (pref === "prefCaptionsOpacity") {
4702            property = "opacity";
4703          }
4704          if (pref === "prefCaptionsOpacity") {
4705            newValue =
4706              parseFloat($("#" + this.mediaId + "_" + pref).val()) / 100.0;
4707          } else {
4708            newValue = $("#" + this.mediaId + "_" + pref).val();
4709          }
4710          $element.css(property, newValue);
4711        } else {
4712          // no property was specified, update all styles with current saved prefs
4713          opacity = parseFloat(this.prefCaptionsOpacity) / 100.0;
4714          $element.css({
4715            "font-family": this.prefCaptionsFont,
4716            color: this.prefCaptionsColor,
4717            "background-color": this.prefCaptionsBGColor,
4718            opacity: opacity,
4719          });
4720          if ($element === this.$captionsDiv) {
4721            if (typeof this.$captionsDiv !== "undefined") {
4722              this.$captionsDiv.css({
4723                "font-size": this.prefCaptionsSize,
4724              });
4725            }
4726          }
4727          if (this.prefCaptionsPosition === "below") {
4728            // also need to add the background color to the wrapper div
4729            if (typeof this.$captionsWrapper !== "undefined") {
4730              this.$captionsWrapper.css({
4731                "background-color": this.prefCaptionsBGColor,
4732                opacity: "1",
4733              });
4734            }
4735          } else if (this.prefCaptionsPosition === "overlay") {
4736            // no background color for overlay wrapper, captions are displayed in-line
4737            if (typeof this.$captionsWrapper !== "undefined") {
4738              this.$captionsWrapper.css({
4739                "background-color": "transparent",
4740                opacity: "",
4741              });
4742            }
4743          }
4744          this.positionCaptions();
4745        }
4746      }
4747    };
4748    AblePlayer.prototype.positionCaptions = function (position) {
4749      // set caption position to either 'overlay' or 'below'
4750      // if position parameter was passed to this function, use that
4751      // otherwise use user preference
4752      if (typeof position === "undefined") {
4753        position = this.prefCaptionsPosition;
4754      }
4755      if (typeof this.$captionsWrapper !== "undefined") {
4756        if (position == "below") {
4757          this.$captionsWrapper
4758            .removeClass("able-captions-overlay")
4759            .addClass("able-captions-below");
4760          // also need to update in-line styles
4761          this.$captionsWrapper.css({
4762            "background-color": this.prefCaptionsBGColor,
4763            opacity: "1",
4764          });
4765        } else {
4766          this.$captionsWrapper
4767            .removeClass("able-captions-below")
4768            .addClass("able-captions-overlay");
4769          this.$captionsWrapper.css({
4770            "background-color": "transparent",
4771            opacity: "",
4772          });
4773        }
4774      }
4775    };
4776  }
4777
4778  function addChaptersFunctions(AblePlayer) {
4779
4780  	AblePlayer.prototype.populateChaptersDiv = function() {
4781
4782  		var headingLevel, headingType, headingId, $chaptersHeading;
4783  		if ( ! this.chaptersDivLocation ) {
4784  			return;
4785  		}
4786  		if ($('#' + this.chaptersDivLocation)) {
4787
4788  			this.$chaptersDiv = $('#' + this.chaptersDivLocation);
4789  			this.$chaptersDiv.addClass('able-chapters-div');
4790
4791  			// empty content from previous build before starting fresh
4792  			this.$chaptersDiv.empty();
4793
4794  			// add optional header
4795  			if (this.chaptersTitle) {
4796  				headingLevel = this.getNextHeadingLevel(this.$chaptersDiv);
4797  				headingType = 'h' + headingLevel.toString();
4798  				headingId = this.mediaId + '-chapters-heading';
4799  				$chaptersHeading = $('<' + headingType + '>', {
4800  					'class': 'able-chapters-heading',
4801  					'id': headingId
4802  				}).text(this.chaptersTitle);
4803  				this.$chaptersDiv.append($chaptersHeading);
4804  			}
4805
4806  			this.$chaptersNav = $('<nav>');
4807  			if (this.chaptersTitle) {
4808  				this.$chaptersNav.attr( 'aria-labelledby', headingId );
4809  			} else {
4810  				this.$chaptersNav.attr( 'aria-label', this.translate( 'chapters', 'Chapters' ) );
4811  			}
4812  			this.$chaptersDiv.append(this.$chaptersNav);
4813
4814  			// populate this.$chaptersNav with a list of chapters
4815  			this.updateChaptersList();
4816  		}
4817  	};
4818
4819  	AblePlayer.prototype.updateChaptersList = function() {
4820
4821  		var thisObj, cues, $chaptersList, c, thisChapter,
4822  			$chapterItem, $chapterButton, hasDefault,
4823  			getClickFunction, $clickedItem;
4824
4825  		thisObj = this;
4826
4827  		// TODO: Update this so it can change the chapters popup menu
4828  		// currently it only works if chapters are in an external container
4829  		if (!this.$chaptersNav) {
4830  			return false;
4831  		}
4832
4833  		if (typeof this.useChapterTimes === 'undefined') {
4834  			this.useChapterTimes = (this.seekbarScope === 'chapter' && this.selectedChapters.cues.length) ? true : false;
4835  		}
4836  		if (this.useChapterTimes) {
4837  			cues = this.selectedChapters.cues;
4838  		} else if (this.chapters.length >= 1) {
4839  			cues = this.chapters[0].cues;
4840  		} else {
4841  			cues = [];
4842  		}
4843  		if (cues.length > 0) {
4844  			$chaptersList = $('<ul>');
4845  			for (c = 0; c < cues.length; c++) {
4846  				thisChapter = c;
4847  				$chapterItem = $('<li></li>');
4848  				$chapterButton = $('<button>',{
4849  					'type': 'button',
4850  					'val': thisChapter
4851  				}).text(this.flattenCueForCaption(cues[thisChapter]));
4852
4853  				// add event listeners
4854  				getClickFunction = function (time) {
4855  					return function () {
4856  						thisObj.seekTrigger = 'chapter';
4857  						$clickedItem = $(this).closest('li');
4858  						$chaptersList = $(this).closest('ul').find('li');
4859  						$chaptersList.removeClass('able-current-chapter')
4860  							.children('button').removeAttr('aria-current');
4861  						$clickedItem.addClass('able-current-chapter')
4862  							.children('button').attr('aria-current','true');
4863  						// Need to updateChapter before seeking to it
4864  						// Otherwise seekBar is redrawn with wrong chapterDuration and/or chapterTime
4865  						thisObj.updateChapter(time);
4866  						thisObj.seekTo(time);
4867  					}
4868  				};
4869  				$chapterButton.on('click',getClickFunction(cues[thisChapter].start)); // works with Enter too
4870  				$chapterButton.on('focus',function() {
4871  					$(this).closest('ul').find('li').removeClass('able-focus');
4872  					$(this).closest('li').addClass('able-focus');
4873  				});
4874  				$chapterItem.on('hover',function() {
4875  					$(this).closest('ul').find('li').removeClass('able-focus');
4876  					$(this).addClass('able-focus');
4877  				});
4878  				$chapterItem.on('mouseleave',function() {
4879  					$(this).removeClass('able-focus');
4880  				});
4881  				$chapterButton.on('blur',function() {
4882  					$(this).closest('li').removeClass('able-focus');
4883  				});
4884
4885  				// put it all together
4886  				$chapterItem.append($chapterButton);
4887  				$chaptersList.append($chapterItem);
4888  				if (this.defaultChapter === cues[thisChapter].id) {
4889  					$chapterButton.attr('aria-current','true').parent('li').addClass('able-current-chapter');
4890  					this.currentChapter = cues[thisChapter];
4891  					hasDefault = true;
4892  				}
4893  			}
4894  			if (!hasDefault) {
4895  				// select the first chapter
4896  				this.currentChapter = cues[0];
4897  				$chaptersList.find('button').first().attr('aria-current','true')
4898  					.parent('li').addClass('able-current-chapter');
4899  			}
4900  			this.$chaptersNav.html($chaptersList);
4901  		}
4902  		return false;
4903  	};
4904
4905  	AblePlayer.prototype.seekToChapter = function(chapterId) {
4906
4907  		// step through chapters looking for matching ID
4908  		var i=0;
4909  		while (i < this.selectedChapters.cues.length) {
4910  			if (this.selectedChapters.cues[i].id == chapterId) {
4911  				// found the target chapter! Seek to it
4912  				this.seekTo(this.selectedChapters.cues[i].start);
4913  				this.updateChapter(this.selectedChapters.cues[i].start);
4914  				break;
4915  			}
4916  			i++;
4917  		}
4918  	};
4919
4920  	AblePlayer.prototype.updateChapter = function (now) {
4921
4922  		// as time-synced chapters change during playback, track changes in current chapter
4923  		if (typeof this.selectedChapters === 'undefined') {
4924  			return;
4925  		}
4926
4927  		var chapters, i, thisChapterIndex;
4928
4929  		chapters = this.selectedChapters.cues;
4930  		for (i = 0; i < chapters.length; i++) {
4931  			if ((chapters[i].start <= now) && (chapters[i].end > now)) {
4932  				thisChapterIndex = i;
4933  				break;
4934  			}
4935  		}
4936  		if (typeof thisChapterIndex !== 'undefined') {
4937  			if (this.currentChapter !== chapters[thisChapterIndex]) {
4938  				// this is a new chapter
4939  				this.currentChapter = chapters[thisChapterIndex];
4940  				if (this.useChapterTimes) {
4941  					this.chapterDuration = this.getChapterDuration();
4942  					this.seekIntervalCalculated = false; // will be recalculated in setSeekInterval()
4943  				}
4944  				if (typeof this.$chaptersDiv !== 'undefined') {
4945  					// chapters are listed in an external container
4946  					this.$chaptersDiv.find('ul').find('li')
4947  						.removeClass('able-current-chapter')
4948  						.children('button').removeAttr('aria-current');
4949  					this.$chaptersDiv.find('ul').find('li').eq(thisChapterIndex)
4950  						.addClass('able-current-chapter')
4951  						.children('button').attr('aria-current','true');
4952  				}
4953  			}
4954  		}
4955  	};
4956
4957  	AblePlayer.prototype.getChapterDuration = function () {
4958
4959  		// called if this.seekbarScope === 'chapter'
4960  		// get duration of the current chapter
4961
4962  		var lastChapterIndex, chapterEnd;
4963
4964  		if (typeof this.currentChapter === 'undefined') {
4965  			return 0;
4966  		}
4967  		if (typeof this.duration === 'undefined') {
4968  			return 0;
4969  		}
4970  		lastChapterIndex = this.selectedChapters.cues.length-1;
4971  		if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) {
4972  			// this is the last chapter
4973  			if (this.currentChapter.end !== this.duration) {
4974  				// chapter ends before or after video ends, adjust chapter end to match video end
4975  				chapterEnd = this.duration;
4976  				this.currentChapter.end = this.duration;
4977  			} else {
4978  				chapterEnd = this.currentChapter.end;
4979  			}
4980  		} else { // this is not the last chapter
4981  			chapterEnd = this.currentChapter.end;
4982  		}
4983  		return chapterEnd - this.currentChapter.start;
4984  	};
4985
4986  	AblePlayer.prototype.getChapterElapsed = function () {
4987  		// called if this.seekbarScope === 'chapter'
4988  		// get current elapsed time, relative to the current chapter duration
4989
4990  		if (typeof this.currentChapter === 'undefined') {
4991  			return 0;
4992  		}
4993
4994  		if (this.elapsed > this.currentChapter.start) {
4995  			return this.elapsed - this.currentChapter.start;
4996  		} else {
4997  			return 0;
4998  		}
4999  	};
5000
5001  	AblePlayer.prototype.convertChapterTimeToVideoTime = function (chapterTime) {
5002
5003  		// chapterTime is the time within the current chapter
5004  		// return the same time, relative to the entire video
5005  		if (typeof this.currentChapter !== 'undefined') {
5006  			var newTime = this.currentChapter.start + chapterTime;
5007  			if (newTime > this.currentChapter.end) {
5008  				return this.currentChapter.end;
5009  			} else {
5010  				return newTime;
5011  			}
5012  		} else {
5013  			return chapterTime;
5014  		}
5015  	};
5016
5017  	AblePlayer.prototype.getChapterClickFunction = function (time) {
5018
5019  		// Returns the function used when a chapter is clicked in the chapters menu.
5020  		var thisObj = this;
5021  		return function () {
5022  			thisObj.seekTrigger = 'chapter';
5023  			thisObj.seekTo(time);
5024  			// stopgap to prevent spacebar in Firefox from reopening popup
5025  			// immediately after closing it (used in handleChapters())
5026  			thisObj.hidingPopup = true;
5027  			thisObj.chaptersPopup.hide();
5028  			// Ensure stopgap gets cancelled if handleChapters() isn't called
5029  			// e.g., if user triggered button with Enter or mouse click, not spacebar
5030  			setTimeout(function() {
5031  				thisObj.hidingPopup = false;
5032  			}, 100);
5033  			thisObj.$chaptersButton.trigger('focus');
5034  		}
5035  	};
5036
5037  }
5038
5039  function addControlFunctions(AblePlayer) {
5040
5041  	AblePlayer.prototype.seekTo = function (newTime) {
5042
5043  		var thisObj = this;
5044
5045  		// define variables to be used for analytics
5046  		// e.g., to measure the extent to which users seek back and forward
5047  		this.seekFromTime = this.media.currentTime;
5048  		this.seekToTime = newTime;
5049
5050  		this.seeking = true;
5051  		this.liveUpdatePending = true;
5052
5053  		if (this.speakingDescription) {
5054  			this.synth.cancel();
5055  		}
5056
5057  		this.syncSignVideo( {'time' : this.startTime } );
5058
5059  		if (this.player === 'html5') {
5060  			var seekable;
5061
5062  			this.startTime = newTime;
5063  			// Check HTML5 media "seekable" property to be sure media is seekable to startTime
5064  			seekable = this.media.seekable;
5065  			if (seekable.length > 0 && this.startTime >= seekable.start(0) && this.startTime <= seekable.end(0)) {
5066  				// ok to seek to startTime
5067  				// canplaythrough will be triggered when seeking is complete
5068  				// this.seeking will be set to false at that point
5069  				this.media.currentTime = this.startTime;
5070  				this.seekStatus = 'complete';
5071  				this.syncSignVideo( { 'time' : this.startTime } );
5072  			}
5073  		} else if (this.player === 'youtube') {
5074  			this.youTubePlayer.seekTo(newTime,true);
5075  			if (newTime > 0) {
5076  				if (typeof this.$posterImg !== 'undefined') {
5077  					this.$posterImg.hide();
5078  				}
5079  			}
5080  			this.syncSignVideo( {'time' : newTime } );
5081  		} else if (this.player === 'vimeo') {
5082  			this.vimeoPlayer.setCurrentTime(newTime).then(function() {
5083  				// seek finished.
5084  				// successful completion also fires a 'seeked' event (see event.js)
5085  				thisObj.elapsed = newTime;
5086  				thisObj.refreshControls('timeline');
5087  			});
5088  		}
5089  		this.refreshControls('timeline');
5090  	};
5091
5092  	AblePlayer.prototype.getMediaTimes = function (duration, elapsed) {
5093
5094  		 // Returns an array with keys 'duration' and 'elapsed'
5095  		 // Vars passed to this function come courtesy of select Vimeo events
5096  		 // Use those if they're available.
5097  		 // Otherwise, will need to call the relevant media API
5098  		 // This function should only be called from onMediaUpdateTime()
5099  		 // If duration and elapsed are needed other times, use this.duration and this.elapsed
5100
5101  		// both values are expressed in seconds, and all player APIs are similar:
5102  		// they return a value that is rounded to the nearest second before playback begins,
5103  		// then to the nearest thousandth of a second after playback begins
5104  		// With HTML5 media API, some browsers are more precise (e.g., Firefox rounds to 6 decimal points)
5105  		// but inconsistent (values with 9 decimal points have been sporadically observed in Safari)
5106  		// For standardization, values are rounded to 6 decimal points before they're returned
5107
5108  		var deferred, promise, thisObj, mediaTimes;
5109  		mediaTimes = {};
5110
5111  		deferred = new this.defer();
5112  		promise = deferred.promise();
5113  		thisObj = this;
5114  		if (typeof duration !== 'undefined' && typeof elapsed !== 'undefined') {
5115  			mediaTimes['duration'] = duration;
5116  			mediaTimes['elapsed'] = elapsed;
5117  			deferred.resolve(mediaTimes);
5118  		} else {
5119  			this.getDuration().then(function(duration) {
5120  				mediaTimes['duration'] = thisObj.roundDown(duration,6);
5121  				thisObj.getElapsed().then(function(elapsed) {
5122  					mediaTimes['elapsed'] = thisObj.roundDown(elapsed,6);
5123  					deferred.resolve(mediaTimes);
5124  				});
5125  			});
5126  		}
5127  		return promise;
5128  	};
5129
5130  	AblePlayer.prototype.getDuration = function () {
5131
5132  		// returns duration of the current media, expressed in seconds
5133  		// function is called by getMediaTimes, and return value is sanitized there
5134  		var deferred, promise;
5135
5136  		deferred = new this.defer();
5137  		promise = deferred.promise();
5138
5139  		if (this.player === 'vimeo') {
5140  			if (this.vimeoPlayer) {
5141  				 this.vimeoPlayer.getDuration().then(function(duration) {
5142  					if (duration === undefined || isNaN(duration) || duration === -1) {
5143  						deferred.resolve(0);
5144  					} else {
5145  						deferred.resolve(duration);
5146  					}
5147  				});
5148  			} else { // vimeoPlayer hasn't been initialized yet.
5149  				deferred.resolve(0);
5150  			}
5151  		} else {
5152  			var duration;
5153  			if (this.player === 'html5') {
5154  				duration = this.media.duration;
5155  			} else if (this.player === 'youtube') {
5156  				if (this.youTubePlayerReady) {
5157  					if (this.duration > 0) {
5158  						// duration was already retrieved while checking for captions
5159  						duration = this.duration;
5160  					} else {
5161  						duration = this.youTubePlayer.getDuration();
5162  					}
5163  				} else { // the YouTube player hasn't initialized yet
5164  					duration = 0;
5165  				}
5166  			}
5167  			if (duration === undefined || isNaN(duration) || duration === -1) {
5168  				deferred.resolve(0);
5169  			} else {
5170  				deferred.resolve(duration);
5171  			}
5172  		}
5173  		return promise;
5174  	};
5175
5176  	AblePlayer.prototype.getElapsed = function () {
5177
5178  		// returns elapsed time of the current media, expressed in seconds
5179  		// function is called by getMediaTimes, and return value is sanitized there
5180
5181  		var deferred, promise;
5182
5183  		deferred = new this.defer();
5184  		promise = deferred.promise();
5185
5186  		if (this.player === 'vimeo') {
5187  			if (this.vimeoPlayer) {
5188  				this.vimeoPlayer.getCurrentTime().then(function(elapsed) {
5189  					if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) {
5190  						deferred.resolve(0);
5191  					} else {
5192  						deferred.resolve(elapsed);
5193  					}
5194  				});
5195  			} else { // vimeoPlayer hasn't been initialized yet.
5196  				deferred.resolve(0);
5197  			}
5198  		} else {
5199  			var elapsed;
5200  			if (this.player === 'html5') {
5201  				elapsed = this.media.currentTime;
5202  			} else if (this.player === 'youtube') {
5203  				if (this.youTubePlayerReady) {
5204  					elapsed = this.youTubePlayer.getCurrentTime();
5205  				} else { // the YouTube player hasn't initialized yet
5206  					elapsed = 0;
5207  				}
5208  			}
5209  			if (elapsed === undefined || isNaN(elapsed) || elapsed === -1) {
5210  				deferred.resolve(0);
5211  			} else {
5212  				deferred.resolve(elapsed);
5213  			}
5214  		}
5215  		return promise;
5216  	};
5217
5218  	AblePlayer.prototype.getPlayerState = function () {
5219
5220  		// Returns one of the following states:
5221  		// - 'stopped' - Not yet played for the first time, or otherwise reset to unplayed.
5222  		// - 'ended' - Finished playing.
5223  		// - 'paused' - Not playing, but not stopped or ended.
5224  		// - 'buffering' - Momentarily paused to load, but will resume once data is loaded.
5225  		// - 'playing' - Currently playing.
5226
5227  		var deferred, promise, thisObj;
5228  		deferred = new this.defer();
5229  		promise = deferred.promise();
5230  		thisObj = this;
5231
5232  		if (this.player === 'html5') {
5233  			if (this.media.ended) {
5234  				deferred.resolve('ended');
5235  			} else if (this.media.paused) {
5236  				deferred.resolve('paused');
5237  			} else if (this.media.readyState !== 4) {
5238  				deferred.resolve('buffering');
5239  			} else {
5240  				deferred.resolve('playing');
5241  			}
5242  		} else if (this.player === 'youtube' && this.youTubePlayerReady) {
5243  			var state = this.youTubePlayer.getPlayerState();
5244  			if (state === -1 || state === 5) {
5245  				deferred.resolve('stopped');
5246  			} else if (state === 0) {
5247  				deferred.resolve('ended');
5248  			} else if (state === 1) {
5249  				deferred.resolve('playing');
5250  			} else if (state === 2) {
5251  				deferred.resolve('paused');
5252  			} else if (state === 3) {
5253  				deferred.resolve('buffering');
5254  			}
5255  		} else if (this.player === 'vimeo' && this.vimeoPlayer) {
5256  				// curiously, Vimeo's API has no getPlaying(), getBuffering(), or getState() methods
5257  			// so maybe if it's neither paused nor ended, it must be playing???
5258  			this.vimeoPlayer.getPaused().then(function(paused) {
5259  				if (paused) {
5260  					deferred.resolve('paused');
5261  				} else {
5262  					thisObj.vimeoPlayer.getEnded().then(function(ended) {
5263  						if (ended) {
5264  							deferred.resolve('ended');
5265  						} else {
5266  							deferred.resolve('playing');
5267  						}
5268  					});
5269  				}
5270  			});
5271  		}
5272  		return promise;
5273  	};
5274
5275  	AblePlayer.prototype.isPlaybackRateSupported = function () {
5276
5277  		if (this.player === 'html5') {
5278  			return (this.media.playbackRate) ? true : false;
5279  		} else if (this.player === 'youtube') {
5280  			// Youtube supports varying playback rates per video.
5281  			// Only expose controls if more than one playback rate is available.
5282  			if (this.youTubePlayerReady) {
5283  				return (this.youTubePlayer.getAvailablePlaybackRates().length > 1) ? true : false;
5284  			} else {
5285  				return false;
5286  			}
5287  		} else if (this.player === 'vimeo') {
5288  			// since this takes longer to determine, it was set previously in initVimeoPlayer()
5289  			return this.vimeoSupportsPlaybackRateChange;
5290  		}
5291  	};
5292
5293  	AblePlayer.prototype.setPlaybackRate = function (rate) {
5294
5295  		rate = Math.max(0.5, rate);
5296
5297  		if (this.hasClosedDesc && this.descMethod === 'text') {
5298  			// keep speech rate in sync with playback rate even if descOn is false
5299  			this.syncSpeechToPlaybackRate(rate);
5300  		}
5301
5302  		this.syncSignVideo( {'rate' : rate } );
5303
5304  		if (this.player === 'html5') {
5305  			this.media.playbackRate = rate;
5306  		} else if (this.player === 'youtube') {
5307  			this.youTubePlayer.setPlaybackRate(rate);
5308  		} else if (this.player === 'vimeo') {
5309  			this.vimeoPlayer.setPlaybackRate(rate);
5310  		}
5311  		this.syncSignVideo( { 'rate' : rate } );
5312  		this.playbackRate = rate;
5313  		this.$speed.text( this.translate( 'speed', 'Speed' ) + ': ' + rate.toFixed(2).toString() + 'x');
5314  	};
5315
5316  	AblePlayer.prototype.getPlaybackRate = function () {
5317
5318  		if (this.player === 'html5') {
5319  			return this.media.playbackRate;
5320  		} else if (this.player === 'youtube' && (this.youTubePlayerReady)) {
5321  			return this.youTubePlayer.getPlaybackRate();
5322  		}
5323  	};
5324
5325  	AblePlayer.prototype.isPaused = function () {
5326
5327  		// Note there are three player states that count as paused in this sense,
5328  		// and one of them is named 'paused'.
5329  		// A better name would be 'isCurrentlyNotPlayingOrBuffering'
5330
5331  		if (this.player === 'vimeo') {
5332  			// just rely on value of this.playing
5333  			return (this.playing) ? false : true;
5334  		} else {
5335  			this.getPlayerState().then(function(state) {
5336  				// if any of the following is true, consider the media 'paused'
5337  				return state === 'paused' || state === 'stopped' || state === 'ended';
5338  			});
5339  		}
5340  	};
5341
5342  	AblePlayer.prototype.syncSignVideo = function(options) {
5343  		if (this.hasSignLanguage && ( this.signVideo || this.signYoutube ) ) {
5344  			if (options && typeof options.time !== 'undefined') {
5345  				if ( this.signVideo ) {
5346  					this.signVideo.currentTime = options.time;
5347  				} else {
5348  					this.youTubeSignPlayer.seekTo(options.time,true);
5349  				}
5350  			}
5351  			if (options && typeof options.rate !== 'undefined') {
5352  				if ( this.signVideo ) {
5353  					this.signVideo.playbackRate = options.rate;
5354  				} else {
5355  					this.youTubeSignPlayer.setPlaybackRate(options.rate);
5356  				}
5357  			}
5358  			if (options && typeof options.pause !== 'undefined') {
5359  				if ( this.signVideo ) {
5360  					this.signVideo.pause(true);
5361  				} else {
5362  					this.youTubeSignPlayer.pauseVideo();
5363  				}
5364  			}
5365  			if (options && typeof options.play !== 'undefined') {
5366  				if ( this.signVideo ) {
5367  					this.signVideo.play(true);
5368  				} else {
5369  					this.youTubeSignPlayer.playVideo();
5370  				}
5371  			}
5372  			if (options && typeof options.volume !== 'undefined') {
5373  				if ( this.signVideo ) {
5374  					this.signVideo.volume = 0;
5375  				}
5376  			}
5377  		}
5378  	};
5379
5380  	AblePlayer.prototype.pauseMedia = function () {
5381
5382  		this.syncSignVideo( { 'pause' : true } );
5383
5384  		if (this.player === 'html5') {
5385  			this.media.pause(true);
5386  		} else if (this.player === 'youtube') {
5387  			this.youTubePlayer.pauseVideo();
5388  		} else if (this.player === 'vimeo') {
5389  			this.vimeoPlayer.pause();
5390  		}
5391  	};
5392
5393  	AblePlayer.prototype.playMedia = function () {
5394
5395  		this.syncSignVideo( { 'play' : true } );
5396
5397  		if (this.player === 'html5') {
5398  			this.media.play(true);
5399  		} else if (this.player === 'youtube') {
5400
5401  			this.youTubePlayer.playVideo();
5402  			if (typeof this.$posterImg !== 'undefined') {
5403  				this.$posterImg.hide();
5404  			}
5405  			this.stoppingYouTube = false;
5406  		} else if (this.player === 'vimeo') {
5407  			 this.vimeoPlayer.play();
5408  		}
5409  		this.startedPlaying = true;
5410  		if (this.hideControls) {
5411  			// wait briefly after playback begins, then hide controls
5412  			this.hidingControls = true;
5413  			this.invokeHideControlsTimeout();
5414  		}
5415  	};
5416
5417  	AblePlayer.prototype.fadeControls = function(direction) {
5418
5419  		// Visibly fade controls without hiding them from screen reader users
5420  		// direction is either 'out' or 'in'
5421
5422  		// After the player fades, it's replaced by an empty space
5423  		// Would be better if the video and captions expanded to fill the void
5424  		// replace JS animation with CSS animation in 12/2025.
5425
5426  		if (direction == 'out') {
5427  			// get the original height of two key components:
5428  			this.$playerDiv.addClass( 'fade-out' ).removeClass( 'fade-in' );
5429  		} else if (direction == 'in') {
5430  			this.$playerDiv.addClass( 'fade-in' ).removeClass( 'fade-out' );
5431  		}
5432  	};
5433
5434  	AblePlayer.prototype.invokeHideControlsTimeout = function () {
5435
5436  		// invoke timeout for waiting a few seconds after a mouse move or key down
5437  		// before hiding controls again
5438  		var thisObj = this;
5439  		this.hideControlsTimeout = window.setTimeout(function() {
5440  			if (typeof thisObj.playing !== 'undefined' && thisObj.playing === true && thisObj.hideControls) {
5441  				thisObj.fadeControls('out');
5442  				thisObj.controlsHidden = true;
5443  			}
5444  		},5000);
5445  		this.hideControlsTimeoutStatus = 'active';
5446  	};
5447
5448  	AblePlayer.prototype.refreshControls = function(context = 'init', duration, elapsed) {
5449
5450  		// context is one of the following:
5451  		// 'init' - initial build (or subsequent change that requires full rebuild)
5452  		// 'timeline' - a change may effect time-related controls
5453  		// 'captions' - a change may effect caption-related controls
5454  		// 'descriptions' - a change may effect description-related controls
5455  		// 'transcript' - a change may effect the transcript window or button
5456  		// 'fullscreen' - a change has been triggered by full screen toggle
5457  		// 'playpause' - a change triggered by either a 'play' or 'pause' event
5458
5459  		// NOTE: context is not currently supported.
5460  		// The steps in this function have too many complex interdependencies
5461  		// The gains in efficiency are offset by the possibility of introducing bugs
5462  		// For now, executing everything
5463  		context = 'init';
5464
5465  		// duration and elapsed are passed from callback functions of Vimeo API events
5466  		// duration is expressed as sss.xxx
5467  		// elapsed is expressed as sss.xxx
5468
5469  		var thisObj, textByState, timestamp,  captionsCount, newTop,	statusBarWidthBreakpoint;
5470
5471  		thisObj = this;
5472  		// wait until new source has loaded before refreshing controls
5473  		// some critical events won't fire until playback of new media starts
5474  		if ( this.swappingSrc && this.playing ) {
5475  			return;
5476  		}
5477
5478  		if ( context === 'timeline' || context === 'init' ) {
5479  			// Update timeline controls.
5480  			var lastChapterIndex, displayElapsed, updateLive, widthUsed,
5481  				leftControls, rightControls, seekbarWidth, buffered;
5482  			// all timeline-related functionality requires duration
5483  			if (typeof this.duration === 'undefined') {
5484  				// wait until duration is known before proceeding with refresh
5485  				return;
5486  			}
5487  			if (this.useChapterTimes) {
5488  				this.chapterDuration = this.getChapterDuration();
5489  				this.chapterElapsed = this.getChapterElapsed();
5490  			}
5491
5492  			if ( !this.useFixedSeekInterval && !this.seekIntervalCalculated && this.duration > 0) {
5493  				// couldn't calculate seekInterval previously; try again.
5494  				this.setSeekInterval();
5495  			}
5496
5497  			if (this.seekBar) {
5498  				if (this.useChapterTimes) {
5499  					lastChapterIndex = this.selectedChapters.cues.length-1;
5500  					if (this.selectedChapters.cues[lastChapterIndex] == this.currentChapter) {
5501  						// this is the last chapter
5502  						if (this.currentChapter.end !== this.duration) {
5503  							// chapter ends before or after video ends
5504  							// need to adjust seekbar duration to match video end
5505  							this.seekBar.setDuration(this.duration - this.currentChapter.start);
5506  						} else {
5507  							this.seekBar.setDuration(this.chapterDuration);
5508  						}
5509  					} else {
5510  						// this is not the last chapter
5511  						this.seekBar.setDuration(this.chapterDuration);
5512  					}
5513  				} else if ( !(this.duration === undefined || isNaN(this.duration) || this.duration === -1) ) {
5514  					this.seekBar.setDuration(this.duration);
5515  				}
5516  				if (!(this.seekBar.tracking)) {
5517  					// Only update the aria live region if we have an update pending
5518  					// (from a seek button control) or if the seekBar has focus.
5519  					// We use document.activeElement instead of $(':focus') due to a strange bug:
5520  					// When the seekHead element is focused, .is(':focus') is failing and $(':focus') is returning an undefined element.
5521  					updateLive = this.liveUpdatePending || this.seekBar.seekHead.is($(document.activeElement));
5522  					this.liveUpdatePending = false;
5523  					if (this.useChapterTimes) {
5524  						this.seekBar.setPosition(this.chapterElapsed, updateLive);
5525  					} else {
5526  						this.seekBar.setPosition(this.elapsed, updateLive);
5527  					}
5528  				}
5529
5530  				// When seeking, display the seek bar time instead of the actual elapsed time.
5531  				if (this.seekBar.tracking) {
5532  					displayElapsed = this.seekBar.lastTrackPosition;
5533  				} else {
5534  					displayElapsed = ( this.useChapterTimes ) ? this.chapterElapsed : this.elapsed;
5535  				}
5536  			}
5537  			// update elapsed & duration
5538  			if (typeof this.$durationContainer !== 'undefined') {
5539  				if (this.useChapterTimes) {
5540  					this.$durationContainer.text( this.formatSecondsAsColonTime(this.chapterDuration));
5541  				} else {
5542  					this.$durationContainer.text( this.formatSecondsAsColonTime(this.duration));
5543  				}
5544  			}
5545  			if (typeof this.$elapsedTimeContainer !== 'undefined') {
5546  				this.$elapsedTimeContainer.text(this.formatSecondsAsColonTime(displayElapsed));
5547  			}
5548
5549  			if (this.skin === 'legacy') {
5550  				// Update seekbar width.
5551  				// To do this, we need to calculate the width of all buttons surrounding it.
5552  				if (this.seekBar) {
5553  					let controlWrapper = this.seekBar.wrapperDiv.parent().parent();
5554  					leftControls = this.seekBar.wrapperDiv.parent().prev('div.able-left-controls');
5555  					rightControls = leftControls.next('div.able-right-controls');
5556  					widthUsed = leftControls.outerWidth(true);
5557  					rightControls.children().each(function () {
5558  						if ($(this).attr('role')=='button') {
5559  							widthUsed += $(this).outerWidth(true) + 5;
5560  						}
5561  					});
5562  					if (this.fullscreen) {
5563  						seekbarWidth = $(window).width() - widthUsed;
5564  					} else {
5565  						// seekbar is wide enough to fill the remaining space
5566  						// include a 10px buffer to account for minor browser differences or custom styles.
5567  						seekbarWidth = controlWrapper.width() - widthUsed - 10;
5568  					}
5569  					// Sometimes some minor fluctuations based on browser weirdness, so set a threshold.
5570  					if (Math.abs(seekbarWidth - this.seekBar.getWidth()) > 5) {
5571  						this.seekBar.setWidth(seekbarWidth);
5572  					}
5573  				}
5574  			}
5575
5576  			// Update buffering progress.
5577  			// TODO: Currently only using the first HTML5 buffered interval,
5578  			// but this fails sometimes when buffering is split into two or more intervals.
5579  			if (this.player === 'html5' && this.media.buffered.length > 0) {
5580  				buffered = this.media.buffered.end(0);
5581  				if (this.useChapterTimes) {
5582  					if (buffered > this.chapterDuration) {
5583  						buffered = this.chapterDuration;
5584  					}
5585  					if (this.seekBar) {
5586  						this.seekBar.setBuffered(buffered / this.chapterDuration);
5587  					}
5588  				} else if ( this.seekBar && !isNaN(buffered) ) {
5589  					this.seekBar.setBuffered(buffered / duration);
5590  				}
5591  			} else if (this.player === 'youtube' && this.seekBar && this.youTubePlayerReady ) {
5592  				this.seekBar.setBuffered(this.youTubePlayer.getVideoLoadedFraction());
5593  			} else if (this.player === 'vimeo') ;
5594  		}
5595
5596  		if (context === 'descriptions' || context == 'init') {
5597  			if (this.$descButton) {
5598  				this.toggleButtonState(
5599  					this.$descButton,
5600  					this.descOn,
5601  					this.translate( 'turnOffDescriptions', 'Turn off descriptions' ),
5602  					this.translate( 'turnOnDescriptions', 'Turn on descriptions' ),
5603  				);
5604  			}
5605  		}
5606
5607  		if (context === 'captions' || context == 'init') {
5608
5609  			if (this.$ccButton) {
5610
5611  				captionsCount = this.captions.length;
5612  				if (captionsCount > 1) {
5613  					this.$ccButton.attr({
5614  						'aria-haspopup': 'true',
5615  						'aria-controls': this.mediaId + '-captions-menu'
5616  					});
5617  				}
5618  				var ariaLabelOn = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' );
5619  				var ariaLabelOff = ( captionsCount > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' );
5620  				var ariaPressed = ( captionsCount > 1 ) ? true : false;
5621
5622  				this.toggleButtonState(
5623  					this.$ccButton,
5624  					this.captionsOn,
5625  					ariaLabelOff,
5626  					ariaLabelOn,
5627  					ariaPressed
5628  				);
5629  			}
5630  		}
5631
5632  		if (context === 'fullscreen' || context == 'init'){
5633  			if (this.$fullscreenButton) {
5634  				if (!this.fullscreen) {
5635  					this.$fullscreenButton.attr( 'aria-label', this.translate( 'enterFullScreen', 'Enter full screen' ) );
5636  					this.getIcon( this.$fullscreenButton, 'fullscreen-expand' );
5637  				} else {
5638  					this.$fullscreenButton.attr('aria-label', this.translate( 'exitFullScreen', 'Exit full screen' ) );
5639  					this.getIcon( this.$fullscreenButton, 'fullscreen-collapse' );
5640  				}
5641  			}
5642  		}
5643  		if (context === 'playpause' || context == 'init'){
5644  			if (typeof this.$bigPlayButton !== 'undefined' && typeof this.seekBar !== 'undefined') {
5645  				// Choose show/hide for big play button and adjust position.
5646  				if (this.paused && !this.seekBar.tracking) {
5647  					if (!this.hideBigPlayButton) {
5648  						this.$bigPlayButton.show();
5649  						this.$bigPlayButton.attr('aria-hidden', 'false');
5650  					}
5651  				} else {
5652  					this.$bigPlayButton.hide();
5653  					this.$bigPlayButton.attr('aria-hidden', 'true');
5654  				}
5655  			}
5656  		}
5657
5658  		if (context === 'transcript' || context == 'init'){
5659
5660  			if (this.transcriptType) {
5661  				// Sync checkbox and autoScrollTranscript with user preference
5662  				if (this.prefAutoScrollTranscript === 1) {
5663  					this.autoScrollTranscript = true;
5664  					this.$autoScrollTranscriptCheckbox.prop('checked',true);
5665  				} else {
5666  					this.autoScrollTranscript = false;
5667  					this.$autoScrollTranscriptCheckbox.prop('checked',false);
5668  				}
5669
5670  				// If transcript locked, scroll transcript to current highlight location.
5671  				if (this.autoScrollTranscript && this.currentHighlight) {
5672  					newTop = Math.floor(this.$transcriptDiv.scrollTop() +
5673  						$(this.currentHighlight).position().top -
5674  						(this.$transcriptDiv.height() / 2) +
5675  						($(this.currentHighlight).height() / 2));
5676  					if (newTop !== Math.floor(this.$transcriptDiv.scrollTop())) {
5677  						// Set a flag to ignore the coming scroll event.
5678  						// there's no other way I know of to differentiate programmatic and user-initiated scroll events.
5679  						this.scrollingTranscript = true;
5680  						// only scroll once after moving a highlight
5681  						if (this.movingHighlight) {
5682  							this.$transcriptDiv.scrollTop(newTop);
5683  							this.movingHighlight = false;
5684  						}
5685  					}
5686  				}
5687  			}
5688  		}
5689
5690  		if (context === 'init') {
5691
5692  			if (this.$chaptersButton) {
5693  				this.$chaptersButton.attr({
5694  					'aria-label': this.translate( 'chapters', 'Chapters' ),
5695  					'aria-haspopup': 'true',
5696  					'aria-controls': this.mediaId + '-chapters-menu'
5697  				});
5698  			}
5699  		}
5700
5701  		if (context === 'timeline' || context === 'playpause' || context === 'init') {
5702
5703  			// update status
5704  			textByState = {
5705  				'stopped': this.translate( 'statusStopped', 'Stopped' ),
5706  				'paused': this.translate( 'statusPaused', 'Paused' ),
5707  				'playing': this.translate( 'statusPlaying', 'Playing' ),
5708  				'buffering': this.translate( 'statusBuffering', 'Buffering' ),
5709  				'ended': this.translate( 'statusEnd', 'End of track' )
5710  			};
5711
5712  			if (this.stoppingYouTube) {
5713  				// stoppingYouTube is true temporarily while video is paused and seeking to 0
5714  				// See notes in handleRestart()
5715  				// this.stoppingYouTube will be reset when seek to 0 is finished (in event.js > onMediaUpdateTime())
5716  				if (this.$status.text() !== this.translate( 'statusStopped', 'Stopped' ) ) {
5717  					this.$status.text( this.translate( 'statusStopped', 'Stopped' ) );
5718  				}
5719  				this.getIcon( this.$playpauseButton, 'play' );
5720  			} else if (typeof this.$status !== 'undefined' && typeof this.seekBar !== 'undefined') {
5721  				// Update the text only if it's changed since it has role="alert";
5722  				// also don't update while tracking, since this may Pause/Play the player but we don't want to send a Pause/Play update.
5723  				this.getPlayerState().then(function(currentState) {
5724  					if (thisObj.$status.text() !== textByState[currentState] && !thisObj.seekBar.tracking) {
5725  						// Debounce updates; only update after status has stayed steadily different for a while
5726  						// "A while" is defined differently depending on context
5727  						if (thisObj.swappingSrc) {
5728  							// this is where most of the chatter occurs (e.g., playing, paused, buffering, playing),
5729  							// so set a longer wait time before writing a status message
5730  							if (!thisObj.debouncingStatus) {
5731  								thisObj.statusMessageThreshold = 2000; // in ms (2 seconds)
5732  							}
5733  						} else if (!thisObj.debouncingStatus) {
5734  							// for all other contexts (e.g., users clicks Play/Pause)
5735  							// user should receive more rapid feedback
5736  							thisObj.statusMessageThreshold = 250; // in ms
5737  						}
5738  						timestamp = (new Date()).getTime();
5739  						if (!thisObj.statusDebounceStart) {
5740  							thisObj.statusDebounceStart = timestamp;
5741  							// Call refreshControls() again after allotted time has passed
5742  							thisObj.debouncingStatus = true;
5743  							thisObj.statusTimeout = setTimeout(function () {
5744  								thisObj.debouncingStatus = false;
5745  								thisObj.refreshControls(context);
5746  							}, thisObj.statusMessageThreshold);
5747  						} else if ((timestamp - thisObj.statusDebounceStart) > thisObj.statusMessageThreshold) {
5748  							thisObj.$status.text(textByState[currentState]);
5749  							thisObj.statusDebounceStart = null;
5750  							clearTimeout(thisObj.statusTimeout);
5751  							thisObj.statusTimeout = null;
5752  						}
5753  					} else {
5754  						thisObj.statusDebounceStart = null;
5755  						thisObj.debouncingStatus = false;
5756  						clearTimeout(thisObj.statusTimeout);
5757  						thisObj.statusTimeout = null;
5758  					}
5759  					// Don't change play/pause button display while using the seek bar (or if YouTube stopped)
5760  					if (!thisObj.seekBar.tracking && !thisObj.stoppingYouTube) {
5761  						if (currentState === 'paused' || currentState === 'stopped' || currentState === 'ended') {
5762  							thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'play', 'Play' ) );
5763  							thisObj.getIcon( thisObj.$playpauseButton, 'play' );
5764  						} else {
5765  							thisObj.$playpauseButton.attr('aria-label', thisObj.translate( 'pause', 'Pause' ) );
5766  							thisObj.getIcon( thisObj.$playpauseButton, 'pause' );
5767  						}
5768  					}
5769  				});
5770  			}
5771  		}
5772
5773  		// Show/hide status bar content conditionally
5774  		if (!this.fullscreen) {
5775  			statusBarWidthBreakpoint = 300;
5776  			if (this.$statusBarDiv.width() < statusBarWidthBreakpoint) {
5777  				// Player is too small for a speed span
5778  				this.$statusBarDiv.find('span.able-speed').hide();
5779  				this.hidingSpeed = true;
5780  			} else {
5781  				if (this.hidingSpeed) {
5782  					this.$statusBarDiv.find('span.able-speed').show();
5783  					this.hidingSpeed = false;
5784  				}
5785  			}
5786  		}
5787
5788  	};
5789
5790  	AblePlayer.prototype.handlePlay = function(e) {
5791  		if (this.paused) {
5792  			// user clicked play
5793  			this.okToPlay = true;
5794  			this.playMedia();
5795  			if (this.synth.paused) {
5796  				// media was paused while description was speaking
5797  				// resume utterance
5798  				this.synth.resume();
5799  			}
5800  		} else {
5801  			// user clicked pause
5802  			this.okToPlay = false;
5803  			this.pauseMedia();
5804  			if (this.speakingDescription) {
5805  				// pause the current utterance
5806  				// it will resume when the user presses play
5807  				this.synth.pause();
5808  			}
5809  		}
5810  		if (this.speechEnabled === null) {
5811  			this.initSpeech('play');
5812  		}
5813  	};
5814
5815  	AblePlayer.prototype.handleRestart = function() {
5816
5817  		if (this.speakingDescription) {
5818  			// cancel audio description
5819  			this.synth.cancel();
5820  		}
5821  		this.seekTo(0);
5822  	};
5823
5824  	AblePlayer.prototype.handlePrevTrack = function() {
5825
5826  		// currently on the first track
5827  		// wrap to bottom and play the last track
5828  		let newIndex = (this.playlistIndex === 0) ? this.$playlist.length - 1 : this.playlistIndex - 1;
5829  		this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
5830  		this.cuePlaylistItem(newIndex);
5831  	};
5832
5833  	AblePlayer.prototype.handleNextTrack = function() {
5834
5835  		// currently on the last track
5836  		// wrap to top and play the first track
5837  		let newIndex = (this.playlistIndex === this.$playlist.length - 1) ? 0 : this.playlistIndex + 1;
5838  		this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
5839  		this.cuePlaylistItem(newIndex);
5840  	};
5841
5842  	AblePlayer.prototype.handleRewind = function() {
5843
5844  		var targetTime;
5845
5846  		targetTime = this.elapsed - this.seekInterval;
5847  		if (this.useChapterTimes && (targetTime < this.currentChapter.start)) {
5848  			targetTime = this.currentChapter.start;
5849  		} else if (targetTime < 0) {
5850  			targetTime = 0;
5851  		}
5852  		this.seekTo(targetTime);
5853  	};
5854
5855  	AblePlayer.prototype.handleFastForward = function() {
5856
5857  		var targetTime, lastChapterIndex;
5858
5859  		lastChapterIndex = this.chapters.length-1;
5860  		targetTime = this.elapsed + this.seekInterval;
5861  		if (this.useChapterTimes) {
5862  			if (this.chapters[lastChapterIndex] == this.currentChapter) {
5863  				// this is the last chapter
5864  				if (targetTime > this.duration || targetTime > this.currentChapter.end) {
5865  					// targetTime would exceed the end of the video (or chapter)
5866  					// scrub to end of whichever is earliest
5867  					targetTime = Math.min(this.duration, this.currentChapter.end);
5868  				} else if (this.duration % targetTime < this.seekInterval) {
5869  					// nothing left but pocket change after seeking to targetTime
5870  					// go ahead and seek to end of video (or chapter), whichever is earliest
5871  					targetTime = Math.min(this.duration, this.currentChapter.end);
5872  				}
5873  			} else {
5874  				// this is not the last chapter
5875  				if (targetTime > this.currentChapter.end) {
5876  					// targetTime would exceed the end of the chapter
5877  					// scrub exactly to end of chapter
5878  					targetTime = this.currentChapter.end;
5879  				}
5880  			}
5881  		} else {
5882  			// not using chapter times
5883  			if (targetTime > this.duration) {
5884  				targetTime = this.duration;
5885  			}
5886  		}
5887  		this.seekTo(targetTime);
5888  	};
5889
5890  	AblePlayer.prototype.handleRateIncrease = function() {
5891  		this.changeRate(1);
5892  	};
5893
5894  	AblePlayer.prototype.handleRateDecrease = function() {
5895  		this.changeRate(-1);
5896  	};
5897
5898  	// Increases or decreases playback rate, where dir is 1 or -1 indication direction.
5899  	AblePlayer.prototype.changeRate = function (dir) {
5900
5901  		var rates, currentRate, index, newRate, vimeoMin, vimeoMax;
5902
5903  		if (this.player === 'html5') {
5904  			this.setPlaybackRate(this.getPlaybackRate() + (0.25 * dir));
5905  		} else if (this.player === 'youtube') {
5906  			if (this.youTubePlayerReady) {
5907  				rates = this.youTubePlayer.getAvailablePlaybackRates();
5908  				currentRate = this.getPlaybackRate();
5909  				index = rates.indexOf(currentRate);
5910  				if (index === -1) {
5911  					console.log('ERROR: Youtube returning unknown playback rate ' + currentRate.toString());
5912  				} else {
5913  					index += dir;
5914  					// Can only increase or decrease rate if there's another rate available.
5915  					if (index < rates.length && index >= 0) {
5916  						this.setPlaybackRate(rates[index]);
5917  					}
5918  				}
5919  			}
5920  		} else if (this.player === 'vimeo') {
5921  			// range is 0.5 to 2
5922  			// increase/decrease in inrements of 0.5
5923  			vimeoMin = 0.5;
5924  			vimeoMax = 2;
5925  			if (dir === 1) {
5926  				newRate = (this.vimeoPlaybackRate + 0.5 <= vimeoMax) ? this.vimeoPlaybackRate + 0.5 : vimeoMax;
5927  			} else if (dir === -1) {
5928  				newRate = (this.vimeoPlaybackRate - 0.5 >= vimeoMin) ? this.vimeoPlaybackRate - 0.5 : vimeoMin;
5929  			}
5930  			this.setPlaybackRate(newRate);
5931  		}
5932  	};
5933
5934  	AblePlayer.prototype.handleCaptionToggle = function() {
5935
5936  		var thisObj = this;
5937  		var captions, ariaPressed;
5938  		if (this.hidingPopup) {
5939  			// stopgap to prevent spacebar in Firefox from reopening popup
5940  			// immediately after closing it
5941  			this.hidingPopup = false;
5942  			return false;
5943  		}
5944
5945  		captions = (this.captions.length) ? this.captions : [];
5946  		if (captions.length === 1) {
5947  			// When there's only one set of captions, just do an on/off toggle.
5948  			if (this.captionsOn === true) {
5949  				// turn them off
5950  				this.captionsOn = false;
5951  				this.prefCaptions = 0;
5952  				ariaPressed = false;
5953  				this.updatePreferences('prefCaptions');
5954  				if (this.usingYouTubeCaptions) {
5955  					this.youTubePlayer.unloadModule('captions');
5956  				} else if (this.usingVimeoCaptions) {
5957  					this.vimeoPlayer.disableTextTrack();
5958  				} else {
5959  					this.$captionsWrapper.hide();
5960  				}
5961  			} else {
5962  				// captions are off. Turn them on.
5963  				this.captionsOn = true;
5964  				this.prefCaptions = 1;
5965  				ariaPressed = true;
5966  				this.updatePreferences('prefCaptions');
5967  				if (this.usingYouTubeCaptions) {
5968  					this.youTubePlayer.loadModule('captions');
5969  				} else if (this.usingVimeoCaptions) {
5970  					this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) {
5971  						switch (error.name) {
5972  							case 'InvalidTrackLanguageError':
5973  								// There is no text track for the specified language
5974  								console.log(`No Vimeo text track is available in the specified language (${this.captionLang})`);
5975  								break;
5976  							case 'InvalidTrackError':
5977  								// There is no such text track
5978  								console.log('No Vimeo text track is available');
5979  								break;
5980  							default:
5981  								// some other error occurred
5982  								console.log('Error enabling Vimeo text track');
5983  								break;
5984  						}
5985  					});
5986  				} else {
5987  					this.$captionsWrapper.show();
5988  				}
5989  				for (var i=0; i<captions.length; i++) {
5990  					if (captions[i].def === true) { // this is the default language
5991  						this.selectedCaptions = captions[i];
5992  					}
5993  				}
5994  				this.selectedCaptions = this.captions[0];
5995  				if (this.descriptions.length >= 0) {
5996  					this.selectedDescriptions = this.descriptions[0];
5997  				}
5998  			}
5999  		} else {
6000  			// there is more than one caption track.
6001  			// clicking on a track is handled via caption.js > getCaptionClickFunction()
6002  			if (this.captionsPopup && this.captionsPopup.is(':visible')) {
6003  				this.captionsPopup.hide();
6004  				this.hidingPopup = false;
6005  				this.$ccButton.attr('aria-expanded', 'false');
6006  				this.waitThenFocus(this.$ccButton);
6007  			} else {
6008  				this.closePopups();
6009  				if (this.captionsPopup) {
6010  					this.captionsPopup.show();
6011  					this.$ccButton.attr('aria-expanded','true');
6012
6013  					// Gives time to "register" expanded ccButton
6014  					setTimeout(function() {
6015  						thisObj.captionsPopup.css('top', thisObj.$ccButton.position().top - thisObj.captionsPopup.outerHeight());
6016  						thisObj.captionsPopup.css('left', thisObj.$ccButton.position().left);
6017  						// Place focus on the first button (even if another button is checked)
6018  						thisObj.captionsPopup.find('li').removeClass('able-focus');
6019  						thisObj.captionsPopup.find('li').first().trigger('focus').addClass('able-focus');
6020  					}, 50);
6021  				}
6022  			}
6023  		}
6024  		var ariaLabelOn = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'showCaptions', 'Show captions' );
6025  		var ariaLabelOff = ( captions.length > 1 ) ? this.translate( 'captions', 'Captions' ) : this.translate( 'hideCaptions', 'Hide captions' );
6026
6027  		this.toggleButtonState(
6028  			this.$ccButton,
6029  			this.captionsOn,
6030  			ariaLabelOff,
6031  			ariaLabelOn,
6032  			ariaPressed
6033  		);
6034  	};
6035
6036  	/**
6037  	 * Gives enough time for DOM changes to take effect before adjusting focus.
6038  	 * Helpful for allowing screen reading of elements whose state is intermittently changed.
6039  	 *
6040  	 * @param {*} $el element to focus on
6041  	 * @param {*} timeout optional wait time in milliseconds before focus
6042  	 */
6043  	AblePlayer.prototype.waitThenFocus = function($el, timeout) {
6044
6045  		// Default wait time of 50 ms
6046  		var _timeout = (timeout === undefined || timeout === null) ? 50 : timeout;
6047
6048  		setTimeout(function() {
6049  			$el.trigger('focus');
6050  		}, _timeout);
6051  	};
6052
6053  	AblePlayer.prototype.handleChapters = function () {
6054  		if (this.hidingPopup) {
6055  			// stopgap to prevent spacebar in Firefox from reopening popup
6056  			// immediately after closing it
6057  			this.hidingPopup = false;
6058  			return false;
6059  		}
6060  		if (this.chaptersPopup.is(':visible')) {
6061  			this.chaptersPopup.hide();
6062  			this.hidingPopup = false;
6063  			this.$chaptersButton.attr('aria-expanded','false').trigger('focus');
6064  		} else {
6065  			this.closePopups();
6066  			this.chaptersPopup.show();
6067  			this.$chaptersButton.attr('aria-expanded','true');
6068  			this.chaptersPopup.css('top', this.$chaptersButton.position().top - this.chaptersPopup.outerHeight());
6069  			this.chaptersPopup.css('left', this.$chaptersButton.position().left);
6070
6071  			// Highlight the current chapter, if any chapters are checked
6072  			// Otherwise, place focus on the first chapter
6073  			this.chaptersPopup.find('li').removeClass('able-focus');
6074  			if (this.chaptersPopup.find('li[aria-checked="true"]').length) {
6075  				this.chaptersPopup.find('li[aria-checked="true"]').trigger('focus').addClass('able-focus');
6076  			} else {
6077  				this.chaptersPopup.find('li').first().addClass('able-focus').attr('aria-checked','true').trigger('focus');
6078  			}
6079  		}
6080  	};
6081
6082  	AblePlayer.prototype.handleDescriptionToggle = function() {
6083
6084  		this.descOn = !this.descOn;
6085  		this.prefDesc = + this.descOn; // convert boolean to integer
6086  		this.updatePreferences('prefDesc');
6087  		if (typeof this.$descDiv !== 'undefined') {
6088  			if (!this.$descDiv.is(':hidden')) {
6089  				this.$descDiv.hide();
6090  			}
6091  			// NOTE: now showing $descDiv here if previously hidden
6092  			// that's handled elsewhere, dependent on whether there's text to show
6093  		}
6094  		this.initDescription();
6095  		this.refreshControls('descriptions');
6096  	};
6097
6098  	AblePlayer.prototype.handlePrefsClick = function(pref) {
6099
6100  		// NOTE: the prefs menu is positioned near the right edge of the player
6101  		// This assumes the Prefs button is also positioned in that vicinity
6102  		// (last or second-last button the right)
6103
6104  		// NOTE: If previously unable to fully populate the Description dialog
6105  		// because the Web Speech API failed to getVoices()
6106  		// now is a good time to try again
6107  		// so the Description dialog can be rebuilt before the user requests it
6108
6109  		var thisObj, prefsButtonPosition, prefsMenuRight, prefsMenuLeft;
6110
6111  		thisObj = this;
6112
6113  		if (this.speechEnabled === null) {
6114  			this.initSpeech('prefs');
6115  		}
6116  		if (this.hidingPopup) {
6117  			// stopgap to prevent spacebar in Firefox from reopening popup
6118  			// immediately after closing it
6119  			this.hidingPopup = false;
6120  			return false;
6121  		}
6122  		if (this.prefsPopup.is(':visible')) {
6123  			this.prefsPopup.hide();
6124  			this.$prefsButton.attr('aria-expanded','false');
6125  			// restore each menu item to original hidden state
6126  			this.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
6127  			if (!this.showingPrefsDialog) {
6128  				this.$prefsButton.trigger('focus');
6129  			}
6130  			// wait briefly, then reset hidingPopup
6131  			setTimeout(function() {
6132  				thisObj.hidingPopup = false;
6133  			},100);
6134  		} else {
6135  			this.closePopups();
6136  			this.prefsPopup.show();
6137  			this.$prefsButton.attr('aria-expanded','true');
6138  			this.$prefsButton.trigger('focus'); // focus first on prefs button to announce expanded state
6139  			// give time for focus on button then adjust popup settings and focus
6140  			setTimeout(function() {
6141  				prefsButtonPosition = thisObj.$prefsButton.position();
6142  				prefsMenuRight = thisObj.$ableDiv.width() - 5;
6143  				prefsMenuLeft = prefsMenuRight - thisObj.prefsPopup.width();
6144  				thisObj.prefsPopup.css('top', prefsButtonPosition.top - thisObj.prefsPopup.outerHeight());
6145  				thisObj.prefsPopup.css('left', prefsMenuLeft);
6146  				// remove prior focus and set focus on first item; also change tabindex from -1 to 0
6147  				thisObj.prefsPopup.find('li').removeClass('able-focus').attr('tabindex','0');
6148  				thisObj.prefsPopup.find('li').first().trigger('focus').addClass('able-focus');
6149  			}, 50);
6150  		}
6151  	};
6152
6153  	AblePlayer.prototype.handleTranscriptToggle = function () {
6154  		var thisObj = this;
6155  		var visible = this.$transcriptDiv.is(':visible');
6156  		if ( visible ) {
6157  			this.$transcriptArea.hide();
6158  			this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) );
6159  			this.prefTranscript = 0;
6160  			if ( this.transcriptType === 'popup' ) {
6161  				this.$transcriptButton.trigger('focus').addClass('able-focus');
6162  				// wait briefly before resetting stopgap var
6163  				// otherwise the keypress used to select 'Close' will trigger the transcript button
6164  				// Benchmark tests: If this is gonna happen, it typically happens in around 3ms; max 12ms
6165  				// Setting timeout to 100ms is a virtual guarantee of proper functionality
6166  				setTimeout(function() {
6167  					thisObj.closingTranscript = false;
6168  				}, 100);
6169  			}
6170  		} else {
6171  			if ( this.transcriptType === 'popup' ) {
6172  				this.positionDraggableWindow('transcript');
6173  				this.$transcriptArea.show();
6174  				// showing transcriptArea has a cascading effect of showing all content *within* transcriptArea
6175  				// need to re-hide the popup menu
6176  				this.$transcriptPopup.hide();
6177  				this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) );
6178  				this.prefTranscript = 1;
6179  				// move focus to first focusable element (window options button)
6180  				this.focusNotClick = true;
6181  				this.$transcriptArea.find('button').first().trigger('focus');
6182  				// wait briefly before resetting stopgap var
6183  				setTimeout(function() {
6184  					thisObj.focusNotClick = false;
6185  				}, 100);
6186  			} else {
6187  				this.toggleButtonState( this.$transcriptButton, ! visible, this.translate( 'hideTranscript', 'Hide transcript' ), this.translate( 'showTranscript', 'Show transcript' ) );
6188  				this.$transcriptArea.show();
6189  			}
6190  		}
6191  		this.updatePreferences('prefTranscript');
6192  	};
6193
6194  	AblePlayer.prototype.handleSignToggle = function () {
6195
6196  		var thisObj = this;
6197  		var visible = this.$signWindow.is(':visible');
6198  		if ( visible ) {
6199  			this.$signWindow.hide();
6200  			this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) );
6201  			this.prefSign = 0;
6202  			this.$signButton.trigger('focus').addClass('able-focus');
6203  			// wait briefly before resetting stopgap var
6204  			// otherwise the keypress used to select 'Close' will trigger the transcript button
6205  			setTimeout(function() {
6206  				thisObj.closingSign = false;
6207  			}, 100);
6208  		} else {
6209  			this.positionDraggableWindow('sign');
6210  			this.$signWindow.show();
6211  			// showing signWindow has a cascading effect of showing all content *within* signWindow
6212  			// need to re-hide the popup menu
6213  			this.$signPopup.hide();
6214  			this.toggleButtonState( this.$signButton, ! visible, this.translate( 'hideSign', 'Hide sign language' ), this.translate( 'showSign', 'Show sign language' ) );
6215  			this.prefSign = 1;
6216  			this.focusNotClick = true;
6217  			this.$signWindow.find('button').first().trigger('focus');
6218  			// wait briefly before resetting stopgap var
6219  			// otherwise the keypress used to select 'Close' will trigger the transcript button
6220  			setTimeout(function() {
6221  				thisObj.focusNotClick = false;
6222  			}, 100);
6223  		}
6224  		this.updatePreferences('prefSign');
6225  	};
6226
6227  	AblePlayer.prototype.setFullscreen = function (fullscreen) {
6228
6229  		if (this.fullscreen == fullscreen) {
6230  			return;
6231  		}
6232  		var thisObj = this;
6233  		var $el = this.$ableWrapper;
6234  		var el = $el[0];
6235
6236  		if (this.nativeFullscreenSupported()) {
6237  			// Note: many varying names for options for browser compatibility.
6238  			if (fullscreen) {
6239  				var scroll = {
6240  					x: window.pageXOffset || 0,
6241  					y: window.pageYOffset || 0
6242  				};
6243  				if (this.prefTranscript === 1) {
6244  					// transcript is on. Go ahead and reposition it
6245  					this.rePositionDraggableWindow("transcript");
6246  				}
6247  				if (this.prefSign === 1) {
6248  					// sign is on. Go ahead and reposition it
6249  					this.rePositionDraggableWindow("sign");
6250  				}
6251  				this.scrollPosition = scroll;
6252  				// Initialize fullscreen
6253  				if (el.requestFullscreen) {
6254  					el.requestFullscreen();
6255  				} else if (el.webkitRequestFullscreen) {
6256  					el.webkitRequestFullscreen();
6257  				}
6258  				this.fullscreen = true;
6259  			} else {
6260  				// Exit fullscreen
6261  				this.restoringAfterFullscreen = true;
6262  				if (document.exitFullscreen) {
6263  					document.exitFullscreen();
6264  				} else if (document.webkitExitFullscreen) {
6265  					document.webkitExitFullscreen();
6266  				} else if (document.webkitCancelFullscreen) {
6267  					document.webkitCancelFullscreen();
6268  				}
6269  				if (this.prefTranscript === 1) {
6270  					// transcript is on. Go ahead and reposition it
6271  					this.positionDraggableWindow("transcript");
6272  				}
6273  				if (this.prefSign === 1) {
6274  					// sign is on. Go ahead and reposition it
6275  					this.positionDraggableWindow("sign");
6276  				}
6277  				this.fullscreen = false;
6278  			}
6279  		}
6280  		// add event handlers for changes in fullscreen mode.
6281  		// Browsers natively trigger this event with the Escape key,
6282  		// in addition to clicking the exit fullscreen button
6283  		$(document).on('fullscreenchange webkitfullscreenchange', function(e) {
6284  			// NOTE: e.type = the specific event that fired (in case needing to control for browser-specific idiosyncrasies)
6285  			if (!thisObj.fullscreen) {
6286  				// user has just exited full screen
6287  				thisObj.restoringAfterFullscreen = true;
6288  			} else if (!thisObj.clickedFullscreenButton) {
6289  				// user triggered fullscreenchange without clicking fullscreen button
6290  				thisObj.fullscreen = false;
6291  				thisObj.restoringAfterFullscreen = true;
6292  			}
6293  			thisObj.resizePlayer();
6294  			thisObj.refreshControls('fullscreen');
6295  			// Reset scrollPosition after closing fullscreen.
6296  			if ( thisObj.scrollPosition ) {
6297  				scroll = thisObj.scrollPosition;
6298  				window.scrollTo( scroll.x, scroll.y );
6299  			}
6300  			// NOTE: The fullscreenchange (or browser-equivalent) event is triggered twice
6301  			// when exiting fullscreen via the "Exit fullscreen" button (only once if using Escape)
6302  			// Not sure why, but consequently we need to be sure thisObj.clickedFullscreenButton
6303  			// continues to be true through both events
6304  			// Could use a counter variable to control that (reset to false after the 2nd trigger)
6305  			// However, since I don't know why it's happening, and whether it's 100% reliable
6306  			// resetting clickedFullscreenButton after a timeout seems to be better approach
6307  			setTimeout(function() {
6308  				thisObj.clickedFullscreenButton = false;
6309  				thisObj.restoringAfterFullscreen = false;
6310  			},100);
6311  		});
6312  	};
6313
6314  	AblePlayer.prototype.handleFullscreenToggle = function () {
6315
6316  		var stillPaused = this.paused;
6317  		this.setFullscreen(!this.fullscreen);
6318  		if (stillPaused) {
6319  			this.pauseMedia(); // when toggling fullscreen and media is just paused, keep media paused.
6320  		} else if (!stillPaused) {
6321  			this.playMedia(); // when toggling fullscreen and media is playing, continue playing.
6322  		}
6323  		// automatically hide controller in fullscreen mode
6324  		// then reset back to original setting after exiting fullscreen mode
6325  		if (this.fullscreen) {
6326  			this.hideControls = true;
6327  			if (this.playing) {
6328  				// go ahead and hide the controls
6329  				this.fadeControls('out');
6330  				this.controlsHidden = true;
6331  			}
6332  		} else {
6333  			// exit fullscreen mode
6334  			this.hideControls = this.hideControlsOriginal;
6335  			if (!this.hideControls) { // do not hide controls
6336  				if (this.controlsHidden) {
6337  					this.fadeControls('in');
6338  					this.controlsHidden = false;
6339  				}
6340  				// if there's an active timeout to fade controls out again, clear it
6341  				if (this.hideControlsTimeoutStatus === 'active') {
6342  					window.clearTimeout(this.hideControlsTimeout);
6343  					this.hideControlsTimeoutStatus = 'clear';
6344  				}
6345  			}
6346  		}
6347  		// don't resizePlayer yet; that will be called in response to the window resize event
6348  	};
6349
6350  	AblePlayer.prototype.handleTranscriptLockToggle = function (val) {
6351
6352  		this.autoScrollTranscript = val; // val is boolean
6353  		this.prefAutoScrollTranscript = +val; // convert boolean to numeric 1 or 0 for cookie
6354  		this.updatePreferences('prefAutoScrollTranscript');
6355  		this.refreshControls('transcript');
6356  	};
6357
6358  	AblePlayer.prototype.getIcon = function( $button, id) {
6359  		// Remove existing HTML before generating.
6360  		// iconData: [0 = svg viewbox, 1 = svg path]
6361  		// Font and image icon functionality was removed in 5.0.0 in favor of SVG.
6362  		var iconData = this.getIconData( id );
6363
6364  		var existingIcon = $button.find( 'svg#ableplayer-' + id );
6365  		// Avoid repainting icon if there's no change.
6366  		if ( existingIcon.length > 0 ) {
6367  			return;
6368  		}
6369  		$button.find('svg').remove();
6370
6371  		// Outdented for simpler diff
6372  			// Function to create SVG nodes.
6373  			function getNode(n, v) {
6374  				n = document.createElementNS("http://www.w3.org/2000/svg", n);
6375  				for (var p in v) {
6376  					n.setAttributeNS(null, p.replace(/[A-Z]/g, function(m) {
6377  						return "-" + m.toLowerCase();
6378  					}), v[p]);
6379  				}
6380  				return n;
6381  			}
6382  			var icon = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
6383  			icon.setAttribute( 'focusable', 'false' );
6384  			icon.setAttribute( 'aria-hidden', 'true');
6385  			icon.setAttribute( 'viewBox', iconData[0] );
6386  			icon.setAttribute( 'id', 'ableplayer-' + id );
6387  			let path = getNode( 'path', { d: iconData[1] } );
6388  			icon.appendChild( path );
6389
6390  			$button.append( icon );
6391  			// Refresh the DOM.
6392  			$button.html($button.html());
6393  	};
6394
6395  	AblePlayer.prototype.setText = function( $button, text ) {
6396  		$button.attr( 'aria-label', text );
6397  	};
6398
6399  	AblePlayer.prototype.toggleButtonState = function($button, isOn, onLabel, offLabel, ariaPressed = false, ariaExpanded = false) {
6400  		// isOn means "the feature is being turned on".
6401  		let buttonOff = ( $button.hasClass( 'buttonOff' ) ) ? true : false;
6402  		if ( buttonOff && ! isOn || ! buttonOff && isOn ) {
6403  			// Only toggle state if button state does not match feature state.
6404  			return;
6405  		}
6406  		if (! isOn) {
6407  			$button.addClass('buttonOff').attr('aria-label', offLabel);
6408  			if ( ariaPressed ) {
6409  				$button.attr('aria-pressed', 'false');
6410  			}
6411  			if ( ariaExpanded ) {
6412  				$button.attr( 'aria-expanded', 'false' );
6413  			}
6414  		} else {
6415  			$button.removeClass('buttonOff').attr('aria-label', onLabel);
6416  			if ( ariaPressed ) {
6417  				$button.attr('aria-pressed', 'true');
6418  			}
6419  			if ( ariaExpanded ) {
6420  				$button.attr( 'aria-expanded', 'true' );
6421  			}
6422  		}
6423  	};
6424
6425  	AblePlayer.prototype.showTooltip = function($tooltip) {
6426
6427  		$tooltip.show();
6428  	};
6429
6430  	AblePlayer.prototype.showAlert = function( msg, location = 'main' ) {
6431
6432  		// location is either of the following:
6433  		// 'main' (default)
6434  		// 'screenreader (visibly hidden)
6435  		// 'sign' (sign language window)
6436  		// 'transcript' (transcript window)
6437  		var thisObj, $alertBox, $parentWindow;
6438
6439  		thisObj = this;
6440  		$alertBox = thisObj.$alertBox;
6441  		$parentWindow = thisObj.$ableDiv;
6442  		if (location === 'transcript') {
6443  			$parentWindow = thisObj.$transcriptArea;
6444  		} else if (location === 'sign') {
6445  			$parentWindow = thisObj.$signWindow;
6446  		} else if (location === 'screenreader') {
6447  			$alertBox = thisObj.$srAlertBox;
6448  		}
6449  		$alertBox.find('span').text(msg);
6450  		$alertBox.appendTo($parentWindow);
6451  		$alertBox.css( {'display': 'flex'} );
6452
6453  		if (location !== 'screenreader') {
6454  			setTimeout( function () {
6455  				$alertBox.hide();
6456  			}, 30000 );
6457  		}
6458  	};
6459
6460  	AblePlayer.prototype.showedAlert = function (which) {
6461
6462  		// returns true if the target alert has already been shown
6463  		// useful for throttling alerts that only need to be shown once
6464  		// e.g., move alerts with instructions for dragging a window
6465  		if (which === 'transcript') {
6466  			return this.showedTranscriptAlert ?? false;
6467  		} else if (which === 'sign') {
6468  			return this.showedSignAlert ?? false;
6469  		}
6470  		return false;
6471  	};
6472
6473  	// Resizes all relevant player attributes.
6474  	AblePlayer.prototype.resizePlayer = function (width, height) {
6475
6476  		var captionSize, newWidth, newHeight, $iframe;
6477
6478  		if (this.mediaType === 'audio') {
6479  			return;
6480  		}
6481  		if (typeof width !== 'undefined' && typeof height !== 'undefined') {
6482  			// this is being called the first time a player is initialized
6483  			// width and height were collected from the HTML, YouTube, or Vimeo media API
6484  			// so are reflective of the actual size of the media
6485  			// use these values to calculate aspectRatio
6486  			this.aspectRatio = height / width;
6487  			if (this.playerWidth) {
6488  				// default width is already defined via a width or data-width attribute. Use that.
6489  				newWidth = this.playerWidth;
6490  				if (this.playerHeight) {
6491  					newHeight = this.playerHeight;
6492  				} else {
6493  					newHeight = Math.round(newWidth * this.aspectRatio);
6494  					this.playerHeight = newHeight;
6495  				}
6496  			} else {
6497  				// playerWidth was not defined via HTML attributes
6498  				newWidth = (this.player === 'html5') ? $(window).width() : this.$ableWrapper.width();
6499  				newHeight = Math.round(newWidth * this.aspectRatio);
6500  			}
6501  		} else if (this.fullscreen) {
6502  			this.$ableWrapper.addClass('fullscreen');
6503  			newWidth = $(window).width();
6504  			// the 5 pixel buffer is arbitrary, but results in a better fit for all browsers
6505  			newHeight = $(window).height() - this.$playerDiv.outerHeight() - 5;
6506  			this.positionCaptions('overlay');
6507  		} else { // not fullscreen, and not first time initializing player
6508  			this.$ableWrapper.removeClass('fullscreen');
6509  			if (this.player === 'html5') {
6510  				newWidth = (this.playerWidth) ? this.playerWidth : $(window).width();
6511  			} else {
6512  				newWidth = this.$ableWrapper.width();
6513  			}
6514  			newHeight = Math.round(newWidth * this.aspectRatio);
6515  			this.positionCaptions(this.prefCaptionsPosition);
6516  		}
6517  		if (this.debug) {
6518  			console.log('resizePlayer to ' + newWidth + 'x' + newHeight);
6519  		}
6520  		// Now size the player with newWidth and newHeight
6521  		if (this.player === 'youtube' || this.player === 'vimeo') {
6522  			$iframe = this.$ableWrapper.find('iframe');
6523  			if (this.player === 'youtube' && this.youTubePlayer) {
6524  				// alternatively, YouTube API offers a method for setting the video size
6525  				// this adds width and height attributes to the iframe
6526  				// but might have other effects, so best to do it this way
6527  				this.youTubePlayer.setSize(newWidth,newHeight);
6528  			} else {
6529  				// Vimeo API does not have a method for changing size of player
6530  				// Therefore, need to change iframe attributes directly
6531  				$iframe.attr({
6532  					'width': newWidth,
6533  					'height': newHeight
6534  				});
6535  			}
6536  			if (this.playerWidth && this.playerHeight) {
6537  				if (this.fullscreen) {
6538  					// remove constraints
6539  					$iframe.css({
6540  						'max-width': '',
6541  						'max-height': ''
6542  					});
6543  				} else {
6544  					// use CSS on iframe to enforce explicitly defined size constraints
6545  					$iframe.css({
6546  						'max-width': this.playerWidth + 'px',
6547  						'max-height': this.playerHeight + 'px'
6548  					});
6549  				}
6550  			}
6551  		} else if (this.player === 'html5') {
6552  			if (this.fullscreen) {
6553  				this.$media.attr({
6554  					'width': newWidth,
6555  					'height': newHeight
6556  				});
6557  				this.$ableWrapper.css({
6558  					'width': newWidth,
6559  					'height': newHeight
6560  				});
6561  			} else {
6562  					// No constraints. Let CSS handle the positioning.
6563  				this.$media.removeAttr('width height');
6564  				this.$ableWrapper.removeAttr( 'style' );
6565  			}
6566  		}
6567  		// Resize captions
6568  		if (typeof this.$captionsDiv !== 'undefined') {
6569
6570  			// Font-size is too small in full screen view
6571  			// use viewport units (vw) for large viewports
6572  			// % units work fine if not fullscreen
6573  			// prefCaptionSize is expressed as a percentage
6574  			var isSmallScreen = false;
6575  			var windowWidth = window.screen.width;
6576  			if ( windowWidth < 1200 ) {
6577  				isSmallScreen = true;
6578  			}
6579  			captionSize = parseInt(this.prefCaptionsSize,10);
6580  			if (this.fullscreen && ! isSmallScreen ) {
6581  				captionSize = (captionSize / 100) + 'vw';
6582  			} else if ( this.fullscreen && isSmallScreen ) {
6583  				captionSize = '1.2rem';
6584  			} else {
6585  				captionSize = captionSize + '%';
6586  			}
6587  			this.$captionsDiv.css({
6588  				'font-size': captionSize
6589  			});
6590  		}
6591  		this.refreshControls();
6592  	};
6593
6594  	AblePlayer.prototype.retrieveOffscreenWindow = function( which, width, height ) {
6595
6596  		// check to be sure popup windows ('transcript' or 'sign') are positioned on-screen
6597  		// (they sometimes disappear off-screen when entering fullscreen mode)
6598  		// if off-screen, recalculate so they are back on screen
6599
6600  		var window, windowPos, windowTop, windowLeft, windowRight, windowWidth, windowBottom, windowHeight;
6601
6602  		if (which == 'transcript') {
6603  			window = this.$transcriptArea;
6604  		} else if (which == 'sign') {
6605  			window = this.$signWindow;
6606  		}
6607  		windowWidth = window.width();
6608  		windowHeight = window.height();
6609  		windowPos = window.position();
6610  		windowTop = windowPos.top;
6611  		windowLeft = windowPos.left;
6612  		windowRight = windowLeft + windowWidth;
6613  		windowBottom = windowTop + windowHeight;
6614
6615  		if (windowTop < 0) { // off-screen to the top
6616  			windowTop = 10;
6617  			window.css('top',windowTop);
6618  		}
6619  		if (windowLeft < 0) { // off-screen to the left
6620  			windowLeft = 10;
6621  			window.css('left',windowLeft);
6622  		}
6623  		if (windowRight > width) { // off-screen to the right
6624  			windowLeft = (width - 20) - windowWidth;
6625  			window.css('left',windowLeft);
6626  		}
6627  		if (windowBottom > height) { // off-screen to the bottom
6628  			windowTop = (height - 10) - windowHeight;
6629  			window.css('top',windowTop);
6630  		}
6631  	};
6632
6633  	AblePlayer.prototype.updateZIndex = function(which) {
6634
6635  		// update z-index of 'transcript' or 'sign', relative to each other
6636  		// direction is always 'up' (i.e., move window to top)
6637  		// windows come to the top when the user clicks on them
6638  		var defHighZ, defLowZ, transcriptZ, signZ, newHighZ, newLowZ;
6639
6640  		// set the default z-indexes, as defined in ableplayer.css
6641  		defHighZ = 8000; // by default, assigned to the sign window
6642  		defLowZ = 7000; // by default, assigned to the transcript area
6643
6644  		// Previously collected the highest z-index. Removed in 4.6.
6645  		// If something on the page has a higher z-index than the transcript or sign window, do we care?
6646  		// Excluding it here assumes "No". Our immediate concern is with the relationship between our own components.
6647  		// If we elevate our z-indexes so our content is on top, we run the risk of starting a z-index war.
6648
6649  		if (typeof this.$transcriptArea === 'undefined' || typeof this.$signWindow === 'undefined' ) {
6650  			// at least one of the windows doesn't exist, so there's no conflict
6651  			// since z-index may have been stored to a cookie on another page, need to restore default
6652  			if (typeof this.$transcriptArea !== 'undefined') {
6653  				transcriptZ = parseInt(this.$transcriptArea.css('z-index'));
6654  				if (transcriptZ > defLowZ) {
6655  					// restore to the default
6656  					this.$transcriptArea.css('z-index',defLowZ);
6657  				}
6658  			} else if (typeof this.$signWindow !== 'undefined') {
6659  				signZ = parseInt(this.$signWindow.css('z-index'));
6660  				if (signZ > defHighZ) {
6661  					// restore to the default
6662  					this.$signWindow.css('z-index',defHighZ);
6663  				}
6664  			}
6665  			return false;
6666  		}
6667
6668  		// both windows exist
6669
6670  		// get current values
6671  		transcriptZ = parseInt(this.$transcriptArea.css('z-index'));
6672  		signZ = parseInt(this.$signWindow.css('z-index'));
6673
6674  		if (transcriptZ === signZ) {
6675  			// the two windows are equal; restore defaults (the target window will be on top)
6676  			newHighZ = defHighZ;
6677  			newLowZ = defLowZ;
6678  		} else if (transcriptZ > signZ) {
6679  			if (which === 'transcript') {
6680  				// transcript is already on top; nothing to do
6681  				return false;
6682  			} else {
6683  				// swap z's
6684  				newHighZ = transcriptZ;
6685  				newLowZ = signZ;
6686  			}
6687  		} else { // signZ is greater
6688  			if (which === 'sign') {
6689  				// sign is already on top; nothing to do
6690  				return false;
6691  			} else {
6692  				newHighZ = signZ;
6693  				newLowZ = transcriptZ;
6694  			}
6695  		}
6696  		// now assign the new values
6697  		if (which === 'transcript') {
6698  			this.$transcriptArea.css('z-index',newHighZ);
6699  			this.$signWindow.css('z-index',newLowZ);
6700  		} else if (which === 'sign') {
6701  			this.$signWindow.css('z-index',newHighZ);
6702  			this.$transcriptArea.css('z-index',newLowZ);
6703  		}
6704  	};
6705
6706  	AblePlayer.prototype.syncTrackLanguages = function (source, language) {
6707
6708  		// this function is called when the player is built (source == 'init')
6709  		// and again when user changes the language of either 'captions' or 'transcript'
6710  		// It syncs the languages of chapters, descriptions, and metadata tracks
6711  		// NOTE: Caption and transcript languages are somewhat independent from one another
6712  		// If a user changes the caption language, the transcript follows
6713  		// However, if a user changes the transcript language, this only affects the transcript
6714  		// This was a group decision based on the belief that users may want a transcript
6715  		// that is in a different language than the captions
6716
6717  		var i, captions, descriptions, chapters, meta;
6718
6719  		// Captions
6720  		for (i = 0; i < this.captions.length; i++) {
6721  			if (this.captions[i].language === language) {
6722  				captions = this.captions[i];
6723  			}
6724  		}
6725  		// Chapters
6726  		for (i = 0; i < this.chapters.length; i++) {
6727  			if (this.chapters[i].language === language) {
6728  				chapters = this.chapters[i];
6729  			}
6730  		}
6731  		// Descriptions
6732  		for (i = 0; i < this.descriptions.length; i++) {
6733  			if (this.descriptions[i].language === language) {
6734  				descriptions = this.descriptions[i];
6735  			}
6736  		}
6737  		// Metadata
6738  		for (i = 0; i < this.meta.length; i++) {
6739  			if (this.meta[i].language === language) {
6740  				meta = this.meta[i];
6741  			}
6742  		}
6743  		// regardless of source...
6744  		this.transcriptLang = language;
6745  		if (source === 'init' || source === 'captions') {
6746  			this.captionLang = language;
6747  			this.selectedCaptions = captions;
6748  			this.selectedChapters = chapters;
6749  			this.selectedDescriptions = descriptions;
6750  			this.selectedMeta = meta;
6751  			this.transcriptCaptions = captions;
6752  			this.transcriptChapters = chapters;
6753  			this.transcriptDescriptions = descriptions;
6754  			this.updateChaptersList();
6755  			// the following was commented out in Oct/Nov 2018.
6756  			// chapters popup is setup automatically when setupPopups() is called later with no param
6757  			// not sure why it was included here.
6758  			// this.setupPopups('chapters');
6759  		} else if (source === 'transcript') {
6760  			this.transcriptCaptions = captions;
6761  			this.transcriptChapters = chapters;
6762  			this.transcriptDescriptions = descriptions;
6763  		}
6764  		if (this.selectedDescriptions) {
6765  			// updating description voice to match new description language
6766  			this.setDescriptionVoice();
6767  			if (this.$sampleDescDiv) {
6768  				if (this.sampleText) {
6769  					for (i = 0; i < this.sampleText.length; i++) {
6770  						if (this.sampleText[i].lang === this.selectedDescriptions.language) {
6771  							this.currentSampleText = this.sampleText[i]['text'];
6772  							this.$sampleDescDiv.html(this.currentSampleText);
6773  						}
6774  					}
6775  				}
6776  			}
6777  		}
6778  		this.updateTranscript();
6779  	};
6780
6781  }
6782
6783  function addDescriptionFunctions(AblePlayer) {
6784  	AblePlayer.prototype.initDescription = function() {
6785
6786  		// set default mode for delivering description (open vs closed)
6787  		// based on availability and user preference
6788
6789  		// called when player is being built, or when a user
6790  		// toggles the Description button or changes a description-related preference
6791
6792  		// The following variables are applicable to delivery of description:
6793  		// defaultStateDescriptions == 'on' or 'off', defined by website owner (overridden by prefDesc)
6794  		// prefDesc == 1 if user wants description (i.e., Description button is on); else 0
6795  		// prefDescPause == 1 to pause video when description starts; else 0
6796  		// prefDescVisible == 1 to visibly show text-based description area; else 0
6797  		// prefDescMethod == either 'video' or 'text' (as of v4.0.10, prefDescMethod is always 'video')
6798  		// descMethod is the format actually used ('video' or 'text'), regardless of user preference
6799  		// hasOpenDesc == true if a described version of video is available via data-desc-src attribute
6800  		// hasClosedDesc == true if a description text track is available
6801  		// descOn == true if description of either type is on
6802  		// readDescriptionsAloud == true if text description is to be announced audibly; otherwise false
6803  		// descReader == either 'browser' or 'screenreader'
6804
6805  		var deferred, promise;
6806
6807  		deferred = new this.defer();
6808  		promise = deferred.promise();
6809
6810  		if (this.mediaType === 'audio') {
6811  			deferred.resolve();
6812  		}
6813
6814  		// check to see if there's an open-described version of this video
6815  		// checks only the first source since if a described version is provided,
6816  		// it must be provided for all sources
6817  		this.descFile = this.$sources.first().attr('data-desc-src');
6818  		if (typeof this.descFile !== 'undefined') {
6819  			this.hasOpenDesc = true;
6820  		} else {
6821  			// there's no open-described version via data-desc-src,
6822  			// but what about data-youtube-desc-src or data-vimeo-desc-src?
6823  			// if these exist, they would have been defined earlier
6824  			this.hasOpenDesc = (this.youTubeDescId || this.vimeoDescId) ? true : false;
6825  		}
6826
6827  		// Set this.descMethod based on media availability & user preferences
6828  		// no description is available for this video
6829  		this.descMethod = null;
6830  		if (this.hasOpenDesc && this.hasClosedDesc) {
6831  			// both formats are available. User gets their preference.
6832  			this.descMethod = (this.prefDescMethod) ? this.prefDescMethod : 'video';
6833  		} else if (this.hasOpenDesc) {
6834  			this.descMethod = 'video';
6835  		} else if (this.hasClosedDesc) {
6836  			this.descMethod = 'text';
6837  		}
6838
6839  		// Set the default state of descriptions
6840  		this.descOn = false;
6841  		if (this.descMethod) {
6842  			if (this.prefDesc === 1) {
6843  				this.descOn = true;
6844  			} else if (this.prefDesc === 0) {
6845  				this.descOn = false;
6846  			} else {
6847  				// user has no prefs. Use default state.
6848  				this.descOn = (this.defaultStateDescriptions === 1) ? true : false;
6849  			}
6850  		}
6851
6852  		// If a video has text audio descriptions, inject the description area.
6853  		if (typeof this.$descDiv === 'undefined' && this.hasClosedDesc ) {
6854  			this.injectTextDescriptionArea();
6855  		}
6856
6857  		if (this.descOn) {
6858  			if (this.descMethod === 'video' && !this.usingDescribedVersion() ) {
6859  				// switched from non-described to described version
6860  				this.swapDescription();
6861  			}
6862  			if (this.hasClosedDesc) {
6863  				if (this.prefDescVisible) {
6864  					// make description text visible
6865  					if (typeof this.$descDiv !== 'undefined') {
6866  						this.$descDiv.show();
6867  						this.$descDiv.removeClass('able-offscreen');
6868  					}
6869  				} else {
6870  					// keep it visible to screen readers, but hide it visibly
6871  					if (typeof this.$descDiv !== 'undefined') {
6872  						this.$descDiv.addClass('able-offscreen');
6873  					}
6874  				}
6875  			}
6876  		} else { // description is off.
6877  			if (this.descMethod === 'video') { // user has turned off described version of video
6878  				if (this.usingDescribedVersion()) {
6879  					// user was using the described verion. Swap for non-described version
6880  					this.swapDescription();
6881  				}
6882  			} else if (this.descMethod === 'text') { // user has turned off text description
6883  				// hide description div from everyone, including screen reader users
6884  				if (typeof this.$descDiv !== 'undefined') {
6885  					this.$descDiv.hide();
6886  					this.$descDiv.removeClass('able-offscreen');
6887  				}
6888  			}
6889  		}
6890  		deferred.resolve();
6891  		return promise;
6892  	};
6893
6894  	AblePlayer.prototype.usingDescribedVersion = function () {
6895
6896  		// Returns true if currently using audio description, false otherwise.
6897
6898  		if (this.player === 'youtube') {
6899  			return (this.activeYouTubeId === this.youTubeDescId);
6900  		} else if (this.player === 'vimeo') {
6901  			return (this.activeVimeoId === this.vimeoDescId);
6902  		} else {
6903  			return (this.$sources.first().attr('data-desc-src') === this.$sources.first().attr('src'));
6904  		}
6905  	};
6906
6907  /**
6908     * Initializes speech synthesis capabilities for the player.
6909     * This method addresses browser and OS limitations that require user interaction
6910     * before speech synthesis functions become available. It handles different contexts
6911     * like initialization, playing media, accessing preferences, or announcing descriptions.
6912     * @param {string} context - The context in which the function is called ('init', 'play', 'prefs', 'desc').
6913     */
6914  	AblePlayer.prototype.initSpeech = function (context) {
6915  		var thisObj = this;
6916
6917  		// Function to attempt enabling speech synthesis
6918  		function attemptEnableSpeech() {
6919  			var greeting = new SpeechSynthesisUtterance("\x20");
6920  			greeting.onend = function () {
6921  				thisObj.getBrowserVoices();
6922  				if (
6923  					(Array.isArray(thisObj.descVoices) && thisObj.descVoices.length) ||
6924  					context !== "init"
6925  				) {
6926  					thisObj.speechEnabled = true;
6927  				}
6928  			};
6929  			thisObj.synth.speak(greeting);
6930  		}
6931
6932  		// Function to handle the initial click and enable speech synthesis
6933  		function handleInitialClick() {
6934  			attemptEnableSpeech();
6935  			// Once the utterance starts, remove this specific click event listener
6936  			// Ensures the event handler only runs once and cleans up after itself
6937  			$(document).off("click", handleInitialClick);
6938  		}
6939
6940  		if (this.speechEnabled === null) {
6941  			if (window.speechSynthesis) {
6942  				// Browser supports speech synthesis
6943  				this.synth = window.speechSynthesis;
6944  				this.synth.cancel(); // Cancel any ongoing speech synthesis
6945
6946  				if (context === "init") {
6947  					// Attempt to enable speech synthesis directly for browsers that don't require a click
6948  					attemptEnableSpeech();
6949  					// For initial setup, require a user click to enable speech synthesis
6950  					// Scoping to a particular handler to avoid conflicts with other click events
6951  					$(document).on("click", handleInitialClick);
6952  				} else {
6953  					// For other contexts, attempt to enable speech synthesis directly
6954  					attemptEnableSpeech();
6955  				}
6956  			} else {
6957  				// Browser does not support speech synthesis
6958  				this.speechEnabled = false;
6959  			}
6960  		}
6961  	};
6962
6963  	AblePlayer.prototype.getBrowserVoices = function () {
6964
6965  		// define this.descVoices array
6966  		// includes only languages that match the language of the captions or player
6967
6968  		var voices, trackLangs, voiceLang, preferredLang;
6969
6970  		preferredLang = (this.captionLang) ? this.captionLang.substring(0,2).toLowerCase() : this.lang.substring(0,2).toLowerCase();
6971
6972  		this.descVoices = [];
6973  		voices = this.synth.getVoices();
6974  		trackLangs = this.getTrackLangs();
6975  		if (voices.length > 0) {
6976  			this.descVoices = [];
6977  			// available languages are identified with local suffixes (e.g., en-US)
6978  			for (var i=0; i<voices.length; i++) {
6979  				// match only the first 2 characters of the lang code
6980  				voiceLang = voices[i].lang.substring(0,2).toLowerCase();
6981  				if (voiceLang === preferredLang && (trackLangs.indexOf(voiceLang) !== -1)) {
6982  					// this voice matches preferredLang
6983  					// AND there's a matching description track in this language
6984  					// Add this voice to final array
6985  					this.descVoices.push(voices[i]);
6986  				}
6987  			}
6988  			if (!this.descVoices.length) {
6989  				// no voices available in the default language(s)
6990  				// just use all voices, regardless of language
6991  				this.descVoices = voices;
6992  			}
6993  		}
6994  		return false;
6995  	};
6996
6997  	AblePlayer.prototype.getTrackLangs = function () {
6998
6999  		// returns an array of languages (from srclang atttributes)
7000  		// in which there are description tracks
7001  		// use only first two characters of the lang code
7002  		var descLangs = [];
7003  		if (this.tracks) {
7004  			for (var i=0; i < this.tracks.length; i++) {
7005  				if (this.tracks[i].kind === 'descriptions' || this.tracks[i].kind === 'captions' ) {
7006  					descLangs.push(this.tracks[i].language.substring(0,2).toLowerCase());
7007  				}
7008  			}
7009  		}
7010  		return descLangs;
7011  	};
7012
7013  	AblePlayer.prototype.setDescriptionVoice = function () {
7014
7015  		// set description voice on player init, or when user changes caption language
7016  		// Voice is determined in the following order of precedence:
7017  		// 1. User's preferred voice for this language, saved in preferences
7018  		// 2. The first available voice in the array of available voices for this browser in this language
7019
7020  		var preferences, voices, prefDescVoice, descVoice, descLang, prefVoiceFound;
7021  		preferences = this.getPref();
7022  		prefDescVoice = (typeof preferences.voices !== 'undefined') ? this.getPrefVoice() : null;
7023
7024  		this.getBrowserVoices();
7025  		this.rebuildVoicePrefsForm( '_prefDescVoice' );
7026  		this.rebuildVoicePrefsForm( '_prefCaptionsVoice' );
7027
7028  		if (this.selectedDescriptions) {
7029  			descLang = this.selectedDescriptions.language;
7030  		} else if (this.captionLang) {
7031  			descLang = this.captionLang;
7032  		} else {
7033  			descLang = this.lang;
7034  		}
7035
7036  		if (this.synth) {
7037  			voices = this.synth.getVoices();
7038  			if (voices.length > 0) {
7039  				if (prefDescVoice) {
7040  					// select the language that matches prefDescVoice, if it's available
7041  					prefVoiceFound = false;
7042  					for (var i=0; i<voices.length; i++) {
7043  						// first, be sure voice is the correct language
7044  						if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) {
7045  							if (voices[i].name === prefDescVoice) {
7046  								descVoice = voices[i].name;
7047  								prefVoiceFound = true;
7048  								break;
7049  							}
7050  						}
7051  					}
7052  				}
7053  				if (!prefVoiceFound) {
7054  					// select the first language that matches the first 2 characters of the lang code
7055  					for (i=0; i<voices.length; i++) {
7056  						if (voices[i].lang.substring(0,2).toLowerCase() === descLang.substring(0,2).toLowerCase()) {
7057  							descVoice = voices[i].name;
7058  							break;
7059  						}
7060  					}
7061  				}
7062  				// make this the user's current preferred voice
7063  				this.prefDescVoice = descVoice;
7064  				this.prefDescVoiceLang = descLang;
7065  				// select this voice in the Description Prefs dialog
7066  				if (this.$voiceSelectField) {
7067  					this.$voiceSelectField.val(this.prefDescVoice);
7068  				}
7069  				this.updatePreferences('voice');
7070  			}
7071  		}
7072  	};
7073
7074  	AblePlayer.prototype.swapDescription = function() {
7075
7076  		// swap described and non-described source media, depending on which is playing
7077  		// this function is only called in two circumstances:
7078  		// 1. Swapping to described version when initializing player (based on user prefs & availability)
7079  		// (playerCreated == false)
7080  		// 2. User is toggling description
7081  		// (playerCreated == true)
7082
7083  		var thisObj, i, origSrc, descSrc;
7084
7085  		thisObj = this;
7086
7087  		// We are no longer loading the previous media source
7088  		// Only now, as a new source is requested, is it safe to reset this var
7089  		// It will be reset to true when media.load() is called
7090  		this.loadingMedia = false;
7091
7092  		// get element that has focus at the time swap is initiated
7093  		// after player is rebuilt, focus will return to that same element
7094  		// (if it exists)
7095  		this.$focusedElement = $(':focus');
7096  		this.activeMedia = this.mediaId;
7097
7098  		// get current time of current source, and attempt to start new video at the same time
7099  		// whether this is possible will be determined after the new media source has loaded
7100  		// see onMediaNewSourceLoad()
7101  		if (this.elapsed > 0) {
7102  			this.swapTime = this.elapsed;
7103  		} else {
7104  			this.swapTime = 0;
7105  		}
7106  		if (this.duration > 0) {
7107  			this.prevDuration = this.duration;
7108  		}
7109
7110  		// Capture current playback state, so media can resume after source is swapped
7111  		if (!this.okToPlay) {
7112  			this.okToPlay = this.playing;
7113  		}
7114
7115  		if (this.descOn) {
7116  			this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) );
7117  		} else {
7118  			this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) );
7119  		}
7120
7121  		if (this.player === 'html5') {
7122
7123  			this.swappingSrc = true;
7124  			this.paused = true;
7125
7126  			if (this.usingDescribedVersion()) {
7127  				// the described version is currently playing. Swap to non-described
7128  				for (i=0; i < this.$sources.length; i++) {
7129  					// for all <source> elements, replace src with data-orig-src
7130  					origSrc = purify.sanitize( this.$sources[i].getAttribute('data-orig-src') );
7131  					if (origSrc) {
7132  						this.$sources[i].setAttribute('src',origSrc);
7133  					}
7134  				}
7135  			} else {
7136  				// the non-described version is currently playing. Swap to described.
7137  				for (i=0; i < this.$sources.length; i++) {
7138  					// for all <source> elements, replace src with data-desc-src (if one exists)
7139  					// then store original source in a new data-orig-src attribute
7140  					origSrc = purify.sanitize( this.$sources[i].getAttribute('src') );
7141  					descSrc = purify.sanitize( this.$sources[i].getAttribute('data-desc-src') );
7142  					if (descSrc) {
7143  						this.$sources[i].setAttribute('src',descSrc);
7144  						this.$sources[i].setAttribute('data-orig-src',origSrc);
7145  					}
7146  				}
7147  			}
7148
7149  			if (this.recreatingPlayer) {
7150  				// stopgap to prevent multiple firings of recreatePlayer()
7151  				return;
7152  			}
7153  			if (this.playerCreated) {
7154  				// delete old player, then recreate it with new source & tracks
7155  				this.deletePlayer('swap-desc-html');
7156  				this.recreatePlayer().then(function() {
7157  					if (!thisObj.loadingMedia) {
7158  						thisObj.media.load();
7159  						thisObj.loadingMedia = true;
7160  					}
7161  				});
7162  			}
7163  		} else if (this.player === 'youtube') {
7164
7165  			// if the described version is currently playing, swap to non-described
7166  			this.activeYouTubeId = (this.usingDescribedVersion()) ? this.youTubeId : this.youTubeDescId;
7167
7168  			if (typeof this.youTubePlayer !== 'undefined') {
7169  				thisObj.swappingSrc = true;
7170  				if (thisObj.playing) {
7171  					// loadVideoById() loads and immediately plays the new video at swapTime
7172  					thisObj.youTubePlayer.loadVideoById(thisObj.activeYouTubeId,thisObj.swapTime);
7173  				} else {
7174  					// cueVideoById() loads the new video and seeks to swapTime, but does not play
7175  					thisObj.youTubePlayer.cueVideoById(thisObj.activeYouTubeId,thisObj.swapTime);
7176  				}
7177  			}
7178  			if (this.playerCreated) {
7179  				this.deletePlayer('swap-desc-youtube');
7180  			}
7181  			// player needs to be recreated with new source
7182  			if (this.recreatingPlayer) {
7183  				// stopgap to prevent multiple firings of recreatePlayer()
7184  				return;
7185  			}
7186  			this.recreatePlayer().then(function() {
7187  				// nothing to do here
7188  				// next steps occur when youtube onReady event fires
7189  				// see youtube.js > finalizeYoutubeInit()
7190  			});
7191  		} else if (this.player === 'vimeo') {
7192  			if (this.usingDescribedVersion()) {
7193  				// the described version is currently playing. Swap to non-described
7194  				this.activeVimeoId = this.vimeoId;
7195  				this.showAlert( this.translate( 'alertNonDescribedVersion', 'Using the non-described version of this video' ) );
7196  			} else {
7197  				// the non-described version is currently playing. Swap to described.
7198  				this.activeVimeoId = this.vimeoDescId;
7199  				this.showAlert( this.translate( 'alertDescribedVersion', 'Using the audio described version of this video' ) );
7200  			}
7201  			if (this.playerCreated) {
7202  				this.deletePlayer('swap-desc-vimeo');
7203  			}
7204  			// player needs to be recreated with new source
7205  			if (this.recreatingPlayer) {
7206  				// stopgap to prevent multiple firings of recreatePlayer()
7207  				return;
7208  			}
7209  			this.recreatePlayer().then(function() {
7210  				// load the new video source
7211  				thisObj.vimeoPlayer.loadVideo(thisObj.activeVimeoId).then(function() {
7212  					if (thisObj.playing) {
7213  						// video was playing when user requested an alternative version
7214  						// seek to swapTime and continue playback (playback happens automatically)
7215  						thisObj.vimeoPlayer.setCurrentTime(thisObj.swapTime);
7216  					} else {
7217  						// Vimeo autostarts immediately after video loads
7218  						// The "Described" button should not trigger playback, so stop this before the user notices.
7219  						thisObj.vimeoPlayer.pause();
7220  					}
7221  				});
7222  			});
7223  		}
7224  	};
7225
7226  	AblePlayer.prototype.showDescription = function(now) {
7227  		if (!this.playing || !this.hasClosedDesc || this.swappingSrc || !this.descOn || ( this.descMethod === 'video' && !this.prefDescVisible ) ) {
7228  			return;
7229  		}
7230
7231  		var cues, d, thisDescription, descText;
7232
7233  		var flattenComponentForDescription = function (component) {
7234  			var result = [];
7235  			if (component.type === 'string') {
7236  				result.push(component.value);
7237  			} else {
7238  				for (var i = 0; i < component.children.length; i++) {
7239  					result.push(flattenComponentForDescription(component.children[i]));
7240  				}
7241  			}
7242  			return result.join('');
7243  		};
7244  		cues = [];
7245  		if (this.selectedDescriptions) {
7246  			cues = this.selectedDescriptions.cues;
7247  		} else if (this.descriptions.length >= 1) {
7248  			cues = this.descriptions[0].cues;
7249  		}
7250  		for (d = 0; d < cues.length; d++) {
7251  			if ((cues[d].start <= now) && (cues[d].end > now)) {
7252  				thisDescription = d;
7253  				break;
7254  			}
7255  		}
7256  		if (typeof thisDescription !== 'undefined') {
7257  			if (this.currentDescription !== thisDescription) {
7258  				// temporarily remove aria-live from $status to prevent description from being interrupted
7259  				this.$status.removeAttr('aria-live');
7260  				descText = flattenComponentForDescription(cues[thisDescription].components);
7261  				if (this.descReader === 'screenreader') {
7262  					// load the new description into the container div for screen readers to read
7263  					this.$descDiv.html(descText);
7264  				} else if (this.speechEnabled) {
7265  					if ( 'video' !== this.descMethod ) {
7266  						// use browser's built-in speech synthesis
7267  						this.announceText('description',descText);
7268  					}
7269  					if (this.prefDescVisible) {
7270  						// write description to the screen for sighted users
7271  						// but remove ARIA attributes since it isn't intended to be read by screen readers
7272  						this.$descDiv.html(descText).removeAttr('aria-live aria-atomic');
7273  					}
7274  				} else {
7275  					// browser does not support speech synthesis
7276  					// load the new description into the container div for screen readers to read
7277  					this.$descDiv.html(descText);
7278  				}
7279  				// Only pause video if not using a described video.
7280  				if (this.prefDescPause && this.descMethod === 'text') {
7281  					this.pauseMedia();
7282  					this.pausedForDescription = true;
7283  				}
7284  				this.currentDescription = thisDescription;
7285  			}
7286  		} else {
7287  			this.$descDiv.html('');
7288  			this.currentDescription = -1;
7289  			// restore aria-live to $status
7290  			this.$status.attr('aria-live','polite');
7291  		}
7292  	};
7293
7294  	AblePlayer.prototype.syncSpeechToPlaybackRate = function(rate) {
7295
7296  		// called when user changed playback rate
7297  		// adjust rate of audio description to match
7298  		var speechRate;
7299
7300  		if (rate === 0.5) {
7301  			speechRate = 0.7; // option 1 in prefs menu
7302  		} else if (rate === 0.75) {
7303  			speechRate =  0.8; // option 2 in prefs menu
7304  		} else if (rate === 1.0) {
7305  			speechRate =  1; // option 4 in prefs menu (normal speech, default)
7306  		} else if (rate === 1.25) {
7307  			speechRate =  1.1; // option 5 in prefs menu
7308  		} else if (rate === 1.5) {
7309  			speechRate =  1.2; // option 6 in prefs menu
7310  		} else if (rate === 1.75) {
7311  			speechRate =  1.5; // option 7 in prefs menu
7312  		} else if (rate === 2.0) {
7313  			speechRate =  2; // option 8 in prefs menu (fast)
7314  		} else if (rate === 2.25) {
7315  			speechRate =  2.5; // option 9 in prefs menu (very fast)
7316  		} else if (rate >= 2.5) {
7317  			speechRate =  3; // option 10 in prefs menu (super fast)
7318  		}
7319  		this.prefDescRate = speechRate;
7320  	};
7321
7322  	AblePlayer.prototype.announceText = function(context, text, rate) {
7323
7324  		// this function announces text using speech synthesis
7325  		// it's only called if already determined that browser supports speech synthesis
7326  		// context is either:
7327  		// 'description' - actual description text extracted from WebVTT file
7328  		// 'sample' - called when user changes a setting in Description Prefs dialog
7329  		// 'caption' - called when announcing a caption.
7330
7331  		var thisObj, voiceName, i, voice, pitch, volume, utterance,
7332  			timeElapsed, secondsElapsed;
7333
7334  		thisObj = this;
7335
7336  		if (!this.speechEnabled) {
7337  			// voices array failed to load the first time. Try again
7338  			this.initSpeech('desc');
7339  		}
7340
7341  		if (context === 'sample') {
7342  			// get settings from form
7343  			voiceName = $('#' + this.mediaId + '_prefDescVoice').val();
7344  			pitch = $('#' + this.mediaId + '_prefDescPitch').val();
7345  			rate = $('#' + this.mediaId + '_prefDescRate').val();
7346  			volume = $('#' + this.mediaId + '_prefDescVolume').val();
7347  		} else if ( context === 'captionSample' ) {
7348  			// get settings from form
7349  			voiceName = $('#' + this.mediaId + '_prefCaptionsVoice').val();
7350  			pitch = $('#' + this.mediaId + '_prefCaptionsPitch').val();
7351  			rate = $('#' + this.mediaId + '_prefCaptionsRate').val();
7352  			volume = $('#' + this.mediaId + '_prefCaptionsVolume').val();
7353  		} else if ( context === 'description' ) {
7354  			// get settings from global prefs
7355  			voiceName = this.prefDescVoice;
7356  			pitch = this.prefDescPitch;
7357  			rate = this.prefDescRate;
7358  			volume = this.prefDescVolume;
7359  		} else {
7360  			// get settings from global prefs
7361  			voiceName = this.prefCaptionsVoice;
7362  			pitch = this.prefCaptionsPitch;
7363  			rate = ( rate < this.prefCaptionsRate ) ? this.prefCaptionsRate : rate;
7364  			volume = this.prefCaptionsVolume;
7365  		}
7366
7367  		// get the voice associated with the user's chosen voice name
7368  		if (this.descVoices) {
7369  			if (this.descVoices.length > 0) {
7370  				if (voiceName) {
7371  					// get the voice that matches user's preferred voiceName
7372  					for (i = 0; i < this.descVoices.length; i++) {
7373  						if (this.descVoices[i].name == voiceName) {
7374  							voice = this.descVoices[i];
7375  							break;
7376  						}
7377  					}
7378  				}
7379  				if (typeof voice === 'undefined') {
7380  					// no matching voice was found
7381  					// use the first voice in the array
7382  					voice = this.descVoices[0];
7383  				}
7384  			}
7385  		} else {
7386  			voice = null;
7387  		}
7388  		utterance = new SpeechSynthesisUtterance();
7389  		if (voice) {
7390  			utterance.voice = voice;
7391  		}
7392  		utterance.voiceURI = 'native';
7393  		utterance.volume = volume;
7394  		utterance.rate = rate;
7395  		utterance.pitch = pitch;
7396  		utterance.text = text;
7397  		// TODO: Consider the best language for the utterance:
7398  		// language of the web page? (this.lang)
7399  		// language of the WebVTT description track?
7400  		// language of the user's chosen voice?
7401  		// If there's a mismatch between any of these, the description will likely be unintelligible
7402  		utterance.lang = this.lang;
7403  		utterance.onstart = function(e) {
7404  			// utterance has started
7405  		};
7406  		utterance.onpause = function(e) {
7407  			// utterance has paused
7408  		};
7409  		utterance.onend = function(e) {
7410  			// utterance has ended
7411  			if ( 'description' === context ) {
7412  				this.speakingDescription = false;
7413  			} else if ( 'caption' === context ) {
7414  				this.speakingCaption = false;
7415  			}
7416  			timeElapsed = e.elapsedTime;
7417  			// As of Firefox 95, e.elapsedTime is expressed in seconds
7418  			// Other browsers (tested in Chrome & Edge) express this in milliseconds
7419  			// Assume no utterance will require over 100 seconds to express...
7420  			// If a large value, time is likely expressed in milliseconds.
7421  			secondsElapsed = (timeElapsed > 100) ? (e.elapsedTime/1000).toFixed(2) : (e.elapsedTime).toFixed(2);
7422
7423  			if (this.debug) {
7424  				console.log('Finished speaking. That took ' + secondsElapsed + ' seconds.');
7425  			}
7426  			if (context === 'description') {
7427  				if (thisObj.prefDescPause) {
7428  					if (thisObj.pausedForDescription) {
7429  						thisObj.playMedia();
7430  						this.pausedForDescription = false;
7431  					}
7432  				}
7433  			}
7434  		};
7435  		utterance.onerror = function(e) {
7436  			// handle error
7437  			console.log('Web Speech API error',e);
7438  		};
7439  		if (this.synth.paused) {
7440  			this.synth.resume();
7441  		}
7442  		this.synth.speak(utterance);
7443  		if ( 'description' === context ) {
7444  			this.speakingDescription = true;
7445  		} else if ( 'caption' === context ) {
7446  			this.speakingCaption = true;
7447  		}
7448  	};
7449
7450  }
7451
7452  // Outdented for a simpler diff
7453  	var focusableElementsSelector = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]";
7454
7455  	// Based on the incredible accessible modal dialog.
7456  	function AccessibleDialog( modalDiv, $returnElement, title, closeButtonLabel) {
7457
7458  		this.title = title;
7459  		this.closeButtonLabel = closeButtonLabel;
7460  		this.focusedElementBeforeModal = $returnElement;
7461  		this.baseId = $(modalDiv).attr('id') || Math.floor(Math.random() * 1000000000).toString();
7462  		var thisObj = this;
7463  		var modal = modalDiv;
7464  		this.modal = modal;
7465
7466  		modal.addClass('able-modal-dialog');
7467
7468  		var closeButton = $('<button>',{
7469  				'class': 'modalCloseButton',
7470  				'title': thisObj.closeButtonLabel,
7471  				'aria-label': thisObj.closeButtonLabel
7472  		}).text('×');
7473  		closeButton.on( 'keydown', function (e) {
7474  			if (e.key === ' ') {
7475  				thisObj.hide();
7476  			}
7477  		}).on( 'click', function () {
7478  			thisObj.hide();
7479  		});
7480
7481  		var titleH1 = $('<h1></h1>');
7482  		titleH1.attr('id', 'modalTitle-' + this.baseId);
7483  		titleH1.text(title);
7484  		this.titleH1 = titleH1;
7485
7486  		modal.attr({
7487  			'aria-labelledby': 'modalTitle-' + this.baseId,
7488  		});
7489  		var modalHeader = $( '<div>', {
7490  			'class': 'able-modal-header'
7491  		});
7492  		modalHeader.prepend(titleH1);
7493  		modalHeader.prepend(closeButton);
7494  		modal.prepend(modalHeader);
7495
7496  		modal.attr({
7497  			'aria-hidden': 'true',
7498  			'role': 'dialog',
7499  			'aria-modal': 'true'
7500  		});
7501
7502  		modal.on( 'keydown', function (e) {
7503  			if (e.key === 'Escape') {
7504  				thisObj.hide();
7505  				e.preventDefault();
7506  			} else if (e.key === 'Tab') {
7507  				// Manually loop tab navigation inside the modal.
7508  				var parts = modal.find('*');
7509  				var focusable = parts.filter(focusableElementsSelector).filter(':visible');
7510
7511  				if (focusable.length === 0) {
7512  					return;
7513  				}
7514
7515  				var focused = $(':focus');
7516  				var currentIndex = focusable.index(focused);
7517  				if (e.shiftKey) {
7518  					// If backwards from first element, go to last.
7519  					if (currentIndex === 0) {
7520  						focusable.get(focusable.length - 1).trigger('focus');
7521  						e.preventDefault();
7522  					}
7523  				} else {
7524  					if (currentIndex === focusable.length - 1) {
7525  						focusable.get(0).trigger('focus');
7526  						e.preventDefault();
7527  					}
7528  				}
7529  			}
7530  			e.stopPropagation();
7531  		});
7532
7533  		if ( $( 'body' ).hasClass( 'able-modal-active' ) ) {
7534  			$( 'body > *') .not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert');
7535  			$( 'body' ).removeClass( 'able-modal-active' );
7536  		}
7537  	}
7538  	AccessibleDialog.prototype.show = function () {
7539  		if (!this.overlay) {
7540  			// Generate overlay.
7541  			var overlay = $('<div></div>').attr({
7542  				 'class': 'able-modal-overlay',
7543  				 'tabindex': '-1'
7544  			});
7545  			this.overlay = overlay;
7546  			$('body').append(overlay);
7547
7548  			// Keep from moving focus out of dialog when clicking outside of it.
7549  			overlay.on('mousedown.accessibleModal', function (e) {
7550  				e.preventDefault();
7551  				thisObj.hide();
7552  			});
7553  		}
7554
7555  		$('body > *').not('.able-modal-overlay').not('.able-modal-dialog').attr('inert', true);
7556  		$( 'body' ).addClass( 'able-modal-active' );
7557
7558  		this.overlay.css('display', 'block');
7559  		this.modal.css('display', 'block');
7560  		this.modal.attr({
7561  			'aria-hidden': 'false',
7562  			'tabindex': '-1'
7563  		});
7564
7565  		var focusable = this.modal.find("*").filter(focusableElementsSelector).filter(':visible');
7566  		if (focusable.length === 0) {
7567  			this.focusedElementBeforeModal.blur();
7568  		}
7569  		var thisObj = this;
7570  		setTimeout(function () {
7571  			// set focus on the first focusable element
7572  			thisObj.modal.find('button.modalCloseButton').first().trigger('focus');
7573  		}, 300);
7574  	};
7575
7576  	AccessibleDialog.prototype.hide = function () {
7577  		if (this.overlay) {
7578  			this.overlay.css('display', 'none');
7579  		}
7580  		this.modal.css('display', 'none');
7581  		this.modal.attr('aria-hidden', 'true');
7582  		$('body > *').not('.able-modal-overlay').not('.able-modal-dialog').removeAttr('inert');
7583  		$( 'body' ).removeClass( 'able-modal-active' );
7584
7585  		this.focusedElementBeforeModal.trigger('focus');
7586  	};
7587
7588  	AccessibleDialog.prototype.getInputs = function () {
7589
7590  		// return an array of input elements within this dialog
7591  		if (this.modal) {
7592  			var inputs = this.modal.find('input');
7593  			return inputs;
7594  		}
7595  		return false;
7596  	};
7597
7598  function addDragdropFunctions(AblePlayer) {
7599  	AblePlayer.prototype.initDragDrop = function ( which ) {
7600
7601  		// supported values of which: 'sign', 'transcript'
7602
7603  		// NOTE: "Drag and Drop" for Able Player is a metaphor only!!!
7604  		// HTML5 Drag & Drop API enables moving elements to new locations in the DOM
7605  		// Thats not our purpose; we're simply changing the visible position on-screen
7606  		// Therefore, the drag & drop interface was overhauled in v2.3.41 to simply
7607  		// use mouse (and keyboard) events to change CSS positioning properties
7608
7609  		// There are nevertheless lessons to be learned from Drag & Drop about accessibility:
7610  		// http://dev.opera.com/articles/accessible-drag-and-drop/
7611
7612  		var thisObj, $window, $toolbar, windowName, $dragHandle, $resizeHandle, $resizeSvg,
7613  			i, x1, y1, x2, y2, $resizeLine, resizeZIndex;
7614
7615  		thisObj = this;
7616
7617  		if (which === 'transcript') {
7618  			$window = this.$transcriptArea;
7619  			windowName = 'transcript-window';
7620  			$toolbar = this.$transcriptToolbar;
7621  			$toolbar.attr( 'aria-label', this.translate( 'transcriptControls', 'Transcript Window Controls' ) );
7622  		} else if (which === 'sign') {
7623  			$window = this.$signWindow;
7624  			windowName = 'sign-window';
7625  			$toolbar = this.$signToolbar;
7626  			$toolbar.attr( 'aria-label', this.translate( 'signControls', 'Sign Language Window Controls' ) );
7627  		}
7628
7629  		// add class to trigger change in cursor on hover
7630  		$toolbar.addClass('able-draggable');
7631  		$toolbar.attr( 'role', 'application' );
7632
7633  		$dragHandle = $('<div>',{
7634  			'class': 'able-drag-handle'
7635  		});
7636
7637  		$dragHandle.html('<svg version="1.1" viewBox="262.48 487.5 675.03 225" xmlns="http://www.w3.org/2000/svg"><path d="m900 562.5h-600c-13.398 0-25.777-7.1484-32.477-18.75-6.6992-11.602-6.6992-25.898 0-37.5 6.6992-11.602 19.078-18.75 32.477-18.75h600c13.398 0 25.777 7.1484 32.477 18.75 6.6992 11.602 6.6992 25.898 0 37.5-6.6992 11.602-19.078 18.75-32.477 18.75z" fill="#fff"></path>  <path d="m900 712.5h-600c-13.398 0-25.777-7.1484-32.477-18.75-6.6992-11.602-6.6992-25.898 0-37.5 6.6992-11.602 19.078-18.75 32.477-18.75h600c13.398 0 25.777 7.1484 32.477 18.75 6.6992 11.602 6.6992 25.898 0 37.5-6.6992 11.602-19.078 18.75-32.477 18.75z" fill="#fff"></path></svg>');
7638  		// add resize handle selector to bottom right corner
7639  		$resizeHandle = $('<div>',{
7640  			'class': 'able-resizable'
7641  		});
7642
7643  		// fill it with three parallel diagonal lines
7644  		$resizeSvg = $('<svg>').attr({
7645  			'width': '100%',
7646  			'height': '100%',
7647  			'viewBox': '0 0 100 100',
7648  			'preserveAspectRatio': 'none'
7649  		});
7650  		for (i=1; i<=3; i++) {
7651  			if (i === 1) {
7652  				x1 = '100';
7653  				y1 = '0';
7654  				x2 = '0';
7655  				y2 = '100';
7656  			} else if (i === 2) {
7657  				x1 = '33';
7658  				y1 = '100';
7659  				x2 = '100';
7660  				y2 = '33';
7661  			} else if (i === 3) {
7662  				x1 = '67';
7663  				y1 = '100';
7664  				x2 = '100';
7665  				y2 = '67';
7666  			}
7667  			$resizeLine = $('<line>').attr({
7668  				'x1': x1,
7669  				'y1': y1,
7670  				'x2': x2,
7671  				'y2': y2,
7672  				'vector-effect': 'non-scaling-stroke'
7673  			});
7674  			$resizeSvg.append($resizeLine);
7675  		}
7676  		$resizeHandle.html($resizeSvg);
7677
7678  		// assign z-index that's slightly higher than parent window
7679  		resizeZIndex = parseInt($window.css('z-index')) + 100;
7680  		$resizeHandle.css('z-index',resizeZIndex);
7681  		$window.append($resizeHandle);
7682  		$toolbar.append($dragHandle);
7683
7684  		// Final step: Need to refresh the DOM in order for browser to process & display the SVG
7685  		$resizeHandle.html($resizeHandle.html());
7686
7687  		// add event listener to toolbar to start and end drag
7688  		// other event listeners will be added when drag starts
7689  		$dragHandle.on('mousedown mouseup touchstart touchend', function(e) {
7690  			e.stopPropagation();
7691  			if (e.type === 'mousedown' || e.type === 'touchstart' ) {
7692  				if (!thisObj.windowMenuClickRegistered) {
7693  					thisObj.windowMenuClickRegistered = true;
7694  					thisObj.startMouseX = e.pageX;
7695  					thisObj.startMouseY = e.pageY;
7696  					thisObj.dragDevice = 'mouse'; // ok to use this even if device is a touchpad
7697  					thisObj.startDrag(which, $window);
7698  				}
7699  			} else if (e.type === 'mouseup' || e.type === 'touchend') {
7700  				if (thisObj.dragging && thisObj.dragDevice === 'mouse') {
7701  					thisObj.endDrag(which);
7702  				}
7703  			}
7704  			return false;
7705  		});
7706
7707  		// add event listeners for resizing
7708  		$resizeHandle.on('mousedown mouseup touchstart touchend', function(e) {
7709  			e.stopPropagation();
7710  			if (e.type === 'mousedown' || e.type === 'touchstart') {
7711  				if (!thisObj.windowMenuClickRegistered) {
7712  					thisObj.windowMenuClickRegistered = true;
7713  					thisObj.startMouseX = e.pageX;
7714  					thisObj.startMouseY = e.pageY;
7715  					thisObj.startResize(which, $window);
7716  				}
7717  			} else if (e.type === 'mouseup' || e.type === 'touchend') {
7718  				if (thisObj.resizing) {
7719  					thisObj.endResize(which);
7720  				}
7721  			}
7722  			return false;
7723  		});
7724
7725  		// whenever a window is clicked, bring it to the foreground
7726  		$window.on('click', function() {
7727
7728  			if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
7729  				thisObj.updateZIndex(which);
7730  			}
7731  			thisObj.finishingDrag = false;
7732  		});
7733  		this.addWindowMenu(which,$window,windowName);
7734  	};
7735
7736  	AblePlayer.prototype.addWindowMenu = function(which, $window, windowName) {
7737
7738  		var thisObj, menuId, $newButton, tooltipId, $tooltip, $popup;
7739
7740  		thisObj = this;
7741
7742  		// Add a Boolean that will be set to true temporarily if window button or a menu item is clicked
7743  		// This will prevent the click event from also triggering a mousedown event on the toolbar
7744  		// (which would unexpectedly send the window into drag mode)
7745  		this.windowMenuClickRegistered = false;
7746
7747  		// Add another Boolean that will be set to true temporarily when mouseup fires at the end of a drag
7748  		// this will prevent the click event from being triggered
7749  		this.finishingDrag = false;
7750
7751  		// add button to draggable window which triggers a popup menu
7752  		menuId = this.mediaId + '-' + windowName + '-menu';
7753  		$newButton = $('<button>',{
7754  			'type': 'button',
7755  			'tabindex': '0',
7756  			'aria-haspopup': 'true',
7757  			'aria-controls': menuId,
7758  			'aria-expanded': 'false',
7759  			'class': 'able-button-handler-preferences'
7760  		});
7761  		this.getIcon( $newButton, 'preferences' );
7762  		this.setText( $newButton, this.translate( 'windowButtonLabel', 'Window options' ) );
7763
7764  		// add a tooltip that displays aria-label on mouseenter or focus
7765  		tooltipId = this.mediaId + '-' + windowName + '-tooltip';
7766  		$tooltip = $('<div>',{
7767  			'class' : 'able-tooltip',
7768  			'id' : tooltipId
7769  		}).hide();
7770
7771  		$newButton.on('mouseenter focus',function(e) {
7772  			var label = $(this).attr('aria-label');
7773  			var tooltip = AblePlayer.localGetElementById($newButton[0], tooltipId).text(label);
7774  			// get height of the tooltip
7775  			var tooltipHeight = tooltip.height();
7776  			var tooltipY = ( tooltipHeight + 2 ) * -1;
7777  			var tooltipX = 0;
7778  			var tooltipStyle = {
7779  				right: '',
7780  				left: tooltipX + 'px',
7781  				top: tooltipY + 'px'
7782  			};
7783  			tooltip.css(tooltipStyle);
7784  			thisObj.showTooltip(tooltip);
7785  			$(this).on('mouseleave blur',function() {
7786  				AblePlayer.localGetElementById($newButton[0], tooltipId).text('').hide();
7787  			});
7788  		});
7789
7790  		// setup popup menu
7791  		$popup = this.setupPopups(windowName); // 'transcript-window' or 'sign-window'
7792  		// define vars and assemble all the parts
7793  		if (which === 'transcript') {
7794  			this.$transcriptPopupButton = $newButton;
7795  			this.$transcriptPopup = $popup;
7796  			this.$transcriptToolbar.prepend($newButton,$tooltip,$popup);
7797  		} else if (which === 'sign') {
7798  			this.$signPopupButton = $newButton;
7799  			this.$signPopup = $popup;
7800  			this.$signToolbar.append($newButton,$tooltip,$popup);
7801  		}
7802
7803  		// handle button click
7804  		$newButton.on('click keydown',function(e) {
7805
7806  			if (thisObj.focusNotClick) {
7807  				return false;
7808  			}
7809  			if (thisObj.dragging) {
7810  				thisObj.dragKeys(which, e);
7811  				return false;
7812  			}
7813  			e.stopPropagation();
7814  			if (!thisObj.windowMenuClickRegistered && !thisObj.finishingDrag) {
7815  				console.log( 'firing' );
7816  				// don't set windowMenuClickRegistered yet; that happens in handler function
7817  				thisObj.handleWindowButtonClick(which, e);
7818  			}
7819  			thisObj.finishingDrag = false;
7820  		});
7821
7822  		this.addResizeDialog(which, $window);
7823  	};
7824
7825  	AblePlayer.prototype.addResizeDialog = function (which) {
7826
7827  		var thisObj, $windowPopup, $windowButton, widthId, heightId,
7828  			$resizeForm, $resizeWrapper, $resizeControls, $resizeWidthDiv, $resizeWidthInput, $resizeWidthLabel,
7829  			$resizeHeightDiv, $resizeHeightInput, $resizeHeightLabel, $saveButton, $cancelButton,
7830  			newWidth, newHeight, resizeDialog;
7831
7832  		thisObj = this;
7833
7834  		if (which === 'transcript') {
7835  			$windowPopup = this.$transcriptPopup;
7836  			$windowButton = this.$transcriptPopupButton;
7837  		} else if (which === 'sign') {
7838  			$windowPopup = this.$signPopup;
7839  			$windowButton = this.$signPopupButton;
7840  		}
7841
7842  		widthId = this.mediaId + '-resize-' + which + '-width';
7843  		heightId = this.mediaId + '-resize-' + which + '-height';
7844
7845  		$resizeForm = $('<div></div>',{
7846  			'class' : 'able-resize-form'
7847  		});
7848
7849  		// inner container for all content, will be assigned to modal div's aria-describedby
7850  		$resizeWrapper = $('<div></div>');
7851  		$resizeControls = $( '<div class="able-prefs-buttons"></div>' );
7852
7853  		// width field
7854  		$resizeWidthDiv = $('<div></div>');
7855  		$resizeWidthInput = $('<input>',{
7856  			'type': 'number',
7857  			'id': widthId,
7858  			'min': 0,
7859  			'value': '',
7860  		});
7861  		$resizeWidthLabel = $('<label>',{
7862  			'for': widthId
7863  		}).text( this.translate( 'width', 'Width' ) );
7864
7865  		// height field
7866  		$resizeHeightDiv = $('<div></div>');
7867  		$resizeHeightInput = $('<input>',{
7868  			'type': 'number',
7869  			'id': heightId,
7870  			'min': 0,
7871  			'value': '',
7872  		});
7873  		$resizeHeightLabel = $('<label>',{
7874  			'for': heightId
7875  		}).text( this.translate( 'height', 'Height' ) );
7876
7877  		// Add save and cancel buttons.
7878  		$saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>');
7879  		$cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>');
7880  		$saveButton.on('click',function () {
7881  			newWidth = $('#' + widthId).val();
7882  			newHeight = $('#' + heightId).val();
7883  			thisObj.resizeObject(which,newWidth,newHeight);
7884  			thisObj.updatePreferences(which);
7885
7886  			resizeDialog.hide();
7887  			$windowPopup.hide();
7888  			$windowButton.trigger('focus');
7889  		});
7890  		$cancelButton.on('click',function () {
7891  			resizeDialog.hide();
7892  			$windowPopup.hide();
7893  			$windowButton.trigger('focus');
7894  		});
7895
7896  		// Now assemble all the parts
7897  		$resizeWidthDiv.append($resizeWidthLabel,$resizeWidthInput);
7898  		$resizeHeightDiv.append($resizeHeightLabel,$resizeHeightInput);
7899  		$resizeWrapper.append($resizeWidthDiv,$resizeHeightDiv);
7900  		$resizeControls.append($saveButton,$cancelButton);
7901  		$resizeForm.append($resizeWrapper,$resizeControls);
7902
7903  		// must be appended to the BODY!
7904  		// otherwise when aria-hidden="true" is applied to all background content
7905  		// that will include an ancestor of the dialog,
7906  		// which will render the dialog unreadable by screen readers
7907  		$('body').append($resizeForm);
7908  		resizeDialog = new AccessibleDialog(
7909  			$resizeForm,
7910  			$windowButton,
7911  			this.translate( 'windowResizeHeading', 'Resize Window' ),
7912  			this.translate( 'closeButtonLabel', 'Close' ),
7913  		);
7914  		if (which === 'transcript') {
7915  			this.transcriptResizeDialog = resizeDialog;
7916  		} else if (which === 'sign') {
7917  			this.signResizeDialog = resizeDialog;
7918  		}
7919  	};
7920
7921  	AblePlayer.prototype.handleWindowButtonClick = function (which, e) {
7922
7923  		var thisObj, $windowPopup, $windowButton, $toolbar, popupTop;
7924
7925  		thisObj = this;
7926  		if (this.focusNotClick) {
7927  			// transcript or sign window has just opened,
7928  			// and focus moved to the window button
7929  			// ignore the keystroke that triggered the popup
7930  			return false;
7931  		}
7932
7933  		if (which === 'transcript') {
7934  			$windowPopup = this.$transcriptPopup;
7935  			$windowButton = this.$transcriptPopupButton;
7936  			$toolbar = this.$transcriptToolbar;
7937  		} else if (which === 'sign') {
7938  			$windowPopup = this.$signPopup;
7939  			$windowButton = this.$signPopupButton;
7940  			$toolbar = this.$signToolbar;
7941  		}
7942  		if (e.type === 'keydown') {
7943  			// user pressed a key
7944  			if (e.key === ' ' || e.key === 'Enter') {
7945  				this.windowMenuClickRegistered = true;
7946  			} else if (e.key === 'Escape') {
7947  				if ($windowPopup.is(':visible')) {
7948  					// close the popup menu
7949  					$windowPopup.hide();
7950  					// also reset the Boolean
7951  					thisObj.windowMenuClickRegistered = false;
7952  					// also restore menu items to their original state
7953  					$windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
7954  					// also return focus to window options button
7955  					$windowButton.trigger('focus');
7956  				} else {
7957  					// popup isn't open. Close the window
7958  					if (which === 'sign') {
7959  						this.handleSignToggle();
7960  					} else if (which === 'transcript') {
7961  						this.handleTranscriptToggle();
7962  					}
7963  				}
7964  			} else {
7965  				return false;
7966  			}
7967  		} else {
7968  			// this was a mouse event
7969  			this.windowMenuClickRegistered = true;
7970  		}
7971
7972  		if ( $windowPopup.is(':visible') ) {
7973  			$windowPopup.hide();
7974  			thisObj.windowMenuClickRegistered = false; // reset
7975  			$windowPopup.find('li').removeClass('able-focus');
7976  			$windowButton.attr('aria-expanded','false').trigger('focus');
7977  		} else {
7978  			// first, be sure window is on top
7979  			this.updateZIndex(which);
7980  			popupTop = $toolbar.outerHeight() - 1;
7981  			$windowPopup.css('top', popupTop);
7982  			$windowPopup.show();
7983  			$windowButton.attr('aria-expanded','true');
7984  			$(this).find('li').first().trigger('focus').addClass('able-focus');
7985  			thisObj.windowMenuClickRegistered = false; // reset
7986  		}
7987  	};
7988
7989  	AblePlayer.prototype.handleMenuChoice = function (which, choice, e) {
7990
7991  		var thisObj, $window, $windowPopup, $windowButton, resizeDialog, startingWidth, startingHeight,
7992  		aspectRatio, tempWidth, tempHeight;
7993
7994  		thisObj = this;
7995  		if (which === 'transcript') {
7996  			$window = this.$transcriptArea;
7997  			$windowPopup = this.$transcriptPopup;
7998  			$windowButton = this.$transcriptPopupButton;
7999  			resizeDialog = this.transcriptResizeDialog;
8000  		} else if (which === 'sign') {
8001  			$window = this.$signWindow;
8002  			$windowPopup = this.$signPopup;
8003  			$windowButton = this.$signPopupButton;
8004  			resizeDialog = this.signResizeDialog;
8005
8006  			startingWidth = $window.outerWidth();
8007  			startingHeight = $window.outerHeight();
8008  			aspectRatio = startingWidth / startingHeight;
8009  			// make height a read-only field
8010  			// and calculate its value based on width to preserve aspect ratio
8011  			let widthId = this.mediaId + '-resize-' + which + '-width';
8012  			let heightId = this.mediaId + '-resize-' + which + '-height';
8013  			$( '#' + heightId ).prop('readonly',true);
8014  			$( '#' + widthId ).on('input',function() {
8015  				tempWidth = $(this).val();
8016  				tempHeight = Math.round(tempWidth/aspectRatio);
8017  				$( '#' + heightId ).val(tempHeight);
8018  			});
8019  		}
8020  		this.$activeWindow = $window;
8021
8022  		if (e.type === 'keydown') {
8023  			if (e.key === 'Escape') { // escape
8024  				// hide the popup menu
8025  				$windowPopup.hide();
8026  				// also reset the Boolean
8027  				thisObj.windowMenuClickRegistered = false;
8028  				// also restore menu items to their original state
8029  				$windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
8030  				$windowButton.attr('aria-expanded','false');
8031  				// also return focus to window options button
8032  				$windowButton.trigger('focus');
8033
8034  				return false;
8035  			} else {
8036  				// all other keys will be handled by upstream functions
8037  				if (choice !== 'close') {
8038  					this.$activeWindow = $window;
8039  				}
8040  				return false;
8041  			}
8042  		}
8043
8044  		// hide the popup menu
8045  		$windowPopup.hide();
8046  		// also reset the boolean
8047  		thisObj.windowMenuClickRegistered = false;
8048  		// also restore menu items to their original state
8049  		$windowPopup.find('li').removeClass('able-focus').attr('tabindex','-1');
8050  		$windowButton.attr('aria-expanded','false');
8051
8052  		if (choice !== 'close') {
8053  			$windowButton.trigger('focus');
8054  		}
8055  		if (choice === 'move') {
8056  			// temporarily add role="application" to activeWindow
8057  			// otherwise, screen readers incercept arrow keys and moving window will not work
8058  			this.$activeWindow.attr('role','application');
8059
8060  			if (!this.showedAlert(which)) {
8061  				this.showAlert( this.translate( 'windowMoveAlert', 'Drag or use arrow keys to move the window; Enter to stop' ),which);
8062  				if (which === 'transcript') {
8063  					this.showedTranscriptAlert = true;
8064  				} else if (which === 'sign') {
8065  					this.showedSignAlert = true;
8066  				}
8067  			}
8068  			this.dragDevice = (e.type === 'keydown') ? 'keyboard' : 'mouse';
8069  			this.startDrag(which, $window);
8070  			$windowPopup.hide().parent().attr( 'tabindex', '-1' ).trigger('focus');
8071  		} else if (choice == 'resize') {
8072  			// resize through the menu uses a form, not drag
8073  			var resizeFields = resizeDialog.getInputs();
8074  			if (resizeFields) {
8075  				// reset width and height values in form
8076  				resizeFields[0].value = Math.round( $window.outerWidth() );
8077  				resizeFields[1].value = Math.round( $window.outerHeight() );
8078  			}
8079  			resizeDialog.show();
8080  		} else if (choice == 'close') {
8081  			// close window, place focus on corresponding button on controller bar
8082  			if (which === 'transcript') {
8083  				this.closingTranscript = true; // stopgap to prevent double-firing of keypress
8084  				this.handleTranscriptToggle();
8085  			} else if (which === 'sign') {
8086  				this.closingSign = true; // stopgap to prevent double-firing of keypress
8087  				this.handleSignToggle();
8088  			}
8089  		}
8090  	};
8091
8092  	AblePlayer.prototype.startDrag = function(which, $element) {
8093
8094  		var thisObj, $windowPopup, startPos, newX, newY;
8095
8096  		thisObj = this;
8097
8098  		if (!this.$activeWindow) {
8099  			this.$activeWindow = $element;
8100  		}
8101  		this.dragging = true;
8102
8103  		if (which === 'transcript') {
8104  			$windowPopup = this.$transcriptPopup;
8105  		} else if (which === 'sign') {
8106  			$windowPopup = this.$signPopup;
8107  		}
8108
8109  		// if window's popup menu is open, close it
8110  		if ($windowPopup.is(':visible')) {
8111  			$windowPopup.hide();
8112  		}
8113
8114  		// be sure this window is on top
8115  		this.updateZIndex(which);
8116
8117  		// get starting position of element
8118  		startPos = this.$activeWindow.position();
8119  		this.dragStartX = startPos.left;
8120  		this.dragStartY = startPos.top;
8121
8122  		if (typeof this.startMouseX === 'undefined') {
8123  			this.dragDevice = 'keyboard';
8124  			this.dragKeyX = this.dragStartX;
8125  			this.dragKeyY = this.dragStartY;
8126  			// add stopgap to prevent the Enter that triggered startDrag() from also triggering dragEnd()
8127  			this.startingDrag = true;
8128  		} else {
8129  			this.dragDevice = 'mouse';
8130  			// get offset between mouse position and top left corner of draggable element
8131  			this.dragOffsetX = this.startMouseX - this.dragStartX;
8132  			this.dragOffsetY = this.startMouseY - this.dragStartY;
8133  		}
8134
8135  		// prepare element for dragging
8136  		this.$activeWindow.addClass('able-drag').css({
8137  			'position': 'absolute',
8138  			'top': this.dragStartY + 'px',
8139  			'left': this.dragStartX + 'px'
8140  		}).trigger('focus');
8141
8142  		// add device-specific event listeners
8143  		if (this.dragDevice === 'mouse') { // might also be a touchpad
8144  			$(document).on('mousemove touchmove',function(e) {
8145  				if (thisObj.dragging) {
8146  					// calculate new top left based on current mouse position - offset
8147  					newX = e.pageX - thisObj.dragOffsetX;
8148  					newY = e.pageY - thisObj.dragOffsetY;
8149  					thisObj.resetDraggedObject( newX, newY );
8150  				}
8151  			});
8152  		} else if (this.dragDevice === 'keyboard') {
8153  			this.$activeWindow.on('keydown',function(e) {
8154  				if (thisObj.dragging) {
8155  					thisObj.dragKeys(which, e);
8156  				}
8157  			});
8158  		}
8159  		return false;
8160  	};
8161
8162  	/**
8163  	 * Handle moving the transcript or sign window from the keyboard.
8164  	 *
8165  	 * @param {string} which 'transcript' or 'sign' window.
8166  	 * @param {Event} e Triggered event.
8167  	 */
8168  	AblePlayer.prototype.dragKeys = function(which, e) {
8169
8170  		var key, keySpeed;
8171
8172  		// stopgap to prevent firing on initial Enter or space
8173  		// that selected "Move" from menu
8174  		if (this.startingDrag) {
8175  			this.startingDrag = false;
8176  			return false;
8177  		}
8178  		key = e.key;
8179  		keySpeed = 10; // pixels per keypress event
8180
8181  		switch (key) {
8182  			case 'ArrowLeft':	// left
8183  				 this.dragKeyX -= keySpeed;
8184  				 this.$srAlertBox.text( this.translate( 'windowMoveLeft', 'Window moved left' ) );
8185  				break;
8186  			case 'ArrowUp':	// up
8187  				this.dragKeyY -= keySpeed;
8188  				this.$srAlertBox.text( this.translate( 'windowMoveUp', 'Window moved up' ) );
8189  				break;
8190  			case 'ArrowRight':	// right
8191  				this.dragKeyX += keySpeed;
8192  				this.$srAlertBox.text( this.translate( 'windowMoveRight', 'Window moved right' ) );
8193  				break;
8194  			case 'ArrowDown':	// down
8195  				this.dragKeyY += keySpeed;
8196  				this.$srAlertBox.text( this.translate( 'windowMoveDown', 'Window moved down' ) );
8197  				break;
8198  			case 'Enter': 	// enter
8199  			case 'Escape': 	// escape
8200  				this.$srAlertBox.text( this.translate( 'windowMoveStopped', 'Window move stopped' ) );
8201  				this.endDrag(which);
8202  				return false;
8203  			default:
8204  				return false;
8205  		}
8206  		this.resetDraggedObject(this.dragKeyX,this.dragKeyY);
8207  		if (e.preventDefault) {
8208  			e.preventDefault();
8209  		}
8210  		return false;
8211  	};
8212
8213  	AblePlayer.prototype.resetDraggedObject = function ( x, y) {
8214  		setTimeout( () => {
8215  			this.$srAlertBox.text( '' );
8216  		}, 2000 );
8217
8218  		this.$activeWindow.css({
8219  			'left': x + 'px',
8220  			'top': y + 'px'
8221  		});
8222  	},
8223
8224  	AblePlayer.prototype.resizeObject = function ( which, width, height ) {
8225
8226  		var innerHeight;
8227
8228  		// which is either 'transcript' or 'sign'
8229  		this.$activeWindow.css({
8230  			'width': width + 'px',
8231  			'height': height + 'px'
8232  		});
8233
8234  		if (which === 'transcript') {
8235  			// $activeWindow is the outer $transcriptArea
8236  			// but the inner able-transcript also needs to be resized proportionally
8237  			// (it's 50px less than its outer container)
8238  			innerHeight = height - 50;
8239  			this.$transcriptDiv.css('height', innerHeight + 'px');
8240  		}
8241  	};
8242
8243  	AblePlayer.prototype.endDrag = function(which) {
8244
8245  		var thisObj, $windowButton;
8246  		thisObj = this;
8247
8248  		if (which === 'transcript') {
8249  			$windowButton = this.$transcriptPopupButton;
8250  		} else if (which === 'sign') {
8251  			$windowButton = this.$signPopupButton;
8252  		}
8253
8254  		$(document).off('mousemove mouseup touchmove touchup');
8255  		this.$activeWindow.off('keydown').removeClass('able-drag');
8256  		// restore activeWindow role from 'application' to 'dialog'
8257  		this.$activeWindow.attr('role','dialog');
8258  		this.$activeWindow = null;
8259
8260  		if (this.dragDevice === 'keyboard') {
8261  			$windowButton.trigger('focus');
8262  		}
8263  		this.dragging = false;
8264
8265  		// save final position of dragged element
8266  		this.updatePreferences(which);
8267
8268  		// reset starting mouse positions
8269  		this.startMouseX = undefined;
8270  		this.startMouseY = undefined;
8271
8272  		// Boolean to stop stray events from firing
8273  		this.windowMenuClickRegistered = false;
8274  		this.finishingDrag = true; // will be reset after window click event
8275  		// finishingDrag should be reset after window click event,
8276  		// which is triggered automatically after mouseup
8277  		// However, in case that's not reliable in some browsers
8278  		// need to ensure this gets cancelled
8279  		setTimeout(function() {
8280  			thisObj.finishingDrag = false;
8281  		}, 100);
8282  	};
8283
8284  	AblePlayer.prototype.startResize = function(which, $element) {
8285
8286  		var thisObj, $windowPopup, newWidth, newHeight;
8287
8288  		thisObj = this;
8289  		this.$activeWindow = $element;
8290  		this.resizing = true;
8291
8292  		$windowPopup = (which === 'transcript') ? this.$transcriptPopup : this.$signPopup;
8293
8294  		// if window's popup menu is open, close it & place focus on button (???)
8295  		if ($windowPopup.is(':visible')) {
8296  			$windowPopup.hide().parent().trigger('focus');
8297  		}
8298
8299  		// get starting width and height
8300  		this.dragKeyX = this.dragStartX;
8301  		this.dragKeyY = this.dragStartY;
8302  		this.dragStartWidth = this.$activeWindow.width();
8303  		this.dragStartHeight = this.$activeWindow.outerHeight();
8304
8305  		// add event listeners
8306  		$(document).on('mousemove touchmove',function(e) {
8307  			if (thisObj.resizing) {
8308  				// calculate new width and height based on changes to mouse position
8309  				newWidth = thisObj.dragStartWidth + (e.pageX - thisObj.startMouseX);
8310  				newHeight = thisObj.dragStartHeight + (e.pageY - thisObj.startMouseY);
8311  				thisObj.resizeObject( which, newWidth, newHeight );
8312  			}
8313  		});
8314
8315  		return false;
8316  	};
8317
8318  	AblePlayer.prototype.endResize = function(which) {
8319
8320  		var $windowButton;
8321
8322  		if (which === 'transcript') {
8323  			$windowButton = this.$transcriptPopupButton;
8324  		} else if (which === 'sign') {
8325  			$windowButton = this.$signPopupButton;
8326  		}
8327
8328  		$(document).off('mousemove mouseup touchmove touchup');
8329  		this.$activeWindow.off('keydown');
8330  		$windowButton.show().trigger('focus');
8331  		this.resizing = false;
8332  		this.$activeWindow.removeClass('able-resize');
8333
8334  		// save final width and height of dragged element
8335  		this.updatePreferences(which);
8336
8337  		// Booleans for preventing stray events
8338  		this.windowMenuClickRegistered = false;
8339  		this.finishingDrag = true;
8340
8341  		// finishingDrag should e reset after window click event,
8342  		// which is triggered automatically after mouseup
8343  		// However, in case that's not reliable in some browsers
8344  		// need to ensure this gets cancelled
8345  		setTimeout(function() {
8346  			this.finishingDrag = false;
8347  		}, 100);
8348  	};
8349  }
8350
8351  function addEventFunctions(AblePlayer) {
8352  	// Media events
8353  	AblePlayer.prototype.onMediaUpdateTime = function (duration, elapsed) {
8354
8355  		// duration and elapsed are passed from callback functions of Vimeo API events
8356  		// duration is expressed as sss.xxx
8357  		// elapsed is expressed as sss.xxx
8358  		var thisObj = this;
8359  		this.getMediaTimes(duration,elapsed).then(function(mediaTimes) {
8360  			thisObj.duration = mediaTimes['duration'];
8361  			thisObj.elapsed = mediaTimes['elapsed'];
8362  			if (thisObj.duration > 0) {
8363  				// do all the usual time-sync stuff during playback
8364  				if (thisObj.prefHighlight === 1) {
8365  					thisObj.highlightTranscript(thisObj.elapsed);
8366  				}
8367  				thisObj.updateCaption(thisObj.elapsed);
8368  				thisObj.showDescription(thisObj.elapsed);
8369  				thisObj.updateChapter(thisObj.elapsed);
8370  				thisObj.updateMeta(thisObj.elapsed);
8371  				thisObj.refreshControls('timeline', thisObj.duration, thisObj.elapsed);
8372  			}
8373  		});
8374  	};
8375
8376  	AblePlayer.prototype.onMediaPause = function () {
8377
8378  		if (this.controlsHidden) {
8379  			this.fadeControls('in');
8380  			this.controlsHidden = false;
8381  		}
8382  		if (this.hideControlsTimeoutStatus === 'active') {
8383  			window.clearTimeout(this.hideControlsTimeout);
8384  			this.hideControlsTimeoutStatus = 'clear';
8385
8386  		}
8387  		this.refreshControls('playpause');
8388  	};
8389
8390  	AblePlayer.prototype.onMediaComplete = function () {
8391  		// if there's a playlist, advance to next item and start playing
8392  		if (this.hasPlaylist && !this.cueingPlaylistItem) {
8393  			if (this.playlistIndex === (this.$playlist.length - 1)) {
8394  				// this is the last track in the playlist
8395  				if (this.loop) {
8396  					this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
8397  					this.cuePlaylistItem(0);
8398  				} else {
8399  					this.playing = false;
8400  					this.paused = true;
8401  				}
8402  			} else {
8403  				// this is not the last track. Play the next one.
8404  				this.playlistIndex++;
8405  				this.cueingPlaylistItem = true; // stopgap to prevent multiple firings
8406  				this.cuePlaylistItem(this.playlistIndex);
8407  			}
8408  		}
8409  		this.refreshControls();
8410  	};
8411
8412  	AblePlayer.prototype.onMediaNewSourceLoad = function () {
8413
8414  		var loadIsComplete = false;
8415
8416  		if (this.cueingPlaylistItem) {
8417  			// this variable was set to address bugs caused by multiple firings of media 'end' event
8418  			// safe to reset now
8419  			this.cueingPlaylistItem = false;
8420  		}
8421  		if (this.recreatingPlayer) {
8422  			// same as above; different bugs
8423  			this.recreatingPlayer = false;
8424  		}
8425  		if (this.playbackRate) {
8426  			// user has set playbackRate on a previous src or track
8427  			// use that setting on the new src or track too
8428  			this.setPlaybackRate(this.playbackRate);
8429  		}
8430  		if (this.userClickedPlaylist) {
8431  			if (!this.startedPlaying || this.okToPlay) {
8432  				// start playing; no further user action is required
8433  				this.playMedia();
8434  				loadIsComplete = true;
8435  			 }
8436  		} else if (this.seekTrigger == 'restart' ||
8437  				this.seekTrigger == 'chapter' ||
8438  				this.seekTrigger == 'transcript' ||
8439  				this.seekTrigger == 'search'
8440  				) {
8441  			// by clicking on any of these elements, user is likely intending to play
8442  			// Not included: elements where user might click multiple times in succession
8443  			// (i.e., 'rewind', 'forward', or seekbar); for these, video remains paused until user initiates play
8444  			this.playMedia();
8445  			loadIsComplete = true;
8446  		} else if (this.swappingSrc) {
8447  			// new source file has just been loaded
8448  			if (this.hasPlaylist) {
8449  				// a new source file from the playlist has just been loaded
8450  				if ((this.playlistIndex !== this.$playlist.length) || this.loop) {
8451  					// this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
8452  					this.playMedia();
8453  					loadIsComplete = true;
8454  				}
8455  			} else if (this.swapTime > 0) {
8456  				if (this.seekStatus === 'complete') {
8457  					if (this.okToPlay) {
8458  						// should be able to resume playback
8459  						this.playMedia();
8460  					}
8461  					loadIsComplete = true;
8462  				} else if (this.seekStatus === 'seeking') ; else {
8463  					if (this.swapTime === this.elapsed) {
8464  						// seek is finished!
8465  						this.seekStatus = 'complete';
8466  						if (this.okToPlay) {
8467  							// should be able to resume playback
8468  							this.playMedia();
8469  						}
8470  						loadIsComplete = true;
8471  					} else {
8472  						// seeking hasn't started yet
8473  						// first, determine whether it's possible
8474  						if (this.hasDescTracks) {
8475  							// do nothing. Unable to seek ahead if there are descTracks
8476  							loadIsComplete = true;
8477  						} else if (this.durationsAreCloseEnough(this.duration,this.prevDuration)) {
8478  							// durations of two sources are close enough to making seek ahead in new source ok
8479  							this.seekStatus = 'seeking';
8480  							this.seekTo(this.swapTime);
8481  						} else {
8482  							// durations of two sources are too dissimilar to support seeking ahead to swapTime.
8483  							loadIsComplete = true;
8484  						}
8485  					}
8486  				}
8487  			} else {
8488  				// swapTime is 0. No seeking required.
8489  				if (this.playing) {
8490  					this.playMedia();
8491  					// swap is complete. Reset vars.
8492  					loadIsComplete = true;
8493  				}
8494  			}
8495  		} else if (!this.startedPlaying) {
8496  			if (this.startTime > 0) {
8497  				if (this.seeking) {
8498  					// a seek has already been initiated
8499  					// since canplaythrough has been triggered, the seek is complete
8500  					this.seeking = false;
8501  					if (this.okToPlay) {
8502  						this.playMedia();
8503  					}
8504  					loadIsComplete = true;
8505  				} else {
8506  					// haven't started seeking yet
8507  					this.seekTo(this.startTime);
8508  				}
8509  			} else if (this.defaultChapter && typeof this.selectedChapters !== 'undefined') {
8510  				this.seekToChapter(this.defaultChapter);
8511  			} else {
8512  				// there is no startTime, therefore no seeking required
8513  				if (this.okToPlay) {
8514  					this.playMedia();
8515  				}
8516  				loadIsComplete = true;
8517  			}
8518  		} else if (this.hasPlaylist) {
8519  			// new source media is part of a playlist, but user didn't click on it
8520  			// (and somehow, swappingSrc is false)
8521  			// this may happen when the previous track ends and next track loads
8522  			// this same code is called above when swappingSrc is true
8523  			if ((this.playlistIndex !== this.$playlist.length) || this.loop) {
8524  				// this is not the last track in the playlist (OR playlist is looping so it doesn't matter)
8525  				this.playMedia();
8526  				loadIsComplete = true;
8527  			}
8528  		} else {
8529  			// None of the above.
8530  			// User is likely seeking to a new time, but not loading a new media source
8531  			// need to reset vars
8532  			loadIsComplete = true;
8533  		}
8534  		if (loadIsComplete) {
8535  			// reset vars
8536  			this.swappingSrc = false;
8537  			this.seekStatus = null;
8538  			this.swapTime = 0;
8539  			this.seekTrigger = null;
8540  			this.seekingFromTranscript = false;
8541  			this.userClickedPlaylist = false;
8542  			this.okToPlay = false;
8543  		}
8544  		this.refreshControls();
8545  		if (this.$focusedElement) {
8546  			this.restoreFocus();
8547  			this.$focusedElement = null;
8548  			this.activeMedia = null;
8549  		}
8550  	};
8551
8552  	AblePlayer.prototype.durationsAreCloseEnough = function(d1,d2) {
8553
8554  		// Compare the durations of two media sources to determine whether it's ok to seek ahead after swapping src
8555  		// The durations may not be exact, but they might be "close enough"
8556  		// returns true if "close enough", otherwise false
8557
8558  		var tolerance, diff;
8559
8560  		tolerance = 1;  // number of seconds between rounded durations that is considered "close enough"
8561  		diff = Math.abs(Math.round(d1) - Math.round(d2));
8562
8563  		return (diff <= tolerance) ? true : false;
8564  	};
8565
8566  	AblePlayer.prototype.restoreFocus = function() {
8567
8568  		// function called after player has been rebuilt (during media swap)
8569  		// the original focusedElement no longer exists,
8570  		// but this function finds a match in the new player
8571  		// and places focus there
8572
8573  		var classList, $mediaParent;
8574
8575  		if ( this.$focusedElement && null !== this.activeMedia ) {
8576  			$mediaParent = $( '#' + this.activeMedia ).closest( '.able' );
8577  			if ( (this.$focusedElement).attr('role') === 'button' ) {
8578  				classList = this.$focusedElement.attr("class").split(/\s+/);
8579  				$.each(classList, function(index, item) {
8580  					if (item.substring(0,20) === 'able-button-handler-') {
8581  						$mediaParent.find('div.able-controller div.' + item).trigger('focus');
8582  					}
8583  				});
8584  			}
8585  		}
8586
8587  	};
8588
8589  	AblePlayer.prototype.addSeekbarListeners = function () {
8590
8591  		var thisObj = this;
8592
8593  		// Handle seek bar events.
8594  		this.seekBar.seekbarDiv.on('startTracking', function (e) {
8595  			thisObj.pausedBeforeTracking = thisObj.paused;
8596  			thisObj.pauseMedia();
8597  		}).on('tracking', function (e, position) {
8598  			// Scrub transcript, captions, and metadata.
8599  			thisObj.highlightTranscript(position);
8600  			thisObj.updateCaption(position);
8601  			thisObj.showDescription(position);
8602  			thisObj.updateChapter(thisObj.convertChapterTimeToVideoTime(position));
8603  			thisObj.updateMeta(position);
8604  			thisObj.refreshControls();
8605  		}).on('stopTracking', function (e, position) {
8606  			if (thisObj.useChapterTimes) {
8607  				thisObj.seekTo(thisObj.convertChapterTimeToVideoTime(position));
8608  			} else {
8609  				thisObj.seekTo(position);
8610  			}
8611  			if (!thisObj.pausedBeforeTracking) {
8612  				setTimeout(function () {
8613  					thisObj.playMedia();
8614  				}, 200);
8615  			}
8616  		});
8617  	};
8618
8619  	AblePlayer.prototype.onClickPlayerButton = function (el) {
8620  		var whichButton, prefsPopup;
8621  		whichButton = this.getButtonNameFromClass($(el).attr('class'));
8622  		switch ( whichButton ) {
8623  			case 'play':
8624  				this.clickedPlay = true;
8625  				this.handlePlay();
8626  				break;
8627  			case 'restart':
8628  				this.seekTrigger = 'restart';
8629  				this.handleRestart();
8630  				break;
8631  			case 'previous':
8632  				this.userClickedPlaylist = true;
8633  				this.okToPlay = true;
8634  				this.seekTrigger = 'previous';
8635  				this.buttonWithFocus = 'previous';
8636  				this.handlePrevTrack();
8637  				break;
8638  			case 'next':
8639  				this.userClickedPlaylist = true;
8640  				this.okToPlay = true;
8641  				this.seekTrigger = 'next';
8642  				this.buttonWithFocus = 'next';
8643  				this.handleNextTrack();
8644  				break;
8645  			case 'rewind':
8646  				this.seekTrigger = 'rewind';
8647  				this.handleRewind();
8648  				break;
8649  			case 'forward':
8650  				this.seekTrigger = 'forward';
8651  				this.handleFastForward();
8652  				break;
8653  			case 'mute':
8654  				this.handleMute();
8655  				break;
8656  			case 'volume':
8657  				this.handleVolumeButtonClick();
8658  				break;
8659  			case 'faster':
8660  				this.handleRateIncrease();
8661  				break;
8662  			case 'slower':
8663  				this.handleRateDecrease();
8664  				break;
8665  			case 'captions':
8666  				this.handleCaptionToggle();
8667  				break;
8668  			case 'chapters':
8669  				this.handleChapters();
8670  				break;
8671  			case 'descriptions':
8672  				this.handleDescriptionToggle();
8673  				break;
8674  			case 'sign':
8675  				if ( ! this.closingSign ) {
8676  					this.handleSignToggle();
8677  				}
8678  				break;
8679  			case 'preferences':
8680  				if ($(el).attr('data-prefs-popup') === 'menu') {
8681  					this.handlePrefsClick();
8682  				} else {
8683  					this.showingPrefsDialog = true; // stopgap
8684  					this.closePopups();
8685  					prefsPopup = $(el).attr('data-prefs-popup');
8686  					if (prefsPopup === 'keyboard') {
8687  						this.keyboardPrefsDialog.show();
8688  					} else if (prefsPopup === 'captions') {
8689  						this.captionPrefsDialog.show();
8690  					} else if (prefsPopup === 'descriptions') {
8691  						this.descPrefsDialog.show();
8692  					} else if (prefsPopup === 'transcript') {
8693  						this.transcriptPrefsDialog.show();
8694  					}
8695  					this.showingPrefsDialog = false;
8696  				}
8697  				break;
8698  			case 'transcript':
8699  				if ( !this.closingTranscript ) {
8700  					this.handleTranscriptToggle();
8701  				}
8702  				break;
8703  			case 'fullscreen':
8704  				this.clickedFullscreenButton = true;
8705  				this.handleFullscreenToggle();
8706  				break;
8707  		}
8708  	};
8709
8710  	AblePlayer.prototype.getButtonNameFromClass = function (classString) {
8711  		// player control buttons all have class="able-button-handler-x"  where x is the identifier
8712  		// buttons might also have other classes assigned though
8713  		var classes, i;
8714
8715  		classes = classString.split(' ');
8716  		for (i = 0; i < classes.length; i++) {
8717  			if (classes[i].substring(0,20) === 'able-button-handler-') {
8718  				return classes[i].substring(20);
8719  			}
8720  		}
8721  		return classString;
8722  	};
8723
8724  	AblePlayer.prototype.okToHandleKeyPress = function () {
8725  		let defaultReturn = true;
8726  		if ( this.prefNoKeyShortcuts === 1 ) {
8727  			defaultReturn = false;
8728  		}
8729  		// returns true unless user's focus is on a UI element or user has disabled keyboard shortcuts.
8730  		// that is likely to need supported keystrokes, including space
8731  		var activeElement = AblePlayer.getActiveDOMElement();
8732
8733  		return ($(activeElement).prop('tagName') === 'INPUT') ? false : defaultReturn;
8734  	};
8735
8736  	AblePlayer.prototype.onPlayerKeyPress = function (e) {
8737
8738  		// handle keystrokes (using DHTML Style Guide recommended key combinations)
8739  		// https://web.archive.org/web/20130127004544/http://dev.aol.com/dhtml_style_guide/#mediaplayer
8740  		// Modifier keys Alt + Ctrl are on by default, but can be changed within Preferences
8741  		// - Style guide only supports Play/Pause, Stop, Mute, Captions, & Volume Up & Down
8742  		// The rest are reasonable best choices
8743  		// - If there are multiple players on a single page, keystroke handlers
8744  		// are only bound to the FIRST player
8745  		// - The DHTML Style Guide is now the W3C WAI-ARIA Authoring Guide and has undergone many revisions
8746  		// including removal of the "media player" design pattern. There's an issue about that:
8747  		// https://github.com/w3c/aria-practices/issues/27
8748
8749  		var key, $thisElement;
8750
8751  		// Convert to lower case.
8752  		key = e.key;
8753  		$thisElement = $(document.activeElement);
8754
8755  		if (key === 'Escape') {
8756  			if (this.$transcriptArea && $.contains(this.$transcriptArea[0],$thisElement[0]) && !this.hidingPopup) {
8757  				// This element is part of transcript area.
8758  				this.handleTranscriptToggle();
8759  				return false;
8760  			}
8761  		}
8762  		if (!this.okToHandleKeyPress()) {
8763  			return false;
8764  		}
8765
8766  		// Only use keypress to control player if focus is NOT on a form field or contenteditable element
8767  		// (or a textarea element with player in stenoMode)
8768  		if (!(
8769  			$(':focus').is('[contenteditable]') ||
8770  			$(':focus').is('input') ||
8771  			($(':focus').is('textarea') && !this.stenoMode) ||
8772  			$(':focus').is('select') ||
8773  			e.target.hasAttribute('contenteditable') ||
8774  			e.target.tagName === 'INPUT' ||
8775  			(e.target.tagName === 'TEXTAREA' && !this.stenoMode) ||
8776  			e.target.tagName === 'SELECT'
8777  		)){
8778  			if (key === 'Escape') {
8779  				this.closePopups();
8780  				this.$tooltipDiv.hide();
8781  				this.seekBar.hideSliderTooltips();
8782  			} else if (key === ' ') {
8783  				// disable spacebar support for play/pause toggle as of 4.2.10
8784  				// spacebar should not be handled everywhere on the page, since users use that to scroll the page
8785  				// when the player has focus, most controls are buttons so spacebar should be used to trigger the buttons
8786  				if ($thisElement.attr('role') === 'button') {
8787  					// register a click on this element
8788  					e.preventDefault();
8789  					$thisElement.trigger( 'click' );
8790  				}
8791  			} else if ( key === 'p' ) {
8792  				if (this.usingModifierKeys(e)) {
8793  					e.preventDefault();
8794  					this.handlePlay();
8795  				}
8796  			} else if (key === 's') {
8797  				if (this.usingModifierKeys(e)) {
8798  					e.preventDefault();
8799  					this.handleRestart();
8800  				}
8801  			} else if (key === 'm') {
8802  				if (this.usingModifierKeys(e)) {
8803  					e.preventDefault();
8804  					this.handleMute();
8805  				}
8806  			} else if (key === 'v') {
8807  				if (this.usingModifierKeys(e)) {
8808  					e.preventDefault();
8809  					this.handleVolumeButtonClick();
8810  				}
8811  			} else if (key >= 0 && key <= 9) {
8812  				if (this.usingModifierKeys(e)) {
8813  					e.preventDefault();
8814  					this.handleVolumeKeystroke(key);
8815  				}
8816  			} else if (key === 'c') {
8817  				if (this.usingModifierKeys(e)) {
8818  					e.preventDefault();
8819  					this.handleCaptionToggle();
8820  				}
8821  			} else if (key === 'd') {
8822  				if (this.usingModifierKeys(e)) {
8823  					e.preventDefault();
8824  					this.handleDescriptionToggle();
8825  				}
8826  			} else if (key === 'f') {
8827  				if (this.usingModifierKeys(e)) {
8828  					e.preventDefault();
8829  					this.handleFastForward();
8830  				}
8831  			} else if (key === 'r') {
8832  				if (this.usingModifierKeys(e)) {
8833  					e.preventDefault();
8834  					this.handleRewind();
8835  				}
8836  			} else if (key === 'b') {
8837  				if (this.usingModifierKeys(e)) {
8838  					e.preventDefault();
8839  					this.handlePrevTrack();
8840  				}
8841  			} else if (key === 'n') {
8842  				if (this.usingModifierKeys(e)) {
8843  					e.preventDefault();
8844  					this.handleNextTrack();
8845  				}
8846  			} else if (key === 'e') {
8847  				if (this.usingModifierKeys(e)) {
8848  					e.preventDefault();
8849  					this.handlePrefsClick();
8850  				}
8851  			} else if (key === 'Enter') {
8852  				if ($thisElement.attr('role') === 'button' || $thisElement.prop('tagName') === 'SPAN') {
8853  					// register a click on this element
8854  					// if it's a transcript span the transcript span click handler will take over
8855  					$thisElement.trigger( 'click' );
8856  				} else if ($thisElement.prop('tagName') === 'LI') {
8857  					$thisElement.trigger( 'click' );
8858  				}
8859  			}
8860  		}
8861  	};
8862
8863  	AblePlayer.prototype.addHtml5MediaListeners = function () {
8864
8865  		var thisObj = this;
8866
8867  		// NOTE: iOS and some browsers do not support autoplay
8868  		// and no events are triggered until media begins to play
8869  		// Able Player gets around this by automatically loading media in some circumstances
8870  		// (see initialize.js > initPlayer() for details)
8871
8872  		this.$media
8873  			.on('emptied',function() {
8874  				// do something
8875  			})
8876  			.on('loadedmetadata',function() {
8877  				// should be able to get duration now
8878  				thisObj.duration = thisObj.media.duration;
8879  			})
8880  			.on('canplay',function() {
8881  				// previously handled seeking to startTime here
8882  				// but it's probably safer to wait for canplaythrough
8883  				// so we know player can seek ahead to anything
8884  			})
8885  			.on('canplaythrough',function() {
8886  				// previously onMediaNewSourceLoad() was called on 'loadedmetadata'
8887  				// but that proved to be too soon for some of this functionality.
8888  				// TODO: Monitor this. If moving it here causes performance issues,
8889  				// consider moving some or all of this functionality to 'canplay'
8890  				thisObj.onMediaNewSourceLoad();
8891  			})
8892  			.on('play',function() {
8893  				// 'play' indicates that the play method has been called.
8894  				// Don't do anything until playback has actually started.
8895  			})
8896  			.on('playing',function() {
8897  				// 'playing' indicates that the video is playing.
8898  				thisObj.playing = true;
8899  				thisObj.paused = false;
8900  				thisObj.swappingSrc = false;
8901  				thisObj.refreshControls('playpause');
8902  			})
8903  			.on('ended',function() {
8904  				thisObj.playing = false;
8905  				thisObj.paused = true;
8906  				thisObj.onMediaComplete();
8907  			})
8908  			.on('progress', function() {
8909  				thisObj.refreshControls('timeline');
8910  			})
8911  			.on('waiting',function() {
8912  				// could fire a notification about loss of data.
8913  			})
8914  			.on('durationchange',function() {
8915  				// Display new duration.
8916  				thisObj.refreshControls('timeline');
8917  			})
8918  			.on('timeupdate',function() {
8919  				thisObj.onMediaUpdateTime(); // includes a call to refreshControls()
8920  			})
8921  			.on('pause',function() {
8922  				if (!thisObj.clickedPlay) {
8923  					// 'pause' was triggered automatically, not initiated by user
8924  					// this happens in some browsers when swapping source
8925  					// (e.g., between tracks in a playlist or swapping description)
8926  					if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else {
8927  						thisObj.playing = false;
8928  						thisObj.paused = true;
8929  					}
8930  				} else {
8931  					thisObj.playing = false;
8932  					thisObj.paused = true;
8933  				}
8934  				thisObj.clickedPlay = false; // done with this variable
8935  				thisObj.onMediaPause(); // includes a call to refreshControls()
8936  			})
8937  			.on('ratechange',function() {
8938  				// do something
8939  			})
8940  			.on('volumechange',function() {
8941  				thisObj.volume = thisObj.getVolume();
8942  			})
8943  			.on('error',function() {
8944  				if (thisObj.debug) {
8945  					switch (thisObj.media.error.code) {
8946  						case 1:
8947  							console.log('HTML5 Media Error: MEDIA_ERR_ABORTED');
8948  							break;
8949  						case 2:
8950  							console.log('HTML5 Media Error: MEDIA_ERR_NETWORK ');
8951  							break;
8952  						case 3:
8953  							console.log('HTML5 Media Error: MEDIA_ERR_DECODE ');
8954  							break;
8955  						case 4:
8956  							console.log('HTML5 Media Error: MEDIA_ERR_SRC_NOT_SUPPORTED ');
8957  							break;
8958  					}
8959  				}
8960  			});
8961  	};
8962
8963  	AblePlayer.prototype.addVimeoListeners = function () {
8964
8965  		var thisObj = this;
8966
8967  		// Vimeo doesn't seem to support chaining of on() functions
8968  		// so each event listener must be attached separately
8969  		this.vimeoPlayer.on('loaded', function(vimeoId) {
8970  			 // Triggered when a new video is loaded in the player
8971  			thisObj.onMediaNewSourceLoad();
8972  		 });
8973  		this.vimeoPlayer.on('play', function(e) {
8974  			// Triggered when the video plays
8975  			thisObj.playing = true;
8976  			thisObj.startedPlaying = true;
8977  			thisObj.paused = false;
8978  			thisObj.refreshControls('playpause');
8979  		});
8980  		this.vimeoPlayer.on('ended', function(e) {
8981  			// Triggered any time the video playback reaches the end.
8982  			// Note: when loop is turned on, the ended event will not fire.
8983  			thisObj.playing = false;
8984  			thisObj.paused = true;
8985  			thisObj.onMediaComplete();
8986  		});
8987  		this.vimeoPlayer.on('bufferstart', function() {
8988  			// Triggered when buffering starts in the player.
8989  			// This is also triggered during preload and while seeking.
8990  			// There is no associated data with this event.
8991  		});
8992  		this.vimeoPlayer.on('bufferend', function() {
8993  			// Triggered when buffering ends in the player.
8994  			// This is also triggered at the end of preload and seeking.
8995  			// There is no associated data with this event.
8996  		});
8997  		this.vimeoPlayer.on('progress', function(e) {
8998  			// Triggered as the video is loaded.
8999  			 // Reports back the amount of the video that has been buffered (NOT the amount played)
9000  			 // Data has keys duration, percent, and seconds
9001  		});
9002  		this.vimeoPlayer.on('seeking', function(e) {
9003  		 	// Triggered when the player starts seeking to a specific time.
9004  			 // A timeupdate event will also be fired at the same time.
9005  		});
9006  		this.vimeoPlayer.on('seeked', function(e) {
9007  			// Triggered when the player seeks to a specific time.
9008  			// A timeupdate event will also be fired at the same time.
9009  		});
9010  		this.vimeoPlayer.on('timeupdate',function(e) {
9011  			// Triggered as the currentTime of the video updates.
9012  			 // It generally fires every 250ms, but it may vary depending on the browser.
9013  			thisObj.onMediaUpdateTime(e['duration'], e['seconds']);
9014  		});
9015  		this.vimeoPlayer.on('pause',function(e) {
9016  			// Triggered when the video pauses
9017  			if (!thisObj.clickedPlay) {
9018  					// 'pause' was triggered automatically, not initiated by user
9019  				// this happens in some browsers (not Chrome, as of 70.x)
9020  				// when swapping source (e.g., between tracks in a playlist, or swapping description)
9021  				if (thisObj.hasPlaylist || thisObj.swappingSrc) ; else {
9022  					thisObj.playing = false;
9023  					thisObj.paused = true;
9024  				}
9025  			} else {
9026  				thisObj.playing = false;
9027  				thisObj.paused = true;
9028  			}
9029  			thisObj.clickedPlay = false; // done with this variable
9030  			thisObj.onMediaPause();
9031  			thisObj.refreshControls('playpause');
9032  		});
9033  		this.vimeoPlayer.on('playbackratechange',function(e) {
9034  			// Triggered when the playback rate of the video in the player changes.
9035  			// The ability to change rate can be disabled by the creator
9036  			// and the event will not fire for those videos.
9037  			// data contains one key: 'playbackRate'
9038  			thisObj.vimeoPlaybackRate = e['playbackRate'];
9039  		});
9040  		this.vimeoPlayer.on('texttrackchange', function(e) {
9041  			// Triggered when the active text track (captions/subtitles) changes.
9042  			// The values will be null if text tracks are turned off.
9043  			// data contains three keys: kind, label, language
9044  		});
9045  		this.vimeoPlayer.on('volumechange',function(e) {
9046  			// Triggered when the volume in the player changes.
9047  			// Some devices do not support setting the volume of the video
9048  			// independently from the system volume,
9049  			// so this event will never fire on those devices.
9050  			thisObj.volume = e['volume'] * 10;
9051  		});
9052  		this.vimeoPlayer.on('error',function(e) {
9053  			// do something with the available data
9054  			// data contains three keys: message, method, name
9055  			// message: A user-friendly error message
9056  			// method: The Vimeo API method call that triggered the error
9057  			// name: Name of the error (not necesssarily user-friendly)
9058  		});
9059  	};
9060
9061  	AblePlayer.prototype.addEventListeners = function () {
9062
9063  		var thisObj = this;
9064
9065  		// Appropriately resize media player for full screen.
9066  		$(window).on('resize',function () {
9067  			thisObj.resizePlayer();
9068  		});
9069
9070  		// Refresh player if it changes from hidden to visible
9071  		// There is no event triggered by a change in visibility
9072  		// but MutationObserver works in most browsers (but NOT in IE 10 or earlier)
9073  		// http://caniuse.com/#feat=mutationobserver
9074  		if (window.MutationObserver) {
9075  			var target = this.$ableDiv[0];
9076  			var observer = new MutationObserver(function(mutations) {
9077  				mutations.forEach(function(mutation) {
9078  					if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
9079  						// the player's style attribute has changed. Check to see if it's visible
9080  						if (thisObj.$ableDiv.is(':visible')) {
9081  							thisObj.refreshControls();
9082  						}
9083  					}
9084  				});
9085  			});
9086  			var config = { attributes: true, childList: true, characterData: true };
9087  			observer.observe(target, config);
9088  		}
9089  		if (typeof this.seekBar !== 'undefined') {
9090  			this.addSeekbarListeners();
9091  		} else {
9092  			// wait a bit and try again
9093  			// TODO: Should set this up to keep trying repeatedly.
9094  			// Seekbar listeners are critical.
9095  			setTimeout(function() {
9096  				if (typeof thisObj.seekBar !== 'undefined') {
9097  					thisObj.addSeekbarListeners();
9098  				}
9099  			},2000);
9100  		}
9101
9102  		// handle clicks on player buttons
9103  		this.$controllerDiv.find('div[role="button"]').on('click',function(e){
9104  			e.stopPropagation();
9105  			thisObj.onClickPlayerButton(this);
9106  		});
9107
9108  		// handle clicks (left only) anywhere on the page. If any popups are open, close them.
9109  		$('body').on('click', function(e) {
9110
9111  			if (e.button !== 0) { // not a left click
9112  				return false;
9113  			}
9114  			if ($('.able-popup:visible').length || $('.able-volume-slider:visible').length ) {
9115  				// at least one popup is visible
9116  				thisObj.closePopups();
9117  			}
9118  			if (e.target.tagName === 'VIDEO') {
9119  				// user clicked the video (not an element that sits on top of the video)
9120  				// handle this as a play/pause toggle click
9121  				thisObj.clickedPlay = true;
9122  			}
9123  		});
9124
9125  		// handle mouse movement over player; make controls visible again if hidden
9126  		this.$ableDiv.on('mousemove',function() {
9127  			if (thisObj.controlsHidden) {
9128  				thisObj.fadeControls('in');
9129  				thisObj.controlsHidden = false;
9130  				// if there's already an active timeout, clear it and start timer again
9131  				if (thisObj.hideControlsTimeoutStatus === 'active') {
9132  					window.clearTimeout(thisObj.hideControlsTimeout);
9133  					thisObj.hideControlsTimeoutStatus = 'clear';
9134  				}
9135  				if (thisObj.hideControls) {
9136  					// after showing controls, hide them again after a brief timeout
9137  					thisObj.invokeHideControlsTimeout();
9138  				}
9139  			} else {
9140  				// if there's already an active timeout, clear it and start timer again
9141  				if (thisObj.hideControlsTimeoutStatus === 'active') {
9142  					window.clearTimeout(thisObj.hideControlsTimeout);
9143  					thisObj.hideControlsTimeoutStatus = 'clear';
9144  					if (thisObj.hideControls) {
9145  						thisObj.invokeHideControlsTimeout();
9146  					}
9147  				}
9148  			}
9149  		});
9150
9151  		// if user presses a key from anywhere on the page, show player controls
9152  		$(document).on( 'keydown', function(e) {
9153  			if (thisObj.controlsHidden) {
9154  				thisObj.fadeControls('in');
9155  				thisObj.controlsHidden = false;
9156  				if (thisObj.hideControlsTimeoutStatus === 'active') {
9157  					window.clearTimeout(thisObj.hideControlsTimeout);
9158  					thisObj.hideControlsTimeoutStatus = 'clear';
9159  				}
9160  				if (thisObj.hideControls) {
9161  					// after showing controls, hide them again after a brief timeout
9162  					thisObj.invokeHideControlsTimeout();
9163  				}
9164  			} else {
9165  				// controls are visible
9166  				// if there's already an active timeout, clear it and start timer again
9167  				if (thisObj.hideControlsTimeoutStatus === 'active') {
9168  					window.clearTimeout(thisObj.hideControlsTimeout);
9169  					thisObj.hideControlsTimeoutStatus = 'clear';
9170
9171  					if (thisObj.hideControls) {
9172  						thisObj.invokeHideControlsTimeout();
9173  					}
9174  				}
9175  			}
9176  		});
9177
9178  		// handle local keydown events if this isn't the only player on the page;
9179  		// otherwise these are dispatched by global handler (see ableplayer-base.js)
9180  		this.$ableDiv.on( 'keydown', function (e) {
9181  			if (!AblePlayer.hasSingleInstance()) {
9182  				thisObj.onPlayerKeyPress(e);
9183  			}
9184  		});
9185
9186  		// If stenoMode is enabled in an iframe, handle keydown events from the iframe
9187  		if (this.stenoMode && (typeof this.stenoFrameContents !== 'undefined')) {
9188  			this.stenoFrameContents.on('keydown',function(e) {
9189  				thisObj.onPlayerKeyPress(e);
9190  			});
9191  		}
9192  		// transcript is not a child of this.$ableDiv
9193  		// therefore, must be added separately
9194  		if (this.$transcriptArea) {
9195  			this.$transcriptArea.on('keydown',function (e) {
9196  				if (!AblePlayer.hasSingleInstance()) {
9197  					thisObj.onPlayerKeyPress(e);
9198  				}
9199  			});
9200  		}
9201
9202  		// handle clicks on playlist items
9203  		if (this.$playlist) {
9204  			this.$playlist.on( 'click', function(e) {
9205  				if (!thisObj.userClickedPlaylist) {
9206  					// stopgap in case multiple clicks are fired on the same playlist item
9207  					thisObj.userClickedPlaylist = true; // will be set to false after new src is loaded & canplaythrough is triggered
9208  					thisObj.playlistIndex = $(this).index();
9209  					thisObj.cuePlaylistItem(thisObj.playlistIndex);
9210  				}
9211  			});
9212  		}
9213
9214  		// Also play/pause when clicking on the media.
9215  		this.$media.on( 'click', function () {
9216  			thisObj.handlePlay();
9217  		});
9218
9219  		// add listeners for media events
9220  		if (this.player === 'html5') {
9221  			this.addHtml5MediaListeners();
9222  		} else if (this.player === 'vimeo') {
9223  			 this.addVimeoListeners();
9224  		} else if (this.player === 'youtube') {
9225  			// Youtube doesn't give us time update events, so we just periodically generate them ourselves
9226  			setInterval(function () {
9227  				thisObj.onMediaUpdateTime();
9228  			}, 300);
9229  		}
9230  	};
9231  }
9232
9233  function addInitializeFunctions(AblePlayer) {
9234  	// Set default variable values.
9235  	AblePlayer.prototype.setDefaults = function () {
9236
9237  		this.playerCreated = false; // will set to true after recreatePlayer() is complete the first time
9238  		this.playing = false; // will change to true after 'playing' event is triggered
9239  		this.paused = true; // will always be the opposite of this.playing (available for convenience)
9240  		this.clickedPlay = false; // will change to true temporarily if user clicks 'play' (or pause)
9241  		this.fullscreen = false; // will change to true if player is in full screen mode
9242  		this.swappingSrc = false; // will change to true temporarily while media source is being swapped
9243  		this.initializing = false; // will change to true temporarily while initPlayer() is processing
9244  		this.cueingPlaylistItems = false; // will change to true temporarily while cueing next playlist item
9245  		this.buttonWithFocus = null; // will change to 'previous' or 'next' if user clicks either of those buttons
9246  		this.speechEnabled = null; // will change either to 'true' in initSpeech(), or false if not supported
9247
9248  		this.setIconColor();
9249  	};
9250
9251  	AblePlayer.prototype.setIconColor = function() {
9252
9253  		// determine the best color choice (white or black) for icons,
9254  		// given the background-color of their container elements
9255  		// Source for relative luminance formula:
9256  		// https://en.wikipedia.org/wiki/Relative_luminance
9257
9258  		// We need to know the color *before* creating the element
9259  		// so the element doesn't exist yet when this function is called
9260  		// therefore, need to create a temporary element then remove it after color is determined
9261  		// Temp element must be added to the DOM or WebKit can't retrieve its CSS properties
9262
9263  		var $elements, i, $el, bgColor, rgb, red, green, blue, luminance, iconColor;
9264
9265  		$elements = ['controller', 'toolbar'];
9266  		for (i=0; i<$elements.length; i++) {
9267  			if ($elements[i] == 'controller') {
9268  				$el =	 $('<div>', {
9269  					'class': 'able-controller'
9270  				}).hide();
9271  			} else if ($elements[i] === 'toolbar') {
9272  				$el =	 $('<div>', {
9273  					'class': 'able-window-toolbar'
9274  				}).hide();
9275  			}
9276  			$('body').append($el);
9277  			bgColor = $el.css('background-color');
9278  			// bgColor is a string in the form 'rgb(R, G, B)', perhaps with a 4th item for alpha;
9279  			// split the 3 or 4 channels into an array
9280  			rgb = bgColor.replace(/[^\d,]/g, '').split(',');
9281  			red = rgb[0];
9282  			green = rgb[1];
9283  			blue = rgb[2];
9284  			luminance = (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
9285  			// range is 1 - 255; therefore 125 is the tipping point
9286  			iconColor = (luminance < 125) ? 'white' : 'black';
9287
9288  			if ($elements[i] === 'controller') {
9289  				this.iconColor = iconColor;
9290  			} else if ($elements[i] === 'toolbar') {
9291  				this.toolbarIconColor = iconColor;
9292  			}
9293  			$el.remove();
9294  		}
9295  	};
9296
9297  	AblePlayer.prototype.getIconData = function(button) {
9298
9299  		// returns array of values for creating <svg> tag for specified button
9300  		// 0 = <svg> viewBox attribute
9301  		// 1 = <path> d (description) attribute
9302  		// 2 = icon class for font icons
9303  		// 3 = img URL for images.
9304  		var svg = Array();
9305
9306  		switch (button) {
9307
9308  			case 'play':
9309  				svg[0] = '0 0 16 20';
9310  				svg[1] = 'M0 18.393v-16.429q0-0.29 0.184-0.402t0.441 0.033l14.821 8.237q0.257 0.145 0.257 0.346t-0.257 0.346l-14.821 8.237q-0.257 0.145-0.441 0.033t-0.184-0.402z';
9311  				break;
9312
9313  			case 'pause':
9314  				svg[0] = '0 0 20 20';
9315  				svg[1] = 'M0 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502zM10 18.036v-15.714q0-0.29 0.212-0.502t0.502-0.212h5.714q0.29 0 0.502 0.212t0.212 0.502v15.714q0 0.29-0.212 0.502t-0.502 0.212h-5.714q-0.29 0-0.502-0.212t-0.212-0.502z';
9316  				break;
9317
9318  			case 'restart':
9319  				svg[0] = '0 0 20 20';
9320  				svg[1] = 'M18 8h-6l2.243-2.243c-1.133-1.133-2.64-1.757-4.243-1.757s-3.109 0.624-4.243 1.757c-1.133 1.133-1.757 2.64-1.757 4.243s0.624 3.109 1.757 4.243c1.133 1.133 2.64 1.757 4.243 1.757s3.109-0.624 4.243-1.757c0.095-0.095 0.185-0.192 0.273-0.292l1.505 1.317c-1.466 1.674-3.62 2.732-6.020 2.732-4.418 0-8-3.582-8-8s3.582-8 8-8c2.209 0 4.209 0.896 5.656 2.344l2.344-2.344v6z';
9321  				break;
9322
9323  			case 'rewind':
9324  				svg[0] = '0 0 20 20';
9325  				svg[1] = 'M11.25 3.125v6.25l6.25-6.25v13.75l-6.25-6.25v6.25l-6.875-6.875z';
9326  				break;
9327
9328  			case 'forward':
9329  				svg[0] = '0 0 20 20';
9330  				svg[1] = 'M10 16.875v-6.25l-6.25 6.25v-13.75l6.25 6.25v-6.25l6.875 6.875z';
9331  				break;
9332
9333  			case 'previous':
9334  				svg[0] = '0 0 20 20';
9335  				svg[1] = 'M5 17.5v-15h2.5v6.875l6.25-6.25v13.75l-6.25-6.25v6.875z';
9336  				break;
9337
9338  			case 'next':
9339  				svg[0] = '0 0 20 20';
9340  				svg[1] = 'M15 2.5v15h-2.5v-6.875l-6.25 6.25v-13.75l6.25 6.25v-6.875z';
9341  				break;
9342
9343  			case 'slower':
9344  				svg[0] = '0 0 11 20';
9345  				svg[1] = 'M0 7.321q0-0.29 0.212-0.502t0.502-0.212h10q0.29 0 0.502 0.212t0.212 0.502-0.212 0.502l-5 5q-0.212 0.212-0.502 0.212t-0.502-0.212l-5-5q-0.212-0.212-0.212-0.502z';
9346  				break;
9347
9348  			case 'faster':
9349  				svg[0] = '0 0 11 20';
9350  				svg[1] = 'M0 12.411q0-0.29 0.212-0.502l5-5q0.212-0.212 0.502-0.212t0.502 0.212l5 5q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-10q-0.29 0-0.502-0.212t-0.212-0.502z';
9351  				break;
9352
9353  			case 'turtle':
9354  				svg[0] = '0 0 20 20';
9355  				svg[1] = 'M17.212 3.846c-0.281-0.014-0.549 0.025-0.817 0.144-1.218 0.542-1.662 2.708-2.163 3.942-1.207 2.972-7.090 4.619-11.755 5.216-0.887 0.114-1.749 0.74-2.428 1.466 0.82-0.284 2.126-0.297 2.74 0.144 0.007 0.488-0.376 1.062-0.625 1.37-0.404 0.5-0.398 0.793 0.12 0.793 0.473 0 0.752 0.007 1.635 0 0.393-0.003 0.618-0.16 1.49-1.49 3.592 0.718 5.986-0.264 5.986-0.264s0.407 1.755 1.418 1.755h1.49c0.633 0 0.667-0.331 0.625-0.433-0.448-1.082-0.68-1.873-0.769-2.5-0.263-1.857 0.657-3.836 2.524-5.457 0.585 0.986 2.253 0.845 2.909-0.096s0.446-2.268-0.192-3.221c-0.49-0.732-1.345-1.327-2.188-1.37zM8.221 4.663c-0.722-0.016-1.536 0.111-2.5 0.409-4.211 1.302-4.177 4.951-3.51 5.745 0 0-0.955 0.479-0.409 1.274 0.448 0.652 3.139 0.191 5.409-0.529s4.226-1.793 5.312-2.692c0.948-0.785 0.551-2.106-0.505-1.947-0.494-0.98-1.632-2.212-3.798-2.26zM18.846 5.962c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577z';
9356  				break;
9357
9358  			case 'rabbit':
9359  				svg[0] = '0 0 20 20';
9360  				svg[1] = 'M10.817 0c-2.248 0-1.586 0.525-1.154 0.505 1.551-0.072 5.199 0.044 6.851 2.428 0 0-1.022-2.933-5.697-2.933zM10.529 0.769c-2.572 0-2.837 0.51-2.837 1.106 0 0.545 1.526 0.836 2.524 0.697 2.778-0.386 4.231-0.12 5.264 0.865-1.010 0.779-0.75 1.401-1.274 1.851-1.093 0.941-2.643-0.673-4.976-0.673-2.496 0-4.712 1.92-4.712 4.76-0.157-0.537-0.769-0.913-1.442-0.913-0.974 0-1.514 0.637-1.514 1.49 0 0.769 1.13 1.791 2.861 0.938 0.499 1.208 2.265 1.364 2.452 1.418 0.538 0.154 1.875 0.098 1.875 0.865 0 0.794-1.034 1.094-1.034 1.707 0 1.070 1.758 0.873 2.284 1.034 1.683 0.517 2.103 1.214 2.788 2.212 0.771 1.122 2.572 1.408 2.572 0.625 0-3.185-4.413-4.126-4.399-4.135 0.608-0.382 2.139-1.397 2.139-3.534 0-1.295-0.703-2.256-1.755-2.861 1.256 0.094 2.572 1.205 2.572 2.74 0 1.877-0.653 2.823-0.769 2.957 1.975-1.158 3.193-3.91 3.029-6.37 0.61 0.401 1.27 0.577 1.971 0.625 0.751 0.052 1.475-0.225 1.635-0.529 0.38-0.723 0.162-2.321-0.12-2.837-0.763-1.392-2.236-1.73-3.606-1.683-1.202-1.671-3.812-2.356-5.529-2.356zM1.37 3.077l-0.553 1.538h3.726c0.521-0.576 1.541-1.207 2.284-1.538h-5.457zM18.846 5.192c0.325 0 0.577 0.252 0.577 0.577s-0.252 0.577-0.577 0.577c-0.325 0-0.577-0.252-0.577-0.577s0.252-0.577 0.577-0.577zM0.553 5.385l-0.553 1.538h3.197c0.26-0.824 0.586-1.328 0.769-1.538h-3.413z';
9361  				break;
9362
9363  			case 'ellipsis':
9364  				svg[0] = '0 0 20 20';
9365  				svg[1] = 'M10.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2zM3.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.986 2.199-2.2s-0.984-2.2-2.199-2.2zM17.001 7.8c-1.215 0-2.201 0.985-2.201 2.2s0.986 2.2 2.201 2.2c1.215 0 2.199-0.985 2.199-2.2s-0.984-2.2-2.199-2.2z';
9366  				break;
9367
9368  			case 'pipe':
9369  				svg[0] = '0 0 20 20';
9370  				svg[1] = 'M10.15 0.179h0.623c0.069 0 0.127 0.114 0.127 0.253v19.494c0 0.139-0.057 0.253-0.127 0.253h-1.247c-0.069 0-0.126-0.114-0.126-0.253v-19.494c0-0.139 0.057-0.253 0.126-0.253h0.623z';
9371  				break;
9372
9373  			case 'captions':
9374  				svg[0] = '0 0 20 20';
9375  				svg[1] = 'M0.033 3.624h19.933v12.956h-19.933v-12.956zM18.098 10.045c-0.025-2.264-0.124-3.251-0.743-3.948-0.112-0.151-0.322-0.236-0.496-0.344-0.606-0.386-3.465-0.526-6.782-0.526s-6.313 0.14-6.907 0.526c-0.185 0.108-0.396 0.193-0.519 0.344-0.607 0.697-0.693 1.684-0.731 3.948 0.037 2.265 0.124 3.252 0.731 3.949 0.124 0.161 0.335 0.236 0.519 0.344 0.594 0.396 3.59 0.526 6.907 0.547 3.317-0.022 6.176-0.151 6.782-0.547 0.174-0.108 0.384-0.183 0.496-0.344 0.619-0.697 0.717-1.684 0.743-3.949v0 0zM9.689 9.281c-0.168-1.77-1.253-2.813-3.196-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.442-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.977zM16.607 9.281c-0.167-1.77-1.252-2.813-3.194-2.813-1.773 0-3.168 1.387-3.168 3.617 0 2.239 1.271 3.636 3.372 3.636 1.676 0 2.851-1.071 3.035-2.852h-2.003c-0.079 0.661-0.397 1.168-1.068 1.168-1.059 0-1.253-0.91-1.253-1.876 0-1.33 0.441-2.010 1.174-2.010 0.653 0 1.068 0.412 1.13 1.129h1.976z';
9376  				break;
9377
9378  			case 'descriptions':
9379  				svg[0] = '0 0 20 20';
9380  				svg[1] = 'M17.623 3.57h-1.555c1.754 1.736 2.763 4.106 2.763 6.572 0 2.191-0.788 4.286-2.189 5.943h1.484c1.247-1.704 1.945-3.792 1.945-5.943-0-2.418-0.886-4.754-2.447-6.572v0zM14.449 3.57h-1.55c1.749 1.736 2.757 4.106 2.757 6.572 0 2.191-0.788 4.286-2.187 5.943h1.476c1.258-1.704 1.951-3.792 1.951-5.943-0-2.418-0.884-4.754-2.447-6.572v0zM11.269 3.57h-1.542c1.752 1.736 2.752 4.106 2.752 6.572 0 2.191-0.791 4.286-2.181 5.943h1.473c1.258-1.704 1.945-3.792 1.945-5.943 0-2.418-0.876-4.754-2.447-6.572v0zM10.24 9.857c0 3.459-2.826 6.265-6.303 6.265v0.011h-3.867v-12.555h3.896c3.477 0 6.274 2.806 6.274 6.279v0zM6.944 9.857c0-1.842-1.492-3.338-3.349-3.338h-0.876v6.686h0.876c1.858 0 3.349-1.498 3.349-3.348v0z';
9381  				break;
9382
9383  			case 'sign':
9384  				svg[0] = '0 0 20 20';
9385  				svg[1] = 'M10.954 10.307c0.378 0.302 0.569 1.202 0.564 1.193 0.697 0.221 1.136 0.682 1.136 0.682 1.070-0.596 1.094-0.326 1.558-0.682 0.383-0.263 0.366-0.344 0.567-1.048 0.187-0.572-0.476-0.518-1.021-1.558-0.95 0.358-1.463 0.196-1.784 0.167-0.145-0.020-0.12 0.562-1.021 1.247zM14.409 17.196c-0.133 0.182-0.196 0.218-0.363 0.454-0.28 0.361 0.076 0.906 0.253 0.82 0.206-0.076 0.341-0.488 0.567-0.623 0.115-0.061 0.422-0.513 0.709-0.82 0.211-0.238 0.363-0.344 0.564-0.594 0.341-0.422 0.412-0.744 0.709-1.193 0.184-0.236 0.312-0.307 0.481-0.594 0.886-1.679 0.628-2.432 1.475-3.629 0.26-0.353 0.552-0.442 0.964-0.653 0.383-2.793-0.888-4.356-0.879-4.361-1.067 0.623-1.644 0.879-2.751 0.82-0.417-0.005-0.636-0.182-1.048-0.145-0.385 0.015-0.582 0.159-0.964 0.29-0.589 0.182-0.91 0.344-1.529 0.535-0.393 0.11-0.643 0.115-1.050 0.255-0.348 0.147-0.182 0.029-0.427 0.312-0.317 0.348-0.238 0.623-0.535 1.222-0.371 0.785-0.326 0.891-0.115 0.987-0.14 0.402-0.174 0.672-0.14 1.107 0.039 0.331-0.101 0.562 0.255 0.825 0.483 0.361 1.499 1.205 1.757 1.217 0.39-0.012 1.521 0.029 2.096-0.368 0.13-0.081 0.167-0.162 0.056 0.145-0.022 0.037-1.433 1.136-1.585 1.131-1.794 0.056-1.193 0.157-1.303 0.115-0.091 0-0.955-1.055-1.477-0.682-0.196 0.12-0.287 0.236-0.363 0.452 0.066 0.137 0.383 0.358 0.675 0.54 0.422 0.27 0.461 0.552 0.881 0.653 0.513 0.115 1.060 0.039 1.387 0.081 0.125 0.034 1.256-0.297 1.961-0.675 0.65-0.336-0.898 0.648-1.276 1.131-1.141 0.358-0.82 0.373-1.362 0.483-0.503 0.115-0.479 0.086-0.822 0.196-0.356 0.086-0.648 0.572-0.312 0.825 0.201 0.167 0.827-0.066 1.445-0.086 0.275-0.005 1.391-0.518 1.644-0.653 0.633-0.339 1.099-0.81 1.472-1.077 0.518-0.361-0.584 0.991-1.050 1.558zM8.855 9.799c-0.378-0.312-0.569-1.212-0.564-1.217-0.697-0.206-1.136-0.667-1.136-0.653-1.070 0.582-1.099 0.312-1.558 0.653-0.388 0.277-0.366 0.363-0.567 1.045-0.187 0.594 0.471 0.535 1.021 1.561 0.95-0.344 1.463-0.182 1.784-0.142 0.145 0.010 0.12-0.572 1.021-1.247zM5.4 2.911c0.133-0.191 0.196-0.228 0.368-0.454 0.27-0.371-0.081-0.915-0.253-0.849-0.211 0.096-0.346 0.508-0.599 0.653-0.093 0.052-0.4 0.503-0.682 0.82-0.211 0.228-0.363 0.334-0.564 0.599-0.346 0.407-0.412 0.729-0.709 1.161-0.184 0.258-0.317 0.324-0.481 0.621-0.886 1.669-0.631 2.422-1.475 3.6-0.26 0.38-0.552 0.461-0.964 0.682-0.383 2.788 0.883 4.346 0.879 4.336 1.068-0.609 1.639-0.861 2.751-0.825 0.417 0.025 0.636 0.201 1.048 0.174 0.385-0.025 0.582-0.169 0.964-0.285 0.589-0.196 0.91-0.358 1.499-0.54 0.422-0.12 0.672-0.125 1.080-0.285 0.348-0.128 0.182-0.010 0.427-0.282 0.312-0.358 0.238-0.633 0.508-1.217 0.398-0.8 0.353-0.906 0.142-0.991 0.135-0.412 0.174-0.677 0.14-1.107-0.044-0.336 0.101-0.572-0.255-0.82-0.483-0.375-1.499-1.22-1.752-1.222-0.395 0.002-1.526-0.039-2.101 0.339-0.13 0.101-0.167 0.182-0.056-0.11 0.022-0.052 1.433-1.148 1.585-1.163 1.794-0.039 1.193-0.14 1.303-0.088 0.091-0.007 0.955 1.045 1.477 0.682 0.191-0.13 0.287-0.245 0.368-0.452-0.071-0.147-0.388-0.368-0.68-0.537-0.422-0.282-0.464-0.564-0.881-0.655-0.513-0.125-1.065-0.049-1.387-0.11-0.125-0.015-1.256 0.317-1.956 0.68-0.66 0.351 0.893-0.631 1.276-1.136 1.136-0.339 0.81-0.353 1.36-0.479 0.501-0.101 0.476-0.071 0.82-0.172 0.351-0.096 0.648-0.577 0.312-0.849-0.206-0.152-0.827 0.081-1.44 0.086-0.28 0.020-1.396 0.533-1.649 0.677-0.633 0.329-1.099 0.8-1.472 1.048-0.523 0.38 0.584-0.967 1.050-1.529z';
9386  				break;
9387
9388  			case 'mute':
9389  			case 'volume-mute':
9390  				svg[0] = '0 0 20 20';
9391  				svg[1] = 'M7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714zM18.75 12.093v1.657h-1.657l-2.093-2.093-2.093 2.093h-1.657v-1.657l2.093-2.093-2.093-2.093v-1.657h1.657l2.093 2.093 2.093-2.093h1.657v1.657l-2.093 2.093z';
9392  				break;
9393
9394  			case 'volume-soft':
9395  				svg[0] = '0 0 20 20';
9396  				svg[1] = 'M10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z';
9397  				break;
9398
9399  			case 'volume-medium':
9400  				svg[0] = '0 0 20 20';
9401  				svg[1] = 'M14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z';
9402  				break;
9403
9404  			case 'volume-loud':
9405  				svg[0] = '0 0 21 20';
9406  				svg[1] = 'M17.384 18.009c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.712-1.712 2.654-3.988 2.654-6.408s-0.943-4.696-2.654-6.408c-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.066 2.066 3.204 4.813 3.204 7.734s-1.138 5.668-3.204 7.734c-0.183 0.183-0.423 0.275-0.663 0.275zM14.053 16.241c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 2.559-2.559 2.559-6.722 0-9.281-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c1.594 1.594 2.471 3.712 2.471 5.966s-0.878 4.373-2.471 5.966c-0.183 0.183-0.423 0.275-0.663 0.275zM10.723 14.473c-0.24 0-0.48-0.092-0.663-0.275-0.366-0.366-0.366-0.96 0-1.326 1.584-1.584 1.584-4.161 0-5.745-0.366-0.366-0.366-0.96 0-1.326s0.96-0.366 1.326 0c2.315 2.315 2.315 6.082 0 8.397-0.183 0.183-0.423 0.275-0.663 0.275zM7.839 1.536c0.501-0.501 0.911-0.331 0.911 0.378v16.172c0 0.709-0.41 0.879-0.911 0.378l-4.714-4.713h-3.125v-7.5h3.125l4.714-4.714z';
9407  				break;
9408
9409  			case 'chapters':
9410  				svg[0] = '0 0 20 20';
9411  				svg[1] = 'M5 2.5v17.5l6.25-6.25 6.25 6.25v-17.5zM15 0h-12.5v17.5l1.25-1.25v-15h11.25z';
9412  				break;
9413
9414  			case 'transcript':
9415  				svg[0] = '0 0 20 20';
9416  				svg[1] = 'M0 19.107v-17.857q0-0.446 0.313-0.759t0.759-0.313h8.929v6.071q0 0.446 0.313 0.759t0.759 0.313h6.071v11.786q0 0.446-0.313 0.759t-0.759 0.312h-15q-0.446 0-0.759-0.313t-0.313-0.759zM4.286 15.536q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM4.286 12.679q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM4.286 9.821q0 0.156 0.1 0.257t0.257 0.1h7.857q0.156 0 0.257-0.1t0.1-0.257v-0.714q0-0.156-0.1-0.257t-0.257-0.1h-7.857q-0.156 0-0.257 0.1t-0.1 0.257v0.714zM11.429 5.893v-5.268q0.246 0.156 0.402 0.313l4.554 4.554q0.156 0.156 0.313 0.402h-5.268z';
9417  				break;
9418
9419  			case 'preferences':
9420  				svg[0] = '0 0 20 20';
9421  				svg[1] = 'M18.238 11.919c-1.049-1.817-0.418-4.147 1.409-5.205l-1.965-3.404c-0.562 0.329-1.214 0.518-1.911 0.518-2.1 0-3.803-1.714-3.803-3.828h-3.931c0.005 0.653-0.158 1.314-0.507 1.919-1.049 1.818-3.382 2.436-5.212 1.382l-1.965 3.404c0.566 0.322 1.056 0.793 1.404 1.396 1.048 1.815 0.42 4.139-1.401 5.2l1.965 3.404c0.56-0.326 1.209-0.513 1.902-0.513 2.094 0 3.792 1.703 3.803 3.808h3.931c-0.002-0.646 0.162-1.3 0.507-1.899 1.048-1.815 3.375-2.433 5.203-1.387l1.965-3.404c-0.562-0.322-1.049-0.791-1.395-1.391zM10 14.049c-2.236 0-4.050-1.813-4.050-4.049s1.813-4.049 4.050-4.049 4.049 1.813 4.049 4.049c-0 2.237-1.813 4.049-4.049 4.049z';
9422  				break;
9423
9424  			case 'close':
9425  				svg[0] = '0 0 16 20';
9426  				svg[1] = 'M1.228 14.933q0-0.446 0.312-0.759l3.281-3.281-3.281-3.281q-0.313-0.313-0.313-0.759t0.313-0.759l1.518-1.518q0.313-0.313 0.759-0.313t0.759 0.313l3.281 3.281 3.281-3.281q0.313-0.313 0.759-0.313t0.759 0.313l1.518 1.518q0.313 0.313 0.313 0.759t-0.313 0.759l-3.281 3.281 3.281 3.281q0.313 0.313 0.313 0.759t-0.313 0.759l-1.518 1.518q-0.313 0.313-0.759 0.313t-0.759-0.313l-3.281-3.281-3.281 3.281q-0.313 0.313-0.759 0.313t-0.759-0.313l-1.518-1.518q-0.313-0.313-0.313-0.759z';
9427  				break;
9428
9429  			case 'fullscreen-expand':
9430  				svg[0] = '0 0 20 20';
9431  				svg[1] = 'M0 18.036v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502zM8.717 8.393q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257z';
9432  				break;
9433
9434  			case 'fullscreen-collapse':
9435  				svg[0] = '0 0 20 20';
9436  				svg[1] = 'M0.145 16.964q0-0.145 0.112-0.257l3.705-3.705-1.607-1.607q-0.212-0.212-0.212-0.502t0.212-0.502 0.502-0.212h5q0.29 0 0.502 0.212t0.212 0.502v5q0 0.29-0.212 0.502t-0.502 0.212-0.502-0.212l-1.607-1.607-3.705 3.705q-0.112 0.112-0.257 0.112t-0.257-0.112l-1.272-1.272q-0.112-0.112-0.112-0.257zM8.571 9.464v-5q0-0.29 0.212-0.502t0.502-0.212 0.502 0.212l1.607 1.607 3.705-3.705q0.112-0.112 0.257-0.112t0.257 0.112l1.272 1.272q0.112 0.112 0.112 0.257t-0.112 0.257l-3.705 3.705 1.607 1.607q0.212 0.212 0.212 0.502t-0.212 0.502-0.502 0.212h-5q-0.29 0-0.502-0.212t-0.212-0.502z';
9437  				break;
9438  		}
9439
9440  		return svg;
9441  	};
9442
9443  	// Initialize player based on data on page.
9444  	// This sets some variables, but does not modify anything. Safe to call multiple times.
9445  	// Can call again after updating this.media so long as new media element has the same ID.
9446  	AblePlayer.prototype.reinitialize = function () {
9447
9448  		var deferred, promise;
9449
9450  		deferred = new this.defer();
9451  		promise = deferred.promise();
9452
9453  		this.startedPlaying = false;
9454  		// TODO: Move this setting to preferences.
9455  		this.autoScrollTranscript = true;
9456  		//this.autoScrollTranscript = this.getPref(autoScrollTranscript); // (doesn't work)
9457
9458  		// Bootstrap from this.media possibly being an ID or other selector.
9459  		this.$media = $(this.media).first();
9460  		this.media = this.$media[0];
9461
9462  		// Set media type to 'audio' or 'video'; this determines some of the behavior of player creation.
9463  		if (this.$media.is('audio')) {
9464  			this.mediaType = 'audio';
9465  		} else if (this.$media.is('video')) {
9466  			this.mediaType = 'video';
9467  		} else {
9468  			// Able Player was initialized with some element other than <video> or <audio>
9469  			this.provideFallback();
9470  			deferred.reject();
9471  			return promise;
9472  		}
9473
9474  		this.$sources = this.$media.find('source');
9475
9476  		this.player = this.getPlayer();
9477  		if (!this.player) {
9478  			// an error was generated in getPlayer()
9479  			this.provideFallback();
9480  		}
9481
9482  		deferred.resolve();
9483  		return promise;
9484  	};
9485
9486  	AblePlayer.prototype.setPlayerSize = function(width, height) {
9487
9488  		// Called again after width and height are known
9489  		if (this.mediaType !== 'audio' && width > 0 && height > 0) {
9490  			this.playerWidth = width;
9491  			this.playerHeight = height;
9492  			this.aspectRatio = height / width;
9493  		}
9494  	};
9495
9496  	// Perform one-time setup for this instance of player; called after player is first initialized.
9497  	AblePlayer.prototype.setupInstance = function () {
9498
9499  		var deferred = new this.defer();
9500  		var promise = deferred.promise();
9501
9502  		if (this.$media.attr('id')) {
9503  			this.mediaId = this.$media.attr('id');
9504  		} else {
9505  			// Ensure the base media element always has an ID.
9506  			this.mediaId = "ableMediaId_" + this.ableIndex;
9507  			this.$media.attr('id', this.mediaId);
9508  		}
9509  		deferred.resolve();
9510  		return promise;
9511  	};
9512
9513  	AblePlayer.prototype.setupInstancePlaylist = function() {
9514
9515  		// find a matching playlist and set this.hasPlaylist
9516  		// if there is one, also set this.$playlist, this.playlistIndex, & this.playlistEmbed
9517  		var thisObj = this;
9518
9519  		this.hasPlaylist = false; // will change to true if a matching playlist is found
9520
9521  		$('.able-playlist').each(function() {
9522  			if ($(this).data('player') === thisObj.mediaId) {
9523  				// this is the playlist for the current player
9524  				thisObj.hasPlaylist = true;
9525  				// If using an embedded player, we'll replace $playlist with the clone later.
9526  				thisObj.$playlist = $(this).find('li');
9527
9528  				// check to see if list item has YouTube as its source
9529  				// if it does, inject a thumbnail from YouTube
9530  				var $youTubeVideos = $(this).find('li[data-youtube-id]');
9531  				$youTubeVideos.each(function() {
9532  					var youTubeId = purify.sanitize( $(this).attr('data-youtube-id') );
9533  					var youTubePoster = thisObj.getYouTubePosterUrl(youTubeId,'120');
9534  					var $youTubeImg = $('<img>',{
9535  						'src': youTubePoster,
9536  						'alt': ''
9537  					});
9538  					$(this).find('button').prepend($youTubeImg);
9539  				});
9540
9541  				// check to see if list item has Vimeo as its source
9542  				// if it does, inject a thumbnail from Vimeo
9543  				var $vimeoVideos = $(this).find('li[data-vimeo-id]');
9544  				$vimeoVideos.each(function() {
9545  					var vimeoId = $(this).attr('data-vimeo-id');
9546  					var vimeoPoster = thisObj.getVimeoPosterUrl(vimeoId,'120');
9547  					var $vimeoImg = $('<img>',{
9548  						'src': vimeoPoster,
9549  						'alt': ''
9550  					});
9551  					$(this).find('button').prepend($vimeoImg);
9552  				});
9553
9554  				// add accessibility to the list markup
9555  				$(this).find('li span').attr('aria-hidden','true');
9556  				thisObj.playlistIndex = 0;
9557  				var dataEmbedded = $(this).data('embedded');
9558  				// is playlist embedded within player?
9559  				thisObj.playlistEmbed = (typeof dataEmbedded !== 'undefined' && dataEmbedded !== false) ? true : false;
9560  			}
9561  		});
9562
9563  		if (this.hasPlaylist && this.loop) {
9564  			// browser will loop the current track in the playlist, rather than the playlist
9565  			// therefore, need to remove loop attribute from media element
9566  			// but keep this.loop as true and handle the playlist looping ourselves
9567  			this.media.removeAttribute('loop');
9568  		}
9569  		if (this.hasPlaylist && this.playlistEmbed) {
9570  			// Copy the playlist out of the dom, so we can reinject when we build the player.
9571  			var parent = this.$playlist.parent();
9572  			this.$playlistDom = parent.clone();
9573  			parent.remove();
9574  		}
9575  		if (this.hasPlaylist && this.$sources.length === 0) {
9576  			// no source elements were provided. Construct them from the first playlist item
9577  			this.cuePlaylistItem(0);
9578  			// redefine this.$sources now that media contains one or more <source> elements
9579  			this.$sources = this.$media.find('source');
9580  		}
9581  	};
9582
9583  	AblePlayer.prototype.recreatePlayer = function () {
9584
9585  		// Creates the appropriate player for the current source.
9586  		// This function is called each time a new media instance is loaded
9587  		// e.g.,
9588  		// User clicks on an item in a playlist
9589  		// User swaps to/from described version of video
9590  		// Blocks of code that only need to be executed once are controlled
9591  		// by this.playerCreated
9592
9593  		// TODO: Ensure when recreating player that we carry over the mediaId
9594  		if (!this.player) {
9595  			console.log("Can't create player; no appropriate player type detected.");
9596  			return;
9597  		}
9598
9599  		var deferred, promise, thisObj, prefsGroups, i;
9600
9601  		deferred = new this.defer();
9602  		promise = deferred.promise();
9603  		thisObj = this;
9604
9605  		this.playerDeleted = false; // reset after deletePlayer()
9606
9607  		// set temp stopgap to prevent this function from executing again before finished
9608  		this.recreatingPlayer = true;
9609
9610  		if (!this.playerCreated) {
9611  			// only call these functions once
9612  			this.loadCurrentPreferences();
9613  			this.injectPlayerCode();
9614  			this.resizePlayer(this.media.videoWidth,this.media.videoHeight);
9615  		}
9616
9617  		this.getSampleDescriptionText();
9618
9619  		this.initSignLanguage();
9620
9621  		this.initPlayer().then(function() {
9622
9623  			thisObj.getTracks().then(function() {
9624
9625  				thisObj.initDescription().then(function() {
9626
9627  					thisObj.setupTracks().then(function() {
9628  						if (thisObj.hasClosedDesc) {
9629  							if (!thisObj.$descDiv || (thisObj.$descDiv && !($.contains(thisObj.$ableDiv[0], thisObj.$descDiv[0])))) {
9630  								// descDiv either doesn't exist, or exists in an orphaned state
9631  								// Either way, it needs to be rebuilt...
9632  								thisObj.injectTextDescriptionArea();
9633  							}
9634  						}
9635  						thisObj.initSpeech('init');
9636
9637  						thisObj.setupTranscript().then(function() {
9638
9639  							thisObj.initStenoFrame().then(function() {
9640
9641  								if (thisObj.stenoMode && thisObj.$stenoFrame) {
9642  									thisObj.stenoFrameContents = thisObj.$stenoFrame.contents();
9643  								}
9644  								thisObj.getMediaTimes().then(function(mediaTimes) {
9645
9646  									thisObj.duration = mediaTimes['duration'];
9647  									thisObj.elapsed = mediaTimes['elapsed'];
9648
9649  									if (typeof thisObj.volume === 'undefined') {
9650  										thisObj.volume = thisObj.defaultVolume;
9651  									}
9652  									if (thisObj.volume) {
9653  										thisObj.setVolume(thisObj.volume);
9654  									}
9655  									if (thisObj.transcriptType) {
9656  										thisObj.addTranscriptAreaEvents();
9657  										thisObj.updateTranscript();
9658  									}
9659  									if (thisObj.captions.length) {
9660  										thisObj.initDefaultCaption();
9661  									}
9662
9663  									// setMediaAttributes() sets textTrack.mode to 'disabled' for all tracks
9664  									// This tells browsers to ignore the text tracks so Able Player can handle them
9665  									// However, timing is critical as browsers - especially Safari - tend to ignore this request
9666  									// unless it's sent late in the intialization process.
9667  									// If browsers ignore the request, the result is redundant captions
9668  									thisObj.setMediaAttributes();
9669  									thisObj.addControls();
9670  									thisObj.addEventListeners();
9671
9672  									// inject each of the hidden forms that will be accessed from the Preferences popup menu
9673  									prefsGroups = thisObj.getPreferencesGroups();
9674  									for (i = 0; i < prefsGroups.length; i++) {
9675  										thisObj.injectPrefsForm(prefsGroups[i]);
9676  									}
9677  									thisObj.setupPopups();
9678  									thisObj.updateCaption();
9679  									thisObj.injectVTS();
9680  									thisObj.populateChaptersDiv();
9681  									thisObj.showSearchResults();
9682
9683  									// Go ahead and load media, without user requesting it
9684  									// Ideally, we would wait until user clicks play, rather than unnecessarily consume their bandwidth
9685  									// However, the media needs to load for us to get the media's duration
9686  									if (thisObj.player === 'html5') {
9687  										if (!thisObj.loadingMedia) {
9688  											thisObj.$media[0].load();
9689  											thisObj.loadingMedia = true;
9690  										}
9691  									}
9692  									// refreshControls is called twice building/initializing the player
9693  									// this is the second. Best to pause a bit before executing, to be sure all prior steps are complete
9694  									setTimeout(function() {
9695  										thisObj.refreshControls();
9696  										deferred.resolve();
9697  									},100);
9698  								});
9699  							});
9700  						});
9701  					});
9702  				});
9703  			});
9704  		},
9705  		function() {	 // initPlayer fail
9706  			thisObj.provideFallback();
9707  		});
9708  		return promise;
9709  	};
9710
9711  	AblePlayer.prototype.initPlayer = function () {
9712
9713  		var thisObj = this;
9714  		var playerPromise;
9715  		// First run player specific initialization.
9716  		if (this.player === 'html5') {
9717  			playerPromise = this.initHtml5Player();
9718  		} else if (this.player === 'youtube') {
9719  			playerPromise = this.initYouTubePlayer();
9720  		} else if (this.player === 'vimeo') {
9721  			playerPromise = this.initVimeoPlayer();
9722  		}
9723  		// After player specific initialization is done, run remaining general initialization.
9724  		var deferred = new this.defer();
9725  		var promise = deferred.promise();
9726  		playerPromise.then(
9727  			function () { // done/resolved
9728  				if (thisObj.useFixedSeekInterval) {
9729  					// if fixed seekInterval was not already assigned (using value of data-seek-interval)
9730  					if (!thisObj.seekInterval) {
9731  						thisObj.seekInterval = thisObj.defaultSeekInterval;
9732  					}
9733  					thisObj.seekIntervalCalculated = true;
9734  				} else {
9735  					thisObj.setSeekInterval();
9736  				}
9737  				deferred.resolve();
9738  			}
9739  		).finally(function () { // failed
9740  			deferred.reject();
9741  			}
9742  		);
9743
9744  		return promise;
9745  	};
9746
9747  	AblePlayer.prototype.initStenoFrame = function() {
9748
9749  		var deferred, promise;
9750  		deferred = new this.defer();
9751  		promise = deferred.promise();
9752
9753  		if (this.stenoMode && this.$stenoFrame) {
9754
9755  			if (this.$stenoFrame[0].contentWindow,document.readyState == 'complete') {
9756  				// iframe has already loaded
9757  				deferred.resolve();
9758  			} else {
9759  				// iframe has not loaded. Wait for it.
9760  				this.$stenoFrame.on('load',function() {
9761  					deferred.resolve();
9762  				});
9763  			}
9764  		} else {
9765  			// there is no stenoFrame to initialize
9766  			deferred.resolve();
9767  		}
9768  		return promise;
9769  	};
9770
9771  	AblePlayer.prototype.setSeekInterval = function () {
9772
9773  		// this function is only called if this.useFixedSeekInterval is false
9774  		// if this.useChapterTimes, this is called as each new chapter is loaded
9775  		// otherwise, it's called once, as the player is initialized
9776  		var duration;
9777  		this.seekInterval = this.defaultSeekInterval;
9778  		duration = (this.useChapterTimes) ? this.chapterDuration : this.duration;
9779
9780  		if (typeof duration === 'undefined' || duration < 1) {
9781  			// no duration; just use default for now but keep trying until duration is available
9782  			this.seekIntervalCalculated = false;
9783  			return;
9784  		} else {
9785  			if (duration <= 20) {
9786  				this.seekInterval = 5;	 // 4 steps max
9787  			} else if (duration <= 30) {
9788  				this.seekInterval = 6; // 5 steps max
9789  			} else if (duration <= 40) {
9790  				this.seekInterval = 8; // 5 steps max
9791  			} else if (duration <= 100) {
9792  				this.seekInterval = 10; // 10 steps max
9793  			} else {
9794  				// never more than 10 steps from start to end
9795  				this.seekInterval = Math.round(duration / 10, 0);
9796  			}
9797  			this.seekIntervalCalculated = true;
9798  		}
9799  	};
9800
9801  	AblePlayer.prototype.initDefaultCaption = function () {
9802
9803  		var captions, i;
9804
9805  		captions = this.captions;
9806  		if (captions.length > 0) {
9807  			for (i=0; i<captions.length; i++) {
9808  				if (captions[i].def === true) {
9809  					this.captionLang = captions[i].language;
9810  					this.selectedCaptions = captions[i];
9811  				}
9812  			}
9813  			if (typeof this.captionLang === 'undefined') {
9814  				// No caption track was flagged as default
9815  				// find and use a caption language that matches the player language
9816  				for (i=0; i<captions.length; i++) {
9817  					if (captions[i].language === this.lang) {
9818  						this.captionLang = captions[i].language;
9819  						this.selectedCaptions = captions[i];
9820  					}
9821  				}
9822  			}
9823  			if (typeof this.captionLang === 'undefined') {
9824  				// Still no matching caption track
9825  				// just use the first track
9826  				this.captionLang = captions[0].language;
9827  				this.selectedCaptions = captions[0];
9828  			}
9829  			if (typeof this.captionLang !== 'undefined') {
9830  				// reset transcript selected <option> to this.captionLang
9831  				if (this.$transcriptLanguageSelect) {
9832  					this.$transcriptLanguageSelect.find('option[lang=' + this.captionLang + ']').prop('selected',true);
9833  				}
9834  				// sync all other tracks to this same languge
9835  				this.syncTrackLanguages('init',this.captionLang);
9836  			}
9837  			if (this.player === 'vimeo') {
9838  				if (this.usingVimeoCaptions && this.prefCaptions == 1) {
9839  					// initialize Vimeo captions to the default language
9840  					this.vimeoPlayer.enableTextTrack(this.captionLang).catch(function(error) {
9841  						switch (error.name) {
9842  							case 'InvalidTrackLanguageError':
9843  								// There is no text track for the specified language
9844  								console.log(`No Vimeo text track is available in the specified language (${this.captionLang})`);
9845  								break;
9846  							case 'InvalidTrackError':
9847  								// There is no such text track
9848  								console.log('No Vimeo text track is available');
9849  								break;
9850  							default:
9851  								// some other error occurred
9852  								console.log('Error enabling Vimeo text track');
9853  								break;
9854  						}
9855  					});
9856  				} else {
9857  					// disable Vimeo captions.
9858  					this.vimeoPlayer.disableTextTrack().then(function() {
9859  						// Vimeo captions disabled
9860  					}).catch(function(error) {
9861  						console.log('Error disabling Vimeo text track: ',error);
9862  					});
9863  				}
9864  			}
9865  		}
9866  	};
9867
9868  	AblePlayer.prototype.initHtml5Player = function () {
9869  		// Nothing special to do!
9870  		var deferred = new this.defer();
9871  		var promise = deferred.promise();
9872  		deferred.resolve();
9873  		return promise;
9874  	};
9875
9876  	// Sets media/track/source attributes; is called whenever player is recreated since $media may have changed.
9877  	AblePlayer.prototype.setMediaAttributes = function () {
9878  		// Firefox puts videos in tab order; remove.
9879  		this.$media.attr('tabindex', -1);
9880
9881  		// Keep native player from displaying captions/subtitles by setting textTrack.mode='disabled'
9882  		// https://dev.w3.org/html5/spec-author-view/video.html#text-track-mode
9883  		// This *should* work but historically hasn't been supported in all browsers
9884  		// As of July 2025, 96% supported per https://caniuse.com/?search=text-track-mode.
9885  		// Workaround for non-supporting browsers is to remove default attribute
9886  		// We're doing that too in track.js > setupCaptions()
9887  		var textTracks = this.$media.get(0).textTracks;
9888  		if (textTracks) {
9889  			var i = 0;
9890  			while (i < textTracks.length) {
9891  				textTracks[i].mode = 'disabled';
9892  				i += 1;
9893  			}
9894  		}
9895  	};
9896
9897  	AblePlayer.prototype.getPlayer = function() {
9898
9899  		// Determine which player to use, if any
9900  		// return 'html5', 'youtube', 'vimeo', or null
9901  		if (this.testFallback) {
9902  			return null;
9903  		} else if (this.youTubeId) {
9904  			// null if attempting to play a YouTube video using an element other than <video>
9905  			return  (this.mediaType !== 'video') ? null : 'youtube';
9906  		} else if (this.vimeoId) {
9907  			// null if attempting to play a Vimeo video using an element other than <video>
9908  			return (this.mediaType !== 'video') ? null : 'vimeo';
9909  		} else if (this.media.canPlayType) {
9910  			return 'html5';
9911  		} else {
9912  			// Browser does not support the available media file
9913  			return null;
9914  		}
9915  	};
9916   }
9917
9918  function addLangsFunctions(AblePlayer) {
9919  	// Look up ISO 639-1 language codes to be used as subtitle labels
9920  	// In some instances "name" has been trunctation for readability
9921  	// Sources:
9922  	// http://stackoverflow.com/questions/3217492/list-of-language-codes-in-yaml-or-json/4900304#4900304
9923  	// https://www.venea.net/web/culture_code
9924
9925  	var isoLangs = {
9926  		"ab":{
9927  				"name":"Abkhaz",
9928  				"nativeName":"аҧсуа"
9929  		},
9930  		"aa":{
9931  				"name":"Afar",
9932  				"nativeName":"Afaraf"
9933  		},
9934  		"af":{
9935  				"name":"Afrikaans",
9936  				"nativeName":"Afrikaans"
9937  		},
9938  		"ak":{
9939  				"name":"Akan",
9940  				"nativeName":"Akan"
9941  		},
9942  		"sq":{
9943  				"name":"Albanian",
9944  				"nativeName":"Shqip"
9945  		},
9946  		"am":{
9947  				"name":"Amharic",
9948  				"nativeName":"አማርኛ"
9949  		},
9950  		"ar":{
9951  				"name":"Arabic",
9952  				"nativeName":"العربية"
9953  		},
9954  		"an":{
9955  				"name":"Aragonese",
9956  				"nativeName":"Aragonés"
9957  		},
9958  		"hy":{
9959  				"name":"Armenian",
9960  				"nativeName":"Հայերեն"
9961  		},
9962  		"as":{
9963  				"name":"Assamese",
9964  				"nativeName":"অসমীয়া"
9965  		},
9966  		"av":{
9967  				"name":"Avaric",
9968  				"nativeName":"авар мацӀ, магӀарул мацӀ"
9969  		},
9970  		"ae":{
9971  				"name":"Avestan",
9972  				"nativeName":"avesta"
9973  		},
9974  		"ay":{
9975  				"name":"Aymara",
9976  				"nativeName":"aymar aru"
9977  		},
9978  		"az":{
9979  				"name":"Azerbaijani",
9980  				"nativeName":"azərbaycan dili"
9981  		},
9982  		"bm":{
9983  				"name":"Bambara",
9984  				"nativeName":"bamanankan"
9985  		},
9986  		"ba":{
9987  				"name":"Bashkir",
9988  				"nativeName":"башҡорт теле"
9989  		},
9990  		"eu":{
9991  				"name":"Basque",
9992  				"nativeName":"euskara, euskera"
9993  		},
9994  		"be":{
9995  				"name":"Belarusian",
9996  				"nativeName":"Беларуская"
9997  		},
9998  		"bn":{
9999  				"name":"Bengali",
10000  				"nativeName":"বাংলা"
10001  		},
10002  		"bh":{
10003  				"name":"Bihari",
10004  				"nativeName":"भोजपुरी"
10005  		},
10006  		"bi":{
10007  				"name":"Bislama",
10008  				"nativeName":"Bislama"
10009  		},
10010  		"bs":{
10011  				"name":"Bosnian",
10012  				"nativeName":"bosanski jezik"
10013  		},
10014  		"br":{
10015  				"name":"Breton",
10016  				"nativeName":"brezhoneg"
10017  		},
10018  		"bg":{
10019  				"name":"Bulgarian",
10020  				"nativeName":"български език"
10021  		},
10022  		"my":{
10023  				"name":"Burmese",
10024  				"nativeName":"ဗမာစာ"
10025  		},
10026  		"ca":{
10027  				"name":"Catalan",
10028  				"nativeName":"Català"
10029  		},
10030  		"ch":{
10031  				"name":"Chamorro",
10032  				"nativeName":"Chamoru"
10033  		},
10034  		"ce":{
10035  				"name":"Chechen",
10036  				"nativeName":"нохчийн мотт"
10037  		},
10038  		"ny":{
10039  				"name":"Chichewa",
10040  				"nativeName":"chiCheŵa, chinyanja"
10041  		},
10042  		"zh":{
10043  				"name":"Chinese",
10044  				"nativeName":"中文 (Zhōngwén), 汉语, 漢語"
10045  		},
10046  		"cv":{
10047  				"name":"Chuvash",
10048  				"nativeName":"чӑваш чӗлхи"
10049  		},
10050  		"kw":{
10051  				"name":"Cornish",
10052  				"nativeName":"Kernewek"
10053  		},
10054  		"co":{
10055  				"name":"Corsican",
10056  				"nativeName":"corsu, lingua corsa"
10057  		},
10058  		"cr":{
10059  				"name":"Cree",
10060  				"nativeName":"ᓀᐦᐃᔭᐍᐏᐣ"
10061  		},
10062  		"hr":{
10063  				"name":"Croatian",
10064  				"nativeName":"hrvatski"
10065  		},
10066  		"cs":{
10067  				"name":"Czech",
10068  				"nativeName":"česky, čeština"
10069  		},
10070  		"da":{
10071  				"name":"Danish",
10072  				"nativeName":"dansk"
10073  		},
10074  		"dv":{
10075  				"name":"Divehi",
10076  				"nativeName":"ދިވެހި"
10077  		},
10078  		"nl":{
10079  				"name":"Dutch",
10080  				"nativeName":"Nederlands, Vlaams"
10081  		},
10082  		"en":{
10083  				"name":"English",
10084  				"nativeName":"English"
10085  		},
10086  		"eo":{
10087  				"name":"Esperanto",
10088  				"nativeName":"Esperanto"
10089  		},
10090  		"et":{
10091  				"name":"Estonian",
10092  				"nativeName":"eesti, eesti keel"
10093  		},
10094  		"ee":{
10095  				"name":"Ewe",
10096  				"nativeName":"Eʋegbe"
10097  		},
10098  		"fo":{
10099  				"name":"Faroese",
10100  				"nativeName":"føroyskt"
10101  		},
10102  		"fj":{
10103  				"name":"Fijian",
10104  				"nativeName":"vosa Vakaviti"
10105  		},
10106  		"fi":{
10107  				"name":"Finnish",
10108  				"nativeName":"suomi, suomen kieli"
10109  		},
10110  		"fr":{
10111  				"name":"French",
10112  				"nativeName":"français, langue française"
10113  		},
10114  		"ff":{
10115  				"name":"Fula",
10116  				"nativeName":"Fulfulde, Pulaar, Pular"
10117  		},
10118  		"gl":{
10119  				"name":"Galician",
10120  				"nativeName":"Galego"
10121  		},
10122  		"ka":{
10123  				"name":"Georgian",
10124  				"nativeName":"ქართული"
10125  		},
10126  		"de":{
10127  				"name":"German",
10128  				"nativeName":"Deutsch"
10129  		},
10130  		"el":{
10131  				"name":"Greek",
10132  				"nativeName":"Ελληνικά"
10133  		},
10134  		"gn":{
10135  				"name":"Guaraní",
10136  				"nativeName":"Avañeẽ"
10137  		},
10138  		"gu":{
10139  				"name":"Gujarati",
10140  				"nativeName":"ગુજરાતી"
10141  		},
10142  		"ht":{
10143  				"name":"Haitian",
10144  				"nativeName":"Kreyòl ayisyen"
10145  		},
10146  		"ha":{
10147  				"name":"Hausa",
10148  				"nativeName":"Hausa, هَوُسَ"
10149  		},
10150  		"he":{
10151  				"name":"Hebrew",
10152  				"nativeName":"עברית"
10153  		},
10154  		"hz":{
10155  				"name":"Herero",
10156  				"nativeName":"Otjiherero"
10157  		},
10158  		"hi":{
10159  				"name":"Hindi",
10160  				"nativeName":"हिन्दी, हिंदी"
10161  		},
10162  		"ho":{
10163  				"name":"Hiri Motu",
10164  				"nativeName":"Hiri Motu"
10165  		},
10166  		"hu":{
10167  				"name":"Hungarian",
10168  				"nativeName":"Magyar"
10169  		},
10170  		"ia":{
10171  				"name":"Interlingua",
10172  				"nativeName":"Interlingua"
10173  		},
10174  		"id":{
10175  				"name":"Indonesian",
10176  				"nativeName":"Bahasa Indonesia"
10177  		},
10178  		"ie":{
10179  				"name":"Interlingue",
10180  				"nativeName":"Originally called Occidental; then Interlingue after WWII"
10181  		},
10182  		"ga":{
10183  				"name":"Irish",
10184  				"nativeName":"Gaeilge"
10185  		},
10186  		"ig":{
10187  				"name":"Igbo",
10188  				"nativeName":"Asụsụ Igbo"
10189  		},
10190  		"ik":{
10191  				"name":"Inupiaq",
10192  				"nativeName":"Iñupiaq, Iñupiatun"
10193  		},
10194  		"io":{
10195  				"name":"Ido",
10196  				"nativeName":"Ido"
10197  		},
10198  		"is":{
10199  				"name":"Icelandic",
10200  				"nativeName":"Íslenska"
10201  		},
10202  		"it":{
10203  				"name":"Italian",
10204  				"nativeName":"Italiano"
10205  		},
10206  		"iu":{
10207  				"name":"Inuktitut",
10208  				"nativeName":"ᐃᓄᒃᑎᑐᑦ"
10209  		},
10210  		"ja":{
10211  				"name":"Japanese",
10212  				"nativeName":"日本語 (にほんご/にっぽんご)"
10213  		},
10214  		"jv":{
10215  				"name":"Javanese",
10216  				"nativeName":"basa Jawa"
10217  		},
10218  		"kl":{
10219  				"name":"Kalaallisut",
10220  				"nativeName":"kalaallisut, kalaallit oqaasii"
10221  		},
10222  		"kn":{
10223  				"name":"Kannada",
10224  				"nativeName":"ಕನ್ನಡ"
10225  		},
10226  		"kr":{
10227  				"name":"Kanuri",
10228  				"nativeName":"Kanuri"
10229  		},
10230  		"ks":{
10231  				"name":"Kashmiri",
10232  				"nativeName":"कश्मीरी, كشميري‎"
10233  		},
10234  		"kk":{
10235  				"name":"Kazakh",
10236  				"nativeName":"Қазақ тілі"
10237  		},
10238  		"km":{
10239  				"name":"Khmer",
10240  				"nativeName":"ភាសាខ្មែរ"
10241  		},
10242  		"ki":{
10243  				"name":"Kikuyu",
10244  				"nativeName":"Gĩkũyũ"
10245  		},
10246  		"rw":{
10247  				"name":"Kinyarwanda",
10248  				"nativeName":"Ikinyarwanda"
10249  		},
10250  		"ky":{
10251  				"name":"Kyrgyz",
10252  				"nativeName":"кыргыз тили"
10253  		},
10254  		"kv":{
10255  				"name":"Komi",
10256  				"nativeName":"коми кыв"
10257  		},
10258  		"kg":{
10259  				"name":"Kongo",
10260  				"nativeName":"KiKongo"
10261  		},
10262  		"ko":{
10263  				"name":"Korean",
10264  				"nativeName":"한국어 (韓國語), 조선말 (朝鮮語)"
10265  		},
10266  		"ku":{
10267  				"name":"Kurdish",
10268  				"nativeName":"Kurdî, كوردی‎"
10269  		},
10270  		"kj":{
10271  				"name":"Kuanyama",
10272  				"nativeName":"Kuanyama"
10273  		},
10274  		"la":{
10275  				"name":"Latin",
10276  				"nativeName":"latine, lingua latina"
10277  		},
10278  		"lb":{
10279  				"name":"Luxembourgish",
10280  				"nativeName":"Lëtzebuergesch"
10281  		},
10282  		"lg":{
10283  				"name":"Luganda",
10284  				"nativeName":"Luganda"
10285  		},
10286  		"li":{
10287  				"name":"Limburgish",
10288  				"nativeName":"Limburgs"
10289  		},
10290  		"ln":{
10291  				"name":"Lingala",
10292  				"nativeName":"Lingála"
10293  		},
10294  		"lo":{
10295  				"name":"Lao",
10296  				"nativeName":"ພາສາລາວ"
10297  		},
10298  		"lt":{
10299  				"name":"Lithuanian",
10300  				"nativeName":"lietuvių kalba"
10301  		},
10302  		"lu":{
10303  				"name":"Luba-Katanga",
10304  				"nativeName":""
10305  		},
10306  		"lv":{
10307  				"name":"Latvian",
10308  				"nativeName":"latviešu valoda"
10309  		},
10310  		"gv":{
10311  				"name":"Manx",
10312  				"nativeName":"Gaelg, Gailck"
10313  		},
10314  		"mk":{
10315  				"name":"Macedonian",
10316  				"nativeName":"македонски јазик"
10317  		},
10318  		"mg":{
10319  				"name":"Malagasy",
10320  				"nativeName":"Malagasy fiteny"
10321  		},
10322  		"ms":{
10323  				"name":"Malay",
10324  				"nativeName":"bahasa Melayu, بهاس ملايو‎"
10325  		},
10326  		"ml":{
10327  				"name":"Malayalam",
10328  				"nativeName":"മലയാളം"
10329  		},
10330  		"mt":{
10331  				"name":"Maltese",
10332  				"nativeName":"Malti"
10333  		},
10334  		"mi":{
10335  				"name":"Māori",
10336  				"nativeName":"te reo Māori"
10337  		},
10338  		"mr":{
10339  				"name":"Marathi",
10340  				"nativeName":"मराठी"
10341  		},
10342  		"mh":{
10343  				"name":"Marshallese",
10344  				"nativeName":"Kajin M̧ajeļ"
10345  		},
10346  		"mn":{
10347  				"name":"Mongolian",
10348  				"nativeName":"монгол"
10349  		},
10350  		"na":{
10351  				"name":"Nauru",
10352  				"nativeName":"Ekakairũ Naoero"
10353  		},
10354  		"nv":{
10355  				"name":"Navajo",
10356  				"nativeName":"Diné bizaad, Dinékʼehǰí"
10357  		},
10358  		"nb":{
10359  				"name":"Norwegian Bokmål",
10360  				"nativeName":"Norsk bokmål"
10361  		},
10362  		"nd":{
10363  				"name":"North Ndebele",
10364  				"nativeName":"isiNdebele"
10365  		},
10366  		"ne":{
10367  				"name":"Nepali",
10368  				"nativeName":"नेपाली"
10369  		},
10370  		"ng":{
10371  				"name":"Ndonga",
10372  				"nativeName":"Owambo"
10373  		},
10374  		"nn":{
10375  				"name":"Norwegian Nynorsk",
10376  				"nativeName":"Norsk nynorsk"
10377  		},
10378  		"no":{
10379  				"name":"Norwegian",
10380  				"nativeName":"Norsk"
10381  		},
10382  		"ii":{
10383  				"name":"Nuosu",
10384  				"nativeName":"ꆈꌠ꒿ Nuosuhxop"
10385  		},
10386  		"nr":{
10387  				"name":"South Ndebele",
10388  				"nativeName":"isiNdebele"
10389  		},
10390  		"oc":{
10391  				"name":"Occitan",
10392  				"nativeName":"Occitan"
10393  		},
10394  		"oj":{
10395  				"name":"Ojibwe",
10396  				"nativeName":"ᐊᓂᔑᓈᐯᒧᐎᓐ"
10397  		},
10398  		"cu":{
10399  				"name":"Church Slavonic",
10400  				"nativeName":"ѩзыкъ словѣньскъ"
10401  		},
10402  		"om":{
10403  				"name":"Oromo",
10404  				"nativeName":"Afaan Oromoo"
10405  		},
10406  		"or":{
10407  				"name":"Oriya",
10408  				"nativeName":"ଓଡ଼ିଆ"
10409  		},
10410  		"os":{
10411  				"name":"Ossetian",
10412  				"nativeName":"ирон æвзаг"
10413  		},
10414  		"pa":{
10415  				"name":"Punjabi",
10416  				"nativeName":"ਪੰਜਾਬੀ, پنجابی‎"
10417  		},
10418  		"pi":{
10419  				"name":"Pāli",
10420  				"nativeName":"पाऴि"
10421  		},
10422  		"fa":{
10423  				"name":"Persian",
10424  				"nativeName":"فارسی"
10425  		},
10426  		"pl":{
10427  				"name":"Polish",
10428  				"nativeName":"polski"
10429  		},
10430  		"ps":{
10431  				"name":"Pashto",
10432  				"nativeName":"پښتو"
10433  		},
10434  		"pt":{
10435  				"name":"Portuguese",
10436  				"nativeName":"Português"
10437  		},
10438  		"qu":{
10439  				"name":"Quechua",
10440  				"nativeName":"Runa Simi, Kichwa"
10441  		},
10442  		"rm":{
10443  				"name":"Romansh",
10444  				"nativeName":"rumantsch grischun"
10445  		},
10446  		"rn":{
10447  				"name":"Kirundi",
10448  				"nativeName":"kiRundi"
10449  		},
10450  		"ro":{
10451  				"name":"Romanian",
10452  				"nativeName":"română"
10453  		},
10454  		"ru":{
10455  				"name":"Russian",
10456  				"nativeName":"русский язык"
10457  		},
10458  		"sa":{
10459  				"name":"Sanskrit",
10460  				"nativeName":"संस्कृतम्"
10461  		},
10462  		"sc":{
10463  				"name":"Sardinian",
10464  				"nativeName":"sardu"
10465  		},
10466  		"sd":{
10467  				"name":"Sindhi",
10468  				"nativeName":"सिन्धी, سنڌي، سندھی‎"
10469  		},
10470  		"se":{
10471  				"name":"Northern Sami",
10472  				"nativeName":"Davvisámegiella"
10473  		},
10474  		"sm":{
10475  				"name":"Samoan",
10476  				"nativeName":"gagana faa Samoa"
10477  		},
10478  		"sg":{
10479  				"name":"Sango",
10480  				"nativeName":"yângâ tî sängö"
10481  		},
10482  		"sr":{
10483  				"name":"Serbian",
10484  				"nativeName":"српски језик"
10485  		},
10486  		"gd":{
10487  				"name":"Gaelic",
10488  				"nativeName":"Gàidhlig"
10489  		},
10490  		"sn":{
10491  				"name":"Shona",
10492  				"nativeName":"chiShona"
10493  		},
10494  		"si":{
10495  				"name":"Sinhalese",
10496  				"nativeName":"සිංහල"
10497  		},
10498  		"sk":{
10499  				"name":"Slovak",
10500  				"nativeName":"slovenčina"
10501  		},
10502  		"sl":{
10503  				"name":"Slovene",
10504  				"nativeName":"slovenščina"
10505  		},
10506  		"so":{
10507  				"name":"Somali",
10508  				"nativeName":"Soomaaliga, af Soomaali"
10509  		},
10510  		"st":{
10511  				"name":"Southern Sotho",
10512  				"nativeName":"Sesotho"
10513  		},
10514  		"es":{
10515  				"name":"Spanish",
10516  				"nativeName":"español, castellano"
10517  		},
10518  		"su":{
10519  				"name":"Sundanese",
10520  				"nativeName":"Basa Sunda"
10521  		},
10522  		"sw":{
10523  				"name":"Swahili",
10524  				"nativeName":"Kiswahili"
10525  		},
10526  		"ss":{
10527  				"name":"Swati",
10528  				"nativeName":"SiSwati"
10529  		},
10530  		"sv":{
10531  				"name":"Swedish",
10532  				"nativeName":"svenska"
10533  		},
10534  		"ta":{
10535  				"name":"Tamil",
10536  				"nativeName":"தமிழ்"
10537  		},
10538  		"te":{
10539  				"name":"Telugu",
10540  				"nativeName":"తెలుగు"
10541  		},
10542  		"tg":{
10543  				"name":"Tajik",
10544  				"nativeName":"тоҷикӣ, toğikī, تاجیکی‎"
10545  		},
10546  		"th":{
10547  				"name":"Thai",
10548  				"nativeName":"ไทย"
10549  		},
10550  		"ti":{
10551  				"name":"Tigrinya",
10552  				"nativeName":"ትግርኛ"
10553  		},
10554  		"bo":{
10555  				"name":"Tibetan",
10556  				"nativeName":"བོད་ཡིག"
10557  		},
10558  		"tk":{
10559  				"name":"Turkmen",
10560  				"nativeName":"Türkmen, Түркмен"
10561  		},
10562  		"tl":{
10563  				"name":"Tagalog",
10564  				"nativeName":"Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔"
10565  		},
10566  		"tn":{
10567  				"name":"Tswana",
10568  				"nativeName":"Setswana"
10569  		},
10570  		"to":{
10571  				"name":"Tonga",
10572  				"nativeName":"faka Tonga"
10573  		},
10574  		"tr":{
10575  				"name":"Turkish",
10576  				"nativeName":"Türkçe"
10577  		},
10578  		"ts":{
10579  				"name":"Tsonga",
10580  				"nativeName":"Xitsonga"
10581  		},
10582  		"tt":{
10583  				"name":"Tatar",
10584  				"nativeName":"татарча, tatarça, تاتارچا‎"
10585  		},
10586  		"tw":{
10587  				"name":"Twi",
10588  				"nativeName":"Twi"
10589  		},
10590  		"ty":{
10591  				"name":"Tahitian",
10592  				"nativeName":"Reo Tahiti"
10593  		},
10594  		"ug":{
10595  				"name":"Uyghur",
10596  				"nativeName":"Uyƣurqə, ئۇيغۇرچە‎"
10597  		},
10598  		"uk":{
10599  				"name":"Ukrainian",
10600  				"nativeName":"українська"
10601  		},
10602  		"ur":{
10603  				"name":"Urdu",
10604  				"nativeName":"اردو"
10605  		},
10606  		"uz":{
10607  				"name":"Uzbek",
10608  				"nativeName":"zbek, Ўзбек, أۇزبېك‎"
10609  		},
10610  		"ve":{
10611  				"name":"Venda",
10612  				"nativeName":"Tshivenḓa"
10613  		},
10614  		"vi":{
10615  				"name":"Vietnamese",
10616  				"nativeName":"Tiếng Việt"
10617  		},
10618  		"vo":{
10619  				"name":"Volapük",
10620  				"nativeName":"Volapük"
10621  		},
10622  		"wa":{
10623  				"name":"Walloon",
10624  				"nativeName":"Walon"
10625  		},
10626  		"cy":{
10627  				"name":"Welsh",
10628  				"nativeName":"Cymraeg"
10629  		},
10630  		"wo":{
10631  				"name":"Wolof",
10632  				"nativeName":"Wollof"
10633  		},
10634  		"fy":{
10635  				"name":"Western Frisian",
10636  				"nativeName":"Frysk"
10637  		},
10638  		"xh":{
10639  				"name":"Xhosa",
10640  				"nativeName":"isiXhosa"
10641  		},
10642  		"yi":{
10643  				"name":"Yiddish",
10644  				"nativeName":"ייִדיש"
10645  		},
10646  		"yo":{
10647  				"name":"Yoruba",
10648  				"nativeName":"Yorùbá"
10649  		},
10650  		"za":{
10651  				"name":"Zhuang",
10652  				"nativeName":"Saɯ cueŋƅ, Saw cuengh"
10653  		},
10654  		"ar-dz":{
10655  				"name":"Arabic (Algeria)",
10656  				"nativeName":"العربية (الجزائر)"
10657  		},
10658  		"ar-bh":{
10659  				"name":"Arabic (Bahrain)",
10660  				"nativeName":"العربية (البحرين)"
10661  		},
10662  		"ar-eg":{
10663  				"name":"Arabic (Egypt)",
10664  				"nativeName":"العربية (مصر)"
10665  		},
10666  		"ar-iq":{
10667  				"name":"Arabic (Iraq)",
10668  				"nativeName":"العربية (العراق)"
10669  		},
10670  		"ar-jo":{
10671  				"name":"Arabic (Jordan)",
10672  				"nativeName":"العربية (الأردن)"
10673  		},
10674  		"ar-kw":{
10675  				"name":"Arabic (Kuwait)",
10676  				"nativeName":"العربية (الكويت)"
10677  		},
10678  		"ar-lb":{
10679  				"name":"Arabic (Lebanon)",
10680  				"nativeName":"العربية (لبنان)"
10681  		},
10682  		"ar-ly":{
10683  				"name":"Arabic (Libya)",
10684  				"nativeName":"العربية (ليبيا)"
10685  		},
10686  		"ar-ma":{
10687  				"name":"Arabic (Morocco)",
10688  				"nativeName":"العربية (المملكة المغربية)"
10689  		},
10690  		"ar-om":{
10691  				"name":"Arabic (Oman)",
10692  				"nativeName":"العربية (عمان)"
10693  		},
10694  		"ar-qa":{
10695  				"name":"Arabic (Qatar)",
10696  				"nativeName":"العربية (قطر)"
10697  		},
10698  		"ar-sa":{
10699  				"name":"Arabic (Saudi Arabia)",
10700  				"nativeName":"العربية (المملكة العربية السعودية)"
10701  		},
10702  		"ar-sy":{
10703  				"name":"Arabic (Syria)",
10704  				"nativeName":"العربية (سوريا)"
10705  		},
10706  		"ar-tn":{
10707  				"name":"Arabic (Tunisia)",
10708  				"nativeName":"العربية (تونس)"
10709  		},
10710  		"ar-ae":{
10711  				"name":"Arabic (U.A.E.)",
10712  				"nativeName":"العربية (الإمارات العربية المتحدة)"
10713  		},
10714  		"ar-ye":{
10715  				"name":"Arabic (Yemen)",
10716  				"nativeName":"العربية (اليمن)"
10717  		},
10718  		"de-at":{
10719  				"name":"German (Austria)",
10720  				"nativeName":"Deutsch (Österreich)"
10721  		},
10722  		"de-li":{
10723  				"name":"German (Liechtenstein)",
10724  				"nativeName":"Deutsch (Liechtenstein)"
10725  		},
10726  		"de-lu":{
10727  				"name":"German (Luxembourg)",
10728  				"nativeName":"Deutsch (Luxemburg)"
10729  		},
10730  		"de-ch":{
10731  				"name":"German (Switzerland)",
10732  				"nativeName":"Deutsch (Schweiz)"
10733  		},
10734  		"en-au":{
10735  				"name":"English (Australia)",
10736  				"nativeName":"English (Australia)"
10737  		},
10738  		"en-bz":{
10739  				"name":"English (Belize)",
10740  				"nativeName":"English (Belize)"
10741  		},
10742  		"en-ca":{
10743  				"name":"English (Canada)",
10744  				"nativeName":"English (Canada)"
10745  		},
10746  		"en-ie":{
10747  				"name":"English (Ireland)",
10748  				"nativeName":"English (Ireland)"
10749  		},
10750  		"en-jm":{
10751  				"name":"English (Jamaica)",
10752  				"nativeName":"English (Jamaica)"
10753  		},
10754  		"en-nz":{
10755  				"name":"English (New Zealand)",
10756  				"nativeName":""
10757  		},
10758  		"en-za":{
10759  				"name":"English (South Africa)",
10760  				"nativeName":"English (South Africa)"
10761  		},
10762  		"en-tt":{
10763  				"name":"English (Trinidad)",
10764  				"nativeName":"English (Trinidad y Tobago)"
10765  		},
10766  		"en-gb":{
10767  				"name":"English (United Kingdom)",
10768  				"nativeName":"English (United Kingdom)"
10769  		},
10770  		"en-us":{
10771  				"name":"English (United States)",
10772  				"nativeName":"English (United States)"
10773  		},
10774  		"es-ar":{
10775  				"name":"Spanish (Argentina)",
10776  				"nativeName":"Español (Argentina)"
10777  		},
10778  		"es-bo":{
10779  				"name":"Spanish (Bolivia)",
10780  				"nativeName":"Español (Bolivia)"
10781  		},
10782  		"es-cl":{
10783  				"name":"Spanish (Chile)",
10784  				"nativeName":"Español (Chile)"
10785  		},
10786  		"es-co":{
10787  				"name":"Spanish (Colombia)",
10788  				"nativeName":"Español (Colombia)"
10789  		},
10790  		"es-cr":{
10791  				"name":"Spanish (Costa Rica)",
10792  				"nativeName":"Español (Costa Rica)"
10793  		},
10794  		"es-do":{
10795  				"name":"Spanish (Dominican Republic)",
10796  				"nativeName":"Español (República Dominicana)"
10797  		},
10798  		"es-ec":{
10799  				"name":"Spanish (Ecuador)",
10800  				"nativeName":"Español (Ecuador)"
10801  		},
10802  		"es-sv":{
10803  				"name":"Spanish (El Salvador)",
10804  				"nativeName":"Español (El Salvador)"
10805  		},
10806  		"es-gt":{
10807  				"name":"Spanish (Guatemala)",
10808  				"nativeName":"Español (Guatemala)"
10809  		},
10810  		"es-hn":{
10811  				"name":"Spanish (Honduras)",
10812  				"nativeName":"Español (Honduras)"
10813  		},
10814  		"es-mx":{
10815  				"name":"Spanish (Mexico)",
10816  				"nativeName":"Español (México)"
10817  		},
10818  		"es-ni":{
10819  				"name":"Spanish (Nicaragua)",
10820  				"nativeName":"Español (Nicaragua)"
10821  		},
10822  		"es-pa":{
10823  				"name":"Spanish (Panama)",
10824  				"nativeName":"Español (Panamá)"
10825  		},
10826  		"es-py":{
10827  				"name":"Spanish (Paraguay)",
10828  				"nativeName":"Español (Paraguay)"
10829  		},
10830  		"es-pe":{
10831  				"name":"Spanish (Peru)",
10832  				"nativeName":"Español (Perú)"
10833  		},
10834  		"es-pr":{
10835  				"name":"Spanish (Puerto Rico)",
10836  				"nativeName":"Español (Puerto Rico)"
10837  		},
10838  		"es-uy":{
10839  				"name":"Spanish (Uruguay)",
10840  				"nativeName":"Español (Uruguay)"
10841  		},
10842  		"es-ve":{
10843  				"name":"Spanish (Venezuela)",
10844  				"nativeName":"Español (Venezuela)"
10845  		},
10846  		"fr-be":{
10847  				"name":"French (Belgium)",
10848  				"nativeName":"français (Belgique)"
10849  		},
10850  		"fr-ca":{
10851  				"name":"French (Canada)",
10852  				"nativeName":"français (Canada)"
10853  		},
10854  		"fr-lu":{
10855  				"name":"French (Luxembourg)",
10856  				"nativeName":"français (Luxembourg)"
10857  		},
10858  		"fr-ch":{
10859  				"name":"French (Switzerland)",
10860  				"nativeName":"français (Suisse)"
10861  		},
10862  		"it-ch":{
10863  				"name":"Italian (Switzerland)",
10864  				"nativeName":"italiano (Svizzera)"
10865  		},
10866  		"nl-be":{
10867  				"name":"Dutch (Belgium)",
10868  				"nativeName":"Nederlands (België)"
10869  		},
10870  		"pt-br":{
10871  				"name":"Portuguese (Brazil)",
10872  				"nativeName":"Português (Brasil)"
10873  		},
10874  		"sv-fi":{
10875  				"name":"Swedish (Finland)",
10876  				"nativeName":"svenska (Finland)"
10877  		},
10878  		"zh-hk":{
10879  				"name":"Chinese (Hong Kong)",
10880  				"nativeName":"中文(香港特别行政區)"
10881  		},
10882  		"zh-cn":{
10883  				"name":"Chinese (PRC)",
10884  				"nativeName":"中文(中华人民共和国)"
10885  		},
10886  		"zh-sg":{
10887  				"name":"Chinese (Singapore)",
10888  				"nativeName":"中文(新加坡)"
10889  		},
10890  		"zh-tw":{
10891  				"name":"Chinese Traditional (Taiwan)",
10892  				"nativeName":"中文(台灣)"
10893  		}
10894  	};
10895
10896  	AblePlayer.prototype.getLanguageName = function (key,whichName) {
10897
10898  		// return language name associated with lang code "key"
10899  		// whichName is either "English" or "local" (i.e., native name)
10900
10901  		var lang, code, subTag;
10902  		lang = isoLangs[key.toLowerCase()];
10903  		if (lang) {
10904  			return (whichName === 'local') ? lang.nativeName : lang.name;
10905  		} else if (key.includes('-')) {
10906  			code = key.substring(0,2);
10907  			subTag = key.substring(3);
10908  			lang = isoLangs[code.toLowerCase()];
10909  			if (lang) {
10910  				return (whichName === 'local') ? lang.nativeName + ' (' + subTag + ')' : lang.name + ' (' + subTag + ')';
10911  			}
10912  		}
10913  		// if all else has failed, use the key as the label
10914  		return key;
10915  	};
10916
10917  }
10918
10919  function addMetadataFunctions(AblePlayer) {
10920    AblePlayer.prototype.updateMeta = function (time) {
10921      if (this.hasMeta) {
10922        if (this.metaType === "text") {
10923          this.$metaDiv.show();
10924          this.showMeta(time || this.elapsed);
10925        } else {
10926          this.showMeta(time || this.elapsed);
10927        }
10928      }
10929    };
10930
10931    AblePlayer.prototype.showMeta = function (now) {
10932      var tempSelectors,
10933        m,
10934        thisMeta,
10935        cues,
10936        cueText,
10937        cueLines,
10938        i,
10939        line,
10940        showDuration,
10941        focusTarget;
10942
10943      tempSelectors = [];
10944      if (this.meta.length >= 1) {
10945        cues = this.meta;
10946      } else {
10947        cues = [];
10948      }
10949      for (m = 0; m < cues.length; m++) {
10950        if (cues[m].start <= now && cues[m].end > now) {
10951          thisMeta = m;
10952          break;
10953        }
10954      }
10955      if (typeof thisMeta !== "undefined") {
10956        if (this.currentMeta !== thisMeta) {
10957          if (this.metaType === "text") {
10958            // it's time to load the new metadata cue into the container div
10959            this.$metaDiv.html(
10960              this.flattenCueForMeta(cues[thisMeta]).replace(/\n/g, "<br>")
10961            );
10962          } else if (this.metaType === "selector") {
10963            // it's time to show content referenced by the designated selector(s)
10964            cueText = this.flattenCueForMeta(cues[thisMeta]);
10965            cueLines = cueText.split("\n");
10966            for (i = 0; i < cueLines.length; i++) {
10967              line = cueLines[i].trim();
10968              if (line.toLowerCase().trim() === "pause") {
10969                // don't show big play button when pausing via metadata
10970                this.hideBigPlayButton = true;
10971                this.pauseMedia();
10972              } else if (line.toLowerCase().substring(0, 6) == "focus:") {
10973                focusTarget = line.substring(6).trim();
10974                if ($(focusTarget).length) {
10975                  $(focusTarget).trigger('focus');
10976                }
10977              } else {
10978                if ($(line).length) {
10979                  // selector exists
10980                  this.currentMeta = thisMeta;
10981                  showDuration = parseInt($(line).attr("data-duration"));
10982                  if (
10983                    typeof showDuration !== "undefined" &&
10984                    !isNaN(showDuration)
10985                  ) {
10986  					$(line).show();
10987  					const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
10988  					delay(showDuration).then(() => {
10989  						$(line).hide();
10990  					});
10991                  } else {
10992                    // no duration specified. Just show the element until end time specified in VTT file
10993                    $(line).show();
10994                  }
10995                  // add to array of visible selectors so it can be hidden at end time
10996                  this.visibleSelectors.push(line);
10997                  tempSelectors.push(line);
10998                }
10999              }
11000            }
11001            // now step through this.visibleSelectors and remove anything that's stale
11002            if (this.visibleSelectors && this.visibleSelectors.length) {
11003              if (this.visibleSelectors.length !== tempSelectors.length) {
11004                for (i = this.visibleSelectors.length - 1; i >= 0; i--) {
11005                  if ($.inArray(this.visibleSelectors[i], tempSelectors) == -1) {
11006                    $(this.visibleSelectors[i]).hide();
11007                    this.visibleSelectors.splice(i, 1);
11008                  }
11009                }
11010              }
11011            }
11012          }
11013        }
11014      } else {
11015        // there is currently no metadata. Empty stale content
11016        if (typeof this.$metaDiv !== "undefined") {
11017          this.$metaDiv.html("");
11018        }
11019        if (this.visibleSelectors && this.visibleSelectors.length) {
11020          for (i = 0; i < this.visibleSelectors.length; i++) {
11021            $(this.visibleSelectors[i]).hide();
11022          }
11023          // reset array
11024          this.visibleSelectors = [];
11025        }
11026        this.currentMeta = -1;
11027      }
11028    };
11029
11030    // Takes a cue and returns the metadata text to display for it.
11031    AblePlayer.prototype.flattenCueForMeta = function (cue) {
11032      var result = [];
11033
11034      var flattenComponent = function (component) {
11035        var result = [],
11036          ii;
11037        if (component.type === "string") {
11038          result.push(component.value);
11039        } else if (component.type === "v") {
11040          result.push("[" + component.value + "]");
11041          for (ii = 0; ii < component.children.length; ii++) {
11042            result.push(flattenComponent(component.children[ii]));
11043          }
11044        } else {
11045          for (ii = 0; ii < component.children.length; ii++) {
11046            result.push(flattenComponent(component.children[ii]));
11047          }
11048        }
11049        return result.join("");
11050      };
11051
11052      for (var ii = 0; ii < cue.components.children.length; ii++) {
11053        result.push(flattenComponent(cue.components.children[ii]));
11054      }
11055
11056      return result.join("");
11057    };
11058  }
11059
11060  function addMiscFunctions(AblePlayer) {
11061    AblePlayer.prototype.getNextHeadingLevel = function ($element) {
11062      // Finds the nearest heading in the ancestor tree
11063      // Loops over each parent of the current element until a heading is found
11064      // If multiple headings are found beneath a given parent, get the closest
11065      // Returns an integer (1-6) representing the next available heading level
11066
11067      var $parents, $foundHeadings, numHeadings, headingType, headingNumber;
11068
11069      $parents = $element.parents();
11070      $parents.each(function () {
11071        $foundHeadings = $(this).children(":header");
11072        numHeadings = $foundHeadings.length;
11073        if (numHeadings) {
11074          headingType = $foundHeadings.eq(numHeadings - 1).prop("tagName");
11075          return false;
11076        }
11077      });
11078      if (typeof headingType === "undefined") {
11079        // page has no headings
11080        headingNumber = 1;
11081      } else {
11082        // Increment closest heading by one if less than 6.
11083        headingNumber = parseInt(headingType[1]);
11084        headingNumber += 1;
11085        if (headingNumber > 6) {
11086          headingNumber = 6;
11087        }
11088      }
11089      return headingNumber;
11090    };
11091
11092    AblePlayer.prototype.countProperties = function (obj) {
11093      // returns the number of properties in an object
11094      var count, prop;
11095      count = 0;
11096      for (prop in obj) {
11097        if (Object.hasOwn(obj, prop)) {
11098          ++count;
11099        }
11100      }
11101      return count;
11102    };
11103
11104    AblePlayer.prototype.formatSecondsAsColonTime = function (
11105      seconds,
11106      showFullTime
11107    ) {
11108      // Takes seconds and converts to string of form hh:mm:ss
11109      // If showFullTime is true, shows 00 for hours if time is less than an hour
11110      //	 and show milliseconds	(e.g., 00:00:04.123 as in Video Track Sorter)
11111      // Otherwise, omits empty hours and milliseconds (e.g., 00:04 as in timer on controller)
11112
11113      var times,format,parts,milliSeconds,numShort,i;
11114
11115      if (showFullTime) {
11116        // preserve milliseconds, if included in seconds
11117        parts = seconds.toString().split(".");
11118        if (parts.length === 2) {
11119          milliSeconds = parts[1];
11120          if (milliSeconds.length < 3) {
11121            numShort = 3 - milliSeconds.length;
11122            for (i = 1; i <= numShort; i++) {
11123              milliSeconds += "0";
11124            }
11125          }
11126        } else {
11127          milliSeconds = "000";
11128        }
11129      }
11130  	times = this.secondsToTime( seconds );
11131  	format = times['value'];
11132
11133  	return (showFullTime) ? format + '.' + milliSeconds : format;
11134    };
11135
11136    AblePlayer.prototype.getSecondsFromColonTime = function (timeStr) {
11137      // Converts string of form hh:mm:ss to seconds
11138      var timeParts, hours, minutes, seconds;
11139
11140      timeParts = timeStr.split(":");
11141      if (timeParts.length === 3) {
11142        hours = parseInt(timeParts[0]);
11143        minutes = parseInt(timeParts[1]);
11144        seconds = parseFloat(timeParts[2]);
11145        return hours * 3600 + minutes * 60 + seconds;
11146      } else if (timeParts.length === 2) {
11147        minutes = parseInt(timeParts[0]);
11148        seconds = parseFloat(timeParts[1]);
11149        return minutes * 60 + seconds;
11150      } else if (timeParts.length === 1) {
11151        seconds = parseFloat(timeParts[0]);
11152        return seconds;
11153      }
11154    };
11155
11156    AblePlayer.prototype.capitalizeFirstLetter = function (string) {
11157      return string.charAt(0).toUpperCase() + string.slice(1);
11158    };
11159
11160    AblePlayer.prototype.roundDown = function (value, decimals) {
11161      // round value down to the nearest X decimal points
11162      // where X is the value of the decimals parameter
11163      return Number(Math.floor(value + "e" + decimals) + "e-" + decimals);
11164    };
11165
11166    AblePlayer.prototype.defer = function() {
11167  	const self = this;
11168  	const promise = new Promise((resolve, reject) => {
11169  		self.resolve = resolve;
11170  		self.reject = reject;
11171  		self.promise = () => promise;
11172  	});
11173    };
11174
11175    AblePlayer.prototype.getScript = function( source, callback ) {
11176  	var script   = document.createElement('script');
11177  	var prior    = document.getElementsByTagName('script')[0];
11178  	script.async = 1;
11179
11180  	script.onload = script.onreadystatechange = function( _, isAbort ) {
11181  		if ( isAbort || !script.readyState || /loaded|complete/.test(script.readyState) ) {
11182  			script.onload = script.onreadystatechange = null;
11183  			script        = undefined;
11184
11185  			if ( !isAbort && callback ) {
11186  				setTimeout(callback, 0);
11187  			}
11188  		}
11189  	};
11190
11191  	script.src = source;
11192  	prior.parentNode.insertBefore(script, prior);
11193    };
11194
11195    AblePlayer.prototype.hasAttr = function (object, attribute) {
11196      // surprisingly, there is no hasAttr() function in Jquery as of 3.2.1
11197      // return true if object has attribute; otherwise false
11198      // selector is a Jquery object
11199      // attribute is a string
11200
11201      var attr = object.attr(attribute);
11202
11203      // For some browsers, `attr` is undefined; for others,
11204      // `attr` is false.	 Check for both.
11205      if (typeof attr !== typeof undefined && attr !== false) {
11206        return true;
11207      } else {
11208        return false;
11209      }
11210    };
11211
11212  }
11213
11214  /*  global Cookies */
11215
11216   function addPreferenceFunctions(AblePlayer) {
11217  	AblePlayer.prototype.setPrefs = function(preferences) {
11218  		if ( typeof Cookies !== 'undefined' ) {
11219  			Cookies.set('Able-Player', JSON.stringify(preferences), {
11220  				expires: 90,
11221  				sameSite: 'strict'
11222  			});
11223  		} else {
11224  			localStorage.setItem( 'Able-Player', JSON.stringify( preferences ) );
11225  		}
11226  	};
11227
11228  	AblePlayer.prototype.getPref = function() {
11229
11230  		var defaultPrefs = {
11231  			preferences: {},
11232  			sign: {},
11233  			transcript: {},
11234  			voices: []
11235  		};
11236
11237  		var preferences;
11238  		try {
11239  			if ( typeof Cookies !== 'undefined' ) {
11240  				preferences = JSON.parse( Cookies.get('Able-Player') );
11241  			} else {
11242  				preferences = JSON.parse( localStorage.getItem('Able-Player') );
11243  			}
11244  		}
11245  		catch (err) {
11246  			// Original preferences can't be parsed; update to default
11247  			this.setPrefs( defaultPrefs );
11248  			preferences = defaultPrefs;
11249  		}
11250  		return (preferences) ? preferences : defaultPrefs;
11251  	};
11252
11253  	AblePlayer.prototype.updatePreferences = function( setting ) {
11254  		// useful for settings updated independently of Preferences dialog
11255  		// e.g., prefAutoScrollTranscript, which is updated in control.js > handleTranscriptLockToggle()
11256  		// setting is any supported preference name (e.g., "prefCaptions")
11257  		// OR 'transcript' or 'sign' (not user-defined preferences, used to save position of draggable windows)
11258  		var preferences, $window, windowPos, available, i, prefName, voiceLangFound, newVoice;
11259  		preferences = this.getPref();
11260  		if (setting === 'transcript' || setting === 'sign') {
11261  			if (setting === 'transcript') {
11262  				$window = this.$transcriptArea;
11263  				windowPos = $window.position();
11264  				if (typeof preferences.transcript === 'undefined') {
11265  					preferences.transcript = {};
11266  				}
11267  				preferences.transcript['position'] = $window.css('position'); // either 'relative' or 'absolute'
11268  				preferences.transcript['zindex'] = $window.css('z-index');
11269  				preferences.transcript['top'] = windowPos.top;
11270  				preferences.transcript['left'] = windowPos.left;
11271  				preferences.transcript['width'] = $window.width();
11272  				preferences.transcript['height'] = $window.height();
11273  			} else if (setting === 'sign') {
11274  				$window = this.$signWindow;
11275  				windowPos = $window.position();
11276  				if (typeof preferences.sign === 'undefined') {
11277  					preferences.sign = {};
11278  				}
11279  				preferences.sign['position'] = $window.css('position'); // either 'relative' or 'absolute'
11280  				preferences.sign['zindex'] = $window.css('z-index');
11281  				preferences.sign['top'] = windowPos.top;
11282  				preferences.sign['left'] = windowPos.left;
11283  				preferences.sign['width'] = $window.width();
11284  				preferences.sign['height'] = $window.height();
11285  			}
11286  		} else if (setting === 'voice') {
11287  			if (typeof preferences.voices === 'undefined') {
11288  				preferences.voices = [];
11289  			}
11290  			// replace preferred voice for this lang in preferences.voices array, if one exists
11291  			// otherwise, add it to the array
11292  			voiceLangFound = false;
11293  			for (var v=0; v < preferences.voices.length; v++) {
11294  				if (preferences.voices[v].lang === this.prefDescVoiceLang) {
11295  					voiceLangFound = true;
11296  					preferences.voices[v].name = this.prefDescVoice;
11297  				}
11298  			}
11299  			if (!voiceLangFound) {
11300  				// no voice has been saved yet for this language. Add it to array.
11301  				newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang};
11302  				preferences.voices.push(newVoice);
11303  			}
11304  		} else {
11305  			available = this.getAvailablePreferences();
11306  			// Rebuild preferences with current preferences values,
11307  			// replacing the one value that's been changed
11308  			for (i = 0; i < available.length; i++) {
11309  				prefName = available[i]['name'];
11310  				if (prefName == setting) {
11311  					// this is the one that requires an update
11312  					preferences.preferences[prefName] = this[prefName];
11313  				}
11314  			}
11315  		}
11316  		// Save updated preferences
11317  		this.setPrefs(preferences);
11318  	};
11319
11320  	AblePlayer.prototype.getPreferencesGroups = function() {
11321
11322  		// return array of groups in the order in which they will appear
11323  		// in the Preferences popup menu
11324  		// Human-readable label for each group is defined in translation table
11325  		if (this.usingYouTubeCaptions) {
11326  			// no transcript is possible
11327  			return ['captions','descriptions','keyboard'];
11328  		} else if (this.usingVimeoCaptions) {
11329  			// users cannot control caption appearance
11330  			// and no transcript is possible
11331  			return ['descriptions','keyboard'];
11332  		} else {
11333  			return ['captions','descriptions','keyboard','transcript'];
11334  		}
11335  	};
11336
11337  	AblePlayer.prototype.getAvailablePreferences = function() {
11338
11339  		// Return the list of currently available preferences.
11340  		// Preferences with no 'label' are set within player, not shown in Prefs dialog
11341  		var prefs = [];
11342
11343  		// Modifier keys preferences
11344  		prefs.push({
11345  			'name': 'prefAltKey', // use alt key with shortcuts
11346  			'label': this.translate( 'prefAltKey', 'Alt' ),
11347  			'group': 'keyboard',
11348  			'default': 1
11349  		});
11350  		prefs.push({
11351  			'name': 'prefCtrlKey', // use ctrl key with shortcuts
11352  			'label': this.translate( 'prefCtrlKey', 'Control' ),
11353  			'group': 'keyboard',
11354  			'default': 1
11355  		});
11356  		prefs.push({
11357  			'name': 'prefShiftKey',
11358  			'label': this.translate( 'prefShiftKey', 'Shift' ),
11359  			'group': 'keyboard',
11360  			'default': 0
11361  		});
11362  		prefs.push({
11363  			'name': 'prefNoKeyShortcuts',
11364  			'label': this.translate( 'prefNoKeyShortcuts', 'Disable Keyboard Shortcuts' ),
11365  			'group': 'keyboard',
11366  			'default': 0
11367  		});
11368
11369  		// Transcript preferences
11370  		prefs.push({
11371  			'name': 'prefTranscript', // transcript default state
11372  			'label': null,
11373  			'group': 'transcript',
11374  			'default': 0 // off because turning it on has a certain WOW factor
11375  		});
11376  		prefs.push({
11377  			'name': 'prefHighlight', // highlight transcript as media plays
11378  			'label': this.translate( 'prefHighlight', 'Highlight transcript as media plays' ),
11379  			'group': 'transcript',
11380  			'default': 1 // on because many users can benefit
11381  		});
11382  		prefs.push({
11383  			'name': 'prefAutoScrollTranscript',
11384  			'label': null,
11385  			'group': 'transcript',
11386  			'default': 1
11387  		});
11388  		prefs.push({
11389  			'name': 'prefTabbable', // tab-enable transcript
11390  			'label': this.translate( 'prefTabbable', 'Keyboard-enable transcript' ),
11391  			'group': 'transcript',
11392  			'default': 0 // off because if users don't need it, it impedes tabbing elsewhere on the page
11393  		});
11394
11395  		// Caption preferences
11396  		prefs.push({
11397  			'name': 'prefCaptions', // closed captions default state
11398  			'label': null,
11399  			'group': 'captions',
11400  			'default': this.defaultStateCaptions
11401  		});
11402
11403  		if (!this.usingYouTubeCaptions) {
11404
11405  			/* // not supported yet
11406  			prefs.push({
11407  				'name': 'prefCaptionsStyle',
11408  				'label': this.translate( 'prefCaptionsStyle', 'Style' ),
11409  				'group': 'captions',
11410  				'default': this.translate( 'captionsStylePopOn', 'Pop-on' )
11411  			});
11412  			*/
11413  			// captions are always positioned above the player for audio
11414  			if (this.mediaType === 'video') {
11415  				prefs.push({
11416  					'name': 'prefCaptionsPosition',
11417  					'label': this.translate( 'prefCaptionsPosition', 'Position' ),
11418  					'group': 'captions',
11419  					'default': this.defaultCaptionsPosition
11420  				});
11421  			}
11422  			prefs.push({
11423  				'name': 'prefCaptionsFont',
11424  				'label': this.translate( 'prefCaptionsFont', 'Font' ),
11425  				'group': 'captions',
11426  				'default': 'sans-serif'
11427  			});
11428  		}
11429  		// This is the one option that is supported by YouTube IFrame API
11430  		prefs.push({
11431  			'name': 'prefCaptionsSize',
11432  			'label': this.translate( 'prefCaptionsSize', 'Font size' ),
11433  			'group': 'captions',
11434  			'default': '100%'
11435  		});
11436
11437  		if (!this.usingYouTubeCaptions) {
11438
11439  			prefs.push({
11440  				'name': 'prefCaptionsColor',
11441  				'label': this.translate( 'prefCaptionsColor', 'Text Color' ),
11442  				'group': 'captions',
11443  				'default': 'white'
11444  			});
11445  			prefs.push({
11446  				'name': 'prefCaptionsBGColor',
11447  				'label': this.translate( 'prefCaptionsBGColor', 'Background' ),
11448  				'group': 'captions',
11449  				'default': 'black'
11450  			});
11451  			prefs.push({
11452  				'name': 'prefCaptionsOpacity',
11453  				'label': this.translate( 'prefCaptionsOpacity', 'Opacity' ),
11454  				'group': 'captions',
11455  				'default': '100%'
11456  			});
11457  			prefs.push({
11458  				'name': 'prefCaptionsSpeak',
11459  				'label': this.translate( 'prefVoicedCaptions', 'Spoken Captions' ),
11460  				'group': 'captions',
11461  				'default': 0
11462  			});
11463  			prefs.push({
11464  				'name': 'prefCaptionsVoice',
11465  				'label': this.translate( 'prefDescVoice', 'Voice' ),
11466  				'group': 'captions',
11467  				'default': null // will be set later, in injectPrefsForm()
11468  			});
11469  			prefs.push({
11470  				'name': 'prefCaptionsPitch',
11471  				'label': this.translate( 'prefDescPitch', 'Pitch' ),
11472  				'group': 'captions',
11473  				'default': 1 // 0 to 2
11474  			});
11475  			prefs.push({
11476  				'name': 'prefCaptionsRate',
11477  				'label': this.translate( 'prefCaptionRate', 'Spoken Caption Rate' ),
11478  				'group': 'captions',
11479  				'default': 1.2 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast)
11480  			});
11481  			prefs.push({
11482  				'name': 'prefCaptionsVolume',
11483  				'label': this.translate( 'volume', 'Volume' ),
11484  				'group': 'captions',
11485  				'default': 1 // 0 to 1
11486  			});
11487  		}
11488
11489  		if (this.mediaType === 'video') {
11490  			// Description preferences
11491  			prefs.push({
11492  				'name': 'prefDesc', // audio description default state
11493  				'label': null,
11494  				'group': 'descriptions',
11495  				'default': this.defaultStateDescriptions
11496  			});
11497  			prefs.push({
11498  				'name': 'prefDescMethod', // audio description default format (if both 'video' and 'text' are available)
11499  				'label': null,
11500  				'group': 'descriptions',
11501  				'default': 'video' // video (an alternative described version) always wins
11502  			});
11503  			prefs.push({
11504  				'name': 'prefDescVoice',
11505  				'label': this.translate( 'prefDescVoice', 'Voice' ),
11506  				'group': 'descriptions',
11507  				'default': null // will be set later, in injectPrefsForm()
11508  			});
11509  			prefs.push({
11510  				'name': 'prefDescPitch',
11511  				'label': this.translate( 'prefDescPitch', 'Pitch' ),
11512  				'group': 'descriptions',
11513  				'default': 1 // 0 to 2
11514  			});
11515  			prefs.push({
11516  				'name': 'prefDescRate',
11517  				'label': this.translate( 'prefDescRate', 'Spoken Description Rate' ),
11518  				'group': 'descriptions',
11519  				'default': 1 // 0.1 to 10 (1 is normal speech; 2 is fast but decipherable; >2 is super fast)
11520  			});
11521  			prefs.push({
11522  				'name': 'prefDescVolume',
11523  				'label': this.translate( 'volume', 'Volume' ),
11524  				'group': 'descriptions',
11525  				'default': 1 // 0 to 1
11526  			});
11527  			// Don't enable pause option if video described files in use.
11528  			if ( this.descMethod !== 'video' ) {
11529  				prefs.push({
11530  					'name': 'prefDescPause', // automatically pause when closed description starts
11531  					'label': this.translate( 'prefDescPause', 'Automatically pause video when description starts' ),
11532  					'group': 'descriptions',
11533  					'default': this.defaultDescPause
11534  				});
11535  			}
11536  			prefs.push({
11537  				'name': 'prefDescVisible', // visibly show closed description (if avilable and used)
11538  				'label': this.translate( 'prefDescVisible', 'Make description visible' ),
11539  				'group': 'descriptions',
11540  				'default': 0 // off as of 4.3.16, to avoid overloading the player with visible features
11541  			});
11542  		}
11543  		// Preferences without a category (not shown in Preferences dialogs)
11544  		prefs.push({
11545  			'name': 'prefSign', // open sign language window by default if avilable
11546  			'label': null,
11547  			'group': null,
11548  			'default': 0 // off because clicking an icon to see the sign window has a powerful impact
11549  		});
11550
11551  		return prefs;
11552  	};
11553
11554  	AblePlayer.prototype.loadCurrentPreferences = function () {
11555
11556  		// Load current/default preferences into the AblePlayer object.
11557
11558  		var available = this.getAvailablePreferences();
11559  		var preferences = this.getPref();
11560  		// Copy current preferences values into this object, and fill in any default values.
11561  		for (var ii = 0; ii < available.length; ii++) {
11562  			var prefName = available[ii]['name'];
11563  			var defaultValue = available[ii]['default'];
11564  			if (preferences.preferences[prefName] !== undefined) {
11565  				this[prefName] = preferences.preferences[prefName];
11566  			} else {
11567  				preferences.preferences[prefName] = defaultValue;
11568  				this[prefName] = defaultValue;
11569  			}
11570  		}
11571
11572  		// Also load array of preferred voices from preferences
11573  		if (typeof preferences.voices !== 'undefined') {
11574  			this.prefVoices = preferences.voices;
11575  		}
11576
11577  		this.setPrefs(preferences);
11578  	};
11579
11580  	AblePlayer.prototype.injectPrefsForm = function (form) {
11581
11582  		// Creates a preferences form and injects it.
11583  		// form is one of the supported forms (groups) defined in getPreferencesGroups()
11584
11585  		var thisObj, available,
11586  			$prefsDiv, formTitle, introText, $prefsIntro,$prefsIntroP2,p3Text,$prefsIntroP3,i, j,
11587  			$fieldset, fieldsetClass, fieldsetId, $legend, legendId, thisPref, $thisDiv, thisClass,
11588  			thisId, $thisLabel, $thisField, captionsOptions,options,$thisOption,optionValue,optionLang,optionText,
11589  			changedPref,changedSpan,changedText, currentDescState, prefDescVoice, prefCaptionVoice, $kbHeading,$kbList,
11590  			kbLabels,keys,kbListText,$kbListItem, dialog,$saveButton,$cancelButton,$buttonContainer;
11591
11592  		thisObj = this;
11593  		available = this.getAvailablePreferences();
11594
11595  		// outer container, will be assigned role="dialog"
11596  		$prefsDiv = $('<div>',{
11597  			'class': 'able-prefs-form '
11598  		});
11599  		var customClass = 'able-prefs-form-' + form;
11600  		$prefsDiv.addClass(customClass);
11601
11602  		// add titles and intros
11603  		if (form == 'captions') {
11604  			formTitle = this.translate( 'prefTitleCaptions', 'Captions Preferences' );
11605  		} else if (form == 'descriptions') {
11606  			formTitle = this.translate( 'prefTitleDescriptions', 'Audio Description Preferences' );
11607  			$prefsIntro = $('<p>',{
11608  				text: this.translate( 'prefIntroDescription1', 'This media player supports audio description in two ways: ' )
11609  			});
11610  			var $prefsIntroUL = $('<ul>');
11611  			var $prefsIntroLI1 = $('<li>',{
11612  				text: this.translate( 'prefDescFormatOption1', 'alternative described version of video' )
11613  			});
11614  			var $prefsIntroLI2 = $('<li>',{
11615  				text: this.translate( 'prefDescFormatOption2', 'text-based description, announced by screen reader' )
11616  			});
11617
11618  			$prefsIntroUL.append($prefsIntroLI1,$prefsIntroLI2);
11619  			let prefDescription1 = '';
11620  			let prefDescription2 = '';
11621  			let prefDescription3 = '';
11622  			let prefDescriptionNone = '';
11623  			if (this.hasOpenDesc && this.hasClosedDesc) {
11624  				prefDescription1 = this.translate( 'prefDescription1', 'The current video has an alternative described version and text-based description, announced by screen reader');
11625  			} else if (this.hasOpenDesc) {
11626  				prefDescription2 = this.translate( 'prefDescription2', 'The current video has an alternative described version.' );
11627  			} else if (this.hasClosedDesc) {
11628  				prefDescription3 = this.translate( 'prefDescription3', 'The current video has text-based description, announced by screen reader.');
11629  			} else {
11630  				prefDescriptionNone = this.translate( 'prefDescriptionNone', 'The current video has no audio description in either format.' );
11631  			}
11632  			currentDescState = prefDescription1 + prefDescription2 + prefDescription3 + prefDescriptionNone;
11633  			$prefsIntroP2 = $('<p>',{
11634  				html: currentDescState
11635  			});
11636
11637  			p3Text = this.translate( 'prefIntroDescription3', 'Use the following form to set your preferences related to text-based audio description.' );
11638  			if (this.hasOpenDesc || this.hasClosedDesc) {
11639  				p3Text += ' ' + this.translate( 'prefIntroDescription4', 'After you save your settings, audio description can be toggled on/off using the Description button.' );
11640  			}
11641  			$prefsIntroP3 = $('<p>',{
11642  				text: p3Text
11643  			});
11644
11645  			$prefsDiv.append( $prefsIntro, $prefsIntroUL, $prefsIntroP2, $prefsIntroP3 );
11646  		} else if (form == 'keyboard') {
11647  			formTitle = this.translate( 'prefTitleKeyboard', 'Keyboard Preferences' );
11648  			introText = this.translate( 'prefIntroKeyboard1', 'The media player on this web page can be operated from anywhere on the page using keyboard shortcuts (see below for a list).' );
11649  			introText += ' ' + this.translate( 'prefIntroKeyboard2', 'Modifier keys (Shift, Alt, and Control) can be assigned below.' );
11650  			introText += ' ' + this.translate( 'prefIntroKeyboard3', 'NOTE: Some key combinations might conflict with keys used by your browser and/or other software applications. Try various combinations of modifier keys to find one that works for you.' );
11651  			$prefsIntro = $('<p>',{
11652  				text: introText
11653  			});
11654  			$prefsDiv.append($prefsIntro);
11655  		} else if (form == 'transcript') {
11656  			formTitle = this.translate( 'prefTitleTranscript', 'Transcript Preferences' );
11657  		}
11658
11659  		$fieldset = $('<div>').attr('role','group');
11660  		fieldsetClass = 'able-prefs-' + form;
11661  		fieldsetId = this.mediaId + '-prefs-' + form;
11662  		legendId = fieldsetId + '-legend';
11663  		$fieldset.addClass(fieldsetClass).attr('id',fieldsetId);
11664  		if (form === 'keyboard') {
11665  			$legend = $('<h2>' + this.translate( 'prefHeadingKeyboard1', 'Modifier keys used for shortcuts' ) + '</h2>');
11666  			$legend.attr('id',legendId);
11667  			$fieldset.attr('aria-labelledby',legendId);
11668  			$fieldset.append($legend);
11669  		} else if (form === 'descriptions') {
11670  			$legend = $('<h2>' + this.translate( 'prefHeadingTextDescription', 'Text-based audio description' ) + '</h2>');
11671  			$legend.attr('id',legendId);
11672  			$fieldset.attr('aria-labelledby',legendId);
11673  			$fieldset.append($legend);
11674  		}
11675  		for (i=0; i<available.length; i++) {
11676
11677  			// only include prefs on the current form if they have a label
11678  			if ((available[i]['group'] == form) && available[i]['label']) {
11679
11680  				thisPref = available[i]['name'];
11681  				thisClass = 'able-' + thisPref;
11682  				thisId = this.mediaId + '_' + thisPref;
11683  				$thisDiv = $('<div>').addClass(thisClass + ' able-player-setting');
11684  				if (form === 'captions' ) {
11685  					$thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>');
11686  					$thisField = $('<select>',{
11687  						name: thisPref,
11688  						id: thisId,
11689  					});
11690  					// add a change handler that updates the style of the sample caption text
11691  					let viewingOptions = ['prefCaptionsPosition','prefCaptionsFont','prefCaptionsSize','prefCaptionsColor','prefCaptionsBGColor','prefCaptionsOpacity'];
11692  					if ( viewingOptions.indexOf(thisPref) !== -1 ) {
11693  						$thisField.on( 'change', function() {
11694  							changedPref = $(this).attr('name');
11695  							thisObj.stylizeCaptions(thisObj.$sampleCapsDiv,changedPref);
11696  						});
11697  					}
11698  					captionsOptions = this.getCaptionsOptions(thisPref);
11699  					if ( ! ( thisPref === 'prefCaptionsVoice' && ! this.descVoices.length ) ) {
11700  						$thisDiv.append($thisLabel,$thisField);
11701  					}
11702  					for (j=0; j < captionsOptions.length; j++) {
11703  						if (thisPref === 'prefCaptionsPosition') {
11704  							optionValue = captionsOptions[j];
11705  							if (optionValue === 'overlay') {
11706  								optionText = this.translate( 'captionsPositionOverlay', 'Overlay' );
11707  							} else if (optionValue === 'below') {
11708  								optionValue = captionsOptions[j];
11709  								optionText = this.translate( 'captionsPositionBelow', 'Below video' );
11710  							}
11711  						} else if (thisPref === 'prefCaptionsFont' || thisPref === 'prefCaptionsColor' || thisPref === 'prefCaptionsBGColor' || thisPref === 'prefCaptionsSpeak' ) {
11712  							optionValue = captionsOptions[j][0];
11713  							optionText = captionsOptions[j][1];
11714  						} else if (thisPref === 'prefCaptionsOpacity') {
11715  							optionValue = captionsOptions[j];
11716  							optionText = captionsOptions[j];
11717  							optionText += (optionValue === '0%') ? ' (' + this.translate( 'transparent', 'transparent' ) + ')' : ' (' + this.translate( 'solid', 'solid' ) + ')';
11718  						} else if (thisPref === 'prefCaptionsSize') {
11719  							optionValue = captionsOptions[j];
11720  							optionText = captionsOptions[j];
11721  						}
11722  						let voicingOptions = ['prefCaptionsPitch','prefCaptionsRate','prefCaptionsVolume'];
11723  						if ( optionValue && voicingOptions.indexOf(thisPref) === -1 ) {
11724  							$thisOption = $('<option>',{
11725  								value: optionValue,
11726  								text: optionText
11727  							});
11728  							if (this[thisPref] === optionValue) {
11729  								$thisOption.prop('selected',true);
11730  							}
11731  							$thisField.append($thisOption);
11732  						}
11733  						// If synth is possible, show voicing options.
11734  						if ( this.synth ) {
11735  							if ( thisPref === 'prefCaptionsVoice' && this.descVoices.length ) {
11736  								prefCaptionVoice = this.getPrefVoice();
11737  								for (j=0; j < this.descVoices.length; j++) {
11738  									optionValue = this.descVoices[j].name;
11739  									optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase();
11740  									optionText = optionValue + ' (' + this.descVoices[j].lang + ')';
11741  									$thisOption = $('<option>',{
11742  										'value': optionValue,
11743  										'data-lang': optionLang,
11744  										text: optionText
11745  									});
11746  									if (prefCaptionVoice === optionValue) {
11747  										$thisOption.prop('selected',true);
11748  									}
11749  									$thisField.append($thisOption);
11750  								}
11751  								this.$voiceSelectField = $thisField;
11752  							} else {
11753  								if ( thisPref == 'prefCaptionsPitch' || thisPref == 'prefCaptionsRate' || thisPref == 'prefCaptionsVolume' ) {
11754  									options = false;
11755  									// Options values described in audio description preferences.
11756  									options = (thisPref == 'prefCaptionsPitch') ? [0,0.5,1,1.5,2] : options;
11757  									options = (thisPref == 'prefCaptionsRate') ? [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3] : options;
11758  									options = (thisPref == 'prefCaptionsVolume') ? [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] : options;
11759  									if ( options ) {
11760  										for (j=0; j < options.length; j++) {
11761  											optionValue = options[j];
11762  											optionText = this.makePrefsValueReadable(thisPref,optionValue);
11763  											$thisOption = $('<option>',{
11764  												value: optionValue,
11765  												text: optionText
11766  											});
11767  											if (this[thisPref] == optionValue) {
11768  												$thisOption.prop('selected',true);
11769  											}
11770  											$thisField.append($thisOption);
11771  										}
11772  										// add a change handler that announces the sample text
11773  										$thisField.on('change',function() {
11774  											let captionSample = thisObj.translate( 'sampleCaptionText', 'Sample caption text' );
11775  											thisObj.announceText('captionSample',captionSample);
11776  										});
11777  										$thisDiv.append($thisLabel,$thisField);
11778  									}
11779  								}
11780  							}
11781  						}
11782  					}
11783  				} else if (form === 'descriptions') {
11784  					$thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>');
11785  					if (thisPref === 'prefDescPause' || thisPref === 'prefDescVisible') {
11786  						// these preferences are checkboxes
11787  						$thisDiv.addClass('able-prefs-checkbox');
11788  						$thisField = $('<input>',{
11789  							type: 'checkbox',
11790  							name: thisPref,
11791  							id: thisId,
11792  							value: 'true'
11793  						});
11794  						// check current active value for this preference
11795  						if (this[thisPref] === 1) {
11796  							$thisField.prop('checked',true);
11797  						}
11798  						$thisDiv.append($thisField,$thisLabel);
11799  					} else if (this.synth) {
11800  						// Only show these options if browser supports speech synthesis
11801  						$thisDiv.addClass('able-prefs-select');
11802  						$thisField = $('<select>',{
11803  							name: thisPref,
11804  							id: thisId,
11805  						});
11806  						if ( thisPref === 'prefDescVoice' && this.descVoices.length) {
11807  							prefDescVoice = this.getPrefVoice();
11808  							for (j=0; j < this.descVoices.length; j++) {
11809  								optionValue = this.descVoices[j].name;
11810  								optionLang = this.descVoices[j].lang.substring(0,2).toLowerCase();
11811  								optionText = optionValue + ' (' + this.descVoices[j].lang + ')';
11812  								$thisOption = $('<option>',{
11813  									'value': optionValue,
11814  									'data-lang': optionLang,
11815  									text: optionText
11816  								});
11817  								if (prefDescVoice === optionValue) {
11818  									$thisOption.prop('selected',true);
11819  								}
11820  								$thisField.append($thisOption);
11821  							}
11822  							this.$voiceSelectField = $thisField;
11823  						} else {
11824  							if (thisPref == 'prefDescPitch') { // 0 to 2
11825  								options = [0,0.5,1,1.5,2];
11826  							} else if (thisPref == 'prefDescRate') { // 0.1 to 10
11827  								// Tests with a variety of voices on MacOS and Windows
11828  								// yielded the following choices that seem reasonable for audio description:
11829  								// 0.5 - too slow (exclude this)
11830  								// 0.7 - casual
11831  								// 0.8 - add this
11832  								// 0.9 - add this
11833  								// 1 - normal
11834  								// 1.1 - add this
11835  								// 1.2 - add this
11836  								// 1.5 - quick
11837  								// 2 - speedy
11838  								// 2.5 - fleet
11839  								// 3 - fast! (some voices don't get any faster than this
11840
11841  								// Note: if these values are modified, must also modfiy them
11842  								// in makePrefsValueReadable()
11843  								options = [0.7,0.8,0.9,1,1.1,1.2,1.5,2,2.5,3];
11844  							} else if (thisPref == 'prefDescVolume') { // 0 (mute) to 1
11845  								options = [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1];
11846  							}
11847  							if (typeof options !== 'undefined') {
11848  								for (j=0; j < options.length; j++) {
11849  									optionValue = options[j];
11850  									optionText = this.makePrefsValueReadable(thisPref,optionValue);
11851  									$thisOption = $('<option>',{
11852  										value: optionValue,
11853  										text: optionText
11854  									});
11855  									if (this[thisPref] == optionValue) {
11856  										$thisOption.prop('selected',true);
11857  									}
11858  									$thisField.append($thisOption);
11859  									$thisDiv.append($thisLabel,$thisField);
11860  								}
11861  							}
11862  						}
11863  						// add a change handler that announces the sample description text
11864  						$thisField.on('change',function() {
11865  							thisObj.announceText('sample',thisObj.currentSampleText);
11866  						});
11867  						$thisDiv.append($thisLabel,$thisField);
11868  					}
11869  				} else { // all other fields are checkboxes
11870  					$thisLabel = $('<label for="' + thisId + '"> ' + available[i]['label'] + '</label>');
11871  					$thisField = $('<input>',{
11872  						type: 'checkbox',
11873  						name: thisPref,
11874  						id: thisId,
11875  						value: 'true'
11876  					});
11877  					// check current active value for this preference
11878  					if (this[thisPref] === 1) {
11879  						$thisField.prop('checked',true);
11880  					}
11881  					if (form === 'keyboard') {
11882  						// add a change handler that updates the list of current keyboard shortcuts
11883  						$thisField.on('change',function() {
11884  							changedPref = $(this).attr('name');
11885  							if (changedPref === 'prefAltKey') {
11886  								changedSpan = '.able-modkey-alt';
11887  								changedText = thisObj.translate( 'prefAltKey', 'Alt' ) + ' + ';
11888  							} else if (changedPref === 'prefCtrlKey') {
11889  								changedSpan = '.able-modkey-ctrl';
11890  								changedText = thisObj.translate( 'prefCtrlKey', 'Control' ) + ' + ';
11891  							} else if (changedPref === 'prefShiftKey') {
11892  								changedSpan = '.able-modkey-shift';
11893  								changedText = thisObj.translate( 'prefShiftKey', 'Shift' ) + ' + ';
11894  							}
11895  							if ( changedPref !== 'prefNoKeyShortcuts' ) {
11896  								if ($(this).is(':checked')) {
11897  									$(changedSpan).text(changedText);
11898  								} else {
11899  									$(changedSpan).text('');
11900  								}
11901  							} else {
11902  								if ($(this).is(':checked')) {
11903  									$('.able-modkey-item').addClass('hidden');
11904  								} else {
11905  									$('.able-modkey-item').removeClass('hidden');
11906  								}
11907  							}
11908  						});
11909  					}
11910  					$thisDiv.append($thisField,$thisLabel);
11911  				}
11912  				if (thisPref === 'prefDescVoice' && !this.descVoices.length) ; else {
11913  					$fieldset.append($thisDiv);
11914  				}
11915  			}
11916  		}
11917  		$prefsDiv.append($fieldset);
11918
11919  		if (form === 'captions') {
11920  			// add a sample closed caption div to prefs dialog
11921  			// do not show this for YouTube captions, since it's not an accurate reflection
11922  			if (!this.usingYouTubeCaptions) {
11923  				this.$sampleCapsDiv = $('<div>',{
11924  					'class': 'able-captions-sample'
11925  				}).text( this.translate( 'sampleCaptionText', 'Sample caption text' ) );
11926  				$prefsDiv.append(this.$sampleCapsDiv);
11927  				this.stylizeCaptions(this.$sampleCapsDiv);
11928  			}
11929  		} else if (form === 'descriptions') {
11930  			if (this.synth) {
11931  				// add a div with sample audio description text
11932  				this.$sampleDescDiv = $('<div>',{
11933  					'class': 'able-desc-sample'
11934  				}).text( this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) );
11935  				$prefsDiv.append(this.$sampleDescDiv);
11936  				this.currentSampleText = this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' );
11937  			}
11938  		} else if (form === 'keyboard') {
11939  			let shortcutClass = (this.prefNoKeyShortcuts === 1 ) ? 'able-modkey-item hidden' : 'able-modkey-item';
11940
11941  			// add a current list of keyboard shortcuts
11942  			$kbHeading = $('<h2>',{
11943  				text: this.translate( 'prefHeadingKeyboard2', 'Current keyboard shortcuts' )
11944  			});
11945  			$kbList = $('<ul>');
11946  			// create arrays of kbLabels and keys
11947  			kbLabels = [];
11948  			keys = [];
11949  			for (i=0; i<this.controls.length; i++) {
11950  				if (this.controls[i] === 'play') {
11951  					kbLabels.push( this.translate( 'play', 'Play' ) + '/' + this.translate( 'pause', 'Pause' ) );
11952  					keys.push('p</span>, <span class="able-help-modifiers"> ' + this.translate( 'spacebar', 'spacebar' ));
11953  				} else if (this.controls[i] === 'restart') {
11954  					kbLabels.push(this.translate( 'restart', 'Restart' ));
11955  					keys.push('s');
11956  				} else if (this.controls[i] === 'previous') {
11957  					kbLabels.push( this.translate( 'prevTrack', 'Previous track' ) );
11958  					keys.push('b'); // b = back
11959  				} else if (this.controls[i] === 'next') {
11960  					kbLabels.push( this.translate( 'nextTrack', 'Next track' ) );
11961  					keys.push('n');
11962  				} else if (this.controls[i] === 'rewind') {
11963  					kbLabels.push(this.translate( 'rewind', 'Rewind' ));
11964  					keys.push('r');
11965  				} else if (this.controls[i] === 'forward') {
11966  					kbLabels.push(this.translate( 'forward', 'Forward' ));
11967  					keys.push('f');
11968  				} else if (this.controls[i] === 'volume') {
11969  					kbLabels.push(this.translate( 'volume', 'Volume' ));
11970  					keys.push('v</span>,' + ' <span class="able-modkey">1-9');
11971  					// mute toggle
11972  					kbLabels.push(this.translate( 'mute', 'Mute' ) + '/' + this.translate( 'unmute', 'Unmute' ));
11973  					keys.push('m');
11974  				} else if (this.controls[i] === 'captions') {
11975  					if (this.captions.length > 1) {
11976  						// caption button launches a Captions popup menu
11977  						kbLabels.push(this.translate( 'captions', 'Captions' ));
11978  					} else {
11979  						// there is only one caption track
11980  						// therefore caption button is a toggle
11981  						if (this.captionsOn) {
11982  							kbLabels.push(this.translate( 'hideCaptions', 'Hide captions' ));
11983  						} else {
11984  							kbLabels.push(this.translate( 'showCaptions', 'Show captions' ));
11985  						}
11986  					}
11987  					keys.push('c');
11988  				} else if (this.controls[i] === 'descriptions') {
11989  					if (this.descOn) {
11990  						kbLabels.push(this.translate( 'turnOffDescriptions', 'Turn off descriptions' ));
11991  					} else {
11992  						kbLabels.push(this.translate( 'turnOnDescriptions', 'Turn on descriptions' ));
11993  					}
11994  					keys.push('d');
11995  				} else if (this.controls[i] === 'prefs') {
11996  					kbLabels.push(this.translate( 'preferences', 'Preferences' ));
11997  					keys.push('e');
11998  				}
11999  			}
12000  			for (i=0; i<keys.length; i++) {
12001  				// alt
12002  				kbListText = '<span class="able-modkey-alt">';
12003  				if (this.prefAltKey === 1) {
12004  					kbListText += this.translate( 'prefAltKey', 'Alt' ) + ' + ';
12005  				}
12006  				kbListText += '</span>';
12007  				// ctrl
12008  				kbListText += '<span class="able-modkey-ctrl">';
12009  				if (this.prefCtrlKey === 1) {
12010  					kbListText += this.translate( 'prefCtrlKey', 'Control' ) + ' + ';
12011  				}
12012  				kbListText += '</span>';
12013  				// shift
12014  				kbListText += '<span class="able-modkey-shift">';
12015  				if (this.prefShiftKey === 1) {
12016  					kbListText += this.translate( 'prefShiftKey', 'Shift' ) + ' + ';
12017  				}
12018  				kbListText += '</span>';
12019  				kbListText += '<span class="able-modkey">' + keys[i] + '</span>';
12020  				kbListText += ' = ' + kbLabels[i];
12021  				$kbListItem = $('<li>',{
12022  					'class': shortcutClass,
12023  					html: kbListText,
12024  				});
12025  				$kbList.append($kbListItem);
12026  			}
12027  			// add Escape key
12028  			kbListText = '<span class="able-modkey">' + this.translate( 'escapeKey', 'Escape' ) + '</span>';
12029  			kbListText += ' = ' + this.translate( 'escapeKeyFunction', 'Close current dialog or popup menu' );
12030  			$kbListItem = $('<li>',{
12031  				html: kbListText
12032  			});
12033  			$kbList.append($kbListItem);
12034  			// put it all together
12035  			$prefsDiv.append($kbHeading,$kbList);
12036  		}
12037
12038  		// $prefsDiv (dialog) must be appended to the BODY!
12039  		$('body').append($prefsDiv);
12040  		dialog = new AccessibleDialog(
12041  			$prefsDiv,
12042  			this.$prefsButton,
12043  			formTitle,
12044  			thisObj.translate( 'closeButtonLabel', 'Close' )
12045  		);
12046
12047  		// Add save and cancel buttons.
12048  		$buttonContainer = $( '<div class="able-prefs-buttons"></div>' );
12049  		$saveButton = $('<button class="modal-button">' + this.translate( 'save', 'Save' ) + '</button>');
12050  		$cancelButton = $('<button class="modal-button">' + this.translate( 'cancel', 'Cancel' ) + '</button>');
12051  		$saveButton.on( 'click', function () {
12052  			dialog.hide();
12053  			thisObj.savePrefsFromForm();
12054  		});
12055  		$cancelButton.on( 'click', function () {
12056  			dialog.hide();
12057  			thisObj.resetPrefsForm();
12058  		});
12059  		$buttonContainer.append( $saveButton,$cancelButton );
12060  		$prefsDiv.append($buttonContainer);
12061  		// Associate the dialog's H1 as aria-labelledby for groups of fields
12062  		// (alternative to fieldset and legend)
12063  		if (form === 'captions' || form === 'transcript') {
12064  			$fieldset.attr('aria-labelledby',dialog.titleH1.attr('id'));
12065  		}
12066
12067  		// add global reference for future control
12068  		if (form === 'captions') {
12069  			this.captionPrefsDialog = dialog;
12070  		} else if (form === 'descriptions') {
12071  			this.descPrefsDialog = dialog;
12072  		} else if (form === 'keyboard') {
12073  			this.keyboardPrefsDialog = dialog;
12074  		} else if (form === 'transcript') {
12075  			this.transcriptPrefsDialog = dialog;
12076  		}
12077
12078  		// Add click handler for dialog close button
12079  		// (button is added in dialog.js)
12080  		$('div.able-prefs-form button.modalCloseButton').on( 'click', function() {
12081  			thisObj.resetPrefsForm();
12082  		});
12083  		// Add handler for escape key
12084  		$('div.able-prefs-form').on( 'keydown', function(e) {
12085  			if (e.key === 'Escape') {
12086  				thisObj.resetPrefsForm();
12087  			}
12088  		});
12089  	};
12090
12091  	AblePlayer.prototype.getPrefVoice = function () {
12092
12093  		// return user's preferred voice for the current language from preferences.voices
12094  		var lang, preferences, i;
12095
12096  		if (this.selectedDescriptions) {
12097  			lang = this.selectedDescriptions.language;
12098  		} else if (this.captionLang) {
12099  			lang = this.captionLang;
12100  		} else {
12101  			lang = this.lang;
12102  		}
12103  		preferences = this.getPref();
12104  		if (preferences.voices) {
12105  			for (i=0; i < preferences.voices.length; i++) {
12106  				if (preferences.voices[i].lang === lang) {
12107  					return preferences.voices[i].name;
12108  				}
12109  			}
12110  		}
12111  		return null; // user has no saved preference
12112  	};
12113
12114  	AblePlayer.prototype.rebuildVoicePrefsForm = function ( field ) {
12115
12116  		// Called if this.descVoices changes, which may happen if:
12117  		//  getBrowserVoices() succeeds after an earlier failure
12118  		//  user changes language of captions/subtitles and descVoices changes to match the new language
12119
12120  		var i, optionValue, optionText, $thisOption;
12121
12122  		this.$voiceSelectField = $('#' + this.mediaId + field);
12123  		this.$voiceSelectField.empty();
12124  		for (i=0; i < this.descVoices.length; i++) {
12125  			optionValue = this.descVoices[i].name;
12126  			optionText = optionValue + ' (' + this.descVoices[i].lang + ')';
12127  			$thisOption = $('<option>',{
12128  				'value': optionValue,
12129  				'data-lang': this.descVoices[i].lang.substring(0,2).toLowerCase(),
12130  				text: optionText
12131  			});
12132  			if (this.prefDescVoice == optionValue) {
12133  				$thisOption.prop('selected',true);
12134  			}
12135  			this.$voiceSelectField.append($thisOption);
12136  		}
12137  	};
12138
12139  	AblePlayer.prototype.makePrefsValueReadable = function(pref,value) {
12140
12141  		// The values for pitch, rate, and volume (web speech API)
12142  		// are strange and inconsistent between variables
12143  		// this function returns text that is more readable than the values themselves
12144
12145  		if (pref === 'prefDescPitch' || pref === 'prefCaptionsPitch' ) {
12146  			if (value === 0) {
12147  				return this.translate( 'prefDescPitch1', 'Very low' );
12148  			} else if (value === 0.5) {
12149  				return this.translate( 'prefDescPitch2', 'Low' );
12150  			} else if (value === 1) {
12151  				return this.translate( 'prefDescPitch3', 'Default' );
12152  			} else if (value === 1.5) {
12153  				return this.translate( 'prefDescPitch4', 'High' );
12154  			} else if (value === 2) {
12155  				return this.translate( 'prefDescPitch5', 'Very high' );
12156  			}
12157  		} else if (pref === 'prefDescRate' || pref === 'prefCaptionsRate' ) {
12158  			// default in the API is 0.1 to 10, where 1 is normal speaking voice
12159  			// our custom range offers several rates close to 1
12160  			// plus a couple of crazy fast ones for sport
12161  			// Our more readable options (1-10) or mapped here to API values
12162  			if (value === 0.7) {
12163  				return 1;
12164  			} else if (value === 0.8) {
12165  				return 2;
12166  			} else if (value === 0.9) {
12167  				return 3;
12168  			} else if (value === 1) {
12169  				return 4;
12170  			} else if (value === 1.1) {
12171  				return 5;
12172  			} else if (value === 1.2) {
12173  				return 6;
12174  			} else if (value === 1.5) {
12175  				return 7;
12176  			} else if (value === 2) {
12177  				return 8;
12178  			} else if (value === 2.5) {
12179  				return 9;
12180  			} else if (value === 3) {
12181  				return 10;
12182  			}
12183  		} else if (pref === 'prefDescVolume' || pref === 'prefCaptionsVolume' ) {
12184  			// values range from 0.1 to 1.0
12185  			return value * 100 + '%';
12186  		}
12187  		return value;
12188  	};
12189
12190  	AblePlayer.prototype.resetPrefsForm = function () {
12191
12192  		// Reset preferences form with default values from preferences
12193  		// Called when:
12194  		// User clicks cancel or close button in Prefs Dialog
12195  		// User presses Escape to close Prefs dialog
12196  		// User clicks Save in Prefs dialog, & there's more than one player on page
12197
12198  		var preferences, available, i, prefName;
12199
12200  		preferences = this.getPref();
12201  		available = this.getAvailablePreferences();
12202  		for (i=0; i<available.length; i++) {
12203  			prefName = available[i]['name'];
12204  			if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) {
12205  				// this is a caption-related select box
12206  				$('select[name="' + prefName + '"]').val(preferences.preferences[prefName]);
12207  			} else { // all others are checkboxes
12208  				if (this[prefName] === 1) {
12209  					$('input[name="' + prefName + '"]').prop('checked',true);
12210  				} else {
12211  					$('input[name="' + prefName + '"]').prop('checked',false);
12212  				}
12213  			}
12214  		}
12215  		// also restore style of sample caption div
12216  		this.stylizeCaptions(this.$sampleCapsDiv);
12217  	};
12218
12219  	AblePlayer.prototype.savePrefsFromForm = function () {
12220
12221  		// Return a prefs object constructed from the form.
12222  		// called when user saves the Preferences form
12223  		// update preferences with new value
12224  		var preferences, available, prefName, prefId,
12225  			voiceSelectId, newVoice, numChanges, voiceLangFound,
12226  			numCapChanges, capSizeChanged, capSizeValue, newValue;
12227
12228  		numChanges = 0;
12229  		numCapChanges = 0; // changes to caption-style-related preferences
12230  		capSizeChanged = false;
12231  		preferences = this.getPref();
12232  		available = this.getAvailablePreferences();
12233  		for (var i=0; i < available.length; i++) {
12234  			// only prefs with labels are used in the Prefs form
12235  			if (available[i]['label']) {
12236  				prefName = available[i]['name'];
12237  				prefId = this.mediaId + '_' + prefName;
12238  				if (prefName === 'prefDescVoice') {
12239  					if (typeof preferences.voices === 'undefined') {
12240  						preferences.voices = [];
12241  					}
12242  					voiceSelectId = this.mediaId + '_prefDescVoice';
12243  					this.prefDescVoice = $('select#' + voiceSelectId).find(':selected').val();
12244  					this.prefDescVoiceLang = $('select#' + voiceSelectId).find(':selected').attr('data-lang');
12245  					// replace preferred voice for this lang in preferences.voices array, if one exists
12246  					// otherwise, add it to the array
12247  					voiceLangFound = false;
12248  					for (var v=0; v < preferences.voices.length; v++) {
12249  						if (preferences.voices[v].lang === this.prefDescVoiceLang) {
12250  							voiceLangFound = true;
12251  							preferences.voices[v].name = this.prefDescVoice;
12252  						}
12253  					}
12254  					if (!voiceLangFound) {
12255  						// no voice has been saved yet for this language. Add it to array.
12256  						newVoice = {'name':this.prefDescVoice, 'lang':this.prefDescVoiceLang};
12257  						preferences.voices.push(newVoice);
12258  					}
12259  					numChanges++;
12260  				} else if (prefName == 'prefDescMethod') {
12261  					// As of v4.0.10, prefDescMethod is no longer a choice
12262  					this.prefDescMethod = 'video';
12263  					if (this.prefDescMethod !== preferences.preferences['prefDescMethod']) { // user's preference has changed
12264  						preferences.preferences['prefDescMethod'] = this.prefDescMethod;
12265  						numChanges++;
12266  					}
12267  				} else if ((prefName.indexOf('Captions') !== -1) && (prefName !== 'prefCaptions')) {
12268  					// this is one of the caption-related select fields
12269  					newValue = $('select[id="' + prefId + '"]').val();
12270  					if (preferences.preferences[prefName] !== newValue) { // user changed setting
12271  						preferences.preferences[prefName] = newValue;
12272  						// also update global var for this pref (for caption fields, not done elsewhere)
12273  						this[prefName] = newValue;
12274  						numChanges++;
12275  						numCapChanges++;
12276  					}
12277  					if (prefName === 'prefCaptionsSize') {
12278  						capSizeChanged = true;
12279  						capSizeValue = newValue;
12280  					}
12281  				} else if ((prefName.indexOf('Desc') !== -1) && (prefName !== 'prefDescPause') && prefName !== 'prefDescVisible') {
12282  					// this is one of the description-related select fields
12283  					newValue = $('select[id="' + prefId + '"]').val();
12284  					if (preferences.preferences[prefName] !== newValue) { // user changed setting
12285  						preferences.preferences[prefName] = newValue;
12286  						// also update global var for this pref
12287  						this[prefName] = newValue;
12288  						numChanges++;
12289  					}
12290  				} else { // all other fields are checkboxes
12291  					if ($('input[id="' + prefId + '"]').is(':checked')) {
12292  						preferences.preferences[prefName] = 1;
12293  						if (this[prefName] === 1) ; else {
12294  							// user has just turned this pref on
12295  							this[prefName] = 1;
12296  							numChanges++;
12297  						}
12298  					} else { // thisPref is not checked
12299  						preferences.preferences[prefName] = 0;
12300  						if (this[prefName] === 1) {
12301  							// user has just turned this pref off
12302  							this[prefName] = 0;
12303  							numChanges++;
12304  						}
12305  					}
12306  				}
12307  			}
12308  		}
12309  		if (numChanges > 0) {
12310  			this.setPrefs(preferences);
12311  			this.showAlert( this.translate( 'prefSuccess', 'Your changes have been saved.' ) );
12312  		} else {
12313  			this.showAlert( this.translate( 'prefNoChange', "You didn't make any changes" ) );
12314  		}
12315  		if (this.player === 'youtube' &&
12316  			(typeof this.usingYouTubeCaptions !== 'undefined' && this.usingYouTubeCaptions) &&
12317  			capSizeChanged) {
12318  				// update font size of YouTube captions
12319  				this.youTubePlayer.setOption('captions','fontSize',this.translatePrefs('size',capSizeValue,'youtube'));
12320  		}
12321  		if (!AblePlayer.hasSingleInstance()) {
12322  			// there are multiple players on this page.
12323  			// update prefs for ALL of them
12324  			for (const instance of AblePlayer.ablePlayerInstances) {
12325  				instance.updatePlayerPrefs();
12326  				instance.loadCurrentPreferences();
12327  				instance.resetPrefsForm();
12328  				if (numCapChanges > 0) {
12329  					instance.stylizeCaptions(instance.$captionsDiv);
12330  					// also apply same changes to descriptions, if present
12331  					if (typeof instance.$descDiv !== 'undefined') {
12332  						instance.stylizeCaptions(instance.$descDiv);
12333  					}
12334  				}
12335  			}
12336  		} else {
12337  			// there is only one player
12338  			this.updatePlayerPrefs();
12339  			if (numCapChanges > 0) {
12340  				this.stylizeCaptions(this.$captionsDiv);
12341  				// also apply same changes to descriptions, if present
12342  				if (typeof this.$descDiv !== 'undefined') {
12343  					this.stylizeCaptions(this.$descDiv);
12344  				}
12345  			}
12346  		}
12347  	};
12348
12349  	AblePlayer.prototype.updatePlayerPrefs = function () {
12350
12351  		// Update player based on current prefs. Safe to call multiple times.
12352  		if (this.$transcriptDiv) {
12353  			// tabbable transcript
12354  			if (this.prefTabbable === 1) {
12355  				this.$transcriptDiv.find('span.able-transcript-seekpoint').attr('tabindex','0');
12356  			} else {
12357  				this.$transcriptDiv.find('span.able-transcript-seekpoint').removeAttr('tabindex');
12358  			}
12359
12360  			// transcript highlights
12361  			if (this.prefHighlight === 0) {
12362  				// user doesn't want highlights; remove any existing highlights
12363  				this.$transcriptDiv.find('span').removeClass('able-highlight');
12364  			}
12365  		}
12366
12367  		// Re-initialize caption and description in case relevant settings have changed
12368  		this.updateCaption();
12369  		this.initDescription();
12370  	};
12371
12372  	AblePlayer.prototype.usingModifierKeys = function(e) {
12373
12374  		// return true if user is holding down required modifier keys
12375  		if ((this.prefAltKey === 1) && !e.altKey) {
12376  			return false;
12377  		}
12378  		if ((this.prefCtrlKey === 1) && !e.ctrlKey) {
12379  			return false;
12380  		}
12381  		if ((this.prefShiftKey === 1) && !e.shiftKey) {
12382  			return false;
12383  		}
12384  		return true;
12385  	};
12386  }
12387
12388  function addSearchFunctions(AblePlayer) {
12389    AblePlayer.prototype.showSearchResults = function () {
12390      // search VTT file for all instances of searchTerms
12391      // Currently just supports search terms separated with one or more spaces
12392
12393      // TODO: Add support for more robust search syntax:
12394      // Search terms wrapped in quotation marks ("") must occur exactly as they appear in the quotes
12395      // Search terms with an attached minus sign (e.g., -term) are to be excluded from results
12396      // Boolean AND/OR operators
12397      // ALSO: Add localization support
12398
12399      var thisObj = this;
12400      if (this.searchDiv && this.searchString) {
12401        // sanitize search string
12402        var cleanSearchString = purify.sanitize(this.searchString);
12403        if ($("#" + this.SearchDiv)) {
12404          var searchStringHtml = "<p>" + this.translate( 'resultsSummary1', 'You searched for:') + ' ';
12405          searchStringHtml +=
12406            '<span id="able-search-term-echo">' + cleanSearchString + "</span>";
12407          searchStringHtml += "</p>";
12408          var resultsArray = this.searchFor(
12409            cleanSearchString,
12410            this.searchIgnoreCaps
12411          );
12412          if (resultsArray.length > 0) {
12413            var $resultsSummary = $("<p>", {
12414              class: "able-search-results-summary",
12415            });
12416            var resultsSummaryText = this.translate( 'resultsSummary2', 'Found %1 matching items.', [ '<strong>' + resultsArray.length + '</strong>' ] );
12417            resultsSummaryText += ' ' + this.translate( 'resultsSummary3', 'Click the time associated with any item to play the video from that point.' );
12418            $resultsSummary.html( resultsSummaryText );
12419            var $resultsList = $("<ul>");
12420            for (var i = 0; i < resultsArray.length; i++) {
12421              var resultId = "aria-search-result-" + i;
12422              var $resultsItem = $("<li>", {});
12423              var itemStartTime = this.secondsToTime(resultsArray[i]["start"]);
12424              var itemLabel =
12425                this.translate( 'searchButtonLabel', 'Play at %1', [ itemStartTime["title"] ] );
12426              var itemStartSpan = $("<button>", {
12427                class: "able-search-results-time",
12428                "data-start": resultsArray[i]["start"],
12429                "aria-label": itemLabel,
12430                "aria-describedby": resultId,
12431              });
12432              itemStartSpan.text(itemStartTime["value"]);
12433              // add a listener for clisk on itemStart
12434              itemStartSpan.on("click", function (e) {
12435                thisObj.seekTrigger = "search";
12436                var spanStart = parseFloat($(this).attr("data-start"));
12437                // Add a tiny amount so that we're inside the span.
12438                spanStart += 0.01;
12439                thisObj.seeking = true;
12440                thisObj.seekTo(spanStart);
12441              });
12442
12443              var itemText = $("<span>", {
12444                class: "able-search-result-text",
12445                id: resultId,
12446              });
12447              itemText.html('...' + resultsArray[i]["caption"] + '...');
12448              $resultsItem.append(itemStartSpan, itemText);
12449              $resultsList.append($resultsItem);
12450            }
12451            $('#' + this.searchDiv)
12452              .html(searchStringHtml)
12453              .append($resultsSummary, $resultsList);
12454          } else {
12455            var noResults = $('<p>').text( this.translate( 'noResultsFound', 'No results found.' ) );
12456            $('#' + this.searchDiv)
12457              .html(searchStringHtml)
12458              .append(noResults);
12459          }
12460        }
12461      }
12462    };
12463
12464    AblePlayer.prototype.searchFor = function (searchString, ignoreCaps) {
12465      // return chronological array of caption cues that match searchTerms
12466      var captionLang, captions, results, caption, c, i, j;
12467      results = [];
12468      // split searchTerms into an array
12469      var searchTerms = searchString.split(" ");
12470      if (this.captions.length > 0) {
12471        // Get caption track that matches this.searchLang
12472        for (i = 0; i < this.captions.length; i++) {
12473          if (this.captions[i].language === this.searchLang) {
12474            captionLang = this.searchLang;
12475            captions = this.captions[i].cues;
12476          }
12477        }
12478        if (captions.length > 0) {
12479          c = 0;
12480          for (i = 0; i < captions.length; i++) {
12481            if (
12482              $.inArray(captions[i].components.children[0]["type"], [
12483                "string",
12484                "i",
12485                "b",
12486                "u",
12487                "v",
12488                "c",
12489              ]) !== -1
12490            ) {
12491              caption = this.flattenCueForCaption(captions[i]);
12492              var captionNormalized = ignoreCaps
12493                ? caption.toLowerCase()
12494                : caption;
12495              for (j = 0; j < searchTerms.length; j++) {
12496                var searchTermNormalized = ignoreCaps
12497                  ? searchTerms[j].toLowerCase()
12498                  : searchTerms[j];
12499                if (captionNormalized.indexOf(searchTermNormalized) !== -1) {
12500                  results[c] = [];
12501                  results[c]["start"] = captions[i].start;
12502                  results[c]["lang"] = captionLang;
12503                  results[c]["caption"] = this.highlightSearchTerm(
12504                    searchTerms,
12505                    caption
12506                  );
12507                  c++;
12508                  break;
12509                }
12510              }
12511            }
12512          }
12513        }
12514      }
12515      return results;
12516    };
12517
12518    AblePlayer.prototype.highlightSearchTerm = function (
12519      searchTerms,
12520      resultString
12521    ) {
12522      // highlight ALL found searchTerms in the current resultString
12523      // Need to step through the remaining terms to see if they're present as well
12524      searchTerms.forEach(function (searchTerm) {
12525        var reg = new RegExp(searchTerm, "gi");
12526        resultString = resultString.replace(
12527          reg,
12528          '<span class="able-search-term">$&</span>'
12529        );
12530      });
12531      return resultString;
12532    };
12533
12534    /**
12535     * Convert a number of seconds into readable time information.
12536     *
12537     * @param {int} totalSecondsFloat
12538     *
12539     * @returns {string[]} array 'value' HH:MM:SS and 'title' speakable time.
12540     */
12541    AblePlayer.prototype.secondsToTime = function (totalSecondsFloat) {
12542      // return an array of totalSeconds converted into two formats
12543      // time['value'] = HH:MM:SS with hours dropped if there are none
12544      // time['title'] = a speakable rendering, so speech rec users can easily speak the link
12545      var totalSeconds = Math.floor(totalSecondsFloat);
12546
12547      var h = parseInt(totalSeconds / 3600, 10) % 24;
12548      var m = parseInt(totalSeconds / 60, 10) % 60;
12549      var s = totalSeconds % 60;
12550      var value = '';
12551      var title = '';
12552      if (h > 0) {
12553        value += String(h).padStart(2, '0') + ':';
12554        title += h > 0 ? h + ' ' + (h === 1 ? this.translate( 'hour', 'hour' ) : this.translate( 'hours', 'hours' ) ) : '';
12555      }
12556
12557      value += String(m).padStart(2, '0') + ':';
12558      title += m > 0 ? m + ' ' + (m === 1 ? this.translate( 'minute', 'minute' ) : this.translate( 'minutes', 'minutes' ) ) : '';
12559
12560  	value += String(s).padStart(2, '0');
12561      title += s > 0 ? s + ' ' + (s === 1 ? this.translate( 'second', 'second' ) : this.translate( 'seconds', 'seconds' ) ) : '';
12562
12563      var time = [];
12564      time["value"] = value;
12565      time["title"] = title;
12566
12567      return time;
12568    };
12569  }
12570
12571  /* global YT */
12572
12573  function addSignFunctions(AblePlayer) {
12574  	AblePlayer.prototype.initSignLanguage = function() {
12575  		this.hasSignLanguage = false;
12576  		// Sign language is only currently supported in HTML5 player and YouTube.
12577  		var hasLocalSrc = ( this.$sources.first().attr('data-sign-src') !== undefined && this.$sources.first().attr('data-sign-src') !== "" );
12578  		// YouTube src can either be on a `source` element or on the `video` element.
12579  		var hasRemoteSrc = ( this.$media.data('youtube-sign-src') !== undefined && this.$media.data('youtube-sign-src') !== "" );
12580  		var hasRemoteSource = ( this.$sources.first().attr('data-youtube-sign-src') !== undefined && this.$sources.first().attr('data-youtube-sign-src') !== '' );
12581  		if ( ! this.isIOS() && ( hasLocalSrc || hasRemoteSrc || hasRemoteSource ) && ( this.player === 'html5' || this.player === 'youtube' ) ) {
12582  			// check to see if there's a sign language video accompanying this video
12583  			// check only the first source
12584  			// If sign language is provided, it must be provided for all sources
12585  			let ytSignSrc = this.youTubeSignId ?? purify.sanitize( this.$sources.first().attr('data-youtube-sign-src') );
12586  			let signSrc = purify.sanitize( this.$sources.first().attr('data-sign-src') );
12587  			let signVideo = purify.sanitize( this.$media.data('youtube-sign-src') );
12588  			this.signFile = (hasLocalSrc ) ? signSrc : false;
12589  			if ( hasRemoteSrc ) {
12590  				this.signYoutubeId = signVideo;
12591  			} else if ( hasRemoteSource ) {
12592  				this.signYoutubeId = ytSignSrc;
12593  			}
12594  			if ( this.signFile || this.signYoutubeId ) {
12595  				if (this.isIOS()) {
12596  					// iOS does not allow multiple videos to play simultaneously
12597  					// Therefore, sign language as rendered by Able Player unfortunately won't work
12598  					if (this.debug) {
12599  						console.log('Sign language has been disabled due to iOS restrictions');
12600  					}
12601  				} else {
12602  					if (this.debug) {
12603  						console.log('This video has an accompanying sign language video: ' + this.signFile);
12604  					}
12605  					this.hasSignLanguage = true;
12606  					this.injectSignPlayerCode();
12607  				}
12608  			}
12609  		}
12610  	};
12611
12612  	AblePlayer.prototype.injectSignPlayerCode = function() {
12613
12614  		// create and inject surrounding HTML structure
12615  		var signVideoId, i, signSrc, srcType, $signSource;
12616
12617  		signVideoId = this.mediaId + '-sign';
12618
12619  		if ( this.signFile || this.signYoutubeId ) {
12620  			if ( null !== this.$signDivLocation ) {
12621  				this.$signDivLocation.addClass( 'able-sign-window able-fixed' );
12622  				this.$signWindow = this.$signDivLocation;
12623  			} else {
12624  				this.$signWindow = $('<div>',{
12625  					'class' : 'able-sign-window',
12626  					'role': 'dialog',
12627  					'aria-label': this.translate( 'sign', 'Sign language' )
12628  				});
12629  				this.$signToolbar = $('<div>',{
12630  					'class': 'able-window-toolbar able-' + this.toolbarIconColor + '-controls'
12631  				});
12632  				this.$signWindow.append(this.$signToolbar);
12633  			}
12634
12635  			this.$ableWrapper.append(this.$signWindow);
12636  		}
12637
12638  		if ( this.signFile ) {
12639  			this.$signVideo = $('<video>',{
12640  				'id' : signVideoId,
12641  				'tabindex' : '-1',
12642  				'muted' : true,
12643  			});
12644  			this.signVideo = this.$signVideo[0];
12645
12646  			if ( this.signFile ) {
12647  				$signSource = $('<source>',{
12648  					'src' : this.signFile,
12649  					'type' : 'video/' + this.signFile.substr(-3)
12650  				});
12651  				this.$signVideo.append($signSource);
12652  			} else {
12653  				// for each original <source>, add a <source> to the sign <video>
12654  				for (i=0; i < this.$sources.length; i++) {
12655  					signSrc = purify.sanitize( this.$sources[i].getAttribute('data-sign-src') );
12656  					srcType = this.$sources[i].getAttribute('type');
12657  					if (signSrc) {
12658  						$signSource = $('<source>',{
12659  							'src' : signSrc,
12660  							'type' : srcType
12661  						});
12662  						this.$signVideo.append($signSource);
12663  					} else {
12664  						// source is missing a sign language version
12665  						// can't include sign language
12666  						this.hasSignLanguage = false;
12667  						return;
12668  					}
12669  				}
12670  			}
12671  			this.$signWindow.append( this.$signVideo );
12672  		} else if ( this.signYoutubeId ) {
12673  			this.signYoutube = this.initYouTubeSignPlayer();
12674  		}
12675
12676  		// make it draggable
12677  		if ( null === this.$signDivLocation ) {
12678  			this.initDragDrop('sign');
12679  		}
12680
12681  		if (this.prefSign === 1) {
12682  			// sign window is on. Go ahead and position it and show it
12683  			if ( null === this.$signDivLocation ) {
12684  				this.positionDraggableWindow('sign',this.getDefaultWidth('sign'));
12685  			}
12686  		} else {
12687  			this.$signWindow.hide();
12688  		}
12689  	};
12690
12691
12692  	AblePlayer.prototype.initYouTubeSignPlayer = function () {
12693
12694  		var thisObj, deferred, promise;
12695  		thisObj = this;
12696  		deferred = new this.defer();
12697  		promise = deferred.promise();
12698
12699  		this.youTubeSignPlayerReady = false;
12700
12701  		if (AblePlayer.youTubeIframeAPIReady) {
12702  			// Script already loaded and ready.
12703  			thisObj.finalizeYoutubeSignInit().then(function() {
12704  				deferred.resolve();
12705  			});
12706  		} else {
12707  			// Has another player already started loading the script? If so, abort...
12708  			if ( ! AblePlayer.loadingYouTubeIframeAPI ) {
12709  				thisObj.getScript('https://www.youtube.com/iframe_api', function () {
12710  					console.log( 'YouTube API loaded' );
12711  				});
12712  			}
12713
12714  			// Otherwise, keeping waiting for script load event...
12715  			$('body').on('youTubeIframeAPIReady', function () {
12716  				thisObj.finalizeYoutubeSignInit().then(function() {
12717  					deferred.resolve();
12718  				});
12719  			});
12720  		}
12721  		return promise;
12722  	};
12723
12724  	AblePlayer.prototype.finalizeYoutubeSignInit = function () {
12725
12726  		// This is called once we're sure the Youtube iFrame API is loaded -- see above
12727  		var deferred, promise, thisObj, containerId, autoplay;
12728
12729  		deferred = new this.defer();
12730  		promise = deferred.promise();
12731  		thisObj = this;
12732  		containerId = this.mediaId + '_youtube_sign';
12733
12734  		this.$signWindow.append($('<div>').attr('id', containerId));
12735  		autoplay = (this.okToPlay) ? 1 : 0;
12736
12737  		// Documentation https://developers.google.com/youtube/player_parameters
12738  		this.youTubeSignPlayer = new YT.Player(containerId, {
12739  			videoId: this.getYouTubeId(this.signYoutubeId),
12740  			host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com',
12741  			playerVars: {
12742  				autoplay: autoplay,
12743  				cc_lang_pref: this.captionLang, // set the caption language
12744  				cc_load_policy: 0,
12745  				controls: 0, // no controls, using our own
12746  				disableKb: 1, // disable keyboard shortcuts, using our own
12747  				enablejsapi: 1,
12748  				hl: this.lang, // set the UI language to match Able Player
12749  				iv_load_policy: 3, // do not show video annotations
12750  				origin: window.location.origin,
12751  				playsinline: this.playsInline,
12752  				rel: 0, // when video ends, show only related videos from same channel (1 shows any)
12753  				start: this.startTime
12754  			},
12755  			events: {
12756  				onReady: function (player) {
12757  					player.target.mute();
12758  					player.target.unloadModule( 'captions' );
12759  					thisObj.youTubeSignPlayerReady = true;
12760
12761  					deferred.resolve();
12762  				},
12763  				onError: function (e) {
12764  					deferred.reject();
12765  				},
12766  				onStateChange: function (e) {
12767  					thisObj.getPlayerState().then(function() {
12768  						// no actions
12769  					});
12770  				},
12771  				onApiChange: function() {
12772  					// No actions
12773  				},
12774  				onPlaybackQualityChange: function () {
12775  					// no actions
12776  				},
12777  			}
12778  		});
12779
12780  		return promise;
12781  	};
12782
12783  }
12784
12785  function addTrackFunctions(AblePlayer) {
12786    // Loads files referenced in track elements, and performs appropriate setup.
12787    // For example, captions and text descriptions.
12788    // This will be called whenever the player is recreated.
12789
12790    AblePlayer.prototype.setupTracks = function () {
12791      var thisObj, deferred, promise, loadingPromises, loadingPromise, i, tracks, track, kind;
12792
12793      thisObj = this;
12794
12795      deferred = new this.defer();
12796      promise = deferred.promise();
12797
12798      loadingPromises = [];
12799
12800      if ($("#able-vts").length) {
12801        // Page includes a container for a VTS instance
12802        this.vtsTracks = [];
12803        this.hasVts = true;
12804      } else {
12805        this.hasVts = false;
12806      }
12807
12808      // Source array for populating the above arrays
12809      // varies, depending on whether there are dedicated description tracks
12810      if (this.hasDescTracks && this.descOn) {
12811        tracks = this.altTracks;
12812      } else {
12813        tracks = this.tracks;
12814      }
12815      for (i = 0; i < tracks.length; i++) {
12816        track = tracks[i];
12817        kind = ( track.kind ) ? track.kind : 'subtitles';
12818
12819        if (!track.src) {
12820          if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) {
12821            // skip all the hullabaloo and go straight to setupCaptions
12822            thisObj.setupCaptions(track);
12823          }
12824          continue;
12825        }
12826  	  var trackSrc = track.src;
12827        loadingPromise = this.loadTextObject(trackSrc);
12828        loadingPromises.push(
12829          loadingPromise.catch(function (src) {
12830            console.warn("Failed to load captions track from " + src);
12831          })
12832        );
12833        loadingPromise.then(
12834          (function (track, kind) {
12835            trackSrc = track.src;
12836            var trackLang = track.language;
12837            var trackLabel = track.label;
12838            var trackDesc = track.desc;
12839
12840            return function (data) {
12841              var cues = thisObj.parseWebVTT(data).cues;
12842              if (thisObj.hasVts) {
12843                thisObj.setupVtsTracks(
12844                  kind,
12845                  trackLang,
12846                  trackDesc,
12847                  trackLabel,
12848                  trackSrc,
12849                  data.text
12850                );
12851              }
12852              if (kind === 'captions' || kind === 'subtitles') {
12853                thisObj.setupCaptions(track, cues);
12854              } else if (kind === 'descriptions') {
12855                thisObj.setupDescriptions(track, cues);
12856              } else if (kind === 'chapters') {
12857                thisObj.setupChapters(track, cues);
12858              } else if (kind === 'metadata') {
12859                thisObj.setupMetadata(cues);
12860              }
12861            };
12862          })(track, kind)
12863        );
12864      }
12865      if (thisObj.usingYouTubeCaptions || thisObj.usingVimeoCaptions) {
12866        deferred.resolve();
12867      } else {
12868        $.when.apply($, loadingPromises).then(function () {
12869          deferred.resolve();
12870        });
12871      }
12872      return promise;
12873    };
12874
12875    AblePlayer.prototype.getTracks = function () {
12876      // define an array tracks with the following structure:
12877      // kind - string, e.g. "captions", "descriptions"
12878      // src - string, URL of WebVTT source file
12879      // language - string, lang code
12880      // label - string to display, e.g., in CC menu
12881      // def - Boolean, true if this is the default track
12882      // cues - array with startTime, endTime, and payload
12883      // desc - Boolean, true if track includes a data-desc attribute
12884
12885      var thisObj, deferred, promise, trackLang, trackLabel, isDefault, forDesc,
12886  	hasDefault, hasTrackInDefLang, trackFound, i;
12887
12888      thisObj = this;
12889      hasDefault = false;
12890
12891      deferred = new this.defer();
12892      promise = deferred.promise();
12893
12894      this.$tracks = this.$media.find('track');
12895      this.tracks = []; // only includes tracks that do NOT have data-desc
12896      this.altTracks = []; // only includes tracks that DO have data-desc
12897
12898      // Arrays for each kind, to be populated later
12899      this.captions = [];
12900      this.descriptions = [];
12901      this.chapters = [];
12902      this.meta = [];
12903
12904      this.hasCaptionsTrack = false; // will change to true if one or more tracks has kind="captions"
12905      this.hasDescTracks = false; // will change to true if one or more tracks has data-desc
12906
12907      if (this.$tracks.length) {
12908        this.usingYouTubeCaptions = false;
12909        // create object from HTML5 tracks
12910        this.$tracks.each(function (index, element) {
12911          if ($(this).attr('kind') === 'captions') {
12912            thisObj.hasCaptionsTrack = true;
12913          } else if ($(this).attr('kind') === 'descriptions') {
12914            thisObj.hasClosedDesc = true;
12915          }
12916
12917          // srcLang should always be included with <track>, but HTML5 spec doesn't require it
12918          // if not provided, assume track is the same language as the default player language
12919          if ($(this).attr('srclang')) {
12920            trackLang = $(this).attr('srclang');
12921          } else {
12922            trackLang = thisObj.lang;
12923          }
12924          if ($(this).attr('label')) {
12925            trackLabel = $(this).attr('label');
12926          } else {
12927            trackLabel = thisObj.getLanguageName(trackLang);
12928          }
12929
12930          if (typeof $(this).attr('default') !== 'undefined' && !hasDefault) {
12931            isDefault = true;
12932            hasDefault = true;
12933          } else if (trackLang === thisObj.lang) {
12934            // this track is in the default lang of the player
12935            // if there is no other default track specified
12936            // this will be the default
12937            hasTrackInDefLang = true;
12938            isDefault = false; // for now; this could change if there's no default attribute
12939          } else {
12940            isDefault = false;
12941          }
12942          if (isDefault) {
12943            // this.captionLang will also be the default language for non-caption tracks
12944            thisObj.captionLang = trackLang;
12945          }
12946
12947          if ($(this).data("desc") !== undefined) {
12948            forDesc = true;
12949            thisObj.hasDescTracks = true;
12950          } else {
12951            forDesc = false;
12952          }
12953          if (forDesc) {
12954            thisObj.altTracks.push({
12955              kind: $(this).attr('kind'),
12956              src: $(this).attr('src'),
12957              language: trackLang,
12958              label: trackLabel,
12959              def: isDefault,
12960              desc: forDesc,
12961            });
12962          } else {
12963            thisObj.tracks.push({
12964              kind: $(this).attr('kind'),
12965              src: $(this).attr('src'),
12966              language: trackLang,
12967              label: trackLabel,
12968              def: isDefault,
12969              desc: forDesc,
12970            });
12971          }
12972
12973          if (index == thisObj.$tracks.length - 1) {
12974            // This is the last track.
12975            if (!hasDefault) {
12976              if (hasTrackInDefLang) {
12977                thisObj.captionLang = thisObj.lang;
12978                trackFound = false;
12979                i = 0;
12980                while (i < thisObj.tracks.length && !trackFound) {
12981                  if (thisObj.tracks[i]['language'] === thisObj.lang) {
12982                    thisObj.tracks[i]['def'] = true;
12983                    trackFound = true;
12984                  }
12985                  i++;
12986                }
12987              } else {
12988                // use the first track
12989                thisObj.tracks[0]['def'] = true;
12990                thisObj.captionLang = thisObj.tracks[0]['language'];
12991              }
12992            }
12993            // Remove 'default' attribute from all <track> elements
12994            // This data has already been saved to this.tracks
12995            // and some browsers will display the default captions,
12996            // despite all standard efforts to suppress them
12997            thisObj.$media.find("track").removeAttr("default");
12998          }
12999        });
13000      }
13001      if (!this.$tracks.length || !this.hasCaptionsTrack) {
13002        // this media has no track elements
13003        // if this is a youtube or vimeo player, check there for captions/subtitles
13004        if (this.player === 'youtube') {
13005          this.getYouTubeCaptionTracks().then(function () {
13006            if (thisObj.hasCaptions) {
13007              thisObj.usingYouTubeCaptions = true;
13008              if (thisObj.$captionsWrapper) {
13009                thisObj.$captionsWrapper.remove();
13010              }
13011            }
13012            deferred.resolve();
13013          });
13014        } else if (this.player === 'vimeo') {
13015          this.getVimeoCaptionTracks().then(function () {
13016            if (thisObj.hasCaptions) {
13017              thisObj.usingVimeoCaptions = true;
13018              if (thisObj.$captionsWrapper) {
13019                thisObj.$captionsWrapper.remove();
13020              }
13021            }
13022            deferred.resolve();
13023          });
13024        } else {
13025          // this is neither YouTube nor Vimeo
13026          // there just ain't no tracks (captions or otherwise)
13027          this.hasCaptions = false;
13028          if (thisObj.$captionsWrapper) {
13029            thisObj.$captionsWrapper.remove();
13030          }
13031          deferred.resolve();
13032        }
13033      } else {
13034        // there is at least one track with kind="captions"
13035        deferred.resolve();
13036      }
13037      return promise;
13038    };
13039
13040    AblePlayer.prototype.setupCaptions = function (track, cues) {
13041      // Setup player for display of captions (one track at a time)
13042      var inserted, i, capLabel;
13043
13044      // Insert track into captions array
13045      // in its proper alphabetical sequence by label
13046      if (typeof cues === "undefined") {
13047        cues = null;
13048      }
13049
13050      if (this.usingYouTubeCaptions || this.usingVimeoCaptions) ; else {
13051        if (this.captions.length === 0) {
13052          // this is the first
13053          this.captions.push({
13054            language: track.language,
13055            label: track.label,
13056            def: track.def,
13057            cues: cues,
13058          });
13059        } else {
13060          // there are already captions in the array
13061          inserted = false;
13062          for (i = 0; i < this.captions.length; i++) {
13063            capLabel = track.label;
13064            if (capLabel.toLowerCase() < this.captions[i].label.toLowerCase()) {
13065              // insert before track i
13066              this.captions.splice(i, 0, {
13067                language: track.language,
13068                label: track.label,
13069                def: track.def,
13070                cues: cues,
13071              });
13072              inserted = true;
13073              break;
13074            }
13075          }
13076          if (!inserted) {
13077            // just add track to the end
13078            this.captions.push({
13079              language: track.language,
13080              label: track.label,
13081              def: track.def,
13082              cues: cues,
13083            });
13084          }
13085        }
13086      }
13087
13088      // there are captions available
13089      this.hasCaptions = true;
13090      this.currentCaption = -1;
13091      if (this.prefCaptions === 1) {
13092        this.captionsOn = true;
13093      } else if (this.prefCaptions === 0) {
13094        this.captionsOn = false;
13095      } else {
13096        // user has no prefs. Use default state.
13097        if (this.defaultStateCaptions === 1) {
13098          this.captionsOn = true;
13099        } else {
13100          this.captionsOn = false;
13101        }
13102      }
13103      if (this.mediaType === 'audio' && this.captionsOn) {
13104        this.$captionsContainer.removeClass('captions-off');
13105      }
13106
13107      if (
13108        !this.$captionsWrapper ||
13109        (this.$captionsWrapper &&
13110          !$.contains(this.$ableDiv[0], this.$captionsWrapper[0]))
13111      ) {
13112        // captionsWrapper either doesn't exist, or exists in an orphaned state
13113        // Either way, it needs to be rebuilt...
13114        this.$captionsDiv = $('<div>', {
13115          class: "able-captions",
13116        });
13117        this.$captionsWrapper = $('<div>', {
13118          class: 'able-captions-wrapper',
13119          'aria-hidden': 'true',
13120        }).hide();
13121        if (this.prefCaptionsPosition === 'below') {
13122          this.$captionsWrapper.addClass('able-captions-below');
13123        } else {
13124          this.$captionsWrapper.addClass('able-captions-overlay');
13125        }
13126        this.$captionsWrapper.append(this.$captionsDiv);
13127        this.$captionsContainer.append(this.$captionsWrapper);
13128      }
13129    };
13130
13131    AblePlayer.prototype.setupDescriptions = function (track, cues) {
13132      // called via setupTracks() only if there is track with kind="descriptions"
13133      // prepares for delivery of text description , in case it's needed
13134      // whether and how it's delivered is controlled within description.js > initDescription()
13135
13136      this.hasClosedDesc = true;
13137      this.currentDescription = -1;
13138      this.descriptions.push({
13139        cues: cues,
13140        language: track.language,
13141      });
13142    };
13143
13144    AblePlayer.prototype.setupChapters = function (track, cues) {
13145      // NOTE: WebVTT supports nested timestamps (to form an outline)
13146      // This is not currently supported.
13147
13148      this.hasChapters = true;
13149      this.chapters.push({
13150        cues: cues,
13151        language: track.language,
13152      });
13153    };
13154
13155    AblePlayer.prototype.setupMetadata = function (cues) {
13156      if (this.metaType === 'text') {
13157        // Metadata is only supported if data-meta-div is provided
13158        // The player does not display metadata internally
13159        if (this.metaDiv) {
13160          if ($('#' + this.metaDiv)) {
13161            // container exists
13162            this.$metaDiv = $('#' + this.metaDiv);
13163            this.hasMeta = true;
13164            this.meta = cues;
13165          }
13166        }
13167      } else if (this.metaType === 'selector') {
13168        this.hasMeta = true;
13169        this.visibleSelectors = [];
13170        this.meta = cues;
13171      }
13172    };
13173
13174    AblePlayer.prototype.loadTextObject = function (src) {
13175      var deferred, promise, thisObj, $tempDiv;
13176
13177      deferred = new this.defer();
13178      promise = deferred.promise();
13179      thisObj = this;
13180
13181      // create a temp div for holding data
13182      $tempDiv = $('<div>', {
13183        style: 'display:none',
13184      });
13185
13186      // Fetch the content manually so it can be sanitized
13187  	fetch(src)
13188  		.then( response => {
13189
13190  			return response.text();
13191    		})
13192  		.then( vtt => {
13193  			// Split the input on double line breaks to handle each cue individually.
13194  			var preParsed = vtt.split(/\r?\n\s*\r?\n/);
13195  			var lines = '', line;
13196
13197  			preParsed.forEach((l) => {
13198  				// Sanitize each line.
13199  				line   = validate.sanitizeVttContent(l);
13200  				lines += line + "\n\n";
13201  			});
13202  			// Load the sanitized content into the $tempDiv
13203  			$tempDiv.html(lines);
13204  			// Resolve the promise with the sanitized content
13205  			let data = { 'src': src, 'text': lines };
13206  			deferred.resolve(data);
13207  			$tempDiv.remove();
13208  		})
13209  		.catch( error => {
13210  			if (thisObj.debug) {
13211  				console.log( "error reading file " + src + ": " + error );
13212  			}
13213  			deferred.reject(src);
13214  			$tempDiv.remove();
13215  		});
13216
13217      return promise;
13218    };
13219  }
13220
13221  function addTranscriptFunctions(AblePlayer) {
13222    AblePlayer.prototype.setupTranscript = function () {
13223      var deferred = new this.defer();
13224      var promise = deferred.promise();
13225
13226      if (this.usingYouTubeCaptions || this.usingVimeoCaptions || this.hideTranscriptButton ) {
13227        // a transcript is not possible or is disabled.
13228        this.transcriptType = null;
13229        deferred.resolve();
13230      } else {
13231        if (!this.transcriptType) {
13232          // previously set transcriptType to null since there are no <track> elements
13233          // check again to see if captions have been collected from other sources (e.g., YouTube)
13234
13235          if (this.captions.length) {
13236            // captions are possible! Use the default type (popup)
13237            // if other types ('external' and 'manual') were desired, transcriptType would not be null here
13238            this.transcriptType = "popup";
13239          }
13240        }
13241        if (this.transcriptType) {
13242          if ( this.transcriptType === "popup" || this.transcriptType === "external" ) {
13243            this.injectTranscriptArea();
13244            deferred.resolve();
13245          } else if (this.transcriptType === "manual") {
13246            this.setupManualTranscript();
13247            deferred.resolve();
13248          }
13249        } else {
13250          // there is no transcript
13251          deferred.resolve();
13252        }
13253      }
13254      return promise;
13255    };
13256
13257    AblePlayer.prototype.injectTranscriptArea = function () {
13258      var $autoScrollLabel,
13259        $autoScrollContainer,
13260        $languageSelectWrapper,
13261        $languageSelectLabel,
13262        i,
13263        $option;
13264
13265      this.$transcriptArea = $("<div>", {
13266        class: "able-transcript-area",
13267        role: "dialog",
13268        "aria-label": this.translate( 'transcriptTitle', 'Transcript' ),
13269      });
13270
13271      this.$transcriptToolbar = $("<div>", {
13272        class: "able-window-toolbar able-" + this.toolbarIconColor + "-controls",
13273      });
13274
13275      this.$transcriptDiv = $("<div>", {
13276        class: "able-transcript",
13277      });
13278
13279      // Transcript toolbar content
13280
13281      // Add auto Scroll checkbox
13282      this.$autoScrollTranscriptCheckbox = $("<input>", {
13283        id: "autoscroll-transcript-checkbox-" + this.mediaId,
13284        type: "checkbox",
13285      });
13286      $autoScrollLabel = $("<label>", {
13287        for: "autoscroll-transcript-checkbox-" + this.mediaId,
13288      }).text( this.translate( 'autoScroll', 'Auto scroll' ) );
13289  	$autoScrollContainer = $( '<div>', {
13290  		'class': 'autoscroll-transcript'
13291  	});
13292  	$autoScrollContainer.append(
13293  		$autoScrollLabel,
13294  		this.$autoScrollTranscriptCheckbox
13295  	);
13296      this.$transcriptToolbar.append( $autoScrollContainer );
13297
13298      // Add field for selecting a transcript language
13299      // Only necessary if there is more than one language
13300      if (this.captions.length > 1) {
13301        $languageSelectWrapper = $("<div>", {
13302          class: "transcript-language-select-wrapper",
13303        });
13304        $languageSelectLabel = $("<label>", {
13305          for: "transcript-language-select-" + this.mediaId,
13306        }).text( this.translate( 'language', 'Language' ) );
13307        this.$transcriptLanguageSelect = $("<select>", {
13308          id: "transcript-language-select-" + this.mediaId,
13309        });
13310        for (i = 0; i < this.captions.length; i++) {
13311          $option = $("<option></option>", {
13312            value: this.captions[i]["language"],
13313            lang: this.captions[i]["language"],
13314          }).text(this.captions[i]["label"]);
13315          if (this.captions[i]["def"]) {
13316            $option.prop("selected", true);
13317          }
13318          this.$transcriptLanguageSelect.append($option);
13319        }
13320      }
13321      if ($languageSelectWrapper) {
13322        $languageSelectWrapper.append(
13323          $languageSelectLabel,
13324          this.$transcriptLanguageSelect
13325        );
13326        this.$transcriptToolbar.append($languageSelectWrapper);
13327      }
13328      this.$transcriptArea.append(this.$transcriptToolbar, this.$transcriptDiv);
13329
13330      // If client has provided separate transcript location, put it there.
13331      // Otherwise append it to the body
13332      if (this.transcriptDivLocation) {
13333  	  this.$transcriptArea.removeAttr( 'role' );
13334  	  this.$transcriptArea.removeAttr( 'aria-label' );
13335        $("#" + this.transcriptDivLocation).append(this.$transcriptArea);
13336      } else {
13337        this.$ableWrapper.append(this.$transcriptArea);
13338      }
13339
13340      // make it draggable (popup only; NOT external transcript)
13341      if (!this.transcriptDivLocation) {
13342        this.initDragDrop("transcript");
13343        if (this.prefTranscript === 1) {
13344          // transcript is on. Go ahead and position it
13345          this.positionDraggableWindow(
13346            "transcript",
13347            this.getDefaultWidth("transcript")
13348          );
13349        }
13350      }
13351
13352      // If client has provided separate transcript location, override user's preference for hiding transcript
13353      if (!this.prefTranscript && !this.transcriptDivLocation) {
13354        this.$transcriptArea.hide();
13355      }
13356    };
13357
13358    AblePlayer.prototype.addTranscriptAreaEvents = function () {
13359      var thisObj = this;
13360
13361      this.$autoScrollTranscriptCheckbox.on( 'click', function () {
13362        thisObj.handleTranscriptLockToggle(
13363          thisObj.$autoScrollTranscriptCheckbox.prop("checked")
13364        );
13365      });
13366
13367      this.$transcriptDiv.on(
13368        "mousewheel DOMMouseScroll click scroll",
13369        function (e) {
13370          // Propagation is stopped in transcript click handler, so clicks are on the scrollbar
13371          // or outside of a clickable span.
13372          if (!thisObj.scrollingTranscript) {
13373            thisObj.autoScrollTranscript = false;
13374            thisObj.refreshControls("transcript");
13375          }
13376          thisObj.scrollingTranscript = false;
13377        }
13378      );
13379
13380      if (typeof this.$transcriptLanguageSelect !== "undefined") {
13381        this.$transcriptLanguageSelect.on('click', function (e) {
13382          // execute default behavior
13383          // prevent propagation of mouse event to toolbar or window
13384          e.stopPropagation();
13385        });
13386
13387        this.$transcriptLanguageSelect.on("change", function () {
13388          var language = thisObj.$transcriptLanguageSelect.val();
13389
13390          thisObj.syncTrackLanguages("transcript", language);
13391        });
13392      }
13393    };
13394
13395    AblePlayer.prototype.transcriptSrcHasRequiredParts = function () {
13396      // check the external transcript to be sure it has all required components
13397      // return true or false
13398      // in the process, define all the needed variables and properties
13399
13400      if ($("#" + this.transcriptSrc).length) {
13401        this.$transcriptArea = $("#" + this.transcriptSrc);
13402        if (this.$transcriptArea.find(".able-window-toolbar").length) {
13403          this.$transcriptToolbar = this.$transcriptArea
13404            .find(".able-window-toolbar")
13405            .eq(0);
13406          if (this.$transcriptArea.find(".able-transcript").length) {
13407            this.$transcriptDiv = this.$transcriptArea
13408              .find(".able-transcript")
13409              .eq(0);
13410            if (this.$transcriptArea.find(".able-transcript-seekpoint").length) {
13411              this.$transcriptSeekpoints = this.$transcriptArea.find(
13412                ".able-transcript-seekpoint"
13413              );
13414              return true;
13415            }
13416          }
13417        }
13418      }
13419      return false;
13420    };
13421
13422    AblePlayer.prototype.setupManualTranscript = function () {
13423      var $autoScrollInput, $autoScrollLabel;
13424
13425      $autoScrollInput = $("<input>", {
13426        id: "autoscroll-transcript-checkbox-" + this.mediaId,
13427        type: "checkbox",
13428      });
13429      $autoScrollLabel = $("<label>", {
13430        for: "autoscroll-transcript-checkbox-" + this.mediaId,
13431      }).text( this.translate( 'autoScroll', 'Auto scroll' ) );
13432
13433      // Add an auto-scroll checkbox to the toolbar.
13434      this.$autoScrollTranscriptCheckbox = $autoScrollInput;
13435      this.$transcriptToolbar.append(
13436        $autoScrollLabel,
13437        this.$autoScrollTranscriptCheckbox
13438      );
13439    };
13440
13441    AblePlayer.prototype.updateTranscript = function () {
13442      if (!this.transcriptType) {
13443        return;
13444      }
13445      if (this.playerCreated && !this.$transcriptArea) {
13446        return;
13447      }
13448      if (this.transcriptType === "external" || this.transcriptType === "popup") {
13449        var chapters, captions, descriptions;
13450
13451        // Language of transcript might be different than language of captions
13452        // But both are in sync by default
13453        if (this.transcriptLang) {
13454          captions = this.transcriptCaptions.cues;
13455        } else {
13456          if (this.transcriptCaptions) {
13457            this.transcriptLang = this.transcriptCaptions.language;
13458            captions = this.transcriptCaptions.cues;
13459          } else if (this.selectedCaptions) {
13460            this.transcriptLang = this.captionLang;
13461            captions = this.selectedCaptions.cues;
13462          }
13463        }
13464
13465        // setup chapters
13466        if (this.transcriptChapters) {
13467          chapters = this.transcriptChapters.cues;
13468        } else if (this.chapters.length > 0) {
13469          // Try and match the caption language.
13470          if (this.transcriptLang) {
13471            for (var i = 0; i < this.chapters.length; i++) {
13472              if (this.chapters[i].language === this.transcriptLang) {
13473                chapters = this.chapters[i].cues;
13474              }
13475            }
13476          }
13477          if (typeof chapters === "undefined") {
13478            chapters = this.chapters[0].cues || [];
13479          }
13480        }
13481
13482        // setup descriptions
13483        if (this.transcriptDescriptions) {
13484          descriptions = this.transcriptDescriptions.cues;
13485        } else if (this.descriptions.length > 0) {
13486          // Try and match the caption language.
13487          if (this.transcriptLang) {
13488            for (i = 0; i < this.descriptions.length; i++) {
13489              if (this.descriptions[i].language === this.transcriptLang) {
13490                descriptions = this.descriptions[i].cues;
13491              }
13492            }
13493          }
13494          if (!descriptions) {
13495            descriptions = this.descriptions[0].cues || [];
13496          }
13497        }
13498
13499        var div = this.generateTranscript(
13500          chapters || [],
13501          captions || [],
13502          descriptions || []
13503        );
13504        this.$transcriptDiv.html(div);
13505        // reset transcript selected <option> to this.transcriptLang
13506        if (this.$transcriptLanguageSelect) {
13507          this.$transcriptLanguageSelect
13508            .find("option:selected")
13509            .prop("selected", false);
13510          this.$transcriptLanguageSelect
13511            .find("option[lang=" + this.transcriptLang + "]")
13512            .prop("selected", true);
13513        }
13514      }
13515
13516      var thisObj = this;
13517
13518      // Make transcript tabbable if preference is turned on.
13519      if (this.prefTabbable === 1) {
13520        this.$transcriptDiv
13521          .find("span.able-transcript-seekpoint")
13522          .attr("tabindex", "0");
13523      }
13524
13525      // handle clicks on text within transcript
13526      // Note: This event listeners handles clicks only, not keydown events
13527      // Pressing Enter on an element that is not natively clickable does NOT trigger click()
13528      // Keydown events are handled elsehwere, both globally (ableplayer-base.js) and locally (event.js)
13529      if (this.$transcriptArea.length > 0) {
13530        this.$transcriptArea
13531          .find("span.able-transcript-seekpoint")
13532          .on( 'click', function (e) {
13533            thisObj.seekTrigger = "transcript";
13534            var spanStart = parseFloat($(this).attr("data-start"));
13535            // Add a tiny amount so that we're inside the span.
13536            spanStart += 0.01;
13537            // Each click within the transcript triggers two click events (not sure why)
13538            // this.seekingFromTranscript is a stopgab to prevent two calls to SeekTo()
13539            if (!thisObj.seekingFromTranscript) {
13540              thisObj.seekingFromTranscript = true;
13541              thisObj.seekTo(spanStart);
13542            } else {
13543              // don't seek a second time, but do reset var
13544              thisObj.seekingFromTranscript = false;
13545            }
13546          });
13547      }
13548    };
13549
13550    AblePlayer.prototype.highlightTranscript = function (currentTime) {
13551      // Show highlight in transcript marking current caption.
13552
13553      if (!this.transcriptType) {
13554        return;
13555      }
13556
13557      var start, end, isChapterHeading;
13558      var thisObj = this;
13559
13560      currentTime = parseFloat(currentTime);
13561
13562      // Highlight the current transcript item.
13563      this.$transcriptArea
13564        .find("span.able-transcript-seekpoint")
13565        .each(function () {
13566          start = parseFloat($(this).attr("data-start"));
13567          end = parseFloat($(this).attr("data-end"));
13568          // be sure this isn't a chapter (don't highlight chapter headings)
13569          if ($(this).parent().hasClass("able-transcript-chapter-heading")) {
13570            isChapterHeading = true;
13571          } else {
13572            isChapterHeading = false;
13573          }
13574
13575          if (currentTime >= start && currentTime <= end && !isChapterHeading) {
13576            // If this item isn't already highlighted, it should be
13577            if (!$(this).hasClass("able-highlight")) {
13578              // remove all previous highlights before adding one to current span
13579              thisObj.$transcriptArea
13580                .find(".able-highlight")
13581                .removeClass("able-highlight");
13582              $(this).addClass("able-highlight");
13583              thisObj.movingHighlight = true;
13584            }
13585            return false;
13586          }
13587        });
13588      thisObj.currentHighlight = thisObj.$transcriptArea.find(".able-highlight");
13589      if (thisObj.currentHighlight.length === 0) {
13590        // Nothing highlighted.
13591        thisObj.currentHighlight = null;
13592      }
13593    };
13594
13595    AblePlayer.prototype.generateTranscript = function (
13596      chapters,
13597      captions,
13598      descriptions
13599    ) {
13600      var thisObj = this;
13601
13602      var $main = $('<div class="able-transcript-container"></div>');
13603      var transcriptTitle, firstStart;
13604
13605      // set language for transcript container
13606      $main.attr("lang", this.transcriptLang);
13607
13608      if (typeof this.transcriptTitle !== "undefined") {
13609        transcriptTitle = this.transcriptTitle;
13610      } else if (this.lyricsMode) {
13611        transcriptTitle = this.translate( 'lyricsTitle', 'Lyrics' );
13612      } else {
13613        transcriptTitle = this.translate( 'transcriptTitle', 'Transcript' );
13614      }
13615
13616      if (!this.transcriptDivLocation) {
13617        // only add an HTML heading to internal transcript
13618        // external transcript is expected to have its own heading
13619        var headingNumber = this.playerHeadingLevel;
13620        headingNumber += 1;
13621        var chapterHeadingNumber = headingNumber + 1;
13622
13623        let transcriptHeading;
13624        if (headingNumber <= 6) {
13625          transcriptHeading = "h" + headingNumber.toString();
13626        } else {
13627          transcriptHeading = "div";
13628        }
13629        var $transcriptHeadingTag = $("<" + transcriptHeading + ">");
13630        $transcriptHeadingTag.addClass("able-transcript-heading");
13631        if (headingNumber > 6) {
13632          $transcriptHeadingTag.attr({
13633            role: "heading",
13634            "aria-level": headingNumber,
13635          });
13636        }
13637        $transcriptHeadingTag.text(transcriptTitle);
13638
13639        // set language of transcript heading to language of player
13640        // this is independent of language of transcript
13641        $transcriptHeadingTag.attr("lang", this.lang);
13642
13643        $main.append($transcriptHeadingTag);
13644      }
13645
13646      var nextChapter = 0;
13647      var nextCap = 0;
13648      var nextDesc = 0;
13649
13650      var addChapter = function (div, chap) {
13651        let chapterHeading;
13652        if (chapterHeadingNumber <= 6) {
13653          chapterHeading = "h" + chapterHeadingNumber.toString();
13654        } else {
13655          chapterHeading = "div";
13656        }
13657
13658        var $chapterHeadingTag = $("<" + chapterHeading + ">", {
13659          class: "able-transcript-chapter-heading",
13660        });
13661        if (chapterHeadingNumber > 6) {
13662          $chapterHeadingTag.attr({
13663            role: "heading",
13664            "aria-level": chapterHeadingNumber,
13665          });
13666        }
13667
13668        var flattenComponentForChapter = function (comp) {
13669          var result = [];
13670          if (comp.type === "string") {
13671            result.push(comp.value);
13672          } else {
13673            for (var i = 0; i < comp.children.length; i++) {
13674              result = result.concat(
13675                flattenComponentForChapter(comp.children[i])
13676              );
13677            }
13678          }
13679          return result;
13680        };
13681
13682        var $chapSpan = $("<span>", {
13683          class: "able-transcript-seekpoint",
13684        });
13685        for (var i = 0; i < chap.components.children.length; i++) {
13686          var results = flattenComponentForChapter(chap.components.children[i]);
13687          for (var jj = 0; jj < results.length; jj++) {
13688            $chapSpan.append(results[jj]);
13689          }
13690        }
13691        $chapSpan.attr("data-start", chap.start.toString());
13692        $chapSpan.attr("data-end", chap.end.toString());
13693        $chapterHeadingTag.append($chapSpan);
13694
13695        div.append($chapterHeadingTag);
13696      };
13697
13698      var addDescription = function (div, desc) {
13699        var $descDiv = $("<div>", {
13700          class: "able-transcript-desc",
13701        });
13702        var $descHiddenSpan = $("<span>", {
13703          class: "able-hidden",
13704        });
13705        $descHiddenSpan.attr("lang", thisObj.lang);
13706        $descHiddenSpan.text( thisObj.translate( 'prefHeadingDescription', 'Audio description' ) + ": ");
13707        $descDiv.append($descHiddenSpan);
13708
13709        var flattenComponentForDescription = function (comp) {
13710          var result = [];
13711          if (comp.type === "string") {
13712            result.push(comp.value);
13713          } else {
13714            for (var i = 0; i < comp.children.length; i++) {
13715              result = result.concat(
13716                flattenComponentForDescription(comp.children[i])
13717              );
13718            }
13719          }
13720          return result;
13721        };
13722
13723        var $descSpan = $("<span>", {
13724          class: "able-transcript-seekpoint",
13725        });
13726        for (var i = 0; i < desc.components.children.length; i++) {
13727          var results = flattenComponentForDescription(
13728            desc.components.children[i]
13729          );
13730          for (var jj = 0; jj < results.length; jj++) {
13731            $descSpan.append(results[jj]);
13732          }
13733        }
13734        $descSpan.attr("data-start", desc.start.toString());
13735        $descSpan.attr("data-end", desc.end.toString());
13736        $descDiv.append($descSpan);
13737
13738        div.append($descDiv);
13739      };
13740
13741      var addCaption = function (div, cap) {
13742        var $capSpan = $("<span>", {
13743          class: "able-transcript-seekpoint able-transcript-caption",
13744        });
13745
13746        var flattenComponentForCaption = function (comp) {
13747          var result = [];
13748
13749          var parts = 0;
13750
13751          var flattenString = function (str) {
13752            parts++;
13753
13754            var flatStr;
13755            var result = [];
13756            if (str === "") {
13757              return result;
13758            }
13759
13760            var openBracket = str.indexOf("[");
13761            var closeBracket = str.indexOf("]");
13762            var openParen = str.indexOf("(");
13763            var closeParen = str.indexOf(")");
13764
13765            var hasBrackets = openBracket !== -1 && closeBracket !== -1;
13766            var hasParens = openParen !== -1 && closeParen !== -1;
13767
13768            if (hasParens || hasBrackets) {
13769              let silentSpanBreak;
13770              if (parts > 1) {
13771                // force a line break between sections that contain parens or brackets
13772                silentSpanBreak = "<br/>";
13773              } else {
13774                silentSpanBreak = "";
13775              }
13776              var silentSpanOpen =
13777                silentSpanBreak + '<span class="able-unspoken">';
13778              var silentSpanClose = "</span>";
13779              if (hasParens && hasBrackets) {
13780                // string has both!
13781                if (openBracket < openParen) {
13782                  // brackets come first. Parse parens separately
13783                  hasParens = false;
13784                } else {
13785                  // parens come first. Parse brackets separately
13786                  hasBrackets = false;
13787                }
13788              }
13789            }
13790            if (hasParens) {
13791              flatStr = str.substring(0, openParen);
13792              flatStr += silentSpanOpen;
13793              flatStr += str.substring(openParen, closeParen + 1);
13794              flatStr += silentSpanClose;
13795              flatStr += flattenString(str.substring(closeParen + 1));
13796              result.push(flatStr);
13797            } else if (hasBrackets) {
13798              flatStr = str.substring(0, openBracket);
13799              flatStr += silentSpanOpen;
13800              flatStr += str.substring(openBracket, closeBracket + 1);
13801              flatStr += silentSpanClose;
13802              flatStr += flattenString(str.substring(closeBracket + 1));
13803              result.push(flatStr);
13804            } else {
13805              result.push(str);
13806            }
13807            return result;
13808          };
13809
13810          if (comp.type === "string") {
13811            result = result.concat(flattenString(comp.value));
13812          } else if (comp.type === "v") {
13813            var $vSpan = $("<span>", {
13814              class: "able-unspoken",
13815            });
13816            // don't display "title=" when rendering the voice tag title in the transcript
13817            comp.value = comp.value.replace(/^title="|"$/g, "");
13818            $vSpan.text("(" + comp.value + ")");
13819            result.push($vSpan);
13820            for (var i = 0; i < comp.children.length; i++) {
13821              let subResults = flattenComponentForCaption(comp.children[i]);
13822              for (let jj = 0; jj < subResults.length; jj++) {
13823                result.push(subResults[jj]);
13824              }
13825            }
13826          } else if (comp.type === "b" || comp.type === "i") {
13827            let $tag;
13828            if (comp.type === "b") {
13829              $tag = $("<strong>");
13830            } else if (comp.type === "i") {
13831              $tag = $("<em>");
13832            }
13833            for (i = 0; i < comp.children.length; i++) {
13834              let subResults = flattenComponentForCaption(comp.children[i]);
13835              for (let jj = 0; jj < subResults.length; jj++) {
13836                $tag.append(subResults[jj]);
13837              }
13838            }
13839            if (comp.type === "b" || comp.type == "i") {
13840              result.push($tag);
13841            }
13842          } else {
13843            for (i = 0; i < comp.children.length; i++) {
13844              result = result.concat(
13845                flattenComponentForCaption(comp.children[i])
13846              );
13847            }
13848          }
13849          return result;
13850        };
13851
13852        for (var i = 0; i < cap.components.children.length; i++) {
13853  		var next_child_tagname;
13854  		if ( i < cap.components.children.length - 1 ) {
13855  			next_child_tagname = cap.components.children[i + 1].tagName;
13856  		}
13857          var results = flattenComponentForCaption(cap.components.children[i]);
13858          for (var jj = 0; jj < results.length; jj++) {
13859            var result = results[jj];
13860            if (typeof result === "string") {
13861             	if (thisObj.lyricsMode) {
13862  				// add <br> WITHIN each caption (if payload includes "\n")
13863  				result = result.replace(/\n/g,'<br>');
13864
13865  				// add <br> BETWEEN each caption, but do not consider sibling style tags within this caption as the next caption!
13866  				if ( !next_child_tagname || ( next_child_tagname !== 'i' && next_child_tagname !== 'b' ) ) {
13867  					result += '<br>';
13868  				}
13869              } else {
13870                // just add a space between captions
13871                result += " ";
13872              }
13873            }
13874            $capSpan.append(result);
13875          }
13876        }
13877        $capSpan.attr("data-start", cap.start.toString());
13878        $capSpan.attr("data-end", cap.end.toString());
13879        div.append($capSpan);
13880        div.append(" \n");
13881      };
13882
13883      // keep looping as long as any one of the three arrays has content
13884      while (
13885        nextChapter < chapters.length ||
13886        nextDesc < descriptions.length ||
13887        nextCap < captions.length
13888      ) {
13889        if (
13890          nextChapter < chapters.length &&
13891          nextDesc < descriptions.length &&
13892          nextCap < captions.length
13893        ) {
13894          // they all three have content
13895          firstStart = Math.min(
13896            chapters[nextChapter].start,
13897            descriptions[nextDesc].start,
13898            captions[nextCap].start
13899          );
13900        } else if (
13901          nextChapter < chapters.length &&
13902          nextDesc < descriptions.length
13903        ) {
13904          // chapters & descriptions have content
13905          firstStart = Math.min(
13906            chapters[nextChapter].start,
13907            descriptions[nextDesc].start
13908          );
13909        } else if (nextChapter < chapters.length && nextCap < captions.length) {
13910          // chapters & captions have content
13911          firstStart = Math.min(
13912            chapters[nextChapter].start,
13913            captions[nextCap].start
13914          );
13915        } else if (nextDesc < descriptions.length && nextCap < captions.length) {
13916          // descriptions & captions have content
13917          firstStart = Math.min(
13918            descriptions[nextDesc].start,
13919            captions[nextCap].start
13920          );
13921        } else {
13922          firstStart = null;
13923        }
13924        if (firstStart !== null) {
13925          if (
13926            typeof chapters[nextChapter] !== "undefined" &&
13927            chapters[nextChapter].start === firstStart
13928          ) {
13929            addChapter($main, chapters[nextChapter]);
13930            nextChapter += 1;
13931          } else if (
13932            typeof descriptions[nextDesc] !== "undefined" &&
13933            descriptions[nextDesc].start === firstStart
13934          ) {
13935            addDescription($main, descriptions[nextDesc]);
13936            nextDesc += 1;
13937          } else {
13938            addCaption($main, captions[nextCap]);
13939            nextCap += 1;
13940          }
13941        } else {
13942          if (nextChapter < chapters.length) {
13943            addChapter($main, chapters[nextChapter]);
13944            nextChapter += 1;
13945          } else if (nextDesc < descriptions.length) {
13946            addDescription($main, descriptions[nextDesc]);
13947            nextDesc += 1;
13948          } else if (nextCap < captions.length) {
13949            addCaption($main, captions[nextCap]);
13950            nextCap += 1;
13951          }
13952        }
13953      }
13954      // organize transcript into blocks using [] and () as starting points
13955      var $components = $main.children();
13956      var spanCount = 0;
13957      $components.each(function () {
13958        if ($(this).hasClass("able-transcript-caption")) {
13959          if (
13960            $(this).text().indexOf("[") !== -1 ||
13961            $(this).text().indexOf("(") !== -1
13962          ) {
13963            // this caption includes a bracket or parenth. Start a new block
13964            // close the previous block first
13965            if (spanCount > 0) {
13966              $main = wrapTranscriptBlocks( $main );
13967              spanCount = 0;
13968            }
13969          }
13970          $(this).addClass("able-block-temp");
13971          spanCount++;
13972        } else {
13973          // this is not a caption. Close the caption block
13974          if (spanCount > 0) {
13975            $main = wrapTranscriptBlocks( $main );
13976            spanCount = 0;
13977          }
13978        }
13979      });
13980  	// Close out remaining temp blocks.
13981  	$main = wrapTranscriptBlocks( $main );
13982
13983      return $main;
13984    };
13985
13986    var wrapTranscriptBlocks = function( $main ) {
13987  	$main.find(".able-block-temp")
13988  		.removeClass("able-block-temp")
13989  		.wrapAll('<div class="able-transcript-block"></div>');
13990
13991  	return $main;
13992    };
13993  }
13994
13995  var playerHeading$k = "Reproductor";
13996  var audioPlayer$k = "Reproductor d'àudio";
13997  var videoPlayer$k = "Reproductor de vídeo";
13998  var faster$k = "Ràpid";
13999  var slower$k = "Lent";
14000  var play$k = "Reprodueix";
14001  var pause$k = "Pausa";
14002  var restart$k = "Reinicia";
14003  var prevTrack$k = "Pista anterior";
14004  var nextTrack$k = "Pista següent";
14005  var rewind$k = "Endarrere";
14006  var forward$k = "Endavant";
14007  var captions$k = "Subtítols";
14008  var showCaptions$k = "Mostra els subtítols";
14009  var hideCaptions$k = "Oculta els subtítols";
14010  var captionsOff$k = "Desactiva els subtítols";
14011  var showTranscript$k = "Mostra la transcripció";
14012  var hideTranscript$k = "Oculta la transcripció";
14013  var turnOnDescriptions$k = "Activa l'audiodescripció";
14014  var turnOffDescriptions$k = "Desactiva l'audiodescripció";
14015  var chapters$k = "Capítols";
14016  var language$k = "Idioma";
14017  var sign$k = "Llengua de signes";
14018  var showSign$k = "Mostra la llengua de signes";
14019  var hideSign$k = "Oculta la llengua de signes";
14020  var seekbarLabel$k = "Línia de temps";
14021  var mute$k = "Silencia";
14022  var unmute$k = "Activa el so";
14023  var volume$k = "Volum";
14024  var volumeUpDown$k = "Apuja i abaixa el volum";
14025  var preferences$k = "Preferències";
14026  var enterFullScreen$k = "Entra en el mode de pantalla completa";
14027  var exitFullScreen$k = "Surt del mode de pantalla completa";
14028  var speed$k = "Velocitat";
14029  var spacebar$k = "Barra espaiadora";
14030  var transcriptTitle$k = "Transcripció";
14031  var lyricsTitle$k = "Lletra";
14032  var autoScroll$k = "Desplaçament automàtic";
14033  var statusPlaying$k = "S'està reproduint";
14034  var statusPaused$k = "En pausa";
14035  var statusStopped$k = "Aturat";
14036  var statusBuffering$k = "Emmagatzemant";
14037  var statusEnd$k = "Fi de pista";
14038  var selectedTrack$j = "Pista seleccionada";
14039  var alertDescribedVersion$j = "S'està utilitzant la versió amb audiodescripció del vídeo";
14040  var alertNonDescribedVersion$j = "S'està utilitzant la versió sense audiodescripció del vídeo";
14041  var prefMenuCaptions$k = "Subtítols";
14042  var prefVoicedCaptions$k = "Spoken Captions";
14043  var prefMenuDescriptions$k = "Descripcions";
14044  var prefMenuKeyboard$k = "Teclat";
14045  var prefMenuTranscript$k = "Transcripció";
14046  var prefTitleCaptions$k = "Preferències dels subtítols";
14047  var prefTitleDescriptions$k = "Preferències de l'audiodescripció";
14048  var prefTitleKeyboard$k = "Preferències del teclat";
14049  var prefTitleTranscript$j = "Preferències de la transcripció";
14050  var prefIntroDescription1$j = "Aquest reproductor suporta l'audiodescripció de dues maneres: ";
14051  var prefDescription1$k = "L'actual vídeo té una versió alternativa amb audiodescripció, descripció textual anunciada pel lector de pantalla.";
14052  var prefDescription2$k = "L'actual vídeo té una versió alternativa amb audiodescripció.";
14053  var prefDescription3$k = "L'actual vídeo té descripció textual.";
14054  var prefIntroDescriptionNone$k = "L'actual vídeo no disposa d'audiodescripció en cap format.";
14055  var prefIntroDescription3$k = "Utilitzeu el formulari següent per definir les preferències relacionades amb l'audiodescripció textual.";
14056  var prefIntroDescription4$k = "Desprès de desar la configuració, podeu commutar l'ús de l'audiodescripció amb el mateix botó.";
14057  var prefIntroKeyboard1$k = "Aquest reproductor pot ser utilitzat des de qualsevol lloc de la pàgina utilitzant les dreceres de teclat (vegeu la llista a continuació).";
14058  var prefIntroKeyboard2$k = "A continuació, podeu asignar les tecles modificadores (Majúscules, Alt, i la tecla d'inserció).";
14059  var prefIntroKeyboard3$k = "NOTA: algunes combinacions de tecles poden entrar en conflicte amb les utilitzades pel navegador o altres aplicacions. Proveu diferents combinacions o tecles modificadores fins a trobar les adequades en cada cas.";
14060  var prefHeadingKeyboard1$k = "Tecles modificadores emprades com a dreceres de teclat";
14061  var prefHeadingKeyboard2$k = "Dreceres de teclat actuals";
14062  var prefHeadingDescription$k = "Audiodescripció";
14063  var prefHeadingTextDescription$k = "Audiodescripció textual";
14064  var prefAltKey$k = "Alt";
14065  var prefCtrlKey$k = "Control";
14066  var prefShiftKey$k = "Majúscula";
14067  var prefNoKeyShortcuts$k = "Desactiva les dreceres de teclat";
14068  var escapeKey$k = "Escapada";
14069  var escapeKeyFunction$k = "Tanca el diàleg o finestre emergent actual";
14070  var prefDescPause$k = "Pausa automàticament el vídeo en el moment que comenci una audiodescripció";
14071  var prefDescVisible$k = "Fes visible la audiodescripció textual si es troba activada";
14072  var prefDescVoice$k = "Veu";
14073  var prefDescRate$k = "Spoken Description Rate";
14074  var prefCaptionRate$k = "Spoken Caption Rate";
14075  var prefDescPitch$k = "Tonalitat";
14076  var prefDescPitch1$k = "Molt baixa";
14077  var prefDescPitch2$k = "Baixa";
14078  var prefDescPitch3$k = "Per defecte";
14079  var prefDescPitch4$k = "Alta";
14080  var prefDescPitch5$k = "Molt alta";
14081  var sampleDescriptionText$k = "Ajusta la configuració per escoltar aquest text de mostra.";
14082  var prefHighlight$k = "Ressalta la transcripció a mesura que avança el contingut";
14083  var prefTabbable$k = "Transcripció operable per teclat";
14084  var prefCaptionsFont$k = "Tipus de lletra";
14085  var prefCaptionsColor$k = "Color del text";
14086  var prefCaptionsBGColor$k = "Fons";
14087  var prefCaptionsSize$k = "Mida del text";
14088  var prefCaptionsOpacity$k = "Opacitat";
14089  var prefCaptionsStyle$k = "Estil";
14090  var serif$k = "Serifa";
14091  var sans$k = "Sensa serifa";
14092  var cursive$k = "Cursiva";
14093  var fantasy$k = "fantasia";
14094  var monospace$k = "Monoespaiada";
14095  var white$k = "Blanc";
14096  var yellow$k = "Groc";
14097  var green$k = "Verd";
14098  var cyan$j = "Cian";
14099  var blue$k = "Blau";
14100  var magenta$k = "Magenta";
14101  var red$k = "Vermell";
14102  var black$k = "Negre";
14103  var transparent$k = "transparent";
14104  var solid$k = "Sòlid";
14105  var captionsStylePopOn$k = "Aparició instantània";
14106  var captionsStyleRollUp$k = "Desplaçament cap amunt";
14107  var prefCaptionsPosition$k = "Posició";
14108  var captionsPositionOverlay$k = "Superposició";
14109  var captionsPositionBelow$k = "A continuació del vídeo";
14110  var sampleCaptionText$k = "Text de mostra dels subtítols";
14111  var prefSuccess$k = "S'han desat els canvis.";
14112  var prefNoChange$k = "No s'ha fet cap canvi.";
14113  var save$k = "Desa";
14114  var cancel$k = "Cancel·la";
14115  var dismissButton$k = "Ignora";
14116  var windowButtonLabel$k = "Opcions de la finestra";
14117  var windowMove$k = "Moure";
14118  var windowMoveLeft$k = "Finestra desplaçada cap a l'esquerra";
14119  var windowMoveRight$k = "Finestra desplaçada cap a la dreta";
14120  var windowMoveUp$k = "Finestra desplaçada cap amunt";
14121  var windowMoveDown$k = "Finestra desplaçada cap avall";
14122  var windowMoveStopped$k = "S'ha aturat el desplaçament de la finestra";
14123  var transcriptControls$k = "Controls de la finestra de transcripció";
14124  var signControls$k = "Controls de la finestra de llengua de signes";
14125  var windowMoveAlert$k = "Arrossegueu o feu servir les tecles de direcció per moure la finestra, polseu retorn per aturar.";
14126  var windowResize$k = "Redimensiona";
14127  var windowResizeHeading$k = "Redimensiona la finestra amb l'intèrpret";
14128  var closeButtonLabel$k = "Tanca";
14129  var width$k = "Amplada";
14130  var height$k = "Alçada";
14131  var resultsSummary1$k = "Heu cercat:";
14132  var resultsSummary2$k = "S'han trobat %1 elements coincidents.";
14133  var resultsSummary3$k = "Feu clic al moment associat a qualsevol element per reproduir el vídeo des d'aquell punt.";
14134  var noResultsFound$k = "No s'han trobat resultats.";
14135  var searchButtonLabel$k = "Reprodueix a %1";
14136  var hour$k = "hora";
14137  var minute$j = "minut";
14138  var second$k = "segon";
14139  var hours$k = "hores";
14140  var minutes$k = "minuts";
14141  var seconds$k = "segons";
14142  var vtsHeading$k = "Gestor de transcripcions de vídeo";
14143  var vtsInstructions1$k = "Utilitzeu el gestor de transcripcions de vídeo per modificar les pistes de text:";
14144  var vtsInstructions2$k = "Reordeneu capítols, descripcions, subtítols o subtítols per a persones sordes perquè apareguin en la seqüència correcta a la transcripció generada automàticament per Able Player.";
14145  var vtsInstructions3$k = "Modifiqueu el contingut o els temps d'inici i final (tots són editables directament a la taula).";
14146  var vtsInstructions4$k = "Afegiu contingut nou, com ara capítols o descripcions.";
14147  var vtsInstructions5$k = "Després d'editar, feu clic al botó \"Desa els canvis\" per generar contingut nou per a tots els fitxers de text temporitzat rellevants. El text nou es pot copiar i enganxar en fitxers WebVTT nous.";
14148  var vtsSelectLanguage$k = "Seleccioneu una llengua";
14149  var vtsSave$k = "Genera contingut .vtt nou";
14150  var vtsReturn$k = "Torna a l'editor";
14151  var vtsCancel$k = "S'ha cancel·lat el desament. Totes les edicions que heu fet s'han restaurat a la taula del GTV.";
14152  var vtsRow$k = "Fila";
14153  var vtsKind$k = "Tipus";
14154  var vtsStart$k = "Inici";
14155  var vtsEnd$k = "Final";
14156  var vtsContent$k = "Contingut";
14157  var vtsActions$k = "Accions";
14158  var vtsNewRow$k = "S'ha inserit una fila nova %1.";
14159  var vtsDeletedRow$k = "S'ha suprimit la fila %1.";
14160  var vtsMovedRow$k = "La fila %1 s'ha mogut %2 i ara és la fila %3.";
14161  var ca = {
14162  	playerHeading: playerHeading$k,
14163  	audioPlayer: audioPlayer$k,
14164  	videoPlayer: videoPlayer$k,
14165  	faster: faster$k,
14166  	slower: slower$k,
14167  	play: play$k,
14168  	pause: pause$k,
14169  	restart: restart$k,
14170  	prevTrack: prevTrack$k,
14171  	nextTrack: nextTrack$k,
14172  	rewind: rewind$k,
14173  	forward: forward$k,
14174  	captions: captions$k,
14175  	showCaptions: showCaptions$k,
14176  	hideCaptions: hideCaptions$k,
14177  	captionsOff: captionsOff$k,
14178  	showTranscript: showTranscript$k,
14179  	hideTranscript: hideTranscript$k,
14180  	turnOnDescriptions: turnOnDescriptions$k,
14181  	turnOffDescriptions: turnOffDescriptions$k,
14182  	chapters: chapters$k,
14183  	language: language$k,
14184  	sign: sign$k,
14185  	showSign: showSign$k,
14186  	hideSign: hideSign$k,
14187  	seekbarLabel: seekbarLabel$k,
14188  	mute: mute$k,
14189  	unmute: unmute$k,
14190  	volume: volume$k,
14191  	volumeUpDown: volumeUpDown$k,
14192  	preferences: preferences$k,
14193  	enterFullScreen: enterFullScreen$k,
14194  	exitFullScreen: exitFullScreen$k,
14195  	speed: speed$k,
14196  	spacebar: spacebar$k,
14197  	transcriptTitle: transcriptTitle$k,
14198  	lyricsTitle: lyricsTitle$k,
14199  	autoScroll: autoScroll$k,
14200  	statusPlaying: statusPlaying$k,
14201  	statusPaused: statusPaused$k,
14202  	statusStopped: statusStopped$k,
14203  	statusBuffering: statusBuffering$k,
14204  	statusEnd: statusEnd$k,
14205  	selectedTrack: selectedTrack$j,
14206  	alertDescribedVersion: alertDescribedVersion$j,
14207  	alertNonDescribedVersion: alertNonDescribedVersion$j,
14208  	prefMenuCaptions: prefMenuCaptions$k,
14209  	prefVoicedCaptions: prefVoicedCaptions$k,
14210  	prefMenuDescriptions: prefMenuDescriptions$k,
14211  	prefMenuKeyboard: prefMenuKeyboard$k,
14212  	prefMenuTranscript: prefMenuTranscript$k,
14213  	prefTitleCaptions: prefTitleCaptions$k,
14214  	prefTitleDescriptions: prefTitleDescriptions$k,
14215  	prefTitleKeyboard: prefTitleKeyboard$k,
14216  	prefTitleTranscript: prefTitleTranscript$j,
14217  	prefIntroDescription1: prefIntroDescription1$j,
14218  	prefDescription1: prefDescription1$k,
14219  	prefDescription2: prefDescription2$k,
14220  	prefDescription3: prefDescription3$k,
14221  	prefIntroDescriptionNone: prefIntroDescriptionNone$k,
14222  	prefIntroDescription3: prefIntroDescription3$k,
14223  	prefIntroDescription4: prefIntroDescription4$k,
14224  	prefIntroKeyboard1: prefIntroKeyboard1$k,
14225  	prefIntroKeyboard2: prefIntroKeyboard2$k,
14226  	prefIntroKeyboard3: prefIntroKeyboard3$k,
14227  	prefHeadingKeyboard1: prefHeadingKeyboard1$k,
14228  	prefHeadingKeyboard2: prefHeadingKeyboard2$k,
14229  	prefHeadingDescription: prefHeadingDescription$k,
14230  	prefHeadingTextDescription: prefHeadingTextDescription$k,
14231  	prefAltKey: prefAltKey$k,
14232  	prefCtrlKey: prefCtrlKey$k,
14233  	prefShiftKey: prefShiftKey$k,
14234  	prefNoKeyShortcuts: prefNoKeyShortcuts$k,
14235  	escapeKey: escapeKey$k,
14236  	escapeKeyFunction: escapeKeyFunction$k,
14237  	prefDescPause: prefDescPause$k,
14238  	prefDescVisible: prefDescVisible$k,
14239  	prefDescVoice: prefDescVoice$k,
14240  	prefDescRate: prefDescRate$k,
14241  	prefCaptionRate: prefCaptionRate$k,
14242  	prefDescPitch: prefDescPitch$k,
14243  	prefDescPitch1: prefDescPitch1$k,
14244  	prefDescPitch2: prefDescPitch2$k,
14245  	prefDescPitch3: prefDescPitch3$k,
14246  	prefDescPitch4: prefDescPitch4$k,
14247  	prefDescPitch5: prefDescPitch5$k,
14248  	sampleDescriptionText: sampleDescriptionText$k,
14249  	prefHighlight: prefHighlight$k,
14250  	prefTabbable: prefTabbable$k,
14251  	prefCaptionsFont: prefCaptionsFont$k,
14252  	prefCaptionsColor: prefCaptionsColor$k,
14253  	prefCaptionsBGColor: prefCaptionsBGColor$k,
14254  	prefCaptionsSize: prefCaptionsSize$k,
14255  	prefCaptionsOpacity: prefCaptionsOpacity$k,
14256  	prefCaptionsStyle: prefCaptionsStyle$k,
14257  	serif: serif$k,
14258  	sans: sans$k,
14259  	cursive: cursive$k,
14260  	fantasy: fantasy$k,
14261  	monospace: monospace$k,
14262  	white: white$k,
14263  	yellow: yellow$k,
14264  	green: green$k,
14265  	cyan: cyan$j,
14266  	blue: blue$k,
14267  	magenta: magenta$k,
14268  	red: red$k,
14269  	black: black$k,
14270  	transparent: transparent$k,
14271  	solid: solid$k,
14272  	captionsStylePopOn: captionsStylePopOn$k,
14273  	captionsStyleRollUp: captionsStyleRollUp$k,
14274  	prefCaptionsPosition: prefCaptionsPosition$k,
14275  	captionsPositionOverlay: captionsPositionOverlay$k,
14276  	captionsPositionBelow: captionsPositionBelow$k,
14277  	sampleCaptionText: sampleCaptionText$k,
14278  	prefSuccess: prefSuccess$k,
14279  	prefNoChange: prefNoChange$k,
14280  	save: save$k,
14281  	cancel: cancel$k,
14282  	dismissButton: dismissButton$k,
14283  	windowButtonLabel: windowButtonLabel$k,
14284  	windowMove: windowMove$k,
14285  	windowMoveLeft: windowMoveLeft$k,
14286  	windowMoveRight: windowMoveRight$k,
14287  	windowMoveUp: windowMoveUp$k,
14288  	windowMoveDown: windowMoveDown$k,
14289  	windowMoveStopped: windowMoveStopped$k,
14290  	transcriptControls: transcriptControls$k,
14291  	signControls: signControls$k,
14292  	windowMoveAlert: windowMoveAlert$k,
14293  	windowResize: windowResize$k,
14294  	windowResizeHeading: windowResizeHeading$k,
14295  	closeButtonLabel: closeButtonLabel$k,
14296  	width: width$k,
14297  	height: height$k,
14298  	resultsSummary1: resultsSummary1$k,
14299  	resultsSummary2: resultsSummary2$k,
14300  	resultsSummary3: resultsSummary3$k,
14301  	noResultsFound: noResultsFound$k,
14302  	searchButtonLabel: searchButtonLabel$k,
14303  	hour: hour$k,
14304  	minute: minute$j,
14305  	second: second$k,
14306  	hours: hours$k,
14307  	minutes: minutes$k,
14308  	seconds: seconds$k,
14309  	vtsHeading: vtsHeading$k,
14310  	vtsInstructions1: vtsInstructions1$k,
14311  	vtsInstructions2: vtsInstructions2$k,
14312  	vtsInstructions3: vtsInstructions3$k,
14313  	vtsInstructions4: vtsInstructions4$k,
14314  	vtsInstructions5: vtsInstructions5$k,
14315  	vtsSelectLanguage: vtsSelectLanguage$k,
14316  	vtsSave: vtsSave$k,
14317  	vtsReturn: vtsReturn$k,
14318  	vtsCancel: vtsCancel$k,
14319  	vtsRow: vtsRow$k,
14320  	vtsKind: vtsKind$k,
14321  	vtsStart: vtsStart$k,
14322  	vtsEnd: vtsEnd$k,
14323  	vtsContent: vtsContent$k,
14324  	vtsActions: vtsActions$k,
14325  	vtsNewRow: vtsNewRow$k,
14326  	vtsDeletedRow: vtsDeletedRow$k,
14327  	vtsMovedRow: vtsMovedRow$k
14328  };
14329
14330  var playerHeading$j = "Přehrávač médií";
14331  var audioPlayer$j = "Audio player";
14332  var videoPlayer$j = "Video player";
14333  var faster$j = "Rychleji";
14334  var slower$j = "Pomaleji";
14335  var play$j = "Spustit";
14336  var pause$j = "Pauza";
14337  var restart$j = "Přehrát od začátku";
14338  var prevTrack$j = "Předchozí stopa";
14339  var nextTrack$j = "Další stopa";
14340  var rewind$j = "Přetočit vzad";
14341  var forward$j = "Přetočit vpřed";
14342  var captions$j = "Titulky";
14343  var showCaptions$j = "Zobrazit titulky";
14344  var hideCaptions$j = "Skrýt titulky";
14345  var captionsOff$j = "Titulky vypnuty";
14346  var showTranscript$j = "Zobrazit přepis";
14347  var hideTranscript$j = "Skrýt přepis";
14348  var turnOnDescriptions$j = "Zapnout popisy";
14349  var turnOffDescriptions$j = "vypnout popisy";
14350  var chapters$j = "Kapitoly";
14351  var language$j = "Jazyk";
14352  var sign$j = "Znaková řeč";
14353  var showSign$j = "Zobrazit znakovou řeč";
14354  var hideSign$j = "Skrýt znakovou řeč";
14355  var seekbarLabel$j = "časová osa";
14356  var mute$j = "Vypnout zvuk";
14357  var unmute$j = "Zapnout zvuk";
14358  var volume$j = "Hlasitost";
14359  var volumeUpDown$j = "Hlasitost zvýšit snížit";
14360  var preferences$j = "Předvolby";
14361  var enterFullScreen$j = "Zobrazit na celou obrazovku";
14362  var exitFullScreen$j = "Ukončit celou obrazovku";
14363  var speed$j = "Rychlost";
14364  var spacebar$j = "mezerník";
14365  var transcriptTitle$j = "Přepis";
14366  var lyricsTitle$j = "Text";
14367  var autoScroll$j = "Automatické posouvání";
14368  var statusPlaying$j = "Přehrávání";
14369  var statusPaused$j = "Pozastaveno";
14370  var statusStopped$j = "Zastaveno";
14371  var statusBuffering$j = "Vyrovnávací paměť";
14372  var statusEnd$j = "Konec stopy";
14373  var selectedTrack$i = "Vybraná stopa";
14374  var alertDescribVersion = "Používání zvukově popsané verze tohoto videa";
14375  var alertNonDescribVersion = "Použití nepopsané verze tohoto videa";
14376  var prefMenuCaptions$j = "Titulky";
14377  var prefVoicedCaptions$j = "Spoken Captions";
14378  var prefMenuDescriptions$j = "Popisy";
14379  var prefMenuKeyboard$j = "Klávesnice";
14380  var prefMenuTranscript$j = "Přepis";
14381  var prefTitleCaptions$j = "Předvolby titulků";
14382  var prefTitleDescriptions$j = "Předvolby zvukového popisu";
14383  var prefTitleKeyboard$j = "Předvolby klávesnice";
14384  var prefTitleTranscript$i = "Předvolby přepisu";
14385  var prefIntroDescription1$i = "Tento přehrávač médií podporuje zvukový popis dvěma způsoby:";
14386  var prefDescription1$j = "Aktuální video má alternativně popsaná verze, textový popis.";
14387  var prefDescription2$j = "Aktuální video má alternativní popsaná verze videa.";
14388  var prefDescription3$j = "Aktuální video má textový popis, oznámený čtečkou obrazovky.";
14389  var prefIntroDescriptionNone$j = "Aktuální video nemá žádný zvukový popis v žádném formátu.";
14390  var prefIntroDescription3$j = "Pomocí následujícího formuláře můžete nastavit předvolby týkající se textového zvukového popisu.";
14391  var prefIntroDescription4$j = "Po uložení nastavení lze zvukový popis zapnout / vypnout pomocí tlačítka Popis.";
14392  var prefIntroKeyboard1$j = "Přehrávač médií na této webové stránce lze ovládat odkudkoli na stránce pomocí klávesových zkratek (seznam níže).";
14393  var prefIntroKeyboard2$j = "Níže lze přiřadit modifikační klávesy (Shift, Alt a Control).";
14394  var prefIntroKeyboard3$j = "POZNÁMKA: Některé kombinace kláves mohou být v konfliktu s klávesami používanými vaším prohlížečem nebo jinými softwarovými aplikacemi. Zkuste najít různé kombinace modifikačních kláves, které vám vyhovují.";
14395  var prefHeadingKeyboard1$j = "Modifikační klávesy používané pro zástupce";
14396  var prefHeadingKeyboard2$j = "Aktuální klávesové zkratky";
14397  var prefHeadingDescription$j = "Zvukový popis";
14398  var prefHeadingTextDescription$j = "Textový zvukový popis";
14399  var prefAltKey$j = "Alt";
14400  var prefCtrlKey$j = "Control";
14401  var prefShiftKey$j = "Shift";
14402  var prefNoKeyShortcuts$j = "Disable keyboard shortcuts";
14403  var escapeKey$j = "Escape";
14404  var escapeKeyFunction$j = "Zavřít aktuální dialogové okno nebo vyskakovací nabídku";
14405  var prefDescPause$j = "Automaticky pozastavit video při spuštění popisu";
14406  var prefDescVisible$j = "Zviditelnit popis";
14407  var prefDescVoice$j = "Voice";
14408  var prefDescRate$j = "Spoken Description Rate";
14409  var prefCaptionRate$j = "Spoken Caption Rate";
14410  var prefDescPitch$j = "Pitch";
14411  var prefDescPitch1$j = "Very low";
14412  var prefDescPitch2$j = "Low";
14413  var prefDescPitch3$j = "Default";
14414  var prefDescPitch4$j = "High";
14415  var prefDescPitch5$j = "Very high";
14416  var sampleDescriptionText$j = "Adjust settings to hear this sample text.";
14417  var prefHighlight$j = "Zvýraznit přepis při přehrávání médií";
14418  var prefTabbable$j = "Přepis umožňující klávesnici";
14419  var prefCaptionsFont$j = "Písmo";
14420  var prefCaptionsColor$j = "Barva textu";
14421  var prefCaptionsBGColor$j = "Pozadí";
14422  var prefCaptionsSize$j = "Velikost písma";
14423  var prefCaptionsOpacity$j = "Neprůhlednost";
14424  var prefCaptionsStyle$j = "Styl";
14425  var serif$j = "patkové";
14426  var sans$j = "bezpatkové";
14427  var cursive$j = "kurzíva";
14428  var fantasy$j = "fantasy";
14429  var monospace$j = "jednoprostorový";
14430  var white$j = "bílá";
14431  var yellow$j = "žlutá";
14432  var green$j = "zelená";
14433  var cyan$i = "azurová";
14434  var blue$j = "modrá";
14435  var magenta$j = "purpurová";
14436  var red$j = "červená";
14437  var black$j = "černá";
14438  var transparent$j = "transparentní";
14439  var solid$j = "jednolitý";
14440  var captionsStylePopOn$j = "Vyskakovat";
14441  var captionsStyleRollUp$j = "Srolovat";
14442  var prefCaptionsPosition$j = "Pozice";
14443  var captionsPositionOverlay$j = "Překrytí";
14444  var captionsPositionBelow$j = "Níže video";
14445  var sampleCaptionText$j = "Ukázkový text titulku";
14446  var prefSuccess$j = "Vaše změny byly uloženy.";
14447  var prefNoChange$j = "Neprovedli jste žádné změny.";
14448  var save$j = "Uložit";
14449  var cancel$j = "Zrušit";
14450  var dismissButton$j = "Dismiss";
14451  var windowButtonLabel$j = "Možnosti okna";
14452  var windowMove$j = "Přesunout";
14453  var windowMoveLeft$j = "Window moved left";
14454  var windowMoveRight$j = "Window moved right";
14455  var windowMoveUp$j = "Window moved up";
14456  var windowMoveDown$j = "Window moved down";
14457  var windowMoveStopped$j = "Window move stopped";
14458  var transcriptControls$j = "Transcript Window Controls";
14459  var signControls$j = "Sign Language Window Controls";
14460  var windowMoveAlert$j = "Přetažením nebo použitím kláves se šipkami přesuňte okno; klávesou Enter zastavíte";
14461  var windowResize$j = "Změnit velikost";
14462  var windowResizeHeading$j = "Změnit velikost okna";
14463  var closeButtonLabel$j = "Zavřít";
14464  var width$j = "Šířka";
14465  var height$j = "Výška";
14466  var resultsSummary1$j = "Hledali jste:";
14467  var resultsSummary2$j = "Nalezeno %1 odpovídající položky.";
14468  var resultsSummary3$j = "Klepnutím na čas spojený s libovolnou položkou přehrajete video od tohoto bodu.";
14469  var noResultsFound$j = "Nebyly nalezeny žádné výsledky.";
14470  var searchButtonLabel$j = "Přehrát v %1";
14471  var hour$j = "hodina";
14472  var minuta = "minuta";
14473  var second$j = "sekunda";
14474  var hours$j = "hodiny";
14475  var minutes$j = "minuty";
14476  var seconds$j = "sekundy";
14477  var vtsHeading$j = "Video Transcript Sorter";
14478  var vtsInstructions1$j = "Use the Video Transcript Sorter to modify text tracks:";
14479  var vtsInstructions2$j = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
14480  var vtsInstructions3$j = "Modify content or start/end times (all are directly editable within the table).";
14481  var vtsInstructions4$j = "Add new content, such as chapters or descriptions.";
14482  var vtsInstructions5$j = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
14483  var vtsSelectLanguage$j = "Select a language";
14484  var vtsSave$j = "Generate new .vtt content";
14485  var vtsReturn$j = "Return to Editor";
14486  var vtsCancel$j = "Cancelling saving. Any edits you made have been restored in the VTS table.";
14487  var vtsRow$j = "Row";
14488  var vtsKind$j = "Kind";
14489  var vtsStart$j = "Start";
14490  var vtsEnd$j = "End";
14491  var vtsContent$j = "Content";
14492  var vtsActions$j = "Actions";
14493  var vtsNewRow$j = "A new row %1 has been inserted.";
14494  var vtsDeletedRow$j = "Row %1 has been deleted.";
14495  var vtsMovedRow$j = "Row %1 has been moved %2 and is now Row %3.";
14496  var cs = {
14497  	playerHeading: playerHeading$j,
14498  	audioPlayer: audioPlayer$j,
14499  	videoPlayer: videoPlayer$j,
14500  	faster: faster$j,
14501  	slower: slower$j,
14502  	play: play$j,
14503  	pause: pause$j,
14504  	restart: restart$j,
14505  	prevTrack: prevTrack$j,
14506  	nextTrack: nextTrack$j,
14507  	rewind: rewind$j,
14508  	forward: forward$j,
14509  	captions: captions$j,
14510  	showCaptions: showCaptions$j,
14511  	hideCaptions: hideCaptions$j,
14512  	captionsOff: captionsOff$j,
14513  	showTranscript: showTranscript$j,
14514  	hideTranscript: hideTranscript$j,
14515  	turnOnDescriptions: turnOnDescriptions$j,
14516  	turnOffDescriptions: turnOffDescriptions$j,
14517  	chapters: chapters$j,
14518  	language: language$j,
14519  	sign: sign$j,
14520  	showSign: showSign$j,
14521  	hideSign: hideSign$j,
14522  	seekbarLabel: seekbarLabel$j,
14523  	mute: mute$j,
14524  	unmute: unmute$j,
14525  	volume: volume$j,
14526  	volumeUpDown: volumeUpDown$j,
14527  	preferences: preferences$j,
14528  	enterFullScreen: enterFullScreen$j,
14529  	exitFullScreen: exitFullScreen$j,
14530  	speed: speed$j,
14531  	spacebar: spacebar$j,
14532  	transcriptTitle: transcriptTitle$j,
14533  	lyricsTitle: lyricsTitle$j,
14534  	autoScroll: autoScroll$j,
14535  	statusPlaying: statusPlaying$j,
14536  	statusPaused: statusPaused$j,
14537  	statusStopped: statusStopped$j,
14538  	statusBuffering: statusBuffering$j,
14539  	statusEnd: statusEnd$j,
14540  	selectedTrack: selectedTrack$i,
14541  	alertDescribVersion: alertDescribVersion,
14542  	alertNonDescribVersion: alertNonDescribVersion,
14543  	prefMenuCaptions: prefMenuCaptions$j,
14544  	prefVoicedCaptions: prefVoicedCaptions$j,
14545  	prefMenuDescriptions: prefMenuDescriptions$j,
14546  	prefMenuKeyboard: prefMenuKeyboard$j,
14547  	prefMenuTranscript: prefMenuTranscript$j,
14548  	prefTitleCaptions: prefTitleCaptions$j,
14549  	prefTitleDescriptions: prefTitleDescriptions$j,
14550  	prefTitleKeyboard: prefTitleKeyboard$j,
14551  	prefTitleTranscript: prefTitleTranscript$i,
14552  	prefIntroDescription1: prefIntroDescription1$i,
14553  	prefDescription1: prefDescription1$j,
14554  	prefDescription2: prefDescription2$j,
14555  	prefDescription3: prefDescription3$j,
14556  	prefIntroDescriptionNone: prefIntroDescriptionNone$j,
14557  	prefIntroDescription3: prefIntroDescription3$j,
14558  	prefIntroDescription4: prefIntroDescription4$j,
14559  	prefIntroKeyboard1: prefIntroKeyboard1$j,
14560  	prefIntroKeyboard2: prefIntroKeyboard2$j,
14561  	prefIntroKeyboard3: prefIntroKeyboard3$j,
14562  	prefHeadingKeyboard1: prefHeadingKeyboard1$j,
14563  	prefHeadingKeyboard2: prefHeadingKeyboard2$j,
14564  	prefHeadingDescription: prefHeadingDescription$j,
14565  	prefHeadingTextDescription: prefHeadingTextDescription$j,
14566  	prefAltKey: prefAltKey$j,
14567  	prefCtrlKey: prefCtrlKey$j,
14568  	prefShiftKey: prefShiftKey$j,
14569  	prefNoKeyShortcuts: prefNoKeyShortcuts$j,
14570  	escapeKey: escapeKey$j,
14571  	escapeKeyFunction: escapeKeyFunction$j,
14572  	prefDescPause: prefDescPause$j,
14573  	prefDescVisible: prefDescVisible$j,
14574  	prefDescVoice: prefDescVoice$j,
14575  	prefDescRate: prefDescRate$j,
14576  	prefCaptionRate: prefCaptionRate$j,
14577  	prefDescPitch: prefDescPitch$j,
14578  	prefDescPitch1: prefDescPitch1$j,
14579  	prefDescPitch2: prefDescPitch2$j,
14580  	prefDescPitch3: prefDescPitch3$j,
14581  	prefDescPitch4: prefDescPitch4$j,
14582  	prefDescPitch5: prefDescPitch5$j,
14583  	sampleDescriptionText: sampleDescriptionText$j,
14584  	prefHighlight: prefHighlight$j,
14585  	prefTabbable: prefTabbable$j,
14586  	prefCaptionsFont: prefCaptionsFont$j,
14587  	prefCaptionsColor: prefCaptionsColor$j,
14588  	prefCaptionsBGColor: prefCaptionsBGColor$j,
14589  	prefCaptionsSize: prefCaptionsSize$j,
14590  	prefCaptionsOpacity: prefCaptionsOpacity$j,
14591  	prefCaptionsStyle: prefCaptionsStyle$j,
14592  	serif: serif$j,
14593  	sans: sans$j,
14594  	cursive: cursive$j,
14595  	fantasy: fantasy$j,
14596  	monospace: monospace$j,
14597  	white: white$j,
14598  	yellow: yellow$j,
14599  	green: green$j,
14600  	cyan: cyan$i,
14601  	blue: blue$j,
14602  	magenta: magenta$j,
14603  	red: red$j,
14604  	black: black$j,
14605  	transparent: transparent$j,
14606  	solid: solid$j,
14607  	captionsStylePopOn: captionsStylePopOn$j,
14608  	captionsStyleRollUp: captionsStyleRollUp$j,
14609  	prefCaptionsPosition: prefCaptionsPosition$j,
14610  	captionsPositionOverlay: captionsPositionOverlay$j,
14611  	captionsPositionBelow: captionsPositionBelow$j,
14612  	sampleCaptionText: sampleCaptionText$j,
14613  	prefSuccess: prefSuccess$j,
14614  	prefNoChange: prefNoChange$j,
14615  	save: save$j,
14616  	cancel: cancel$j,
14617  	dismissButton: dismissButton$j,
14618  	windowButtonLabel: windowButtonLabel$j,
14619  	windowMove: windowMove$j,
14620  	windowMoveLeft: windowMoveLeft$j,
14621  	windowMoveRight: windowMoveRight$j,
14622  	windowMoveUp: windowMoveUp$j,
14623  	windowMoveDown: windowMoveDown$j,
14624  	windowMoveStopped: windowMoveStopped$j,
14625  	transcriptControls: transcriptControls$j,
14626  	signControls: signControls$j,
14627  	windowMoveAlert: windowMoveAlert$j,
14628  	windowResize: windowResize$j,
14629  	windowResizeHeading: windowResizeHeading$j,
14630  	closeButtonLabel: closeButtonLabel$j,
14631  	width: width$j,
14632  	height: height$j,
14633  	resultsSummary1: resultsSummary1$j,
14634  	resultsSummary2: resultsSummary2$j,
14635  	resultsSummary3: resultsSummary3$j,
14636  	noResultsFound: noResultsFound$j,
14637  	searchButtonLabel: searchButtonLabel$j,
14638  	hour: hour$j,
14639  	minuta: minuta,
14640  	second: second$j,
14641  	hours: hours$j,
14642  	minutes: minutes$j,
14643  	seconds: seconds$j,
14644  	vtsHeading: vtsHeading$j,
14645  	vtsInstructions1: vtsInstructions1$j,
14646  	vtsInstructions2: vtsInstructions2$j,
14647  	vtsInstructions3: vtsInstructions3$j,
14648  	vtsInstructions4: vtsInstructions4$j,
14649  	vtsInstructions5: vtsInstructions5$j,
14650  	vtsSelectLanguage: vtsSelectLanguage$j,
14651  	vtsSave: vtsSave$j,
14652  	vtsReturn: vtsReturn$j,
14653  	vtsCancel: vtsCancel$j,
14654  	vtsRow: vtsRow$j,
14655  	vtsKind: vtsKind$j,
14656  	vtsStart: vtsStart$j,
14657  	vtsEnd: vtsEnd$j,
14658  	vtsContent: vtsContent$j,
14659  	vtsActions: vtsActions$j,
14660  	vtsNewRow: vtsNewRow$j,
14661  	vtsDeletedRow: vtsDeletedRow$j,
14662  	vtsMovedRow: vtsMovedRow$j
14663  };
14664
14665  var playerHeading$i = "Medieafspiller";
14666  var audioPlayer$i = "Audio player";
14667  var videoPlayer$i = "Video player";
14668  var faster$i = "Hurtigere";
14669  var slower$i = "Langsommere";
14670  var play$i = "Afspil";
14671  var pause$i = "Pause";
14672  var restart$i = "Genstart";
14673  var prevTrack$i = "Forrige spor";
14674  var nextTrack$i = "Næste spor";
14675  var rewind$i = "Spol tilbage";
14676  var forward$i = "Spol frem";
14677  var captions$i = "Undertekster";
14678  var showCaptions$i = "Vis undertekster";
14679  var hideCaptions$i = "Gem undertekster";
14680  var captionsOff$i = "Slå undertekster fra";
14681  var showTranscript$i = "Vis transskription";
14682  var hideTranscript$i = "Gem transskription";
14683  var turnOnDescriptions$i = "Start synstolkning";
14684  var turnOffDescriptions$i = "Stop synstolkning";
14685  var chapters$i = "Kapitler";
14686  var language$i = "Sprog";
14687  var sign$i = "Tegnsprog";
14688  var showSign$i = "Vis tegnsprog";
14689  var hideSign$i = "Gen tegnsprog";
14690  var seekbarLabel$i = "tidslinie";
14691  var mute$i = "Stop lyd";
14692  var unmute$i = "Start lyd";
14693  var volume$i = "Lydstyrke";
14694  var volumeUpDown$i = "Lydstyrke op";
14695  var preferences$i = "Indstillinger";
14696  var enterFullScreen$i = "Vis i fuldskærm";
14697  var exitFullScreen$i = "Afslut fuldskærmsvisning";
14698  var speed$i = "Hastighed";
14699  var spacebar$i = "mellemrumstast";
14700  var transcriptTitle$i = "Transskription";
14701  var lyricsTitle$i = "Lyrik";
14702  var autoScroll$i = "Auto scroll";
14703  var statusPlaying$i = "Afspiller";
14704  var statusPaused$i = "PÃ¥ pause";
14705  var statusStopped$i = "Stoppet";
14706  var statusBuffering$i = "Henter data";
14707  var statusEnd$i = "Slut på spor";
14708  var selectedTrack$h = "Valgt spor";
14709  var alertDescribedVersion$i = "Anvender synstolket version af denne video";
14710  var alertNonDescribedVersion$i = "Anvende ikke synstolket version af denne video";
14711  var prefMenuCaptions$i = "Undertekster";
14712  var prefVoicedCaptions$i = "Spoken Captions";
14713  var prefMenuDescriptions$i = "Synstolkning";
14714  var prefMenuKeyboard$i = "Tastatur";
14715  var prefMenuTranscript$i = "Transkript";
14716  var prefTitleCaptions$i = "Indstillinger for undertekster";
14717  var prefTitleDescriptions$i = "Indstillinger for synstolkning";
14718  var prefTitleKeyboard$i = "Indstillinger for tastatur";
14719  var prefTitleTranscript$h = "Indstillinger for transskription";
14720  var prefIntroDescription1$h = "Denne medieafspiller understøtter audio på to måder: ";
14721  var prefDescription1$i = "Nuværende videoer har en alternativ synstolket version, textbaseret synstolkning.";
14722  var prefDescription2$i = "Nuværende videoer har alternativ synstolket version af videoen.";
14723  var prefDescription3$i = "Nuværende videoer har textbaseret synstolkning.";
14724  var prefIntroDescriptionNone$i = "Nuværende video har ingen synstolkning i noget format.";
14725  var prefIntroDescription3$i = "Anvende følgende formular for at indstille gældende textbasererede synstolkning.";
14726  var prefIntroDescription4$i = "Efter du gemmer dine indstillinger, kan synstolkning slåes til og fra med synstolkningsknappen.";
14727  var prefIntroKeyboard1$i = "Medieafspilleren på denne webside kan betjenest med tastuturgenveje (se neden for en liste).";
14728  var prefIntroKeyboard2$i = "Meta-taster (Shift, Alt, og Ctrl) kan tildeles nedenfor.";
14729  var prefIntroKeyboard3$i = "BEMÆRK: Visse tastekombinationer kan være i konflikt med din webbrowser eller andre programmers indstillinger. Benyt de tastekombinationer der virker for dig.";
14730  var prefHeadingKeyboard1$i = "Metataster brugt til genveje";
14731  var prefHeadingKeyboard2$i = "Nuværende tastatur genveje";
14732  var prefHeadingDescription$i = "Synstolkning";
14733  var prefHeadingTextDescription$i = "Tekstbaseret synstolkning";
14734  var prefAltKey$i = "Alt";
14735  var prefCtrlKey$i = "Ctrl";
14736  var prefShiftKey$i = "Shift";
14737  var prefNoKeyShortcuts$i = "Disable keyboard shortcuts";
14738  var escapeKey$i = "Escape";
14739  var escapeKeyFunction$i = "Luk nuværinde dialog eller popup-menu";
14740  var prefDescPause$i = "Pause automatisk video når synstolkning starter";
14741  var prefDescVisible$i = "Vis synstolkning";
14742  var prefDescVoice$i = "Stemme";
14743  var prefDescRate$i = "Spoken Description Rate";
14744  var prefCaptionRate$i = "Spoken Caption Rate";
14745  var prefDescPitch$i = "Tonehøjde";
14746  var prefDescPitch1$i = "Meget lavt";
14747  var prefDescPitch2$i = "Lavt";
14748  var prefDescPitch3$i = "Normal";
14749  var prefDescPitch4$i = "Højt";
14750  var prefDescPitch5$i = "Meget højt";
14751  var sampleDescriptionText$i = "Juster indstillinger for at høre denne eksempel tekst.";
14752  var prefHighlight$i = "Marker tekst som læses op i transskriptet";
14753  var prefTabbable$i = "Tastaturaktiveret transskription";
14754  var prefCaptionsFont$i = "Skrifttype";
14755  var prefCaptionsColor$i = "Tekstfarve";
14756  var prefCaptionsBGColor$i = "Baggrundsfarve";
14757  var prefCaptionsSize$i = "Tekststørrelse";
14758  var prefCaptionsOpacity$i = "Gennemsigtighed";
14759  var prefCaptionsStyle$i = "Teksttype";
14760  var serif$i = "serif";
14761  var sans$i = "sans-serif";
14762  var cursive$i = "kursiv";
14763  var fantasy$i = "fantasi";
14764  var monospace$i = "monospace";
14765  var white$i = "hvid";
14766  var yellow$i = "gul";
14767  var green$i = "grøn";
14768  var cyan$h = "cyan";
14769  var blue$i = "blå";
14770  var magenta$i = "lilla";
14771  var red$i = "rød";
14772  var black$i = "sort";
14773  var transparent$i = "gennemsigtig";
14774  var solid$i = "massiv";
14775  var captionsStylePopOn$i = "Pop op";
14776  var captionsStyleRollUp$i = "Pop ned";
14777  var prefCaptionsPosition$i = "Position";
14778  var captionsPositionOverlay$i = "Vis over video";
14779  var captionsPositionBelow$i = "Vis under video";
14780  var sampleCaptionText$i = "Eksempel på undertekst";
14781  var prefSuccess$i = "Dine ændringer er gemt.";
14782  var prefNoChange$i = "Du har ikke lavet nogen ændringer.";
14783  var save$i = "Gem";
14784  var cancel$i = "Afbryd";
14785  var dismissButton$i = "Dismiss";
14786  var windowButtonLabel$i = "Vindueindstillinger";
14787  var windowMove$i = "Flyt";
14788  var windowMoveLeft$i = "Window moved left";
14789  var windowMoveRight$i = "Window moved right";
14790  var windowMoveUp$i = "Window moved up";
14791  var windowMoveDown$i = "Window moved down";
14792  var windowMoveStopped$i = "Window move stopped";
14793  var transcriptControls$i = "Transcript Window Controls";
14794  var signControls$i = "Sign Language Window Controls";
14795  var windowMoveAlert$i = "Flyt med mus eller anvende piletasterne for at flytte vinduer; Enter for at slutte";
14796  var windowResize$i = "Ændre størrelse";
14797  var windowResizeHeading$i = "Ændre vinduestørrelse";
14798  var closeButtonLabel$i = "Luk";
14799  var width$i = "Bredde";
14800  var height$i = "Højde";
14801  var resultsSummary1$i = "Du søgte efter:";
14802  var resultsSummary2$i = "Fundne %1 matchende indhold.";
14803  var resultsSummary3$i = "Klik på tidslinien for at afspille derfra.";
14804  var noResultsFound$i = "Ingen resultater.";
14805  var searchButtonLabel$i = "Afspil fra %1";
14806  var hour$i = "time";
14807  var minute$i = "minut";
14808  var second$i = "sekund";
14809  var hours$i = "timer";
14810  var minutes$i = "minutter";
14811  var seconds$i = "sekunder";
14812  var vtsHeading$i = "Video Transcript Sorter";
14813  var vtsInstructions1$i = "Use the Video Transcript Sorter to modify text tracks:";
14814  var vtsInstructions2$i = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
14815  var vtsInstructions3$i = "Modify content or start/end times (all are directly editable within the table).";
14816  var vtsInstructions4$i = "Add new content, such as chapters or descriptions.";
14817  var vtsInstructions5$i = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
14818  var vtsSelectLanguage$i = "Select a language";
14819  var vtsSave$i = "Generate new .vtt content";
14820  var vtsReturn$i = "Return to Editor";
14821  var vtsCancel$i = "Cancelling saving. Any edits you made have been restored in the VTS table.";
14822  var vtsRow$i = "Row";
14823  var vtsKind$i = "Kind";
14824  var vtsStart$i = "Start";
14825  var vtsEnd$i = "End";
14826  var vtsContent$i = "Content";
14827  var vtsActions$i = "Actions";
14828  var vtsNewRow$i = "A new row %1 has been inserted.";
14829  var vtsDeletedRow$i = "Row %1 has been deleted.";
14830  var vtsMovedRow$i = "Row %1 has been moved %2 and is now Row %3.";
14831  var da = {
14832  	playerHeading: playerHeading$i,
14833  	audioPlayer: audioPlayer$i,
14834  	videoPlayer: videoPlayer$i,
14835  	faster: faster$i,
14836  	slower: slower$i,
14837  	play: play$i,
14838  	pause: pause$i,
14839  	restart: restart$i,
14840  	prevTrack: prevTrack$i,
14841  	nextTrack: nextTrack$i,
14842  	rewind: rewind$i,
14843  	forward: forward$i,
14844  	captions: captions$i,
14845  	showCaptions: showCaptions$i,
14846  	hideCaptions: hideCaptions$i,
14847  	captionsOff: captionsOff$i,
14848  	showTranscript: showTranscript$i,
14849  	hideTranscript: hideTranscript$i,
14850  	turnOnDescriptions: turnOnDescriptions$i,
14851  	turnOffDescriptions: turnOffDescriptions$i,
14852  	chapters: chapters$i,
14853  	language: language$i,
14854  	sign: sign$i,
14855  	showSign: showSign$i,
14856  	hideSign: hideSign$i,
14857  	seekbarLabel: seekbarLabel$i,
14858  	mute: mute$i,
14859  	unmute: unmute$i,
14860  	volume: volume$i,
14861  	volumeUpDown: volumeUpDown$i,
14862  	preferences: preferences$i,
14863  	enterFullScreen: enterFullScreen$i,
14864  	exitFullScreen: exitFullScreen$i,
14865  	speed: speed$i,
14866  	spacebar: spacebar$i,
14867  	transcriptTitle: transcriptTitle$i,
14868  	lyricsTitle: lyricsTitle$i,
14869  	autoScroll: autoScroll$i,
14870  	statusPlaying: statusPlaying$i,
14871  	statusPaused: statusPaused$i,
14872  	statusStopped: statusStopped$i,
14873  	statusBuffering: statusBuffering$i,
14874  	statusEnd: statusEnd$i,
14875  	selectedTrack: selectedTrack$h,
14876  	alertDescribedVersion: alertDescribedVersion$i,
14877  	alertNonDescribedVersion: alertNonDescribedVersion$i,
14878  	prefMenuCaptions: prefMenuCaptions$i,
14879  	prefVoicedCaptions: prefVoicedCaptions$i,
14880  	prefMenuDescriptions: prefMenuDescriptions$i,
14881  	prefMenuKeyboard: prefMenuKeyboard$i,
14882  	prefMenuTranscript: prefMenuTranscript$i,
14883  	prefTitleCaptions: prefTitleCaptions$i,
14884  	prefTitleDescriptions: prefTitleDescriptions$i,
14885  	prefTitleKeyboard: prefTitleKeyboard$i,
14886  	prefTitleTranscript: prefTitleTranscript$h,
14887  	prefIntroDescription1: prefIntroDescription1$h,
14888  	prefDescription1: prefDescription1$i,
14889  	prefDescription2: prefDescription2$i,
14890  	prefDescription3: prefDescription3$i,
14891  	prefIntroDescriptionNone: prefIntroDescriptionNone$i,
14892  	prefIntroDescription3: prefIntroDescription3$i,
14893  	prefIntroDescription4: prefIntroDescription4$i,
14894  	prefIntroKeyboard1: prefIntroKeyboard1$i,
14895  	prefIntroKeyboard2: prefIntroKeyboard2$i,
14896  	prefIntroKeyboard3: prefIntroKeyboard3$i,
14897  	prefHeadingKeyboard1: prefHeadingKeyboard1$i,
14898  	prefHeadingKeyboard2: prefHeadingKeyboard2$i,
14899  	prefHeadingDescription: prefHeadingDescription$i,
14900  	prefHeadingTextDescription: prefHeadingTextDescription$i,
14901  	prefAltKey: prefAltKey$i,
14902  	prefCtrlKey: prefCtrlKey$i,
14903  	prefShiftKey: prefShiftKey$i,
14904  	prefNoKeyShortcuts: prefNoKeyShortcuts$i,
14905  	escapeKey: escapeKey$i,
14906  	escapeKeyFunction: escapeKeyFunction$i,
14907  	prefDescPause: prefDescPause$i,
14908  	prefDescVisible: prefDescVisible$i,
14909  	prefDescVoice: prefDescVoice$i,
14910  	prefDescRate: prefDescRate$i,
14911  	prefCaptionRate: prefCaptionRate$i,
14912  	prefDescPitch: prefDescPitch$i,
14913  	prefDescPitch1: prefDescPitch1$i,
14914  	prefDescPitch2: prefDescPitch2$i,
14915  	prefDescPitch3: prefDescPitch3$i,
14916  	prefDescPitch4: prefDescPitch4$i,
14917  	prefDescPitch5: prefDescPitch5$i,
14918  	sampleDescriptionText: sampleDescriptionText$i,
14919  	prefHighlight: prefHighlight$i,
14920  	prefTabbable: prefTabbable$i,
14921  	prefCaptionsFont: prefCaptionsFont$i,
14922  	prefCaptionsColor: prefCaptionsColor$i,
14923  	prefCaptionsBGColor: prefCaptionsBGColor$i,
14924  	prefCaptionsSize: prefCaptionsSize$i,
14925  	prefCaptionsOpacity: prefCaptionsOpacity$i,
14926  	prefCaptionsStyle: prefCaptionsStyle$i,
14927  	serif: serif$i,
14928  	sans: sans$i,
14929  	cursive: cursive$i,
14930  	fantasy: fantasy$i,
14931  	monospace: monospace$i,
14932  	white: white$i,
14933  	yellow: yellow$i,
14934  	green: green$i,
14935  	cyan: cyan$h,
14936  	blue: blue$i,
14937  	magenta: magenta$i,
14938  	red: red$i,
14939  	black: black$i,
14940  	transparent: transparent$i,
14941  	solid: solid$i,
14942  	captionsStylePopOn: captionsStylePopOn$i,
14943  	captionsStyleRollUp: captionsStyleRollUp$i,
14944  	prefCaptionsPosition: prefCaptionsPosition$i,
14945  	captionsPositionOverlay: captionsPositionOverlay$i,
14946  	captionsPositionBelow: captionsPositionBelow$i,
14947  	sampleCaptionText: sampleCaptionText$i,
14948  	prefSuccess: prefSuccess$i,
14949  	prefNoChange: prefNoChange$i,
14950  	save: save$i,
14951  	cancel: cancel$i,
14952  	dismissButton: dismissButton$i,
14953  	windowButtonLabel: windowButtonLabel$i,
14954  	windowMove: windowMove$i,
14955  	windowMoveLeft: windowMoveLeft$i,
14956  	windowMoveRight: windowMoveRight$i,
14957  	windowMoveUp: windowMoveUp$i,
14958  	windowMoveDown: windowMoveDown$i,
14959  	windowMoveStopped: windowMoveStopped$i,
14960  	transcriptControls: transcriptControls$i,
14961  	signControls: signControls$i,
14962  	windowMoveAlert: windowMoveAlert$i,
14963  	windowResize: windowResize$i,
14964  	windowResizeHeading: windowResizeHeading$i,
14965  	closeButtonLabel: closeButtonLabel$i,
14966  	width: width$i,
14967  	height: height$i,
14968  	resultsSummary1: resultsSummary1$i,
14969  	resultsSummary2: resultsSummary2$i,
14970  	resultsSummary3: resultsSummary3$i,
14971  	noResultsFound: noResultsFound$i,
14972  	searchButtonLabel: searchButtonLabel$i,
14973  	hour: hour$i,
14974  	minute: minute$i,
14975  	second: second$i,
14976  	hours: hours$i,
14977  	minutes: minutes$i,
14978  	seconds: seconds$i,
14979  	vtsHeading: vtsHeading$i,
14980  	vtsInstructions1: vtsInstructions1$i,
14981  	vtsInstructions2: vtsInstructions2$i,
14982  	vtsInstructions3: vtsInstructions3$i,
14983  	vtsInstructions4: vtsInstructions4$i,
14984  	vtsInstructions5: vtsInstructions5$i,
14985  	vtsSelectLanguage: vtsSelectLanguage$i,
14986  	vtsSave: vtsSave$i,
14987  	vtsReturn: vtsReturn$i,
14988  	vtsCancel: vtsCancel$i,
14989  	vtsRow: vtsRow$i,
14990  	vtsKind: vtsKind$i,
14991  	vtsStart: vtsStart$i,
14992  	vtsEnd: vtsEnd$i,
14993  	vtsContent: vtsContent$i,
14994  	vtsActions: vtsActions$i,
14995  	vtsNewRow: vtsNewRow$i,
14996  	vtsDeletedRow: vtsDeletedRow$i,
14997  	vtsMovedRow: vtsMovedRow$i
14998  };
14999
15000  var playerHeading$h = "Medienplayer";
15001  var audioPlayer$h = "Audioplayer";
15002  var videoPlayer$h = "Videoplayer";
15003  var faster$h = "Schneller";
15004  var slower$h = "Langsamer";
15005  var chapters$h = "Kapitel";
15006  var play$h = "Abspielen";
15007  var pause$h = "Pause";
15008  var restart$h = "Neustart";
15009  var prevTrack$h = "Vorheriger Titel";
15010  var nextTrack$h = "Nächster Titel";
15011  var rewind$h = "Zurück";
15012  var forward$h = "Vorwärts";
15013  var captions$h = "Untertitel";
15014  var showCaptions$h = "Untertitel anzeigen";
15015  var hideCaptions$h = "Untertitel ausblenden";
15016  var captionsOff$h = "Untertitel ausschalten";
15017  var showTranscript$h = "Transkription anzeigen";
15018  var hideTranscript$h = "Transkription entfernen";
15019  var turnOnDescriptions$h = "Audiobeschreibung einschalten";
15020  var turnOffDescriptions$h = "Audiobeschreibung ausschalten";
15021  var language$h = "Sprache";
15022  var sign$h = "Gebärdensprache";
15023  var showSign$h = "Gebärdensprache anzeigen";
15024  var hideSign$h = "Gebärdensprache ausblenden";
15025  var seekbarLabel$h = "Suchleiste";
15026  var mute$h = "Ton aus";
15027  var unmute$h = "Ton an";
15028  var volume$h = "Lautstärke";
15029  var volumeUpDown$h = "Lautstärkeregler";
15030  var preferences$h = "Einstellungen";
15031  var enterFullScreen$h = "Vollbildmodus einschalten";
15032  var exitFullScreen$h = "Vollbildmodus verlassen";
15033  var speed$h = "Geschwindigkeit";
15034  var spacebar$h = "Leertaste";
15035  var transcriptTitle$h = "Transkription";
15036  var lyricsTitle$h = "Text";
15037  var autoScroll$h = "Automatisch scrollen";
15038  var statusPlaying$h = "Gestartet";
15039  var statusPaused$h = "Pausiert";
15040  var statusStopped$h = "Angehalten";
15041  var statusBuffering$h = "Daten werden empfangen...";
15042  var statusEnd$h = "Ende des Titels";
15043  var selectedTrack$g = "Ausgewählter Titel";
15044  var alertDescribedVersion$h = "Das Video wird mit Audiobeschreibung abgespielt";
15045  var alertNonDescribedVersion$h = "Das Video wird ohne Audiobeschreibung abgespielt";
15046  var prefMenuCaptions$h = "Untertitel";
15047  var prefVoicedCaptions$h = "Spoken Captions";
15048  var prefMenuDescriptions$h = "Audiobeschreibungen";
15049  var prefMenuKeyboard$h = "Tastatur";
15050  var prefMenuTranscript$h = "Transkription";
15051  var prefTitleCaptions$h = "Untertitel Einstellungen";
15052  var prefTitleDescriptions$h = "Audiobeschreibung Einstellungen";
15053  var prefTitleKeyboard$h = "Tastatur Einstellungen";
15054  var prefTitleTranscript$g = "Transkription Einstellungen";
15055  var prefIntroDescription1$g = "Dieser Media Player unterstützt zwei Arten von Untertiteln: ";
15056  var prefDescription1$h = "Das aktuelle Video hat eine alternative Version der Audiobeschreibung, eine textbasierte Audiobeschreibung.";
15057  var prefDescription2$h = "Das aktuelle Video hat Version des Videos, die eine Audiobeschreibung enthält.";
15058  var prefDescription3$h = "Das aktuelle Video hat Textbasierte Audiobeschreibung, die vom Screen-Reader vorgelesen wird.";
15059  var prefIntroDescriptionNone$h = "Das aktuelle Video hat keine Audiobeschreibung.";
15060  var prefIntroDescription3$h = "Mit der folgenden Auswahl steuern Sie das Abspielen der textbasierten Audiobeschreibung.";
15061  var prefIntroDescription4$h = "Wenn die Audiobeschreibung aktiviert ist, kann sie per Schaltfläche ein- und ausgeschaltet werden.";
15062  var prefIntroKeyboard1$h = "Dieser Media Player lässt sich innerhalb der gesamten Seite per Tastenkürzel bedienen (siehe unten).";
15063  var prefIntroKeyboard2$h = "Die Modifikatortasten (Umschalt, Alt, und Strg) können hier zugeordnet werden.";
15064  var prefIntroKeyboard3$h = "Achtung: Einige Tastenkombinationen sind je nach Browser und Betriebssystem nicht möglich. Versuchen Sie gegebenenfalls andere Kombinationen.";
15065  var prefHeadingKeyboard1$h = "Modifikatortasten für die Tastenkürzel";
15066  var prefHeadingKeyboard2$h = "Aktuell eingestellte Tastenkürzel";
15067  var prefHeadingDescription$h = "Audiobeschreibung";
15068  var prefHeadingTextDescription$h = "Textbasierte Audiobeschreibung";
15069  var prefAltKey$h = "Alt";
15070  var prefCtrlKey$h = "Strg";
15071  var prefShiftKey$h = "Umschalttaste";
15072  var prefNoKeyShortcuts$h = "Tastenkombinationen deaktivieren";
15073  var escapeKey$h = "ESC Taste";
15074  var escapeKeyFunction$h = "Dialogfenster schließen";
15075  var prefDescPause$h = "Video automatisch anhalten, wenn Szenenbeschreibungen eingeblendet werden";
15076  var prefDescVisible$h = "Textbasierte Szenenbeschreibungen einblenden, wenn diese aktiviert sind";
15077  var prefDescVoice$h = "Stimme";
15078  var prefDescRate$h = "Spoken Description Rate";
15079  var prefCaptionRate$h = "Spoken Caption Rate";
15080  var prefDescPitch$h = "Tonlage";
15081  var prefDescPitch1$h = "Sehr tief";
15082  var prefDescPitch2$h = "Tief";
15083  var prefDescPitch3$h = "Mittel";
15084  var prefDescPitch4$h = "Hoch";
15085  var prefDescPitch5$h = "Sehr hoch";
15086  var sampleDescriptionText$h = "Einstellungen bearbeiten um diesen Text vorzulesen.";
15087  var prefHighlight$h = "Transkription hervorheben, während das Medium abgespielt wird";
15088  var prefTabbable$h = "Transkription per Tastatur ein-/ausschaltbar machen";
15089  var prefCaptionsFont$h = "Schriftart";
15090  var prefCaptionsColor$h = "Schriftfarbe";
15091  var prefCaptionsBGColor$h = "Hintergrund";
15092  var prefCaptionsSize$h = "Schriftgöße";
15093  var prefCaptionsOpacity$h = "Deckkraft";
15094  var prefCaptionsStyle$h = "Stil";
15095  var serif$h = "Serifenschrift";
15096  var sans$h = "Serifenlose Schrift";
15097  var cursive$h = "kursiv";
15098  var fantasy$h = "Fantasieschrift";
15099  var monospace$h = "nichtproportionale Schrift";
15100  var white$h = "weiß";
15101  var yellow$h = "gelb";
15102  var green$h = "grün";
15103  var cyan$g = "cyan";
15104  var blue$h = "blau";
15105  var magenta$h = "magenta";
15106  var red$h = "rot";
15107  var black$h = "schwarz";
15108  var transparent$h = "transparent";
15109  var solid$h = "undurchsichtig";
15110  var captionsStylePopOn$h = "Pop-on";
15111  var captionsStyleRollUp$h = "Roll-up";
15112  var prefCaptionsPosition$h = "Position";
15113  var captionsPositionOverlay$h = "Überlagert";
15114  var captionsPositionBelow$h = "Unterhalb";
15115  var sampleCaptionText$h = "Textbeispiel";
15116  var prefSuccess$h = "Ihre Änderungen wurden gespeichert.";
15117  var prefNoChange$h = "Es gab keine Änderungen zu speichern.";
15118  var save$h = "Speichern";
15119  var cancel$h = "Abbrechen";
15120  var dismissButton$h = "Dismiss";
15121  var windowButtonLabel$h = "Fenstereinstellungen";
15122  var windowMove$h = "Verschieben";
15123  var windowMoveLeft$h = "Das Fenster verschob sich nach links";
15124  var windowMoveRight$h = "Das Fenster verschob sich nach rechts";
15125  var windowMoveUp$h = "Das Fenster verschob sich nach oben";
15126  var windowMoveDown$h = "Das Fenster verschob sich nach unten";
15127  var windowMoveStopped$h = "Das Fenster hörte auf sich zu verschieben";
15128  var transcriptControls$h = "Bedienelemente des Transkriptionsfensters";
15129  var signControls$h = "Bedienelemente des Gebärdensprache-Fensters";
15130  var windowMoveAlert$h = "Fenster mit Pfeiltasten oder Maus verschieben; beenden mit Eingabetaste";
15131  var windowResize$h = "Größe verändern";
15132  var windowResizeHeading$h = "Größe des Gebärdensprache-Fenster";
15133  var closeButtonLabel$h = "Schließen";
15134  var width$h = "Breite";
15135  var height$h = "Höhe";
15136  var resultsSummary1$h = "Suche nach:";
15137  var resultsSummary2$h = "Gefunden %1 Treffer.";
15138  var resultsSummary3$h = "Auf den Zeitindex klicken, um das Video vom Zeitpunkt des jeweiligen Suchergebnisses abzuspielen.";
15139  var noResultsFound$h = "Keine Treffer.";
15140  var searchButtonLabel$h = "Abspielen von %1";
15141  var hour$h = "Stunde";
15142  var minute$h = "Minute";
15143  var second$h = "Sekunde";
15144  var hours$h = "Stunden";
15145  var minutes$h = "Minuten";
15146  var seconds$h = "Sekunden";
15147  var vtsHeading$h = "Transkriptions-Sortierer für Videos";
15148  var vtsInstructions1$h = "Verwenden Sie den Transkription-Sortierer für Videos, um Textspuren zu ändern:";
15149  var vtsInstructions2$h = "Ordnen Sie Kapitel, Beschreibungen, Bildunterschriften, und/oder Untertitel so, dass diese in der richtigen Reihenfolge in der selbsterzeugten Transkription des Able Players erscheinen.";
15150  var vtsInstructions3$h = "Verändern Sie Inhalt oder Anfangs- bzw. Endzeiten (alle sind direkt innerhalb der Tabelle editierbar).";
15151  var vtsInstructions4$h = "Fügen Sie neuen Inhalt, wie Kapitel oder Beschreibungen hinzu.";
15152  var vtsInstructions5$h = "Nach dem Editieren klicken Sie auf die Schaltfläche \"Änderungen speichern\" um neuen Inhalt für alle wichtigen zeitgesteuerten Textdateien zu erzeugen. Der neue Text kann in neue WebVTT-Dateien kopiert und eingefügt werden.";
15153  var vtsSelectLanguage$h = "Wählen Sie eine Sprache aus";
15154  var vtsSave$h = "Erzeugen Sie neuen .vtt-Inhalt";
15155  var vtsReturn$h = "Gehen Sie zurück zum Editor";
15156  var vtsCancel$h = "Abbruch des Speicherns. Jegliche Bearbeitungen, die Sie vorgenommen haben, wurden in der VTS-Tabelle gesichert.";
15157  var vtsRow$h = "Reihe";
15158  var vtsKind$h = "Art";
15159  var vtsStart$h = "Anfang";
15160  var vtsEnd$h = "Ende";
15161  var vtsContent$h = "Inhalt";
15162  var vtsActions$h = "Aktionen";
15163  var vtsNewRow$h = "Eine neue Reihe %1 wurde eingefügt.";
15164  var vtsDeletedRow$h = "Reihe %1 wurde gelöscht.";
15165  var vtsMovedRow$h = "Reihe %1 wurde verschoben %2 ist nun Reihe %3.";
15166  var de = {
15167  	playerHeading: playerHeading$h,
15168  	audioPlayer: audioPlayer$h,
15169  	videoPlayer: videoPlayer$h,
15170  	faster: faster$h,
15171  	slower: slower$h,
15172  	chapters: chapters$h,
15173  	play: play$h,
15174  	pause: pause$h,
15175  	restart: restart$h,
15176  	prevTrack: prevTrack$h,
15177  	nextTrack: nextTrack$h,
15178  	rewind: rewind$h,
15179  	forward: forward$h,
15180  	captions: captions$h,
15181  	showCaptions: showCaptions$h,
15182  	hideCaptions: hideCaptions$h,
15183  	captionsOff: captionsOff$h,
15184  	showTranscript: showTranscript$h,
15185  	hideTranscript: hideTranscript$h,
15186  	turnOnDescriptions: turnOnDescriptions$h,
15187  	turnOffDescriptions: turnOffDescriptions$h,
15188  	language: language$h,
15189  	sign: sign$h,
15190  	showSign: showSign$h,
15191  	hideSign: hideSign$h,
15192  	seekbarLabel: seekbarLabel$h,
15193  	mute: mute$h,
15194  	unmute: unmute$h,
15195  	volume: volume$h,
15196  	volumeUpDown: volumeUpDown$h,
15197  	preferences: preferences$h,
15198  	enterFullScreen: enterFullScreen$h,
15199  	exitFullScreen: exitFullScreen$h,
15200  	speed: speed$h,
15201  	spacebar: spacebar$h,
15202  	transcriptTitle: transcriptTitle$h,
15203  	lyricsTitle: lyricsTitle$h,
15204  	autoScroll: autoScroll$h,
15205  	statusPlaying: statusPlaying$h,
15206  	statusPaused: statusPaused$h,
15207  	statusStopped: statusStopped$h,
15208  	statusBuffering: statusBuffering$h,
15209  	statusEnd: statusEnd$h,
15210  	selectedTrack: selectedTrack$g,
15211  	alertDescribedVersion: alertDescribedVersion$h,
15212  	alertNonDescribedVersion: alertNonDescribedVersion$h,
15213  	prefMenuCaptions: prefMenuCaptions$h,
15214  	prefVoicedCaptions: prefVoicedCaptions$h,
15215  	prefMenuDescriptions: prefMenuDescriptions$h,
15216  	prefMenuKeyboard: prefMenuKeyboard$h,
15217  	prefMenuTranscript: prefMenuTranscript$h,
15218  	prefTitleCaptions: prefTitleCaptions$h,
15219  	prefTitleDescriptions: prefTitleDescriptions$h,
15220  	prefTitleKeyboard: prefTitleKeyboard$h,
15221  	prefTitleTranscript: prefTitleTranscript$g,
15222  	prefIntroDescription1: prefIntroDescription1$g,
15223  	prefDescription1: prefDescription1$h,
15224  	prefDescription2: prefDescription2$h,
15225  	prefDescription3: prefDescription3$h,
15226  	prefIntroDescriptionNone: prefIntroDescriptionNone$h,
15227  	prefIntroDescription3: prefIntroDescription3$h,
15228  	prefIntroDescription4: prefIntroDescription4$h,
15229  	prefIntroKeyboard1: prefIntroKeyboard1$h,
15230  	prefIntroKeyboard2: prefIntroKeyboard2$h,
15231  	prefIntroKeyboard3: prefIntroKeyboard3$h,
15232  	prefHeadingKeyboard1: prefHeadingKeyboard1$h,
15233  	prefHeadingKeyboard2: prefHeadingKeyboard2$h,
15234  	prefHeadingDescription: prefHeadingDescription$h,
15235  	prefHeadingTextDescription: prefHeadingTextDescription$h,
15236  	prefAltKey: prefAltKey$h,
15237  	prefCtrlKey: prefCtrlKey$h,
15238  	prefShiftKey: prefShiftKey$h,
15239  	prefNoKeyShortcuts: prefNoKeyShortcuts$h,
15240  	escapeKey: escapeKey$h,
15241  	escapeKeyFunction: escapeKeyFunction$h,
15242  	prefDescPause: prefDescPause$h,
15243  	prefDescVisible: prefDescVisible$h,
15244  	prefDescVoice: prefDescVoice$h,
15245  	prefDescRate: prefDescRate$h,
15246  	prefCaptionRate: prefCaptionRate$h,
15247  	prefDescPitch: prefDescPitch$h,
15248  	prefDescPitch1: prefDescPitch1$h,
15249  	prefDescPitch2: prefDescPitch2$h,
15250  	prefDescPitch3: prefDescPitch3$h,
15251  	prefDescPitch4: prefDescPitch4$h,
15252  	prefDescPitch5: prefDescPitch5$h,
15253  	sampleDescriptionText: sampleDescriptionText$h,
15254  	prefHighlight: prefHighlight$h,
15255  	prefTabbable: prefTabbable$h,
15256  	prefCaptionsFont: prefCaptionsFont$h,
15257  	prefCaptionsColor: prefCaptionsColor$h,
15258  	prefCaptionsBGColor: prefCaptionsBGColor$h,
15259  	prefCaptionsSize: prefCaptionsSize$h,
15260  	prefCaptionsOpacity: prefCaptionsOpacity$h,
15261  	prefCaptionsStyle: prefCaptionsStyle$h,
15262  	serif: serif$h,
15263  	sans: sans$h,
15264  	cursive: cursive$h,
15265  	fantasy: fantasy$h,
15266  	monospace: monospace$h,
15267  	white: white$h,
15268  	yellow: yellow$h,
15269  	green: green$h,
15270  	cyan: cyan$g,
15271  	blue: blue$h,
15272  	magenta: magenta$h,
15273  	red: red$h,
15274  	black: black$h,
15275  	transparent: transparent$h,
15276  	solid: solid$h,
15277  	captionsStylePopOn: captionsStylePopOn$h,
15278  	captionsStyleRollUp: captionsStyleRollUp$h,
15279  	prefCaptionsPosition: prefCaptionsPosition$h,
15280  	captionsPositionOverlay: captionsPositionOverlay$h,
15281  	captionsPositionBelow: captionsPositionBelow$h,
15282  	sampleCaptionText: sampleCaptionText$h,
15283  	prefSuccess: prefSuccess$h,
15284  	prefNoChange: prefNoChange$h,
15285  	save: save$h,
15286  	cancel: cancel$h,
15287  	dismissButton: dismissButton$h,
15288  	windowButtonLabel: windowButtonLabel$h,
15289  	windowMove: windowMove$h,
15290  	windowMoveLeft: windowMoveLeft$h,
15291  	windowMoveRight: windowMoveRight$h,
15292  	windowMoveUp: windowMoveUp$h,
15293  	windowMoveDown: windowMoveDown$h,
15294  	windowMoveStopped: windowMoveStopped$h,
15295  	transcriptControls: transcriptControls$h,
15296  	signControls: signControls$h,
15297  	windowMoveAlert: windowMoveAlert$h,
15298  	windowResize: windowResize$h,
15299  	windowResizeHeading: windowResizeHeading$h,
15300  	closeButtonLabel: closeButtonLabel$h,
15301  	width: width$h,
15302  	height: height$h,
15303  	resultsSummary1: resultsSummary1$h,
15304  	resultsSummary2: resultsSummary2$h,
15305  	resultsSummary3: resultsSummary3$h,
15306  	noResultsFound: noResultsFound$h,
15307  	searchButtonLabel: searchButtonLabel$h,
15308  	hour: hour$h,
15309  	minute: minute$h,
15310  	second: second$h,
15311  	hours: hours$h,
15312  	minutes: minutes$h,
15313  	seconds: seconds$h,
15314  	vtsHeading: vtsHeading$h,
15315  	vtsInstructions1: vtsInstructions1$h,
15316  	vtsInstructions2: vtsInstructions2$h,
15317  	vtsInstructions3: vtsInstructions3$h,
15318  	vtsInstructions4: vtsInstructions4$h,
15319  	vtsInstructions5: vtsInstructions5$h,
15320  	vtsSelectLanguage: vtsSelectLanguage$h,
15321  	vtsSave: vtsSave$h,
15322  	vtsReturn: vtsReturn$h,
15323  	vtsCancel: vtsCancel$h,
15324  	vtsRow: vtsRow$h,
15325  	vtsKind: vtsKind$h,
15326  	vtsStart: vtsStart$h,
15327  	vtsEnd: vtsEnd$h,
15328  	vtsContent: vtsContent$h,
15329  	vtsActions: vtsActions$h,
15330  	vtsNewRow: vtsNewRow$h,
15331  	vtsDeletedRow: vtsDeletedRow$h,
15332  	vtsMovedRow: vtsMovedRow$h
15333  };
15334
15335  var playerHeading$g = "Media player";
15336  var audioPlayer$g = "Audio player";
15337  var videoPlayer$g = "Video player";
15338  var faster$g = "Faster";
15339  var slower$g = "Slower";
15340  var play$g = "Play";
15341  var pause$g = "Pause";
15342  var restart$g = "Restart";
15343  var prevTrack$g = "Previous track";
15344  var nextTrack$g = "Next track";
15345  var rewind$g = "Rewind";
15346  var forward$g = "Forward";
15347  var captions$g = "Captions";
15348  var showCaptions$g = "Show captions";
15349  var hideCaptions$g = "Hide captions";
15350  var captionsOff$g = "Captions off";
15351  var showTranscript$g = "Show transcript";
15352  var hideTranscript$g = "Hide transcript";
15353  var turnOnDescriptions$g = "Turn on descriptions";
15354  var turnOffDescriptions$g = "Turn off descriptions";
15355  var chapters$g = "Chapters";
15356  var language$g = "Language";
15357  var sign$g = "Sign language";
15358  var showSign$g = "Show sign language";
15359  var hideSign$g = "Hide sign language";
15360  var seekbarLabel$g = "timeline";
15361  var mute$g = "Mute";
15362  var unmute$g = "Unmute";
15363  var volume$g = "Volume";
15364  var volumeUpDown$g = "Volume up down";
15365  var preferences$g = "Preferences";
15366  var enterFullScreen$g = "Enter full screen";
15367  var exitFullScreen$g = "Exit full screen";
15368  var speed$g = "Speed";
15369  var spacebar$g = "spacebar";
15370  var transcriptTitle$g = "Transcript";
15371  var lyricsTitle$g = "Lyrics";
15372  var autoScroll$g = "Auto scroll";
15373  var statusPlaying$g = "Playing";
15374  var statusPaused$g = "Paused";
15375  var statusStopped$g = "Stopped";
15376  var statusBuffering$g = "Buffering";
15377  var statusEnd$g = "End of track";
15378  var selectedTrack$f = "Selected Track";
15379  var alertDescribedVersion$g = "Using the audio described version of this video";
15380  var alertNonDescribedVersion$g = "Using the non-described version of this video";
15381  var prefMenuCaptions$g = "Captions";
15382  var prefVoicedCaptions$g = "Spoken Captions";
15383  var prefMenuDescriptions$g = "Descriptions";
15384  var prefMenuKeyboard$g = "Keyboard";
15385  var prefMenuTranscript$g = "Transcript";
15386  var prefTitleCaptions$g = "Captions Preferences";
15387  var prefTitleDescriptions$g = "Audio Description Preferences";
15388  var prefTitleKeyboard$g = "Keyboard Preferences";
15389  var prefTitleTranscript$f = "Transcript Preferences";
15390  var prefIntroDescription1$f = "This media player supports audio description in two ways: ";
15391  var prefDescription1$g = "The current video has an alternative described version and text-based description, announced by screen reader.";
15392  var prefDescription2$g = "The current video has text-based description.";
15393  var prefDescription3$g = "The current video has an alternative described version.";
15394  var prefIntroDescriptionNone$g = "The current video has no audio description in either format.";
15395  var prefIntroDescription3$g = "Use the following form to set your preferences related to text-based audio description.";
15396  var prefIntroDescription4$g = "After you save your settings, audio description can be toggled on/off using the Description button.";
15397  var prefIntroKeyboard1$g = "The media player on this web page can be operated from anywhere on the page using keyboard shortcuts (see below for a list).";
15398  var prefIntroKeyboard2$g = "Modifier keys (Shift, Alt, and Control) can be assigned below.";
15399  var prefIntroKeyboard3$g = "NOTE: Some key combinations might conflict with keys used by your browser and/or other software applications. Try various combinations of modifier keys to find one that works for you.";
15400  var prefHeadingKeyboard1$g = "Modifier keys used for shortcuts";
15401  var prefHeadingKeyboard2$g = "Current keyboard shortcuts";
15402  var prefHeadingDescription$g = "Audio description";
15403  var prefHeadingTextDescription$g = "Text-based audio description";
15404  var prefAltKey$g = "Alt";
15405  var prefCtrlKey$g = "Control";
15406  var prefShiftKey$g = "Shift";
15407  var prefNoKeyShortcuts$g = "Disable keyboard shortcuts";
15408  var escapeKey$g = "Escape";
15409  var escapeKeyFunction$g = "Close current dialog or popup menu";
15410  var prefDescPause$g = "Automatically pause video when description starts";
15411  var prefDescVisible$g = "Make description visible";
15412  var prefDescVoice$g = "Voice";
15413  var prefDescRate$g = "Spoken Description Rate";
15414  var prefCaptionRate$g = "Spoken Caption Rate";
15415  var prefDescPitch$g = "Pitch";
15416  var prefDescPitch1$g = "Very low";
15417  var prefDescPitch2$g = "Low";
15418  var prefDescPitch3$g = "Default";
15419  var prefDescPitch4$g = "High";
15420  var prefDescPitch5$g = "Very high";
15421  var sampleDescriptionText$g = "Adjust settings to hear this sample text.";
15422  var prefHighlight$g = "Highlight transcript as media plays";
15423  var prefTabbable$g = "Keyboard-enable transcript";
15424  var prefCaptionsFont$g = "Font";
15425  var prefCaptionsColor$g = "Text Color";
15426  var prefCaptionsBGColor$g = "Background";
15427  var prefCaptionsSize$g = "Font Size";
15428  var prefCaptionsOpacity$g = "Opacity";
15429  var prefCaptionsStyle$g = "Style";
15430  var serif$g = "serif";
15431  var sans$g = "sans-serif";
15432  var cursive$g = "cursive";
15433  var fantasy$g = "fantasy";
15434  var monospace$g = "monospace";
15435  var white$g = "white";
15436  var yellow$g = "yellow";
15437  var green$g = "green";
15438  var cyan$f = "cyan";
15439  var blue$g = "blue";
15440  var magenta$g = "magenta";
15441  var red$g = "red";
15442  var black$g = "black";
15443  var transparent$g = "transparent";
15444  var solid$g = "solid";
15445  var captionsStylePopOn$g = "Pop-on";
15446  var captionsStyleRollUp$g = "Roll-up";
15447  var prefCaptionsPosition$g = "Position";
15448  var captionsPositionOverlay$g = "Overlay";
15449  var captionsPositionBelow$g = "Below video";
15450  var sampleCaptionText$g = "Sample caption text";
15451  var prefSuccess$g = "Your changes have been saved.";
15452  var prefNoChange$g = "You didn't make any changes.";
15453  var save$g = "Save";
15454  var cancel$g = "Cancel";
15455  var dismissButton$g = "Dismiss";
15456  var windowButtonLabel$g = "Window options";
15457  var windowMove$g = "Move";
15458  var windowMoveLeft$g = "Window moved left";
15459  var windowMoveRight$g = "Window moved right";
15460  var windowMoveUp$g = "Window moved up";
15461  var windowMoveDown$g = "Window moved down";
15462  var windowMoveStopped$g = "Window move stopped";
15463  var transcriptControls$g = "Transcript Window Controls";
15464  var signControls$g = "Sign Language Window Controls";
15465  var windowMoveAlert$g = "Drag or use arrow keys to move the window; Enter to stop";
15466  var windowResize$g = "Resize";
15467  var windowResizeHeading$g = "Resize Window";
15468  var closeButtonLabel$g = "Close";
15469  var width$g = "Width";
15470  var height$g = "Height";
15471  var resultsSummary1$g = "You searched for:";
15472  var resultsSummary2$g = "Found %1 matching items.";
15473  var resultsSummary3$g = "Click the time associated with any item to play the video from that point.";
15474  var noResultsFound$g = "No results found.";
15475  var searchButtonLabel$g = "Play at %1";
15476  var hour$g = "hour";
15477  var minute$g = "minute";
15478  var second$g = "second";
15479  var hours$g = "hours";
15480  var minutes$g = "minutes";
15481  var seconds$g = "seconds";
15482  var vtsHeading$g = "Video Transcript Sorter";
15483  var vtsInstructions1$g = "Use the Video Transcript Sorter to modify text tracks:";
15484  var vtsInstructions2$g = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
15485  var vtsInstructions3$g = "Modify content or start/end times (all are directly editable within the table).";
15486  var vtsInstructions4$g = "Add new content, such as chapters or descriptions.";
15487  var vtsInstructions5$g = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
15488  var vtsSelectLanguage$g = "Select a language";
15489  var vtsSave$g = "Generate new .vtt content";
15490  var vtsReturn$g = "Return to Editor";
15491  var vtsCancel$g = "Cancelling saving. Any edits you made have been restored in the VTS table.";
15492  var vtsRow$g = "Row";
15493  var vtsKind$g = "Kind";
15494  var vtsStart$g = "Start";
15495  var vtsEnd$g = "End";
15496  var vtsContent$g = "Content";
15497  var vtsActions$g = "Actions";
15498  var vtsNewRow$g = "A new row %1 has been inserted.";
15499  var vtsDeletedRow$g = "Row %1 has been deleted.";
15500  var vtsMovedRow$g = "Row %1 has been moved %2 and is now Row %3.";
15501  var en = {
15502  	playerHeading: playerHeading$g,
15503  	audioPlayer: audioPlayer$g,
15504  	videoPlayer: videoPlayer$g,
15505  	faster: faster$g,
15506  	slower: slower$g,
15507  	play: play$g,
15508  	pause: pause$g,
15509  	restart: restart$g,
15510  	prevTrack: prevTrack$g,
15511  	nextTrack: nextTrack$g,
15512  	rewind: rewind$g,
15513  	forward: forward$g,
15514  	captions: captions$g,
15515  	showCaptions: showCaptions$g,
15516  	hideCaptions: hideCaptions$g,
15517  	captionsOff: captionsOff$g,
15518  	showTranscript: showTranscript$g,
15519  	hideTranscript: hideTranscript$g,
15520  	turnOnDescriptions: turnOnDescriptions$g,
15521  	turnOffDescriptions: turnOffDescriptions$g,
15522  	chapters: chapters$g,
15523  	language: language$g,
15524  	sign: sign$g,
15525  	showSign: showSign$g,
15526  	hideSign: hideSign$g,
15527  	seekbarLabel: seekbarLabel$g,
15528  	mute: mute$g,
15529  	unmute: unmute$g,
15530  	volume: volume$g,
15531  	volumeUpDown: volumeUpDown$g,
15532  	preferences: preferences$g,
15533  	enterFullScreen: enterFullScreen$g,
15534  	exitFullScreen: exitFullScreen$g,
15535  	speed: speed$g,
15536  	spacebar: spacebar$g,
15537  	transcriptTitle: transcriptTitle$g,
15538  	lyricsTitle: lyricsTitle$g,
15539  	autoScroll: autoScroll$g,
15540  	statusPlaying: statusPlaying$g,
15541  	statusPaused: statusPaused$g,
15542  	statusStopped: statusStopped$g,
15543  	statusBuffering: statusBuffering$g,
15544  	statusEnd: statusEnd$g,
15545  	selectedTrack: selectedTrack$f,
15546  	alertDescribedVersion: alertDescribedVersion$g,
15547  	alertNonDescribedVersion: alertNonDescribedVersion$g,
15548  	prefMenuCaptions: prefMenuCaptions$g,
15549  	prefVoicedCaptions: prefVoicedCaptions$g,
15550  	prefMenuDescriptions: prefMenuDescriptions$g,
15551  	prefMenuKeyboard: prefMenuKeyboard$g,
15552  	prefMenuTranscript: prefMenuTranscript$g,
15553  	prefTitleCaptions: prefTitleCaptions$g,
15554  	prefTitleDescriptions: prefTitleDescriptions$g,
15555  	prefTitleKeyboard: prefTitleKeyboard$g,
15556  	prefTitleTranscript: prefTitleTranscript$f,
15557  	prefIntroDescription1: prefIntroDescription1$f,
15558  	prefDescription1: prefDescription1$g,
15559  	prefDescription2: prefDescription2$g,
15560  	prefDescription3: prefDescription3$g,
15561  	prefIntroDescriptionNone: prefIntroDescriptionNone$g,
15562  	prefIntroDescription3: prefIntroDescription3$g,
15563  	prefIntroDescription4: prefIntroDescription4$g,
15564  	prefIntroKeyboard1: prefIntroKeyboard1$g,
15565  	prefIntroKeyboard2: prefIntroKeyboard2$g,
15566  	prefIntroKeyboard3: prefIntroKeyboard3$g,
15567  	prefHeadingKeyboard1: prefHeadingKeyboard1$g,
15568  	prefHeadingKeyboard2: prefHeadingKeyboard2$g,
15569  	prefHeadingDescription: prefHeadingDescription$g,
15570  	prefHeadingTextDescription: prefHeadingTextDescription$g,
15571  	prefAltKey: prefAltKey$g,
15572  	prefCtrlKey: prefCtrlKey$g,
15573  	prefShiftKey: prefShiftKey$g,
15574  	prefNoKeyShortcuts: prefNoKeyShortcuts$g,
15575  	escapeKey: escapeKey$g,
15576  	escapeKeyFunction: escapeKeyFunction$g,
15577  	prefDescPause: prefDescPause$g,
15578  	prefDescVisible: prefDescVisible$g,
15579  	prefDescVoice: prefDescVoice$g,
15580  	prefDescRate: prefDescRate$g,
15581  	prefCaptionRate: prefCaptionRate$g,
15582  	prefDescPitch: prefDescPitch$g,
15583  	prefDescPitch1: prefDescPitch1$g,
15584  	prefDescPitch2: prefDescPitch2$g,
15585  	prefDescPitch3: prefDescPitch3$g,
15586  	prefDescPitch4: prefDescPitch4$g,
15587  	prefDescPitch5: prefDescPitch5$g,
15588  	sampleDescriptionText: sampleDescriptionText$g,
15589  	prefHighlight: prefHighlight$g,
15590  	prefTabbable: prefTabbable$g,
15591  	prefCaptionsFont: prefCaptionsFont$g,
15592  	prefCaptionsColor: prefCaptionsColor$g,
15593  	prefCaptionsBGColor: prefCaptionsBGColor$g,
15594  	prefCaptionsSize: prefCaptionsSize$g,
15595  	prefCaptionsOpacity: prefCaptionsOpacity$g,
15596  	prefCaptionsStyle: prefCaptionsStyle$g,
15597  	serif: serif$g,
15598  	sans: sans$g,
15599  	cursive: cursive$g,
15600  	fantasy: fantasy$g,
15601  	monospace: monospace$g,
15602  	white: white$g,
15603  	yellow: yellow$g,
15604  	green: green$g,
15605  	cyan: cyan$f,
15606  	blue: blue$g,
15607  	magenta: magenta$g,
15608  	red: red$g,
15609  	black: black$g,
15610  	transparent: transparent$g,
15611  	solid: solid$g,
15612  	captionsStylePopOn: captionsStylePopOn$g,
15613  	captionsStyleRollUp: captionsStyleRollUp$g,
15614  	prefCaptionsPosition: prefCaptionsPosition$g,
15615  	captionsPositionOverlay: captionsPositionOverlay$g,
15616  	captionsPositionBelow: captionsPositionBelow$g,
15617  	sampleCaptionText: sampleCaptionText$g,
15618  	prefSuccess: prefSuccess$g,
15619  	prefNoChange: prefNoChange$g,
15620  	save: save$g,
15621  	cancel: cancel$g,
15622  	dismissButton: dismissButton$g,
15623  	windowButtonLabel: windowButtonLabel$g,
15624  	windowMove: windowMove$g,
15625  	windowMoveLeft: windowMoveLeft$g,
15626  	windowMoveRight: windowMoveRight$g,
15627  	windowMoveUp: windowMoveUp$g,
15628  	windowMoveDown: windowMoveDown$g,
15629  	windowMoveStopped: windowMoveStopped$g,
15630  	transcriptControls: transcriptControls$g,
15631  	signControls: signControls$g,
15632  	windowMoveAlert: windowMoveAlert$g,
15633  	windowResize: windowResize$g,
15634  	windowResizeHeading: windowResizeHeading$g,
15635  	closeButtonLabel: closeButtonLabel$g,
15636  	width: width$g,
15637  	height: height$g,
15638  	resultsSummary1: resultsSummary1$g,
15639  	resultsSummary2: resultsSummary2$g,
15640  	resultsSummary3: resultsSummary3$g,
15641  	noResultsFound: noResultsFound$g,
15642  	searchButtonLabel: searchButtonLabel$g,
15643  	hour: hour$g,
15644  	minute: minute$g,
15645  	second: second$g,
15646  	hours: hours$g,
15647  	minutes: minutes$g,
15648  	seconds: seconds$g,
15649  	vtsHeading: vtsHeading$g,
15650  	vtsInstructions1: vtsInstructions1$g,
15651  	vtsInstructions2: vtsInstructions2$g,
15652  	vtsInstructions3: vtsInstructions3$g,
15653  	vtsInstructions4: vtsInstructions4$g,
15654  	vtsInstructions5: vtsInstructions5$g,
15655  	vtsSelectLanguage: vtsSelectLanguage$g,
15656  	vtsSave: vtsSave$g,
15657  	vtsReturn: vtsReturn$g,
15658  	vtsCancel: vtsCancel$g,
15659  	vtsRow: vtsRow$g,
15660  	vtsKind: vtsKind$g,
15661  	vtsStart: vtsStart$g,
15662  	vtsEnd: vtsEnd$g,
15663  	vtsContent: vtsContent$g,
15664  	vtsActions: vtsActions$g,
15665  	vtsNewRow: vtsNewRow$g,
15666  	vtsDeletedRow: vtsDeletedRow$g,
15667  	vtsMovedRow: vtsMovedRow$g
15668  };
15669
15670  var playerHeading$f = "Media player";
15671  var audioPlayer$f = "Audio player";
15672  var videoPlayer$f = "Video player";
15673  var faster$f = "Rápido";
15674  var slower$f = "Lento";
15675  var play$f = "Play";
15676  var pause$f = "Pausa";
15677  var restart$f = "Reiniciar";
15678  var prevTrack$f = "Pista Anterior";
15679  var nextTrack$f = "Siguiente Pista";
15680  var rewind$f = "Rebobinar";
15681  var forward$f = "Adelantar";
15682  var captions$f = "Subtítulos";
15683  var showCaptions$f = "Mostrar subtítulos";
15684  var hideCaptions$f = "Ocultar subtítulos";
15685  var captionsOff$f = "Sin subtítulos";
15686  var showTranscript$f = "Mostrar transcripción";
15687  var hideTranscript$f = "Ocultar transcripción";
15688  var turnOnDescriptions$f = "Habilitar descripciones";
15689  var turnOffDescriptions$f = "Deshabilitar descripciones";
15690  var chapters$f = "Capítulos";
15691  var language$f = "Idioma";
15692  var sign$f = "Lengua de señas";
15693  var showSign$f = "Mostrar lengua de señas";
15694  var hideSign$f = "Ocultar lengua de señas";
15695  var seekbarLabel$f = "timeline";
15696  var mute$f = "Silenciar";
15697  var unmute$f = "Habilitar sonido";
15698  var volume$f = "Volumen";
15699  var volumeUpDown$f = "Bajar sonido";
15700  var preferences$f = "Preferencias";
15701  var enterFullScreen$f = "Ver a pantalla completa";
15702  var exitFullScreen$f = "Salir de pantalla completa";
15703  var speed$f = "Velocidad";
15704  var spacebar$f = "Barra espaciadora";
15705  var transcriptTitle$f = "Transcript";
15706  var lyricsTitle$f = "Letra";
15707  var autoScroll$f = "Desplazamiento automático";
15708  var statusPlaying$f = "Reproduciendo";
15709  var statusPaused$f = "Pausado";
15710  var statusStopped$f = "Detenido";
15711  var statusBuffering$f = "Almacenando";
15712  var statusEnd$f = "Fin de pista";
15713  var selectedTrack$e = "Pista seleccionada";
15714  var alertDescribedVersion$f = "Utilizando la versión audiodescrita del vídeo";
15715  var alertNonDescribedVersion$f = "Utilizando la versión no descrita de este vídeo";
15716  var prefMenuCaptions$f = "Subtítulos";
15717  var prefVoicedCaptions$f = "Spoken Captions";
15718  var prefMenuDescriptions$f = "Descripciones";
15719  var prefMenuKeyboard$f = "Teclado";
15720  var prefMenuTranscript$f = "Transcripción";
15721  var prefTitleCaptions$f = "Preferencias de subtítulos";
15722  var prefTitleDescriptions$f = "Preferencias de audiodescripción";
15723  var prefTitleKeyboard$f = "Preferencias de teclado";
15724  var prefTitleTranscript$e = "Preferencias de transcripción";
15725  var prefIntroDescription1$e = "Este reproductor soporta la audiodescripción de dos maneras: ";
15726  var prefDescription1$f = "El vídeo actual tiene una versión alternativa con descripción, descripción en texto.";
15727  var prefDescription2$f = "El vídeo actual tiene versión alternativa del vídeo, descrito.";
15728  var prefDescription3$f = "El vídeo actual tiene descripción en texto, leída por el lector de pantalla.";
15729  var prefIntroDescriptionNone$f = "El vídeo actual no tiene audiodescripción de ninguna manera.";
15730  var prefIntroDescription3$f = "Utilice el siguiente formulario para establecer sus preferencias en cuanto a la audiodescripción en texto.";
15731  var prefIntroDescription4$f = "Una vez guardadas sus preferencias, la audiodescripción puede habilitarse o deshabilitarse mediante el botón Descripción.";
15732  var prefIntroKeyboard1$f = "El reproductor en esta página puede manejarse desde cualquier parte de la página utilizando los atajos de teclado (vea la lista más abajo).";
15733  var prefIntroKeyboard2$f = "Las teclas modificadoras (Mayúsculas, Alt, Control) pueden definirse más abajo.";
15734  var prefIntroKeyboard3$f = "NOTA: Algunas combinaciones de teclas pueden entrar en conflicto con teclas utilizadas por su navegador y/o otras aplicaciones. Intente varias combinaciones de teclas modificadoras para encontrar la que funciona bien en su caso.";
15735  var prefHeadingKeyboard1$f = "Teclas modificadoras utilizadas para atajos de teclado";
15736  var prefHeadingKeyboard2$f = "Atajos de teclado definidos actualmente";
15737  var prefHeadingDescription$f = "Audiodescrita";
15738  var prefHeadingTextDescription$f = "Audiodescrita en texto";
15739  var prefAltKey$f = "Alt";
15740  var prefCtrlKey$f = "Control";
15741  var prefShiftKey$f = "Mayúsculas";
15742  var prefNoKeyShortcuts$f = "Disable keyboard shortcuts";
15743  var escapeKey$f = "Escape";
15744  var escapeKeyFunction$f = "Cerrar el cuadro de diálogo o menú contextual";
15745  var prefDescPause$f = "Pausar automáticamente el video cuando arranque una descripción";
15746  var prefDescVisible$f = "Hacer visibles las descripciones en texto si se están usando";
15747  var prefDescVoice$f = "Voz";
15748  var prefDescRate$f = "Spoken Description Rate";
15749  var prefCaptionRate$f = "Spoken Caption Rate";
15750  var prefDescPitch$f = "Tono";
15751  var prefDescPitch1$f = "Muy grave";
15752  var prefDescPitch2$f = "Grave";
15753  var prefDescPitch3$f = "Normal";
15754  var prefDescPitch4$f = "Aguda";
15755  var prefDescPitch5$f = "Muy aguda";
15756  var sampleDescriptionText$f = "Ajuste la configuración para escuchar este texto de muestra";
15757  var prefHighlight$f = "Resalte la transcripción a medida que avance el contenido";
15758  var prefTabbable$f = "Transcripción manejable por teclado";
15759  var prefCaptionsFont$f = "Fuente";
15760  var prefCaptionsColor$f = "Color del texto";
15761  var prefCaptionsBGColor$f = "Fondo";
15762  var prefCaptionsSize$f = "Tamaño de Fuente";
15763  var prefCaptionsOpacity$f = "Opacidad";
15764  var prefCaptionsStyle$f = "Estilo";
15765  var serif$f = "serif";
15766  var sans$f = "sans-serif";
15767  var cursive$f = "cursiva";
15768  var fantasy$f = "fantasía";
15769  var monospace$f = "mono espaciada";
15770  var white$f = "blanco";
15771  var yellow$f = "amarillo";
15772  var green$f = "verde";
15773  var cyan$e = "cyan";
15774  var blue$f = "azul";
15775  var magenta$f = "magenta";
15776  var red$f = "rojo";
15777  var black$f = "negro";
15778  var transparent$f = "transparente";
15779  var solid$f = "sólido";
15780  var captionsStylePopOn$f = "Pop-on";
15781  var captionsStyleRollUp$f = "Roll-up";
15782  var prefCaptionsPosition$f = "Posición";
15783  var captionsPositionOverlay$f = "Cubrir";
15784  var captionsPositionBelow$f = "Debajo del vídeo";
15785  var sampleCaptionText$f = "Texto de ejemplo de subtítulo";
15786  var prefSuccess$f = "Los cambios han sido guardados.";
15787  var prefNoChange$f = "No se ha hecho ningún cambio.";
15788  var save$f = "Guardar";
15789  var cancel$f = "Cancelar";
15790  var dismissButton$f = "Dismiss";
15791  var windowButtonLabel$f = "Opciones en Windows";
15792  var windowMove$f = "Mover";
15793  var windowMoveLeft$f = "Window moved left";
15794  var windowMoveRight$f = "Window moved right";
15795  var windowMoveUp$f = "Window moved up";
15796  var windowMoveDown$f = "Window moved down";
15797  var windowMoveStopped$f = "Window move stopped";
15798  var transcriptControls$f = "Transcript Window Controls";
15799  var signControls$f = "Sign Language Window Controls";
15800  var windowMoveAlert$f = "Arrastre o use las teclas de flecha para mover la ventana, pulse Enter para parar.";
15801  var windowResize$f = "Redimensionar";
15802  var windowResizeHeading$f = "Redimensionar la ventana con el intérprete";
15803  var closeButtonLabel$f = "Cerrar";
15804  var width$f = "Ancho";
15805  var height$f = "Alto";
15806  var resultsSummary1$f = "You searched for:";
15807  var resultsSummary2$f = "Found %1 matching items.";
15808  var resultsSummary3$f = "Click the time associated with any item to play the video from that point.";
15809  var noResultsFound$f = "No results found.";
15810  var searchButtonLabel$f = "Play at %1";
15811  var hour$f = "hour";
15812  var minute$f = "minute";
15813  var second$f = "second";
15814  var hours$f = "hours";
15815  var minutes$f = "minutes";
15816  var seconds$f = "seconds";
15817  var vtsHeading$f = "Video Transcript Sorter";
15818  var vtsInstructions1$f = "Use the Video Transcript Sorter to modify text tracks:";
15819  var vtsInstructions2$f = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
15820  var vtsInstructions3$f = "Modify content or start/end times (all are directly editable within the table).";
15821  var vtsInstructions4$f = "Add new content, such as chapters or descriptions.";
15822  var vtsInstructions5$f = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
15823  var vtsSelectLanguage$f = "Select a language";
15824  var vtsSave$f = "Generate new .vtt content";
15825  var vtsReturn$f = "Return to Editor";
15826  var vtsCancel$f = "Cancelling saving. Any edits you made have been restored in the VTS table.";
15827  var vtsRow$f = "Row";
15828  var vtsKind$f = "Kind";
15829  var vtsStart$f = "Start";
15830  var vtsEnd$f = "End";
15831  var vtsContent$f = "Content";
15832  var vtsActions$f = "Actions";
15833  var vtsNewRow$f = "A new row %1 has been inserted.";
15834  var vtsDeletedRow$f = "Row %1 has been deleted.";
15835  var vtsMovedRow$f = "Row %1 has been moved %2 and is now Row %3.";
15836  var es = {
15837  	playerHeading: playerHeading$f,
15838  	audioPlayer: audioPlayer$f,
15839  	videoPlayer: videoPlayer$f,
15840  	faster: faster$f,
15841  	slower: slower$f,
15842  	play: play$f,
15843  	pause: pause$f,
15844  	restart: restart$f,
15845  	prevTrack: prevTrack$f,
15846  	nextTrack: nextTrack$f,
15847  	rewind: rewind$f,
15848  	forward: forward$f,
15849  	captions: captions$f,
15850  	showCaptions: showCaptions$f,
15851  	hideCaptions: hideCaptions$f,
15852  	captionsOff: captionsOff$f,
15853  	showTranscript: showTranscript$f,
15854  	hideTranscript: hideTranscript$f,
15855  	turnOnDescriptions: turnOnDescriptions$f,
15856  	turnOffDescriptions: turnOffDescriptions$f,
15857  	chapters: chapters$f,
15858  	language: language$f,
15859  	sign: sign$f,
15860  	showSign: showSign$f,
15861  	hideSign: hideSign$f,
15862  	seekbarLabel: seekbarLabel$f,
15863  	mute: mute$f,
15864  	unmute: unmute$f,
15865  	volume: volume$f,
15866  	volumeUpDown: volumeUpDown$f,
15867  	preferences: preferences$f,
15868  	enterFullScreen: enterFullScreen$f,
15869  	exitFullScreen: exitFullScreen$f,
15870  	speed: speed$f,
15871  	spacebar: spacebar$f,
15872  	transcriptTitle: transcriptTitle$f,
15873  	lyricsTitle: lyricsTitle$f,
15874  	autoScroll: autoScroll$f,
15875  	statusPlaying: statusPlaying$f,
15876  	statusPaused: statusPaused$f,
15877  	statusStopped: statusStopped$f,
15878  	statusBuffering: statusBuffering$f,
15879  	statusEnd: statusEnd$f,
15880  	selectedTrack: selectedTrack$e,
15881  	alertDescribedVersion: alertDescribedVersion$f,
15882  	alertNonDescribedVersion: alertNonDescribedVersion$f,
15883  	prefMenuCaptions: prefMenuCaptions$f,
15884  	prefVoicedCaptions: prefVoicedCaptions$f,
15885  	prefMenuDescriptions: prefMenuDescriptions$f,
15886  	prefMenuKeyboard: prefMenuKeyboard$f,
15887  	prefMenuTranscript: prefMenuTranscript$f,
15888  	prefTitleCaptions: prefTitleCaptions$f,
15889  	prefTitleDescriptions: prefTitleDescriptions$f,
15890  	prefTitleKeyboard: prefTitleKeyboard$f,
15891  	prefTitleTranscript: prefTitleTranscript$e,
15892  	prefIntroDescription1: prefIntroDescription1$e,
15893  	prefDescription1: prefDescription1$f,
15894  	prefDescription2: prefDescription2$f,
15895  	prefDescription3: prefDescription3$f,
15896  	prefIntroDescriptionNone: prefIntroDescriptionNone$f,
15897  	prefIntroDescription3: prefIntroDescription3$f,
15898  	prefIntroDescription4: prefIntroDescription4$f,
15899  	prefIntroKeyboard1: prefIntroKeyboard1$f,
15900  	prefIntroKeyboard2: prefIntroKeyboard2$f,
15901  	prefIntroKeyboard3: prefIntroKeyboard3$f,
15902  	prefHeadingKeyboard1: prefHeadingKeyboard1$f,
15903  	prefHeadingKeyboard2: prefHeadingKeyboard2$f,
15904  	prefHeadingDescription: prefHeadingDescription$f,
15905  	prefHeadingTextDescription: prefHeadingTextDescription$f,
15906  	prefAltKey: prefAltKey$f,
15907  	prefCtrlKey: prefCtrlKey$f,
15908  	prefShiftKey: prefShiftKey$f,
15909  	prefNoKeyShortcuts: prefNoKeyShortcuts$f,
15910  	escapeKey: escapeKey$f,
15911  	escapeKeyFunction: escapeKeyFunction$f,
15912  	prefDescPause: prefDescPause$f,
15913  	prefDescVisible: prefDescVisible$f,
15914  	prefDescVoice: prefDescVoice$f,
15915  	prefDescRate: prefDescRate$f,
15916  	prefCaptionRate: prefCaptionRate$f,
15917  	prefDescPitch: prefDescPitch$f,
15918  	prefDescPitch1: prefDescPitch1$f,
15919  	prefDescPitch2: prefDescPitch2$f,
15920  	prefDescPitch3: prefDescPitch3$f,
15921  	prefDescPitch4: prefDescPitch4$f,
15922  	prefDescPitch5: prefDescPitch5$f,
15923  	sampleDescriptionText: sampleDescriptionText$f,
15924  	prefHighlight: prefHighlight$f,
15925  	prefTabbable: prefTabbable$f,
15926  	prefCaptionsFont: prefCaptionsFont$f,
15927  	prefCaptionsColor: prefCaptionsColor$f,
15928  	prefCaptionsBGColor: prefCaptionsBGColor$f,
15929  	prefCaptionsSize: prefCaptionsSize$f,
15930  	prefCaptionsOpacity: prefCaptionsOpacity$f,
15931  	prefCaptionsStyle: prefCaptionsStyle$f,
15932  	serif: serif$f,
15933  	sans: sans$f,
15934  	cursive: cursive$f,
15935  	fantasy: fantasy$f,
15936  	monospace: monospace$f,
15937  	white: white$f,
15938  	yellow: yellow$f,
15939  	green: green$f,
15940  	cyan: cyan$e,
15941  	blue: blue$f,
15942  	magenta: magenta$f,
15943  	red: red$f,
15944  	black: black$f,
15945  	transparent: transparent$f,
15946  	solid: solid$f,
15947  	captionsStylePopOn: captionsStylePopOn$f,
15948  	captionsStyleRollUp: captionsStyleRollUp$f,
15949  	prefCaptionsPosition: prefCaptionsPosition$f,
15950  	captionsPositionOverlay: captionsPositionOverlay$f,
15951  	captionsPositionBelow: captionsPositionBelow$f,
15952  	sampleCaptionText: sampleCaptionText$f,
15953  	prefSuccess: prefSuccess$f,
15954  	prefNoChange: prefNoChange$f,
15955  	save: save$f,
15956  	cancel: cancel$f,
15957  	dismissButton: dismissButton$f,
15958  	windowButtonLabel: windowButtonLabel$f,
15959  	windowMove: windowMove$f,
15960  	windowMoveLeft: windowMoveLeft$f,
15961  	windowMoveRight: windowMoveRight$f,
15962  	windowMoveUp: windowMoveUp$f,
15963  	windowMoveDown: windowMoveDown$f,
15964  	windowMoveStopped: windowMoveStopped$f,
15965  	transcriptControls: transcriptControls$f,
15966  	signControls: signControls$f,
15967  	windowMoveAlert: windowMoveAlert$f,
15968  	windowResize: windowResize$f,
15969  	windowResizeHeading: windowResizeHeading$f,
15970  	closeButtonLabel: closeButtonLabel$f,
15971  	width: width$f,
15972  	height: height$f,
15973  	resultsSummary1: resultsSummary1$f,
15974  	resultsSummary2: resultsSummary2$f,
15975  	resultsSummary3: resultsSummary3$f,
15976  	noResultsFound: noResultsFound$f,
15977  	searchButtonLabel: searchButtonLabel$f,
15978  	hour: hour$f,
15979  	minute: minute$f,
15980  	second: second$f,
15981  	hours: hours$f,
15982  	minutes: minutes$f,
15983  	seconds: seconds$f,
15984  	vtsHeading: vtsHeading$f,
15985  	vtsInstructions1: vtsInstructions1$f,
15986  	vtsInstructions2: vtsInstructions2$f,
15987  	vtsInstructions3: vtsInstructions3$f,
15988  	vtsInstructions4: vtsInstructions4$f,
15989  	vtsInstructions5: vtsInstructions5$f,
15990  	vtsSelectLanguage: vtsSelectLanguage$f,
15991  	vtsSave: vtsSave$f,
15992  	vtsReturn: vtsReturn$f,
15993  	vtsCancel: vtsCancel$f,
15994  	vtsRow: vtsRow$f,
15995  	vtsKind: vtsKind$f,
15996  	vtsStart: vtsStart$f,
15997  	vtsEnd: vtsEnd$f,
15998  	vtsContent: vtsContent$f,
15999  	vtsActions: vtsActions$f,
16000  	vtsNewRow: vtsNewRow$f,
16001  	vtsDeletedRow: vtsDeletedRow$f,
16002  	vtsMovedRow: vtsMovedRow$f
16003  };
16004
16005  var playerHeading$e = "Lecteur multimédia";
16006  var audioPlayer$e = "Audio player";
16007  var videoPlayer$e = "Video player";
16008  var faster$e = "Plus rapidement";
16009  var slower$e = "Plus lentement";
16010  var play$e = "Lecture";
16011  var pause$e = "Pause";
16012  var restart$e = "Redémarrer";
16013  var prevTrack$e = "Piste Précédente";
16014  var nextTrack$e = "Piste Suivante";
16015  var rewind$e = "Reculer";
16016  var forward$e = "Avancer";
16017  var captions$e = "Sous-titres";
16018  var showCaptions$e = "Afficher les sous-titres";
16019  var hideCaptions$e = "Masquer les sous-titres";
16020  var captionsOff$e = "Sous-titres désactivés";
16021  var showTranscript$e = "Afficher la transcription";
16022  var hideTranscript$e = "Masquer la transcription";
16023  var turnOnDescriptions$e = "Activer les descriptions";
16024  var turnOffDescriptions$e = "Désactiver les descriptions";
16025  var chapters$e = "Chapitres";
16026  var language$e = "Langue";
16027  var sign$e = "Langage gestuel";
16028  var showSign$e = "Afficher le langage gestuel";
16029  var hideSign$e = "Masque le langage gestuel";
16030  var seekbarLabel$e = "timeline";
16031  var mute$e = "Son désactivé";
16032  var unmute$e = "Son activé";
16033  var volume$e = "Volume";
16034  var volumeUpDown$e = "Monter baisser le volume";
16035  var preferences$e = "Préférences";
16036  var enterFullScreen$e = "Activer le mode plein écran";
16037  var exitFullScreen$e = "Quitter le mode plein écran";
16038  var speed$e = "Vitesse";
16039  var spacebar$e = "barre d’espacement";
16040  var transcriptTitle$e = "Transcription";
16041  var lyricsTitle$e = "Paroles";
16042  var autoScroll$e = "Défilement automatique";
16043  var statusPlaying$e = "Lecture en cours";
16044  var statusPaused$e = "Lecture sur pause";
16045  var statusStopped$e = "Lecture interrompue";
16046  var statusBuffering$e = "Tamponnage";
16047  var statusEnd$e = "Fin de la piste";
16048  var selectedTrack$d = "Piste choisie";
16049  var alertDescribedVersion$e = "Utilisation de la version avec description sonore de cette vidéo";
16050  var alertNonDescribedVersion$e = "Utilisation de la version non décrite de cette vidéo";
16051  var prefMenuCaptions$e = "Sous-titres";
16052  var prefVoicedCaptions$e = "Spoken Captions";
16053  var prefMenuDescriptions$e = "Descriptions";
16054  var prefMenuKeyboard$e = "Clavier";
16055  var prefMenuTranscript$e = "Transcription";
16056  var prefTitleCaptions$e = "Préférences liées au sous-titrage";
16057  var prefTitleDescriptions$e = "Préférences liées aux descriptions sonores";
16058  var prefTitleKeyboard$e = "Préférences liées au clavier";
16059  var prefTitleTranscript$d = "Préférence liées à la transcription";
16060  var prefIntroDescription1$d = "Ce lecteur multimédia permet d’entendre les descriptions sonores de deux façons:";
16061  var prefDescription1$e = "Il y a une version autre version avec description, description textuelle.";
16062  var prefDescription2$e = "Il y a une version autre version de la vidéo avec description.";
16063  var prefDescription3$e = "Il y a une version description textuelle, lue à l’aide d’un lecteur d’écran.";
16064  var prefIntroDescriptionNone$e = "Il n’y a pas de version avec description sonore (dans ni l’un ni l’autre des formats) de la présente vidéo.";
16065  var prefIntroDescription3$e = "Utilisez le formulaire suivant pour établir vos préférences liées aux descriptions sonores textuelle.";
16066  var prefIntroDescription4$e = "Après avoir enregistré vos préférences, vous pouvez activer ou désactiver la description sonore avec le bouton Description.";
16067  var prefIntroKeyboard1$e = "Le lecteur multimédia de cette page Web peut être utilisé à partir de n’importe quel endroit sur la page avec des raccourcis du clavier (voir la liste ci-dessous).";
16068  var prefIntroKeyboard2$e = "Des rôles peuvent être assignés aux touches de modification (Shift, Alt, Ctrl) ci-dessous.";
16069  var prefIntroKeyboard3$e = "Certaines combinaisons de touches pourraient entrer en conflit avec les touches utilisées par votre navigateur ou autres applications logicielles. Essayez diverses combinaisons de touches de modification pour en trouver qui fonctionnent pour vous.";
16070  var prefHeadingKeyboard1$e = "Touches de modification utilisées pour des raccourcis";
16071  var prefHeadingKeyboard2$e = "Raccourcis du clavier assignés actuellement";
16072  var prefHeadingDescription$e = "Description sonore";
16073  var prefHeadingTextDescription$e = "Description sonore textuelle";
16074  var prefAltKey$e = "Alt";
16075  var prefCtrlKey$e = "Ctrl";
16076  var prefShiftKey$e = "Shift";
16077  var prefNoKeyShortcuts$e = "Disable keyboard shortcuts";
16078  var escapeKey$e = "Esc";
16079  var escapeKeyFunction$e = "Fermer la fenêtre de dialogue ou le menu contextuel";
16080  var prefDescPause$e = "Mettre la vidéo en pause automatiquement quand la description commence";
16081  var prefDescVisible$e = "Affichez la description";
16082  var prefDescVoice$e = "Voix";
16083  var prefDescRate$e = "Spoken Description Rate";
16084  var prefCaptionRate$e = "Spoken Caption Rate";
16085  var prefDescPitch$e = "Volume";
16086  var prefDescPitch1$e = "Très faible";
16087  var prefDescPitch2$e = "Faible";
16088  var prefDescPitch3$e = "Par défaut";
16089  var prefDescPitch4$e = "Fort";
16090  var prefDescPitch5$e = "Très fort";
16091  var sampleDescriptionText$e = "Régler les paramètres afin d'entendre cet extrait de texte";
16092  var prefHighlight$e = "Surligner la transcription pendant la lecture";
16093  var prefTabbable$e = "Transcription activée par clavier";
16094  var prefCaptionsFont$e = "Police de caractères";
16095  var prefCaptionsColor$e = "Couleur du texte";
16096  var prefCaptionsBGColor$e = "Arrière-plan";
16097  var prefCaptionsSize$e = "Taille de la police";
16098  var prefCaptionsOpacity$e = "Opacité";
16099  var prefCaptionsStyle$e = "Style";
16100  var serif$e = "avec empattement";
16101  var sans$e = "sans empattement";
16102  var cursive$e = "écriture cursive";
16103  var fantasy$e = "écriture de fantaisie";
16104  var monospace$e = "à taille fixe";
16105  var white$e = "blanc";
16106  var yellow$e = "jaune";
16107  var green$e = "vert";
16108  var cyan$d = "cyan";
16109  var blue$e = "bleu";
16110  var magenta$e = "magenta";
16111  var red$e = "rouge";
16112  var black$e = "noir";
16113  var transparent$e = "transparent";
16114  var solid$e = "solide";
16115  var captionsStylePopOn$e = "Pop-on";
16116  var captionsStyleRollUp$e = "Roll-up";
16117  var prefCaptionsPosition$e = "Position";
16118  var captionsPositionOverlay$e = "Superposés";
16119  var captionsPositionBelow$e = "Sous la vidéo";
16120  var sampleCaptionText$e = "Échantillon de sous-titre";
16121  var prefSuccess$e = "Vos changements ont été enregistrés.";
16122  var prefNoChange$e = "Vous n’avez pas fait de changements.";
16123  var save$e = "Enregistrer";
16124  var cancel$e = "Annuler";
16125  var dismissButton$e = "Dismiss";
16126  var windowButtonLabel$e = "Options de fenêtre";
16127  var windowMove$e = "Déplacer";
16128  var windowMoveLeft$e = "Window moved left";
16129  var windowMoveRight$e = "Window moved right";
16130  var windowMoveUp$e = "Window moved up";
16131  var windowMoveDown$e = "Window moved down";
16132  var windowMoveStopped$e = "Window move stopped";
16133  var transcriptControls$e = "Transcript Window Controls";
16134  var signControls$e = "Sign Language Window Controls";
16135  var windowMoveAlert$e = "Faites glisser avec la souris ou utilisez les touches fléchées pour déplacer la fenêtre; appuyez sur « Enter » pour arrêter.";
16136  var windowResize$e = "Redimensionner";
16137  var windowResizeHeading$e = "Redimensionner la fenêtre de l’interprète";
16138  var closeButtonLabel$e = "Fermer";
16139  var width$e = "Largeur";
16140  var height$e = "Hauteur";
16141  var resultsSummary1$e = "Vos résultats de recherche";
16142  var resultsSummary2$e = "Trouvé %1 éléments correspondants.";
16143  var resultsSummary3$e = "Cliquez sur le temps associé à n'importe quel élément pour lire la vidéo à partir de ce point.";
16144  var noResultsFound$e = "Aucun résultat trouvé.";
16145  var searchButtonLabel$e = "Lecture à %1";
16146  var hour$e = "heure";
16147  var minute$e = "minute";
16148  var second$e = "seconde";
16149  var hours$e = "heures";
16150  var minutes$e = "minutes";
16151  var seconds$e = "seconds";
16152  var vtsHeading$e = "Video Transcript Sorter";
16153  var vtsInstructions1$e = "Use the Video Transcript Sorter to modify text tracks:";
16154  var vtsInstructions2$e = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
16155  var vtsInstructions3$e = "Modify content or start/end times (all are directly editable within the table).";
16156  var vtsInstructions4$e = "Add new content, such as chapters or descriptions.";
16157  var vtsInstructions5$e = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
16158  var vtsSelectLanguage$e = "Select a language";
16159  var vtsSave$e = "Generate new .vtt content";
16160  var vtsReturn$e = "Return to Editor";
16161  var vtsCancel$e = "Cancelling saving. Any edits you made have been restored in the VTS table.";
16162  var vtsRow$e = "Row";
16163  var vtsKind$e = "Kind";
16164  var vtsStart$e = "Start";
16165  var vtsEnd$e = "End";
16166  var vtsContent$e = "Content";
16167  var vtsActions$e = "Actions";
16168  var vtsNewRow$e = "A new row %1 has been inserted.";
16169  var vtsDeletedRow$e = "Row %1 has been deleted.";
16170  var vtsMovedRow$e = "Row %1 has been moved %2 and is now Row %3.";
16171  var fr = {
16172  	playerHeading: playerHeading$e,
16173  	audioPlayer: audioPlayer$e,
16174  	videoPlayer: videoPlayer$e,
16175  	faster: faster$e,
16176  	slower: slower$e,
16177  	play: play$e,
16178  	pause: pause$e,
16179  	restart: restart$e,
16180  	prevTrack: prevTrack$e,
16181  	nextTrack: nextTrack$e,
16182  	rewind: rewind$e,
16183  	forward: forward$e,
16184  	captions: captions$e,
16185  	showCaptions: showCaptions$e,
16186  	hideCaptions: hideCaptions$e,
16187  	captionsOff: captionsOff$e,
16188  	showTranscript: showTranscript$e,
16189  	hideTranscript: hideTranscript$e,
16190  	turnOnDescriptions: turnOnDescriptions$e,
16191  	turnOffDescriptions: turnOffDescriptions$e,
16192  	chapters: chapters$e,
16193  	language: language$e,
16194  	sign: sign$e,
16195  	showSign: showSign$e,
16196  	hideSign: hideSign$e,
16197  	seekbarLabel: seekbarLabel$e,
16198  	mute: mute$e,
16199  	unmute: unmute$e,
16200  	volume: volume$e,
16201  	volumeUpDown: volumeUpDown$e,
16202  	preferences: preferences$e,
16203  	enterFullScreen: enterFullScreen$e,
16204  	exitFullScreen: exitFullScreen$e,
16205  	speed: speed$e,
16206  	spacebar: spacebar$e,
16207  	transcriptTitle: transcriptTitle$e,
16208  	lyricsTitle: lyricsTitle$e,
16209  	autoScroll: autoScroll$e,
16210  	statusPlaying: statusPlaying$e,
16211  	statusPaused: statusPaused$e,
16212  	statusStopped: statusStopped$e,
16213  	statusBuffering: statusBuffering$e,
16214  	statusEnd: statusEnd$e,
16215  	selectedTrack: selectedTrack$d,
16216  	alertDescribedVersion: alertDescribedVersion$e,
16217  	alertNonDescribedVersion: alertNonDescribedVersion$e,
16218  	prefMenuCaptions: prefMenuCaptions$e,
16219  	prefVoicedCaptions: prefVoicedCaptions$e,
16220  	prefMenuDescriptions: prefMenuDescriptions$e,
16221  	prefMenuKeyboard: prefMenuKeyboard$e,
16222  	prefMenuTranscript: prefMenuTranscript$e,
16223  	prefTitleCaptions: prefTitleCaptions$e,
16224  	prefTitleDescriptions: prefTitleDescriptions$e,
16225  	prefTitleKeyboard: prefTitleKeyboard$e,
16226  	prefTitleTranscript: prefTitleTranscript$d,
16227  	prefIntroDescription1: prefIntroDescription1$d,
16228  	prefDescription1: prefDescription1$e,
16229  	prefDescription2: prefDescription2$e,
16230  	prefDescription3: prefDescription3$e,
16231  	prefIntroDescriptionNone: prefIntroDescriptionNone$e,
16232  	prefIntroDescription3: prefIntroDescription3$e,
16233  	prefIntroDescription4: prefIntroDescription4$e,
16234  	prefIntroKeyboard1: prefIntroKeyboard1$e,
16235  	prefIntroKeyboard2: prefIntroKeyboard2$e,
16236  	prefIntroKeyboard3: prefIntroKeyboard3$e,
16237  	prefHeadingKeyboard1: prefHeadingKeyboard1$e,
16238  	prefHeadingKeyboard2: prefHeadingKeyboard2$e,
16239  	prefHeadingDescription: prefHeadingDescription$e,
16240  	prefHeadingTextDescription: prefHeadingTextDescription$e,
16241  	prefAltKey: prefAltKey$e,
16242  	prefCtrlKey: prefCtrlKey$e,
16243  	prefShiftKey: prefShiftKey$e,
16244  	prefNoKeyShortcuts: prefNoKeyShortcuts$e,
16245  	escapeKey: escapeKey$e,
16246  	escapeKeyFunction: escapeKeyFunction$e,
16247  	prefDescPause: prefDescPause$e,
16248  	prefDescVisible: prefDescVisible$e,
16249  	prefDescVoice: prefDescVoice$e,
16250  	prefDescRate: prefDescRate$e,
16251  	prefCaptionRate: prefCaptionRate$e,
16252  	prefDescPitch: prefDescPitch$e,
16253  	prefDescPitch1: prefDescPitch1$e,
16254  	prefDescPitch2: prefDescPitch2$e,
16255  	prefDescPitch3: prefDescPitch3$e,
16256  	prefDescPitch4: prefDescPitch4$e,
16257  	prefDescPitch5: prefDescPitch5$e,
16258  	sampleDescriptionText: sampleDescriptionText$e,
16259  	prefHighlight: prefHighlight$e,
16260  	prefTabbable: prefTabbable$e,
16261  	prefCaptionsFont: prefCaptionsFont$e,
16262  	prefCaptionsColor: prefCaptionsColor$e,
16263  	prefCaptionsBGColor: prefCaptionsBGColor$e,
16264  	prefCaptionsSize: prefCaptionsSize$e,
16265  	prefCaptionsOpacity: prefCaptionsOpacity$e,
16266  	prefCaptionsStyle: prefCaptionsStyle$e,
16267  	serif: serif$e,
16268  	sans: sans$e,
16269  	cursive: cursive$e,
16270  	fantasy: fantasy$e,
16271  	monospace: monospace$e,
16272  	white: white$e,
16273  	yellow: yellow$e,
16274  	green: green$e,
16275  	cyan: cyan$d,
16276  	blue: blue$e,
16277  	magenta: magenta$e,
16278  	red: red$e,
16279  	black: black$e,
16280  	transparent: transparent$e,
16281  	solid: solid$e,
16282  	captionsStylePopOn: captionsStylePopOn$e,
16283  	captionsStyleRollUp: captionsStyleRollUp$e,
16284  	prefCaptionsPosition: prefCaptionsPosition$e,
16285  	captionsPositionOverlay: captionsPositionOverlay$e,
16286  	captionsPositionBelow: captionsPositionBelow$e,
16287  	sampleCaptionText: sampleCaptionText$e,
16288  	prefSuccess: prefSuccess$e,
16289  	prefNoChange: prefNoChange$e,
16290  	save: save$e,
16291  	cancel: cancel$e,
16292  	dismissButton: dismissButton$e,
16293  	windowButtonLabel: windowButtonLabel$e,
16294  	windowMove: windowMove$e,
16295  	windowMoveLeft: windowMoveLeft$e,
16296  	windowMoveRight: windowMoveRight$e,
16297  	windowMoveUp: windowMoveUp$e,
16298  	windowMoveDown: windowMoveDown$e,
16299  	windowMoveStopped: windowMoveStopped$e,
16300  	transcriptControls: transcriptControls$e,
16301  	signControls: signControls$e,
16302  	windowMoveAlert: windowMoveAlert$e,
16303  	windowResize: windowResize$e,
16304  	windowResizeHeading: windowResizeHeading$e,
16305  	closeButtonLabel: closeButtonLabel$e,
16306  	width: width$e,
16307  	height: height$e,
16308  	resultsSummary1: resultsSummary1$e,
16309  	resultsSummary2: resultsSummary2$e,
16310  	resultsSummary3: resultsSummary3$e,
16311  	noResultsFound: noResultsFound$e,
16312  	searchButtonLabel: searchButtonLabel$e,
16313  	hour: hour$e,
16314  	minute: minute$e,
16315  	second: second$e,
16316  	hours: hours$e,
16317  	minutes: minutes$e,
16318  	seconds: seconds$e,
16319  	vtsHeading: vtsHeading$e,
16320  	vtsInstructions1: vtsInstructions1$e,
16321  	vtsInstructions2: vtsInstructions2$e,
16322  	vtsInstructions3: vtsInstructions3$e,
16323  	vtsInstructions4: vtsInstructions4$e,
16324  	vtsInstructions5: vtsInstructions5$e,
16325  	vtsSelectLanguage: vtsSelectLanguage$e,
16326  	vtsSave: vtsSave$e,
16327  	vtsReturn: vtsReturn$e,
16328  	vtsCancel: vtsCancel$e,
16329  	vtsRow: vtsRow$e,
16330  	vtsKind: vtsKind$e,
16331  	vtsStart: vtsStart$e,
16332  	vtsEnd: vtsEnd$e,
16333  	vtsContent: vtsContent$e,
16334  	vtsActions: vtsActions$e,
16335  	vtsNewRow: vtsNewRow$e,
16336  	vtsDeletedRow: vtsDeletedRow$e,
16337  	vtsMovedRow: vtsMovedRow$e
16338  };
16339
16340  var playerHeading$d = "נגן מדיה";
16341  var audioPlayer$d = "Audio player";
16342  var videoPlayer$d = "Video player";
16343  var faster$d = "מהר יותר";
16344  var slower$d = "לאט יותר";
16345  var play$d = "נגן";
16346  var pause$d = "הפסקה";
16347  var restart$d = "התחלה מחדש";
16348  var prevTrack$d = "המסלול הקודם";
16349  var nextTrack$d = "המסלול הבא";
16350  var rewind$d = "חזרה";
16351  var forward$d = "קדימה";
16352  var captions$d = "כיתובים";
16353  var showCaptions$d = "הצגת כיתובים";
16354  var hideCaptions$d = "הסתרת כיתובים";
16355  var captionsOff$d = "ללא כיתובים";
16356  var showTranscript$d = "הצג תמליל";
16357  var hideTranscript$d = "הסתר תמליל";
16358  var turnOnDescriptions$d = "הפעל תמליל";
16359  var turnOffDescriptions$d = "סגור תמליל";
16360  var chapters$d = "פרקים";
16361  var language$d = "שפה";
16362  var sign$d = "שפת סימנים";
16363  var showSign$d = "הצג שפת סימנים";
16364  var hideSign$d = "הסתר שפת סימנים";
16365  var seekbarLabel$d = "ציר זמן";
16366  var mute$d = "השתקה";
16367  var unmute$d = "הפסקת השתקה";
16368  var volume$d = "עצמה";
16369  var volumeUpDown$d = "Volume up down";
16370  var preferences$d = "העדפות";
16371  var enterFullScreen$d = "מעבר למסך מלא";
16372  var exitFullScreen$d = "יציאה ממסך מלא";
16373  var speed$d = "מהירות";
16374  var spacebar$d = "מקש הרווח";
16375  var transcriptTitle$d = "תמליל";
16376  var lyricsTitle$d = "מילים";
16377  var autoScroll$d = "גלילה אוטומטית";
16378  var statusPlaying$d = "מנגן";
16379  var statusPaused$d = "מופסק";
16380  var statusStopped$d = "נעצר";
16381  var statusBuffering$d = "חציצה";
16382  var statusEnd$d = "סוף המסלול";
16383  var selectTrack = "מסלול נבחר";
16384  var alertDescribedVersion$d = "שימוש בגירסת אודיו המתוארת של סרטון זה";
16385  var alertNonDescribedVersion$d = "שימוש בגרסה הלא מתוארת של סרטון זה";
16386  var prefMenuCaptions$d = "כיתובים";
16387  var prefVoicedCaptions$d = "Spoken Captions";
16388  var prefMenuDescriptions$d = "תיאורים";
16389  var prefMenuKeyboard$d = "מקלדת";
16390  var prefMenuTranscript$d = "תמליל";
16391  var prefTitleCaptions$d = "העדפות כיתובים";
16392  var prefTitleDescriptions$d = "העדפות תיאור שמע";
16393  var prefTitleKeyboard$d = "העדפות מקלדת";
16394  var PrefTitleTranscript = "העדפות תמליל";
16395  var prefIntrodescription1 = "נגן המדיה תומך בתיאור השמע בשתי דרכים:";
16396  var prefDescription1$d = "הווידאו הנוכחי יש גרסה חלופית המתוארתתיאור מבוסס טקסט";
16397  var prefDescription2$d = "הווידאו הנוכחי ישגרסה חלופית המתוארת של וידאו";
16398  var prefDescription3$d = "הווידאו הנוכחי יש תיאור מבוסס טקסט, הודיעה על ידי קורא מסך";
16399  var prefIntroDescriptionNone$d = "לסרטון הנוכחי אין תיאור שמע בתבנית.";
16400  var prefIntroDescription3$d = "השתמש בטופס הבא כדי לקבוע את ההעדפות שלך הקשורות לתיאור שמע.";
16401  var prefIntroDescription4$d = "לאחר שתשמור את ההגדרות שלך, תיאור השמע יכול להיות מוחל על / כיבוי באמצעות לחצן תיאור.";
16402  var prefIntroKeyboard1$d = "נגן המדיה בדף אינטרנט זה יכול להיות מופעל מכל מקום בדף באמצעות קיצורי מקשים (ראה להלן רשימה).";
16403  var prefIntroKeyboard2$d = "ניתן לשנות את מקשי השינוי (Shift, Alt ו- Control).";
16404  var prefIntroKeyboard3$d = "הערה: ייתכן שחלק מצירופי המקשים מתנגשים עם המקשים המשמשים את הדפדפן ו / או יישומי תוכנה אחרים, נסה שילובים שונים של מפתחות שינוי כדי למצוא אחד המתאים לך.";
16405  var prefHeadingKeyboard1$d = "מקשי משנה המשמשים לקיצורי דרך";
16406  var prefHeadingKeyboard2$d = "קיצורי מקשים נוכחיים";
16407  var prefHeadingDescription$d = "תיאור שמע";
16408  var prefHeadingTextDescription$d = "תיאור מבוסס טקסט מבוסס טקסט";
16409  var prefAltKey$d = "Alt";
16410  var prefCtrlKey$d = "Control";
16411  var prefShiftKey$d = "Shift";
16412  var prefNoKeyShortcuts$d = "Disable keyboard shortcuts";
16413  var escapeKey$d = "Escape";
16414  var escapeKeyFunction$d = "סגור את תיבת הדו שיח הנוכחית או תפריט קופץ";
16415  var prefDescPause$d = "השהה את הווידאו באופן אוטומטי כאשר התיאור מתחיל";
16416  var prefDescVisible$d = "הפוך תיאור גלוי";
16417  var prefDescVoice$d = "Voice";
16418  var prefDescRate$d = "Spoken Description Rate";
16419  var prefCaptionRate$d = "Spoken Caption Rate";
16420  var prefDescPitch$d = "Pitch";
16421  var prefDescPitch1$d = "Very low";
16422  var prefDescPitch2$d = "Low";
16423  var prefDescPitch3$d = "Default";
16424  var prefDescPitch4$d = "High";
16425  var prefDescPitch5$d = "Very high";
16426  var sampleDescriptionText$d = "Adjust settings to hear this sample text.";
16427  var prefHighlight$d = "הדגש תמליל כמו משחק מדיה";
16428  var prefTabbable$d = "הפעל תמליל";
16429  var prefCaptionsFont$d = "גופן";
16430  var prefCaptionsColor$d = "צבע טקסט";
16431  var prefCaptionsBGColor$d = "רקע";
16432  var prefCaptionsSize$d = "גודל גופן";
16433  var prefCaptionsOpacity$d = "אטימות";
16434  var prefCaptionsStyle$d = "סגנון";
16435  var serif$d = "serif";
16436  var sans$d = "sans-serif";
16437  var cursive$d = "cursive";
16438  var fantasy$d = "fantasy";
16439  var monospace$d = "monospace";
16440  var white$d = "לבן";
16441  var yellow$d = "צהוב";
16442  var green$d = "ירוק";
16443  var cyan$c = "טורקיז";
16444  var blue$d = "כחול";
16445  var magenta$d = "מגנטה";
16446  var red$d = "אדום";
16447  var black$d = "שחור";
16448  var transparent$d = "שקוף";
16449  var solid$d = "מלא";
16450  var captionsStylePopOn$d = "Pop-on";
16451  var captionsStyleRollUp$d = "Roll-up";
16452  var prefCaptionsPosition$d = "מיקום";
16453  var captionsPositionOverlay$d = "Overlay";
16454  var captionsPositionBelow$d = "מתחת לוידאו";
16455  var sampleCaptionText$d = "דוגמת טקסט כיתוביות";
16456  var prefSuccess$d = "השינויים שלכם נשמרו.";
16457  var prefNoChange$d = "לא בצעתם כל שינוי.";
16458  var save$d = "שמירה";
16459  var cancel$d = "ביטול";
16460  var dismissButton$d = "Dismiss";
16461  var windowButtonLabel$d = "אפשרויות חלון";
16462  var windowMove$d = "הזזה";
16463  var windowMoveLeft$d = "Window moved left";
16464  var windowMoveRight$d = "Window moved right";
16465  var windowMoveUp$d = "Window moved up";
16466  var windowMoveDown$d = "Window moved down";
16467  var windowMoveStopped$d = "Window move stopped";
16468  var transcriptControls$d = "Transcript Window Controls";
16469  var signControls$d = "Sign Language Window Controls";
16470  var windowMoveAlert$d = "גרור או השתמשו מקשי החצים להזזת החלון; כפתור Enter להפסקה";
16471  var windowResize$d = "שיוי גודל";
16472  var windowResizeHeading$d = "שינוי גודל חלון";
16473  var closeButtonLabel$d = "סגירת";
16474  var width$d = "רוחב";
16475  var height$d = "גובה";
16476  var resultsSummary1$d = "You searched for:";
16477  var resultsSummary2$d = "Found %1 matching items.";
16478  var resultsSummary3$d = "Click the time associated with any item to play the video from that point.";
16479  var noResultsFound$d = "No results found.";
16480  var searchButtonLabel$d = "Play at %1";
16481  var hour$d = "hour";
16482  var minute$d = "minute";
16483  var second$d = "second";
16484  var hours$d = "hours";
16485  var minutes$d = "minutes";
16486  var seconds$d = "seconds";
16487  var vtsHeading$d = "Video Transcript Sorter";
16488  var vtsInstructions1$d = "Use the Video Transcript Sorter to modify text tracks:";
16489  var vtsInstructions2$d = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
16490  var vtsInstructions3$d = "Modify content or start/end times (all are directly editable within the table).";
16491  var vtsInstructions4$d = "Add new content, such as chapters or descriptions.";
16492  var vtsInstructions5$d = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
16493  var vtsSelectLanguage$d = "Select a language";
16494  var vtsSave$d = "Generate new .vtt content";
16495  var vtsReturn$d = "Return to Editor";
16496  var vtsCancel$d = "Cancelling saving. Any edits you made have been restored in the VTS table.";
16497  var vtsRow$d = "Row";
16498  var vtsKind$d = "Kind";
16499  var vtsStart$d = "Start";
16500  var vtsEnd$d = "End";
16501  var vtsContent$d = "Content";
16502  var vtsActions$d = "Actions";
16503  var vtsNewRow$d = "A new row %1 has been inserted.";
16504  var vtsDeletedRow$d = "Row %1 has been deleted.";
16505  var vtsMovedRow$d = "Row %1 has been moved %2 and is now Row %3.";
16506  var he = {
16507  	playerHeading: playerHeading$d,
16508  	audioPlayer: audioPlayer$d,
16509  	videoPlayer: videoPlayer$d,
16510  	faster: faster$d,
16511  	slower: slower$d,
16512  	play: play$d,
16513  	pause: pause$d,
16514  	restart: restart$d,
16515  	prevTrack: prevTrack$d,
16516  	nextTrack: nextTrack$d,
16517  	rewind: rewind$d,
16518  	forward: forward$d,
16519  	captions: captions$d,
16520  	showCaptions: showCaptions$d,
16521  	hideCaptions: hideCaptions$d,
16522  	captionsOff: captionsOff$d,
16523  	showTranscript: showTranscript$d,
16524  	hideTranscript: hideTranscript$d,
16525  	turnOnDescriptions: turnOnDescriptions$d,
16526  	turnOffDescriptions: turnOffDescriptions$d,
16527  	chapters: chapters$d,
16528  	language: language$d,
16529  	sign: sign$d,
16530  	showSign: showSign$d,
16531  	hideSign: hideSign$d,
16532  	seekbarLabel: seekbarLabel$d,
16533  	mute: mute$d,
16534  	unmute: unmute$d,
16535  	volume: volume$d,
16536  	volumeUpDown: volumeUpDown$d,
16537  	preferences: preferences$d,
16538  	enterFullScreen: enterFullScreen$d,
16539  	exitFullScreen: exitFullScreen$d,
16540  	speed: speed$d,
16541  	spacebar: spacebar$d,
16542  	transcriptTitle: transcriptTitle$d,
16543  	lyricsTitle: lyricsTitle$d,
16544  	autoScroll: autoScroll$d,
16545  	statusPlaying: statusPlaying$d,
16546  	statusPaused: statusPaused$d,
16547  	statusStopped: statusStopped$d,
16548  	statusBuffering: statusBuffering$d,
16549  	statusEnd: statusEnd$d,
16550  	selectTrack: selectTrack,
16551  	alertDescribedVersion: alertDescribedVersion$d,
16552  	alertNonDescribedVersion: alertNonDescribedVersion$d,
16553  	prefMenuCaptions: prefMenuCaptions$d,
16554  	prefVoicedCaptions: prefVoicedCaptions$d,
16555  	prefMenuDescriptions: prefMenuDescriptions$d,
16556  	prefMenuKeyboard: prefMenuKeyboard$d,
16557  	prefMenuTranscript: prefMenuTranscript$d,
16558  	prefTitleCaptions: prefTitleCaptions$d,
16559  	prefTitleDescriptions: prefTitleDescriptions$d,
16560  	prefTitleKeyboard: prefTitleKeyboard$d,
16561  	PrefTitleTranscript: PrefTitleTranscript,
16562  	prefIntrodescription1: prefIntrodescription1,
16563  	prefDescription1: prefDescription1$d,
16564  	prefDescription2: prefDescription2$d,
16565  	prefDescription3: prefDescription3$d,
16566  	prefIntroDescriptionNone: prefIntroDescriptionNone$d,
16567  	prefIntroDescription3: prefIntroDescription3$d,
16568  	prefIntroDescription4: prefIntroDescription4$d,
16569  	prefIntroKeyboard1: prefIntroKeyboard1$d,
16570  	prefIntroKeyboard2: prefIntroKeyboard2$d,
16571  	prefIntroKeyboard3: prefIntroKeyboard3$d,
16572  	prefHeadingKeyboard1: prefHeadingKeyboard1$d,
16573  	prefHeadingKeyboard2: prefHeadingKeyboard2$d,
16574  	prefHeadingDescription: prefHeadingDescription$d,
16575  	prefHeadingTextDescription: prefHeadingTextDescription$d,
16576  	prefAltKey: prefAltKey$d,
16577  	prefCtrlKey: prefCtrlKey$d,
16578  	prefShiftKey: prefShiftKey$d,
16579  	prefNoKeyShortcuts: prefNoKeyShortcuts$d,
16580  	escapeKey: escapeKey$d,
16581  	escapeKeyFunction: escapeKeyFunction$d,
16582  	prefDescPause: prefDescPause$d,
16583  	prefDescVisible: prefDescVisible$d,
16584  	prefDescVoice: prefDescVoice$d,
16585  	prefDescRate: prefDescRate$d,
16586  	prefCaptionRate: prefCaptionRate$d,
16587  	prefDescPitch: prefDescPitch$d,
16588  	prefDescPitch1: prefDescPitch1$d,
16589  	prefDescPitch2: prefDescPitch2$d,
16590  	prefDescPitch3: prefDescPitch3$d,
16591  	prefDescPitch4: prefDescPitch4$d,
16592  	prefDescPitch5: prefDescPitch5$d,
16593  	sampleDescriptionText: sampleDescriptionText$d,
16594  	prefHighlight: prefHighlight$d,
16595  	prefTabbable: prefTabbable$d,
16596  	prefCaptionsFont: prefCaptionsFont$d,
16597  	prefCaptionsColor: prefCaptionsColor$d,
16598  	prefCaptionsBGColor: prefCaptionsBGColor$d,
16599  	prefCaptionsSize: prefCaptionsSize$d,
16600  	prefCaptionsOpacity: prefCaptionsOpacity$d,
16601  	prefCaptionsStyle: prefCaptionsStyle$d,
16602  	serif: serif$d,
16603  	sans: sans$d,
16604  	cursive: cursive$d,
16605  	fantasy: fantasy$d,
16606  	monospace: monospace$d,
16607  	white: white$d,
16608  	yellow: yellow$d,
16609  	green: green$d,
16610  	cyan: cyan$c,
16611  	blue: blue$d,
16612  	magenta: magenta$d,
16613  	red: red$d,
16614  	black: black$d,
16615  	transparent: transparent$d,
16616  	solid: solid$d,
16617  	captionsStylePopOn: captionsStylePopOn$d,
16618  	captionsStyleRollUp: captionsStyleRollUp$d,
16619  	prefCaptionsPosition: prefCaptionsPosition$d,
16620  	captionsPositionOverlay: captionsPositionOverlay$d,
16621  	captionsPositionBelow: captionsPositionBelow$d,
16622  	sampleCaptionText: sampleCaptionText$d,
16623  	prefSuccess: prefSuccess$d,
16624  	prefNoChange: prefNoChange$d,
16625  	save: save$d,
16626  	cancel: cancel$d,
16627  	dismissButton: dismissButton$d,
16628  	windowButtonLabel: windowButtonLabel$d,
16629  	windowMove: windowMove$d,
16630  	windowMoveLeft: windowMoveLeft$d,
16631  	windowMoveRight: windowMoveRight$d,
16632  	windowMoveUp: windowMoveUp$d,
16633  	windowMoveDown: windowMoveDown$d,
16634  	windowMoveStopped: windowMoveStopped$d,
16635  	transcriptControls: transcriptControls$d,
16636  	signControls: signControls$d,
16637  	windowMoveAlert: windowMoveAlert$d,
16638  	windowResize: windowResize$d,
16639  	windowResizeHeading: windowResizeHeading$d,
16640  	closeButtonLabel: closeButtonLabel$d,
16641  	width: width$d,
16642  	height: height$d,
16643  	resultsSummary1: resultsSummary1$d,
16644  	resultsSummary2: resultsSummary2$d,
16645  	resultsSummary3: resultsSummary3$d,
16646  	noResultsFound: noResultsFound$d,
16647  	searchButtonLabel: searchButtonLabel$d,
16648  	hour: hour$d,
16649  	minute: minute$d,
16650  	second: second$d,
16651  	hours: hours$d,
16652  	minutes: minutes$d,
16653  	seconds: seconds$d,
16654  	vtsHeading: vtsHeading$d,
16655  	vtsInstructions1: vtsInstructions1$d,
16656  	vtsInstructions2: vtsInstructions2$d,
16657  	vtsInstructions3: vtsInstructions3$d,
16658  	vtsInstructions4: vtsInstructions4$d,
16659  	vtsInstructions5: vtsInstructions5$d,
16660  	vtsSelectLanguage: vtsSelectLanguage$d,
16661  	vtsSave: vtsSave$d,
16662  	vtsReturn: vtsReturn$d,
16663  	vtsCancel: vtsCancel$d,
16664  	vtsRow: vtsRow$d,
16665  	vtsKind: vtsKind$d,
16666  	vtsStart: vtsStart$d,
16667  	vtsEnd: vtsEnd$d,
16668  	vtsContent: vtsContent$d,
16669  	vtsActions: vtsActions$d,
16670  	vtsNewRow: vtsNewRow$d,
16671  	vtsDeletedRow: vtsDeletedRow$d,
16672  	vtsMovedRow: vtsMovedRow$d
16673  };
16674
16675  var playerHeading$c = "Pemutar media";
16676  var audioPlayer$c = "Audio player";
16677  var videoPlayer$c = "Video player";
16678  var faster$c = "Percepat";
16679  var slower$c = "Perlambat";
16680  var play$c = "Mulai";
16681  var pause$c = "Jeda";
16682  var restart$c = "Ulangi";
16683  var prevTrack$c = "Trek sebelumnya";
16684  var nextTrack$c = "Trek berikutnya";
16685  var rewind$c = "Mundur";
16686  var forward$c = "Maju";
16687  var captions$c = "Takarir";
16688  var showCaptions$c = "Tampilkan takarir";
16689  var hideCaptions$c = "Sembunyikan takarir";
16690  var captionsOff$c = "Tanpa takarir";
16691  var showTranscript$c = "Tampilkan transkripsi";
16692  var hideTranscript$c = "Sembunyikan transkripsi";
16693  var turnOnDescriptions$c = "Nyalakan deskripsi";
16694  var turnOffDescriptions$c = "Matikan deskripsi";
16695  var chapters$c = "Bagian";
16696  var language$c = "Bahasa";
16697  var sign$c = "Bahasa isyarat";
16698  var showSign$c = "Tampilkan bahasa isyarat";
16699  var hideSign$c = "Sembunyikan bahasa isyarat";
16700  var seekbarLabel$c = "lini masa";
16701  var mute$c = "Matikan Suara";
16702  var unmute$c = "Nyalakan Suara";
16703  var volume$c = "Volume";
16704  var volumeUpDown$c = "Naik turun volume";
16705  var preferences$c = "Preferensi";
16706  var enterFullScreen$c = "Masuk ke mode layar penuh";
16707  var exitFullScreen$c = "Keluar dari mode layar penuh";
16708  var speed$c = "Kecepatan";
16709  var spacebar$c = "tombol spasi";
16710  var transcriptTitle$c = "Transkripsi";
16711  var lyricsTitle$c = "Lirik";
16712  var autoScroll$c = "Gulir otomatis";
16713  var statusPlaying$c = "Memutar";
16714  var statusPaused$c = "Dijedakan";
16715  var statusStopped$c = "Dihentikan";
16716  var statusBuffering$c = "Sedang Bufer";
16717  var statusEnd$c = "Akhir dari trek";
16718  var selectedTrack$c = "Trek Pilihan";
16719  var alertDescribedVersion$c = "Menggunakan versi dengan deskripsi audio pada video ini";
16720  var alertNonDescribedVersion$c = "Menggunakan versi tanpa deskripsi pada video ini";
16721  var prefMenuCaptions$c = "Takarir";
16722  var prefVoicedCaptions$c = "Spoken Captions";
16723  var prefMenuDescriptions$c = "Deskripsi";
16724  var prefMenuKeyboard$c = "Kibor";
16725  var prefMenuTranscript$c = "Transkripsi";
16726  var prefTitleCaptions$c = "Preferensi Takarir";
16727  var prefTitleDescriptions$c = "Preferensi Deskripsi Audio";
16728  var prefTitleKeyboard$c = "Preferensi Kibor";
16729  var prefTitleTranscript$c = "Preferensi Transkripsi";
16730  var prefIntroDescription1$c = "Pemutar media ini mendukung deskripsi audio dengan dua cara: ";
16731  var prefDescription1$c = "Video ini memiliki versi alternatif berdeskripsi, deskripsi berbasis teks.";
16732  var prefDescription2$c = "Video ini memiliki versi alternatif video dengan deskripsi.";
16733  var prefDescription3$c = "Video ini memiliki deskripsi berbasis teks, dibacakan oleh pembaca layar.";
16734  var prefIntroDescriptionNone$c = "Video ini tidak memiliki deskripsi audio dalam format mana pun.";
16735  var prefIntroDescription3$c = "Gunakan isian berikut untuk memasang preferensi terkait deskripsi audio berbasis teks.";
16736  var prefIntroDescription4$c = "Setelah Anda menyimpan pengaturan, deskripsi audio dapat dinyala/matikan melalui tombol Deskripsi.";
16737  var prefIntroKeyboard1$c = "Pemutar media dalam halaman ini dapat dioperasikan dari bagian mana pun pada halaman dengan menggunakan pintasan kibor (lihat daftar di bawah).";
16738  var prefIntroKeyboard2$c = "Fungsi tombol kombinasi (Shift, Alt, Control) dapat dipasangkan di bawah ini.";
16739  var prefIntroKeyboard3$c = "CATATAN: Beberapa kombinasi tombol mungkin akan bersinggungan dengan yang digunakan oleh browser dan/atau aplikasi perangkat lunak lainnya. Cobalah berbagai kombinasi untuk menemukan kombinasi yang bekerja untuk Anda.";
16740  var prefHeadingKeyboard1$c = "Tombol kombinasi yang digunakan untuk pintasan";
16741  var prefHeadingKeyboard2$c = "Pintasan kibor saat ini";
16742  var prefHeadingDescription$c = "Deskripsi audio";
16743  var prefHeadingTextDescription$c = "Deskripsi audio berbasis teks";
16744  var prefAltKey$c = "Alt";
16745  var prefCtrlKey$c = "Control";
16746  var prefShiftKey$c = "Shift";
16747  var prefNoKeyShortcuts$c = "Disable keyboard shortcuts";
16748  var escapeKey$c = "Escape";
16749  var escapeKeyFunction$c = "Tutup dialog atau menu sembulan yang tampil";
16750  var prefDescPause$c = "Otomatis jedakan video ketika deskripsi dimulai";
16751  var prefDescVisible$c = "Tampilkan deskripsi";
16752  var prefDescVoice$c = "Suara";
16753  var prefDescRate$c = "Spoken Description Rate";
16754  var prefCaptionRate$c = "Spoken Caption Rate";
16755  var prefDescPitch$c = "Titinada";
16756  var prefDescPitch1$c = "Sangat rendah";
16757  var prefDescPitch2$c = "Rendah";
16758  var prefDescPitch3$c = "Bawaan";
16759  var prefDescPitch4$c = "Tinggi";
16760  var prefDescPitch5$c = "Sangat tinggi";
16761  var sampleDescriptionText$c = "Sesuaikan pengaturan untuk mendengarkan teks ini.";
16762  var prefHighlight$c = "Soroti transkripsi yang sesuai saat media diputar";
16763  var prefTabbable$c = "Transkripsi dengan kontrol kibor";
16764  var prefCaptionsFont$c = "Jenis Huruf";
16765  var prefCaptionsColor$c = "Warna Teks";
16766  var prefCaptionsBGColor$c = "Warna Latar";
16767  var prefCaptionsSize$c = "Ukuran Huruf";
16768  var prefCaptionsOpacity$c = "Opasitas";
16769  var prefCaptionsStyle$c = "Gaya";
16770  var serif$c = "serif";
16771  var sans$c = "sans-serif";
16772  var cursive$c = "kursif";
16773  var fantasy$c = "fantasi";
16774  var monospace$c = "monospasi";
16775  var white$c = "putih";
16776  var yellow$c = "kuning";
16777  var green$c = "hijau";
16778  var cyan$b = "sian";
16779  var blue$c = "biru";
16780  var magenta$c = "magenta";
16781  var red$c = "merah";
16782  var black$c = "hitam";
16783  var transparent$c = "transparan";
16784  var solid$c = "solid";
16785  var captionsStylePopOn$c = "Muncul";
16786  var captionsStyleRollUp$c = "Bergulir naik";
16787  var prefCaptionsPosition$c = "Posisi";
16788  var captionsPositionOverlay$c = "Di atas video";
16789  var captionsPositionBelow$c = "Di bawah video";
16790  var sampleCaptionText$c = "Contoh teks takarir";
16791  var prefSuccess$c = "Perubahan telah disimpan.";
16792  var prefNoChange$c = "Anda tidak melakukan perubahan apa pun.";
16793  var save$c = "Simpan";
16794  var cancel$c = "Batal";
16795  var dismissButton$c = "Dismiss";
16796  var windowButtonLabel$c = "Opsi jendela";
16797  var windowMove$c = "Geser";
16798  var windowMoveLeft$c = "Window moved left";
16799  var windowMoveRight$c = "Window moved right";
16800  var windowMoveUp$c = "Window moved up";
16801  var windowMoveDown$c = "Window moved down";
16802  var windowMoveStopped$c = "Window move stopped";
16803  var transcriptControls$c = "Transcript Window Controls";
16804  var signControls$c = "Sign Language Window Controls";
16805  var windowMoveAlert$c = "Seret atau gunakan tombol panah untuk menggeser jendela; Tekan Enter untuk berhenti";
16806  var windowResize$c = "Ubah ukuran";
16807  var windowResizeHeading$c = "Ubah Ukuran Jendela";
16808  var closeButtonLabel$c = "Tutup";
16809  var width$c = "Lebar";
16810  var height$c = "Tinggi";
16811  var resultsSummary1$c = "Anda mencari:";
16812  var resultsSummary2$c = "Ditemukan %1 hal yang sesuai.";
16813  var resultsSummary3$c = "Klik waktu yang terkait dengan suatu hal untuk memutar video dari titik tersebut.";
16814  var noResultsFound$c = "Tidak ada hasil yang ditemukan.";
16815  var searchButtonLabel$c = "Diputar pada %1";
16816  var hour$c = "jam";
16817  var minute$c = "menit";
16818  var second$c = "detik";
16819  var hours$c = "jam";
16820  var minutes$c = "menit";
16821  var seconds$c = "detik";
16822  var vtsHeading$c = "Video Transcript Sorter";
16823  var vtsInstructions1$c = "Use the Video Transcript Sorter to modify text tracks:";
16824  var vtsInstructions2$c = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
16825  var vtsInstructions3$c = "Modify content or start/end times (all are directly editable within the table).";
16826  var vtsInstructions4$c = "Add new content, such as chapters or descriptions.";
16827  var vtsInstructions5$c = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
16828  var vtsSelectLanguage$c = "Select a language";
16829  var vtsSave$c = "Generate new .vtt content";
16830  var vtsReturn$c = "Return to Editor";
16831  var vtsCancel$c = "Cancelling saving. Any edits you made have been restored in the VTS table.";
16832  var vtsRow$c = "Row";
16833  var vtsKind$c = "Kind";
16834  var vtsStart$c = "Start";
16835  var vtsEnd$c = "End";
16836  var vtsContent$c = "Content";
16837  var vtsActions$c = "Actions";
16838  var vtsNewRow$c = "A new row %1 has been inserted.";
16839  var vtsDeletedRow$c = "Row %1 has been deleted.";
16840  var vtsMovedRow$c = "Row %1 has been moved %2 and is now Row %3.";
16841  var id = {
16842  	playerHeading: playerHeading$c,
16843  	audioPlayer: audioPlayer$c,
16844  	videoPlayer: videoPlayer$c,
16845  	faster: faster$c,
16846  	slower: slower$c,
16847  	play: play$c,
16848  	pause: pause$c,
16849  	restart: restart$c,
16850  	prevTrack: prevTrack$c,
16851  	nextTrack: nextTrack$c,
16852  	rewind: rewind$c,
16853  	forward: forward$c,
16854  	captions: captions$c,
16855  	showCaptions: showCaptions$c,
16856  	hideCaptions: hideCaptions$c,
16857  	captionsOff: captionsOff$c,
16858  	showTranscript: showTranscript$c,
16859  	hideTranscript: hideTranscript$c,
16860  	turnOnDescriptions: turnOnDescriptions$c,
16861  	turnOffDescriptions: turnOffDescriptions$c,
16862  	chapters: chapters$c,
16863  	language: language$c,
16864  	sign: sign$c,
16865  	showSign: showSign$c,
16866  	hideSign: hideSign$c,
16867  	seekbarLabel: seekbarLabel$c,
16868  	mute: mute$c,
16869  	unmute: unmute$c,
16870  	volume: volume$c,
16871  	volumeUpDown: volumeUpDown$c,
16872  	preferences: preferences$c,
16873  	enterFullScreen: enterFullScreen$c,
16874  	exitFullScreen: exitFullScreen$c,
16875  	speed: speed$c,
16876  	spacebar: spacebar$c,
16877  	transcriptTitle: transcriptTitle$c,
16878  	lyricsTitle: lyricsTitle$c,
16879  	autoScroll: autoScroll$c,
16880  	statusPlaying: statusPlaying$c,
16881  	statusPaused: statusPaused$c,
16882  	statusStopped: statusStopped$c,
16883  	statusBuffering: statusBuffering$c,
16884  	statusEnd: statusEnd$c,
16885  	selectedTrack: selectedTrack$c,
16886  	alertDescribedVersion: alertDescribedVersion$c,
16887  	alertNonDescribedVersion: alertNonDescribedVersion$c,
16888  	prefMenuCaptions: prefMenuCaptions$c,
16889  	prefVoicedCaptions: prefVoicedCaptions$c,
16890  	prefMenuDescriptions: prefMenuDescriptions$c,
16891  	prefMenuKeyboard: prefMenuKeyboard$c,
16892  	prefMenuTranscript: prefMenuTranscript$c,
16893  	prefTitleCaptions: prefTitleCaptions$c,
16894  	prefTitleDescriptions: prefTitleDescriptions$c,
16895  	prefTitleKeyboard: prefTitleKeyboard$c,
16896  	prefTitleTranscript: prefTitleTranscript$c,
16897  	prefIntroDescription1: prefIntroDescription1$c,
16898  	prefDescription1: prefDescription1$c,
16899  	prefDescription2: prefDescription2$c,
16900  	prefDescription3: prefDescription3$c,
16901  	prefIntroDescriptionNone: prefIntroDescriptionNone$c,
16902  	prefIntroDescription3: prefIntroDescription3$c,
16903  	prefIntroDescription4: prefIntroDescription4$c,
16904  	prefIntroKeyboard1: prefIntroKeyboard1$c,
16905  	prefIntroKeyboard2: prefIntroKeyboard2$c,
16906  	prefIntroKeyboard3: prefIntroKeyboard3$c,
16907  	prefHeadingKeyboard1: prefHeadingKeyboard1$c,
16908  	prefHeadingKeyboard2: prefHeadingKeyboard2$c,
16909  	prefHeadingDescription: prefHeadingDescription$c,
16910  	prefHeadingTextDescription: prefHeadingTextDescription$c,
16911  	prefAltKey: prefAltKey$c,
16912  	prefCtrlKey: prefCtrlKey$c,
16913  	prefShiftKey: prefShiftKey$c,
16914  	prefNoKeyShortcuts: prefNoKeyShortcuts$c,
16915  	escapeKey: escapeKey$c,
16916  	escapeKeyFunction: escapeKeyFunction$c,
16917  	prefDescPause: prefDescPause$c,
16918  	prefDescVisible: prefDescVisible$c,
16919  	prefDescVoice: prefDescVoice$c,
16920  	prefDescRate: prefDescRate$c,
16921  	prefCaptionRate: prefCaptionRate$c,
16922  	prefDescPitch: prefDescPitch$c,
16923  	prefDescPitch1: prefDescPitch1$c,
16924  	prefDescPitch2: prefDescPitch2$c,
16925  	prefDescPitch3: prefDescPitch3$c,
16926  	prefDescPitch4: prefDescPitch4$c,
16927  	prefDescPitch5: prefDescPitch5$c,
16928  	sampleDescriptionText: sampleDescriptionText$c,
16929  	prefHighlight: prefHighlight$c,
16930  	prefTabbable: prefTabbable$c,
16931  	prefCaptionsFont: prefCaptionsFont$c,
16932  	prefCaptionsColor: prefCaptionsColor$c,
16933  	prefCaptionsBGColor: prefCaptionsBGColor$c,
16934  	prefCaptionsSize: prefCaptionsSize$c,
16935  	prefCaptionsOpacity: prefCaptionsOpacity$c,
16936  	prefCaptionsStyle: prefCaptionsStyle$c,
16937  	serif: serif$c,
16938  	sans: sans$c,
16939  	cursive: cursive$c,
16940  	fantasy: fantasy$c,
16941  	monospace: monospace$c,
16942  	white: white$c,
16943  	yellow: yellow$c,
16944  	green: green$c,
16945  	cyan: cyan$b,
16946  	blue: blue$c,
16947  	magenta: magenta$c,
16948  	red: red$c,
16949  	black: black$c,
16950  	transparent: transparent$c,
16951  	solid: solid$c,
16952  	captionsStylePopOn: captionsStylePopOn$c,
16953  	captionsStyleRollUp: captionsStyleRollUp$c,
16954  	prefCaptionsPosition: prefCaptionsPosition$c,
16955  	captionsPositionOverlay: captionsPositionOverlay$c,
16956  	captionsPositionBelow: captionsPositionBelow$c,
16957  	sampleCaptionText: sampleCaptionText$c,
16958  	prefSuccess: prefSuccess$c,
16959  	prefNoChange: prefNoChange$c,
16960  	save: save$c,
16961  	cancel: cancel$c,
16962  	dismissButton: dismissButton$c,
16963  	windowButtonLabel: windowButtonLabel$c,
16964  	windowMove: windowMove$c,
16965  	windowMoveLeft: windowMoveLeft$c,
16966  	windowMoveRight: windowMoveRight$c,
16967  	windowMoveUp: windowMoveUp$c,
16968  	windowMoveDown: windowMoveDown$c,
16969  	windowMoveStopped: windowMoveStopped$c,
16970  	transcriptControls: transcriptControls$c,
16971  	signControls: signControls$c,
16972  	windowMoveAlert: windowMoveAlert$c,
16973  	windowResize: windowResize$c,
16974  	windowResizeHeading: windowResizeHeading$c,
16975  	closeButtonLabel: closeButtonLabel$c,
16976  	width: width$c,
16977  	height: height$c,
16978  	resultsSummary1: resultsSummary1$c,
16979  	resultsSummary2: resultsSummary2$c,
16980  	resultsSummary3: resultsSummary3$c,
16981  	noResultsFound: noResultsFound$c,
16982  	searchButtonLabel: searchButtonLabel$c,
16983  	hour: hour$c,
16984  	minute: minute$c,
16985  	second: second$c,
16986  	hours: hours$c,
16987  	minutes: minutes$c,
16988  	seconds: seconds$c,
16989  	vtsHeading: vtsHeading$c,
16990  	vtsInstructions1: vtsInstructions1$c,
16991  	vtsInstructions2: vtsInstructions2$c,
16992  	vtsInstructions3: vtsInstructions3$c,
16993  	vtsInstructions4: vtsInstructions4$c,
16994  	vtsInstructions5: vtsInstructions5$c,
16995  	vtsSelectLanguage: vtsSelectLanguage$c,
16996  	vtsSave: vtsSave$c,
16997  	vtsReturn: vtsReturn$c,
16998  	vtsCancel: vtsCancel$c,
16999  	vtsRow: vtsRow$c,
17000  	vtsKind: vtsKind$c,
17001  	vtsStart: vtsStart$c,
17002  	vtsEnd: vtsEnd$c,
17003  	vtsContent: vtsContent$c,
17004  	vtsActions: vtsActions$c,
17005  	vtsNewRow: vtsNewRow$c,
17006  	vtsDeletedRow: vtsDeletedRow$c,
17007  	vtsMovedRow: vtsMovedRow$c
17008  };
17009
17010  var playerHeading$b = "Lettore multimediale";
17011  var audioPlayer$b = "Audio player";
17012  var videoPlayer$b = "Video player";
17013  var faster$b = "Più veloce";
17014  var slower$b = "Più lento";
17015  var play$b = "Riproduci";
17016  var pause$b = "Pausa";
17017  var restart$b = "Torna all'inizio";
17018  var prevTrack$b = "Traccia precedente";
17019  var nextTrack$b = "Traccia successiva";
17020  var rewind$b = "Indietro";
17021  var forward$b = "Avanti";
17022  var captions$b = "Sottotitoli";
17023  var showCaptions$b = "Mostra sottotitoli";
17024  var hideCaptions$b = "Nascondi sottotitoli";
17025  var captionsOff$b = "Disattiva sottotitoli";
17026  var showTranscript$b = "Mostra trascrizione";
17027  var hideTranscript$b = "Nascondi trascrizione";
17028  var turnOnDescriptions$b = "Attiva audiodescrizioni";
17029  var turnOffDescriptions$b = "Disattiva audiodescrizioni";
17030  var chapters$b = "Capitoli";
17031  var language$b = "Lingua";
17032  var sign$b = "Lingua dei segni";
17033  var showSign$b = "Mostra lingua dei segni";
17034  var hideSign$b = "Nascondi lingua dei segni";
17035  var seekbarLabel$b = "Tempo trascorso";
17036  var mute$b = "Muto";
17037  var unmute$b = "Riattiva l'audio";
17038  var volume$b = "Volume";
17039  var volumeUpDown$b = "Incremento e diminuzione volume";
17040  var preferences$b = "Preferenze";
17041  var enterFullScreen$b = "Attiva schermo intero";
17042  var exitFullScreen$b = "Disattiva schermo intero";
17043  var speed$b = "Velocità";
17044  var spacebar$b = "barra spaziatrice";
17045  var transcriptTitle$b = "Trascrizione";
17046  var lyricsTitle$b = "Testi";
17047  var autoScroll$b = "Auto scorrimento";
17048  var statusPlaying$b = "In riproduzione";
17049  var statusPaused$b = "In pausa";
17050  var statusStopped$b = "Fermato";
17051  var statusBuffering$b = "Buffering";
17052  var statusEnd$b = "Fine traccia";
17053  var selectedTrack$b = "Traccia selezionata";
17054  var alertDescribedVersion$b = "Sto usando la versione audiodescritta di questo video";
17055  var alertNonDescribedVersion$b = "Sto usando la versione non audiodescritta di questo video";
17056  var prefMenuCaptions$b = "Sottotitoli";
17057  var prefVoicedCaptions$b = "Spoken Captions";
17058  var prefMenuDescriptions$b = "Audioescrizioni";
17059  var prefMenuKeyboard$b = "Tastiera";
17060  var prefMenuTranscript$b = "Trascrizione";
17061  var prefTitleCaptions$b = "Preferenze sottotitoli";
17062  var prefTitleDescriptions$b = "Preferenze audiodescrizione";
17063  var prefTitleKeyboard$b = "Preferenze tastiera";
17064  var prefTitleTranscript$b = "Preferenze trascrizione";
17065  var prefIntroDescription1$b = "Questo lettore multimediale supporta le audiodescrizioni in due modi:";
17066  var prefDescription1$b = "il video corrente ha Una versione di descrizione alternativa, Descrizione testuale.";
17067  var prefDescription2$b = "il video corrente ha Versione di descrizione alternativa per il video.";
17068  var prefDescription3$b = "il video corrente ha Descrizione testuale, letta dal lettore di schermo.";
17069  var prefIntroDescriptionNone$b = "Il video corrente non ha audiodescrizioni.";
17070  var prefIntroDescription3$b = "Usa il seguente modulo per impostare le tue preferenze relative all'audiodescrizione testuale.";
17071  var prefIntroDescription4$b = "Dopo aver salvato le tue impostazioni, le audiodescrizioni possono essere attivate o disattivate usando i pulsanti descrizione.";
17072  var prefIntroKeyboard1$b = "Il lettore multimediale può essere usato dovunque in questa pagina, attraverso la tastiera. Vedi sotto per un elenco di tasti di scelta rapida.";
17073  var prefIntroKeyboard2$b = "I tasti di scelta rapida, (Shift, Alt, e Control) possono essere assegnati di seguito.";
17074  var prefIntroKeyboard3$b = "NOTA: Alcune combinazioni di tasti possono essere in conflitto con i tasti utilizzati dal tuo browser od altro software. Prova varie combinazioni di tasti per trovarne una che funzioni correttamente.";
17075  var prefHeadingKeyboard1$b = "Tasti di scelta rapida";
17076  var prefHeadingKeyboard2$b = "Tasti di scelta rapida correnti";
17077  var prefHeadingDescription$b = "Audiodescrizione";
17078  var prefHeadingTextDescription$b = "audiodescrizione testuale";
17079  var prefAltKey$b = "Alt";
17080  var prefCtrlKey$b = "Control";
17081  var prefShiftKey$b = "Shift";
17082  var prefNoKeyShortcuts$b = "Disable keyboard shortcuts";
17083  var escapeKey$b = "Escape";
17084  var escapeKeyFunction$b = "Chiude il menu o la finestra corrente";
17085  var prefDescPause$b = "Mette automaticamente il video in pausa, quando inizia la descrizione";
17086  var prefDescVisible$b = "Rende la descrizione visibile";
17087  var prefDescVoice$b = "Voice";
17088  var prefDescRate$b = "Spoken Description Rate";
17089  var prefCaptionRate$b = "Spoken Caption Rate";
17090  var prefDescPitch$b = "Pitch";
17091  var prefDescPitch1$b = "Very low";
17092  var prefDescPitch2$b = "Low";
17093  var prefDescPitch3$b = "Default";
17094  var prefDescPitch4$b = "High";
17095  var prefDescPitch5$b = "Very high";
17096  var sampleDescriptionText$b = "Adjust settings to hear this sample text.";
17097  var prefHighlight$b = "Evidenzia la descrizione";
17098  var prefTabbable$b = "Abilita la descrizione";
17099  var prefCaptionsFont$b = "Carattere";
17100  var prefCaptionsColor$b = "Colore del testo";
17101  var prefCaptionsBGColor$b = "Colore di sfondo";
17102  var prefCaptionsSize$b = "Dimensione del carattere";
17103  var prefCaptionsOpacity$b = "Opacità";
17104  var prefCaptionsStyle$b = "Stile";
17105  var serif$b = "serif";
17106  var sans$b = "sans-serif";
17107  var cursive$b = "corsivo";
17108  var fantasy$b = "fantasy";
17109  var monospace$b = "monospace";
17110  var white$b = "bianco";
17111  var yellow$b = "giallo";
17112  var green$b = "verde";
17113  var ciano = "ciano";
17114  var blue$b = "blue";
17115  var magenta$b = "magenta";
17116  var red$b = "rosso";
17117  var black$b = "nero";
17118  var transparent$b = "trasparente";
17119  var solid$b = "solido";
17120  var captionsStylePopOn$b = "A comparsa";
17121  var captionsStyleRollUp$b = "A scorrimento";
17122  var prefCaptionsPosition$b = "Posizione";
17123  var captionsPositionOverlay$b = "Copre";
17124  var captionsPositionBelow$b = "Sotto al video";
17125  var sampleCaptionText$b = "Testo di esempio sottotitoli";
17126  var prefSuccess$b = "I cambiamenti sono stati salvati.";
17127  var prefNoChange$b = "Non è stato effettuato alcun cambiamento.";
17128  var save$b = "Salva";
17129  var cancel$b = "Annulla";
17130  var dismissButton$b = "Dismiss";
17131  var windowButtonLabel$b = "Opzioni finestra";
17132  var windowMove$b = "Sposta";
17133  var windowMoveLeft$b = "Window moved left";
17134  var windowMoveRight$b = "Window moved right";
17135  var windowMoveUp$b = "Window moved up";
17136  var windowMoveDown$b = "Window moved down";
17137  var windowMoveStopped$b = "Window move stopped";
17138  var transcriptControls$b = "Transcript Window Controls";
17139  var signControls$b = "Sign Language Window Controls";
17140  var windowMoveAlert$b = "Trascina o usa le frecce per spostare la finestra. Invio per fermare.";
17141  var windowResize$b = "Ridimensiona";
17142  var windowResizeHeading$b = "Ridimensiona finestra";
17143  var closeButtonLabel$b = "Chiudi";
17144  var width$b = "Larghezza";
17145  var height$b = "Altezza";
17146  var resultsSummary1$b = "You searched for:";
17147  var resultsSummary2$b = "Found %1 matching items.";
17148  var resultsSummary3$b = "Click the time associated with any item to play the video from that point.";
17149  var noResultsFound$b = "No results found.";
17150  var searchButtonLabel$b = "Play at %1";
17151  var hour$b = "hour";
17152  var minute$b = "minute";
17153  var second$b = "second";
17154  var hours$b = "hours";
17155  var minutes$b = "minutes";
17156  var seconds$b = "seconds";
17157  var vtsHeading$b = "Video Transcript Sorter";
17158  var vtsInstructions1$b = "Use the Video Transcript Sorter to modify text tracks:";
17159  var vtsInstructions2$b = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
17160  var vtsInstructions3$b = "Modify content or start/end times (all are directly editable within the table).";
17161  var vtsInstructions4$b = "Add new content, such as chapters or descriptions.";
17162  var vtsInstructions5$b = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
17163  var vtsSelectLanguage$b = "Select a language";
17164  var vtsSave$b = "Generate new .vtt content";
17165  var vtsReturn$b = "Return to Editor";
17166  var vtsCancel$b = "Cancelling saving. Any edits you made have been restored in the VTS table.";
17167  var vtsRow$b = "Row";
17168  var vtsKind$b = "Kind";
17169  var vtsStart$b = "Start";
17170  var vtsEnd$b = "End";
17171  var vtsContent$b = "Content";
17172  var vtsActions$b = "Actions";
17173  var vtsNewRow$b = "A new row %1 has been inserted.";
17174  var vtsDeletedRow$b = "Row %1 has been deleted.";
17175  var vtsMovedRow$b = "Row %1 has been moved %2 and is now Row %3.";
17176  var it = {
17177  	playerHeading: playerHeading$b,
17178  	audioPlayer: audioPlayer$b,
17179  	videoPlayer: videoPlayer$b,
17180  	faster: faster$b,
17181  	slower: slower$b,
17182  	play: play$b,
17183  	pause: pause$b,
17184  	restart: restart$b,
17185  	prevTrack: prevTrack$b,
17186  	nextTrack: nextTrack$b,
17187  	rewind: rewind$b,
17188  	forward: forward$b,
17189  	captions: captions$b,
17190  	showCaptions: showCaptions$b,
17191  	hideCaptions: hideCaptions$b,
17192  	captionsOff: captionsOff$b,
17193  	showTranscript: showTranscript$b,
17194  	hideTranscript: hideTranscript$b,
17195  	turnOnDescriptions: turnOnDescriptions$b,
17196  	turnOffDescriptions: turnOffDescriptions$b,
17197  	chapters: chapters$b,
17198  	language: language$b,
17199  	sign: sign$b,
17200  	showSign: showSign$b,
17201  	hideSign: hideSign$b,
17202  	seekbarLabel: seekbarLabel$b,
17203  	mute: mute$b,
17204  	unmute: unmute$b,
17205  	volume: volume$b,
17206  	volumeUpDown: volumeUpDown$b,
17207  	preferences: preferences$b,
17208  	enterFullScreen: enterFullScreen$b,
17209  	exitFullScreen: exitFullScreen$b,
17210  	speed: speed$b,
17211  	spacebar: spacebar$b,
17212  	transcriptTitle: transcriptTitle$b,
17213  	lyricsTitle: lyricsTitle$b,
17214  	autoScroll: autoScroll$b,
17215  	statusPlaying: statusPlaying$b,
17216  	statusPaused: statusPaused$b,
17217  	statusStopped: statusStopped$b,
17218  	statusBuffering: statusBuffering$b,
17219  	statusEnd: statusEnd$b,
17220  	selectedTrack: selectedTrack$b,
17221  	alertDescribedVersion: alertDescribedVersion$b,
17222  	alertNonDescribedVersion: alertNonDescribedVersion$b,
17223  	prefMenuCaptions: prefMenuCaptions$b,
17224  	prefVoicedCaptions: prefVoicedCaptions$b,
17225  	prefMenuDescriptions: prefMenuDescriptions$b,
17226  	prefMenuKeyboard: prefMenuKeyboard$b,
17227  	prefMenuTranscript: prefMenuTranscript$b,
17228  	prefTitleCaptions: prefTitleCaptions$b,
17229  	prefTitleDescriptions: prefTitleDescriptions$b,
17230  	prefTitleKeyboard: prefTitleKeyboard$b,
17231  	prefTitleTranscript: prefTitleTranscript$b,
17232  	prefIntroDescription1: prefIntroDescription1$b,
17233  	prefDescription1: prefDescription1$b,
17234  	prefDescription2: prefDescription2$b,
17235  	prefDescription3: prefDescription3$b,
17236  	prefIntroDescriptionNone: prefIntroDescriptionNone$b,
17237  	prefIntroDescription3: prefIntroDescription3$b,
17238  	prefIntroDescription4: prefIntroDescription4$b,
17239  	prefIntroKeyboard1: prefIntroKeyboard1$b,
17240  	prefIntroKeyboard2: prefIntroKeyboard2$b,
17241  	prefIntroKeyboard3: prefIntroKeyboard3$b,
17242  	prefHeadingKeyboard1: prefHeadingKeyboard1$b,
17243  	prefHeadingKeyboard2: prefHeadingKeyboard2$b,
17244  	prefHeadingDescription: prefHeadingDescription$b,
17245  	prefHeadingTextDescription: prefHeadingTextDescription$b,
17246  	prefAltKey: prefAltKey$b,
17247  	prefCtrlKey: prefCtrlKey$b,
17248  	prefShiftKey: prefShiftKey$b,
17249  	prefNoKeyShortcuts: prefNoKeyShortcuts$b,
17250  	escapeKey: escapeKey$b,
17251  	escapeKeyFunction: escapeKeyFunction$b,
17252  	prefDescPause: prefDescPause$b,
17253  	prefDescVisible: prefDescVisible$b,
17254  	prefDescVoice: prefDescVoice$b,
17255  	prefDescRate: prefDescRate$b,
17256  	prefCaptionRate: prefCaptionRate$b,
17257  	prefDescPitch: prefDescPitch$b,
17258  	prefDescPitch1: prefDescPitch1$b,
17259  	prefDescPitch2: prefDescPitch2$b,
17260  	prefDescPitch3: prefDescPitch3$b,
17261  	prefDescPitch4: prefDescPitch4$b,
17262  	prefDescPitch5: prefDescPitch5$b,
17263  	sampleDescriptionText: sampleDescriptionText$b,
17264  	prefHighlight: prefHighlight$b,
17265  	prefTabbable: prefTabbable$b,
17266  	prefCaptionsFont: prefCaptionsFont$b,
17267  	prefCaptionsColor: prefCaptionsColor$b,
17268  	prefCaptionsBGColor: prefCaptionsBGColor$b,
17269  	prefCaptionsSize: prefCaptionsSize$b,
17270  	prefCaptionsOpacity: prefCaptionsOpacity$b,
17271  	prefCaptionsStyle: prefCaptionsStyle$b,
17272  	serif: serif$b,
17273  	sans: sans$b,
17274  	cursive: cursive$b,
17275  	fantasy: fantasy$b,
17276  	monospace: monospace$b,
17277  	white: white$b,
17278  	yellow: yellow$b,
17279  	green: green$b,
17280  	ciano: ciano,
17281  	blue: blue$b,
17282  	magenta: magenta$b,
17283  	red: red$b,
17284  	black: black$b,
17285  	transparent: transparent$b,
17286  	solid: solid$b,
17287  	captionsStylePopOn: captionsStylePopOn$b,
17288  	captionsStyleRollUp: captionsStyleRollUp$b,
17289  	prefCaptionsPosition: prefCaptionsPosition$b,
17290  	captionsPositionOverlay: captionsPositionOverlay$b,
17291  	captionsPositionBelow: captionsPositionBelow$b,
17292  	sampleCaptionText: sampleCaptionText$b,
17293  	prefSuccess: prefSuccess$b,
17294  	prefNoChange: prefNoChange$b,
17295  	save: save$b,
17296  	cancel: cancel$b,
17297  	dismissButton: dismissButton$b,
17298  	windowButtonLabel: windowButtonLabel$b,
17299  	windowMove: windowMove$b,
17300  	windowMoveLeft: windowMoveLeft$b,
17301  	windowMoveRight: windowMoveRight$b,
17302  	windowMoveUp: windowMoveUp$b,
17303  	windowMoveDown: windowMoveDown$b,
17304  	windowMoveStopped: windowMoveStopped$b,
17305  	transcriptControls: transcriptControls$b,
17306  	signControls: signControls$b,
17307  	windowMoveAlert: windowMoveAlert$b,
17308  	windowResize: windowResize$b,
17309  	windowResizeHeading: windowResizeHeading$b,
17310  	closeButtonLabel: closeButtonLabel$b,
17311  	width: width$b,
17312  	height: height$b,
17313  	resultsSummary1: resultsSummary1$b,
17314  	resultsSummary2: resultsSummary2$b,
17315  	resultsSummary3: resultsSummary3$b,
17316  	noResultsFound: noResultsFound$b,
17317  	searchButtonLabel: searchButtonLabel$b,
17318  	hour: hour$b,
17319  	minute: minute$b,
17320  	second: second$b,
17321  	hours: hours$b,
17322  	minutes: minutes$b,
17323  	seconds: seconds$b,
17324  	vtsHeading: vtsHeading$b,
17325  	vtsInstructions1: vtsInstructions1$b,
17326  	vtsInstructions2: vtsInstructions2$b,
17327  	vtsInstructions3: vtsInstructions3$b,
17328  	vtsInstructions4: vtsInstructions4$b,
17329  	vtsInstructions5: vtsInstructions5$b,
17330  	vtsSelectLanguage: vtsSelectLanguage$b,
17331  	vtsSave: vtsSave$b,
17332  	vtsReturn: vtsReturn$b,
17333  	vtsCancel: vtsCancel$b,
17334  	vtsRow: vtsRow$b,
17335  	vtsKind: vtsKind$b,
17336  	vtsStart: vtsStart$b,
17337  	vtsEnd: vtsEnd$b,
17338  	vtsContent: vtsContent$b,
17339  	vtsActions: vtsActions$b,
17340  	vtsNewRow: vtsNewRow$b,
17341  	vtsDeletedRow: vtsDeletedRow$b,
17342  	vtsMovedRow: vtsMovedRow$b
17343  };
17344
17345  var playerHeading$a = "メディアプレイヤー";
17346  var audioPlayer$a = "Audio player";
17347  var videoPlayer$a = "Video player";
17348  var faster$a = "はやく";
17349  var slower$a = "おそく";
17350  var play$a = "再生";
17351  var pause$a = "一時停止";
17352  var restart$a = "再開";
17353  var prevTrack$a = "前のトラック";
17354  var nextTrack$a = "次のトラック";
17355  var rewind$a = "巻き戻し";
17356  var forward$a = "早送り";
17357  var captions$a = "キャプション";
17358  var showCaptions$a = "キャプションを表示する";
17359  var hideCaptions$a = "キャプションを非表示にする";
17360  var captionsOff$a = "キャプションを消す";
17361  var showTranscript$a = "書き起こしの表示";
17362  var hideTranscript$a = "書き起こしを非表示にする";
17363  var turnOnDescriptions$a = "音声解説を出す";
17364  var turnOffDescriptions$a = "音声解説を出さない";
17365  var chapters$a = "チャプター";
17366  var language$a = "言語";
17367  var sign$a = "手話";
17368  var showSign$a = "手話を表示";
17369  var hideSign$a = "手話を非表示";
17370  var seekbarLabel$a = "タイムライン";
17371  var mute$a = "消音";
17372  var unmute$a = "消音解除";
17373  var volume$a = "音量";
17374  var volumeUpDown$a = "音量の上下";
17375  var preferences$a = "設定";
17376  var enterFullScreen$a = "全画面表示";
17377  var exitFullScreen$a = "全画面表示の終了";
17378  var speed$a = "再生速度";
17379  var spacebar$a = "スペースキー";
17380  var transcriptTitle$a = "書き起こし";
17381  var lyricsTitle$a = "歌詞";
17382  var autoScroll$a = "自動スクロール";
17383  var statusPlaying$a = "再生中";
17384  var statusPaused$a = "一時停止中";
17385  var statusStopped$a = "停止";
17386  var statusBuffering$a = "バッファリング中";
17387  var statusEnd$a = "トラックの終わり";
17388  var selectedTrack$a = "選択されたトラック";
17389  var alertDescribedVersion$a = "この動画の音声解説付きバージョンを使います";
17390  var alertNonDescribedVersion$a = "この動画の解説なしバージョンを使います";
17391  var prefMenuCaptions$a = "キャプション";
17392  var prefVoicedCaptions$a = "Spoken Captions";
17393  var prefMenuDescriptions$a = "音声解説";
17394  var prefMenuKeyboard$a = "キーボード";
17395  var prefMenuTranscript$a = "字幕";
17396  var prefTitleCaptions$a = "キャプションの設定";
17397  var prefTitleDescriptions$a = "音声解説の設定";
17398  var prefTitleKeyboard$a = "キーボードの設定";
17399  var prefTitleTranscript$a = "字幕の設定";
17400  var prefIntroDescription1$a = "このメディアプレイヤーは次の2つの方法で音声解説をサポートします: ";
17401  var prefDescription1$a = "現在の動画では次の方法が選択可能です: 解説付きの代替バージョン テキストによる解説";
17402  var prefDescription2$a = "現在の動画では次の方法が選択可能です: 解説付きの代替バージョンのビデオ";
17403  var prefDescription3$a = "現在の動画では次の方法が選択可能です: テキストによる解説(スクリーンリーダーによって読み上げられる)";
17404  var prefIntroDescriptionNone$a = "現在の動画にはどちらの形式の音声解説も含まれていません。";
17405  var prefIntroDescription3$a = "次のフォームを使って、音声解説に関連する設定を保存できます。";
17406  var prefIntroDescription4$a = "設定が保存されたら、音声解説ボタンによって音声解説の表示・非表示を切り替えることができます。";
17407  var prefIntroKeyboard1$a = "このページのメディアプレイヤーは、キーボード・ショートカットを使ってこのページのどこからでも操作できます(下の一覧を参照してください)。";
17408  var prefIntroKeyboard2$a = "修飾キー(Shift、Alt と Control) は以下で割り当てることができます。";
17409  var prefIntroKeyboard3$a = "注意: いくつかのキーの組み合わせは、ブラウザやアプリケーション・ソフトで使われているものと衝突する可能性があります。ご利用の環境で正しく動作する、様々なキーの組み合わせを試してください。";
17410  var prefHeadingKeyboard1$a = "ショートカットの修飾キー";
17411  var prefHeadingKeyboard2$a = "現在のキーボード・ショートカット";
17412  var prefHeadingDescription$a = "音声解説";
17413  var prefHeadingTextDescription$a = "テキストによる音声解説";
17414  var prefAltKey$a = "Alt";
17415  var prefCtrlKey$a = "Control";
17416  var prefShiftKey$a = "Shift";
17417  var prefNoKeyShortcuts$a = "Disable keyboard shortcuts";
17418  var escapeKey$a = "エスケープ";
17419  var escapeKeyFunction$a = "現在のダイアログやポップアップメニューを閉じる";
17420  var prefDescPause$a = "解説が表示されたら動画を自動的に停止する";
17421  var prefDescVisible$a = "解説が見えるようにする";
17422  var prefDescVoice$a = "音";
17423  var prefDescRate$a = "Spoken Description Rate";
17424  var prefCaptionRate$a = "Spoken Caption Rate";
17425  var prefDescPitch$a = "ピッチ";
17426  var prefDescPitch1$a = "非常に低い";
17427  var prefDescPitch2$a = "低い";
17428  var prefDescPitch3$a = "標準";
17429  var prefDescPitch4$a = "高い";
17430  var prefDescPitch5$a = "非常に高い";
17431  var sampleDescriptionText$a = "このサンプル・テキストを聞くために設定を合わせます。";
17432  var prefHighlight$a = "メディアの再生に合わせて字幕をハイライトする";
17433  var prefTabbable$a = "キーボード操作可能な字幕";
17434  var prefCaptionsFont$a = "フォント";
17435  var prefCaptionsColor$a = "文字色";
17436  var prefCaptionsBGColor$a = "背景色";
17437  var prefCaptionsSize$a = "フォントサイズ";
17438  var prefCaptionsOpacity$a = "不透明度";
17439  var prefCaptionsStyle$a = "書式";
17440  var serif$a = "セリフ";
17441  var sans$a = "サンセリフ";
17442  var cursive$a = "cursive";
17443  var fantasy$a = "fantasy";
17444  var monospace$a = "monospace";
17445  var white$a = "白";
17446  var yellow$a = "黄色";
17447  var green$a = "緑";
17448  var cyan$a = "シアン";
17449  var blue$a = "青";
17450  var magenta$a = "マゼンタ";
17451  var red$a = "赤";
17452  var black$a = "黒";
17453  var transparent$a = "透明";
17454  var solid$a = "不透明";
17455  var captionsStylePopOn$a = "ポップ・オン";
17456  var captionsStyleRollUp$a = "ロール・アップ";
17457  var prefCaptionsPosition$a = "位置";
17458  var captionsPositionOverlay$a = "オーバーレイ表示";
17459  var captionsPositionBelow$a = "動画の下部";
17460  var sampleCaptionText$a = "キャプション表示の例";
17461  var prefSuccess$a = "変更が保存されました。";
17462  var prefNoChange$a = "設定が変更されていません。";
17463  var save$a = "保存";
17464  var cancel$a = "キャンセル";
17465  var dismissButton$a = "Dismiss";
17466  var windowButtonLabel$a = "ウィンドウの設定";
17467  var windowMove$a = "移動";
17468  var windowMoveLeft$a = "Window moved left";
17469  var windowMoveRight$a = "Window moved right";
17470  var windowMoveUp$a = "Window moved up";
17471  var windowMoveDown$a = "Window moved down";
17472  var windowMoveStopped$a = "Window move stopped";
17473  var transcriptControls$a = "Transcript Window Controls";
17474  var signControls$a = "Sign Language Window Controls";
17475  var windowMoveAlert$a = "マウスをドラッグするか矢印キーでウィンドウを移動できます; Enterで終了";
17476  var windowResize$a = "サイズを変える";
17477  var windowResizeHeading$a = "ウィンドウのサイズ変更";
17478  var closeButtonLabel$a = "閉じる";
17479  var width$a = "幅";
17480  var height$a = "高さ";
17481  var resultsSummary1$a = "次を検索:";
17482  var resultsSummary2$a = "見つかりました %1 個の検索結果";
17483  var resultsSummary3$a = "アイテムに関連付けられている時間をクリックして、その時点からビデオを再生します。";
17484  var noResultsFound$a = "見つかりませんでした。";
17485  var searchButtonLabel$a = "次から再生 %1";
17486  var hour$a = "時間";
17487  var minute$a = "分";
17488  var second$a = "秒";
17489  var hours$a = "時間";
17490  var minutes$a = "分";
17491  var seconds$a = "秒";
17492  var vtsHeading$a = "Video Transcript Sorter";
17493  var vtsInstructions1$a = "Use the Video Transcript Sorter to modify text tracks:";
17494  var vtsInstructions2$a = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
17495  var vtsInstructions3$a = "Modify content or start/end times (all are directly editable within the table).";
17496  var vtsInstructions4$a = "Add new content, such as chapters or descriptions.";
17497  var vtsInstructions5$a = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
17498  var vtsSelectLanguage$a = "Select a language";
17499  var vtsSave$a = "Generate new .vtt content";
17500  var vtsReturn$a = "Return to Editor";
17501  var vtsCancel$a = "Cancelling saving. Any edits you made have been restored in the VTS table.";
17502  var vtsRow$a = "Row";
17503  var vtsKind$a = "Kind";
17504  var vtsStart$a = "Start";
17505  var vtsEnd$a = "End";
17506  var vtsContent$a = "Content";
17507  var vtsActions$a = "Actions";
17508  var vtsNewRow$a = "A new row %1 has been inserted.";
17509  var vtsDeletedRow$a = "Row %1 has been deleted.";
17510  var vtsMovedRow$a = "Row %1 has been moved %2 and is now Row %3.";
17511  var ja = {
17512  	playerHeading: playerHeading$a,
17513  	audioPlayer: audioPlayer$a,
17514  	videoPlayer: videoPlayer$a,
17515  	faster: faster$a,
17516  	slower: slower$a,
17517  	play: play$a,
17518  	pause: pause$a,
17519  	restart: restart$a,
17520  	prevTrack: prevTrack$a,
17521  	nextTrack: nextTrack$a,
17522  	rewind: rewind$a,
17523  	forward: forward$a,
17524  	captions: captions$a,
17525  	showCaptions: showCaptions$a,
17526  	hideCaptions: hideCaptions$a,
17527  	captionsOff: captionsOff$a,
17528  	showTranscript: showTranscript$a,
17529  	hideTranscript: hideTranscript$a,
17530  	turnOnDescriptions: turnOnDescriptions$a,
17531  	turnOffDescriptions: turnOffDescriptions$a,
17532  	chapters: chapters$a,
17533  	language: language$a,
17534  	sign: sign$a,
17535  	showSign: showSign$a,
17536  	hideSign: hideSign$a,
17537  	seekbarLabel: seekbarLabel$a,
17538  	mute: mute$a,
17539  	unmute: unmute$a,
17540  	volume: volume$a,
17541  	volumeUpDown: volumeUpDown$a,
17542  	preferences: preferences$a,
17543  	enterFullScreen: enterFullScreen$a,
17544  	exitFullScreen: exitFullScreen$a,
17545  	speed: speed$a,
17546  	spacebar: spacebar$a,
17547  	transcriptTitle: transcriptTitle$a,
17548  	lyricsTitle: lyricsTitle$a,
17549  	autoScroll: autoScroll$a,
17550  	statusPlaying: statusPlaying$a,
17551  	statusPaused: statusPaused$a,
17552  	statusStopped: statusStopped$a,
17553  	statusBuffering: statusBuffering$a,
17554  	statusEnd: statusEnd$a,
17555  	selectedTrack: selectedTrack$a,
17556  	alertDescribedVersion: alertDescribedVersion$a,
17557  	alertNonDescribedVersion: alertNonDescribedVersion$a,
17558  	prefMenuCaptions: prefMenuCaptions$a,
17559  	prefVoicedCaptions: prefVoicedCaptions$a,
17560  	prefMenuDescriptions: prefMenuDescriptions$a,
17561  	prefMenuKeyboard: prefMenuKeyboard$a,
17562  	prefMenuTranscript: prefMenuTranscript$a,
17563  	prefTitleCaptions: prefTitleCaptions$a,
17564  	prefTitleDescriptions: prefTitleDescriptions$a,
17565  	prefTitleKeyboard: prefTitleKeyboard$a,
17566  	prefTitleTranscript: prefTitleTranscript$a,
17567  	prefIntroDescription1: prefIntroDescription1$a,
17568  	prefDescription1: prefDescription1$a,
17569  	prefDescription2: prefDescription2$a,
17570  	prefDescription3: prefDescription3$a,
17571  	prefIntroDescriptionNone: prefIntroDescriptionNone$a,
17572  	prefIntroDescription3: prefIntroDescription3$a,
17573  	prefIntroDescription4: prefIntroDescription4$a,
17574  	prefIntroKeyboard1: prefIntroKeyboard1$a,
17575  	prefIntroKeyboard2: prefIntroKeyboard2$a,
17576  	prefIntroKeyboard3: prefIntroKeyboard3$a,
17577  	prefHeadingKeyboard1: prefHeadingKeyboard1$a,
17578  	prefHeadingKeyboard2: prefHeadingKeyboard2$a,
17579  	prefHeadingDescription: prefHeadingDescription$a,
17580  	prefHeadingTextDescription: prefHeadingTextDescription$a,
17581  	prefAltKey: prefAltKey$a,
17582  	prefCtrlKey: prefCtrlKey$a,
17583  	prefShiftKey: prefShiftKey$a,
17584  	prefNoKeyShortcuts: prefNoKeyShortcuts$a,
17585  	escapeKey: escapeKey$a,
17586  	escapeKeyFunction: escapeKeyFunction$a,
17587  	prefDescPause: prefDescPause$a,
17588  	prefDescVisible: prefDescVisible$a,
17589  	prefDescVoice: prefDescVoice$a,
17590  	prefDescRate: prefDescRate$a,
17591  	prefCaptionRate: prefCaptionRate$a,
17592  	prefDescPitch: prefDescPitch$a,
17593  	prefDescPitch1: prefDescPitch1$a,
17594  	prefDescPitch2: prefDescPitch2$a,
17595  	prefDescPitch3: prefDescPitch3$a,
17596  	prefDescPitch4: prefDescPitch4$a,
17597  	prefDescPitch5: prefDescPitch5$a,
17598  	sampleDescriptionText: sampleDescriptionText$a,
17599  	prefHighlight: prefHighlight$a,
17600  	prefTabbable: prefTabbable$a,
17601  	prefCaptionsFont: prefCaptionsFont$a,
17602  	prefCaptionsColor: prefCaptionsColor$a,
17603  	prefCaptionsBGColor: prefCaptionsBGColor$a,
17604  	prefCaptionsSize: prefCaptionsSize$a,
17605  	prefCaptionsOpacity: prefCaptionsOpacity$a,
17606  	prefCaptionsStyle: prefCaptionsStyle$a,
17607  	serif: serif$a,
17608  	sans: sans$a,
17609  	cursive: cursive$a,
17610  	fantasy: fantasy$a,
17611  	monospace: monospace$a,
17612  	white: white$a,
17613  	yellow: yellow$a,
17614  	green: green$a,
17615  	cyan: cyan$a,
17616  	blue: blue$a,
17617  	magenta: magenta$a,
17618  	red: red$a,
17619  	black: black$a,
17620  	transparent: transparent$a,
17621  	solid: solid$a,
17622  	captionsStylePopOn: captionsStylePopOn$a,
17623  	captionsStyleRollUp: captionsStyleRollUp$a,
17624  	prefCaptionsPosition: prefCaptionsPosition$a,
17625  	captionsPositionOverlay: captionsPositionOverlay$a,
17626  	captionsPositionBelow: captionsPositionBelow$a,
17627  	sampleCaptionText: sampleCaptionText$a,
17628  	prefSuccess: prefSuccess$a,
17629  	prefNoChange: prefNoChange$a,
17630  	save: save$a,
17631  	cancel: cancel$a,
17632  	dismissButton: dismissButton$a,
17633  	windowButtonLabel: windowButtonLabel$a,
17634  	windowMove: windowMove$a,
17635  	windowMoveLeft: windowMoveLeft$a,
17636  	windowMoveRight: windowMoveRight$a,
17637  	windowMoveUp: windowMoveUp$a,
17638  	windowMoveDown: windowMoveDown$a,
17639  	windowMoveStopped: windowMoveStopped$a,
17640  	transcriptControls: transcriptControls$a,
17641  	signControls: signControls$a,
17642  	windowMoveAlert: windowMoveAlert$a,
17643  	windowResize: windowResize$a,
17644  	windowResizeHeading: windowResizeHeading$a,
17645  	closeButtonLabel: closeButtonLabel$a,
17646  	width: width$a,
17647  	height: height$a,
17648  	resultsSummary1: resultsSummary1$a,
17649  	resultsSummary2: resultsSummary2$a,
17650  	resultsSummary3: resultsSummary3$a,
17651  	noResultsFound: noResultsFound$a,
17652  	searchButtonLabel: searchButtonLabel$a,
17653  	hour: hour$a,
17654  	minute: minute$a,
17655  	second: second$a,
17656  	hours: hours$a,
17657  	minutes: minutes$a,
17658  	seconds: seconds$a,
17659  	vtsHeading: vtsHeading$a,
17660  	vtsInstructions1: vtsInstructions1$a,
17661  	vtsInstructions2: vtsInstructions2$a,
17662  	vtsInstructions3: vtsInstructions3$a,
17663  	vtsInstructions4: vtsInstructions4$a,
17664  	vtsInstructions5: vtsInstructions5$a,
17665  	vtsSelectLanguage: vtsSelectLanguage$a,
17666  	vtsSave: vtsSave$a,
17667  	vtsReturn: vtsReturn$a,
17668  	vtsCancel: vtsCancel$a,
17669  	vtsRow: vtsRow$a,
17670  	vtsKind: vtsKind$a,
17671  	vtsStart: vtsStart$a,
17672  	vtsEnd: vtsEnd$a,
17673  	vtsContent: vtsContent$a,
17674  	vtsActions: vtsActions$a,
17675  	vtsNewRow: vtsNewRow$a,
17676  	vtsDeletedRow: vtsDeletedRow$a,
17677  	vtsMovedRow: vtsMovedRow$a
17678  };
17679
17680  var playerHeading$9 = "Pemain media";
17681  var audioPlayer$9 = "Audio player";
17682  var videoPlayer$9 = "Video player";
17683  var faster$9 = "Lebih laju";
17684  var slower$9 = "Lebih perlahan";
17685  var play$9 = "Main";
17686  var pause$9 = "Jeda";
17687  var restart$9 = "Mula semula";
17688  var prevTrack$9 = "Trek sebelumnya";
17689  var nextTrack$9 = "Trek seterusnya";
17690  var rewind$9 = "Undur";
17691  var forward$9 = "Majukan";
17692  var captions$9 = "Sarikata";
17693  var showCaptions$9 = "Tunjuk sarikata";
17694  var hideCaptions$9 = "Sembunyi sarikata";
17695  var captionsOff$9 = "Sarikata dimatikan";
17696  var showTranscript$9 = "Tunjuk transkrip";
17697  var hideTranscript$9 = "Sembunyi transkrip";
17698  var turnOnDescriptions$9 = "Hidupkan deskripsi";
17699  var turnOffDescriptions$9 = "Matikan deskripsi";
17700  var chapters$9 = "Bab";
17701  var language$9 = "Bahasa";
17702  var sign$9 = "Bahasa isyarat";
17703  var showSign$9 = "Tunjuk bahasa isyarat";
17704  var hideSign$9 = "Sembunyi bahasa isyarat";
17705  var seekbarLabel$9 = "garis masa";
17706  var mute$9 = "Bisukan";
17707  var unmute$9 = "Nyahbisukan";
17708  var volume$9 = "Volum";
17709  var volumeUpDown$9 = "Naik turun volum";
17710  var preferences$9 = "Keutamaan";
17711  var enterFullScreen$9 = "Masuk skrin penuh";
17712  var exitFullScreen$9 = "Keluar skrin penuh";
17713  var speed$9 = "Kelajuan";
17714  var spacebar$9 = "bar ruang";
17715  var transcriptTitle$9 = "Transkrip";
17716  var lyricsTitle$9 = "Lirik";
17717  var autoScroll$9 = "Skrol automatik";
17718  var statusPlaying$9 = "Sedang dimainkan";
17719  var statusPaused$9 = "Dijeda";
17720  var statusStopped$9 = "Dihentikan";
17721  var statusBuffering$9 = "Memuatkan";
17722  var statusEnd$9 = "Akhir trek";
17723  var selectedTrack$9 = "Trek dipilih";
17724  var alertDescribedVersion$9 = "Menggunakan versi video dengan deskripsi audio";
17725  var alertNonDescribedVersion$9 = "Menggunakan versi video tanpa deskripsi audio";
17726  var prefMenuCaptions$9 = "Sarikata";
17727  var prefVoicedCaptions$9 = "Spoken Captions";
17728  var prefMenuDescriptions$9 = "Deskripsi";
17729  var prefMenuKeyboard$9 = "Papan kekunci";
17730  var prefMenuTranscript$9 = "Transkrip";
17731  var prefTitleCaptions$9 = "Keutamaan Sarikata";
17732  var prefTitleDescriptions$9 = "Keutamaan Deskripsi Audio";
17733  var prefTitleKeyboard$9 = "Keutamaan Papan Kekunci";
17734  var prefTitleTranscript$9 = "Keutamaan Transkrip";
17735  var prefIntroDescription1$9 = "Pemain media ini menyokong deskripsi audio dalam dua cara: ";
17736  var prefDescription1$9 = "Video semasa mempunyai versi berdeskripsi alternatif, deskripsi berasaskan teks.";
17737  var prefDescription2$9 = "Video semasa mempunyai versi video berdeskripsi alternatif.";
17738  var prefDescription3$9 = "Video semasa mempunyai deskripsi berasaskan teks, diumumkan oleh pembaca skrin.";
17739  var prefIntroDescriptionNone$9 = "Video semasa tiada deskripsi audio dalam mana-mana format.";
17740  var prefIntroDescription3$9 = "Gunakan borang berikut untuk menetapkan keutamaan berkaitan deskripsi audio berasaskan teks.";
17741  var prefIntroDescription4$9 = "Selepas anda menyimpan tetapan, deskripsi audio boleh dihidupkan/dimatikan menggunakan butang Deskripsi.";
17742  var prefIntroKeyboard1$9 = "Pemain media di laman web ini boleh dikendalikan dari mana-mana sahaja di halaman menggunakan pintasan papan kekunci (lihat di bawah untuk senarai).";
17743  var prefIntroKeyboard2$9 = "Kekunci pengubah (Shift, Alt, dan Control) boleh ditetapkan di bawah.";
17744  var prefIntroKeyboard3$9 = "NOTA: Sesetengah kombinasi kekunci mungkin bertindah dengan kekunci yang digunakan oleh pelayar dan/atau aplikasi lain. Cuba pelbagai kombinasi kekunci pengubah untuk mencari yang sesuai untuk anda.";
17745  var prefHeadingKeyboard1$9 = "Kekunci pengubah untuk pintasan";
17746  var prefHeadingKeyboard2$9 = "Pintasan papan kekunci semasa";
17747  var prefHeadingDescription$9 = "Deskripsi audio";
17748  var prefHeadingTextDescription$9 = "Deskripsi audio berasaskan teks";
17749  var prefAltKey$9 = "Alt";
17750  var prefCtrlKey$9 = "Control";
17751  var prefShiftKey$9 = "Shift";
17752  var prefNoKeyShortcuts$9 = "Disable keyboard shortcuts";
17753  var escapeKey$9 = "Escape";
17754  var escapeKeyFunction$9 = "Tutup dialog atau menu popup semasa";
17755  var prefDescPause$9 = "Jeda video secara automatik apabila deskripsi bermula";
17756  var prefDescVisible$9 = "Paparkan deskripsi";
17757  var prefDescVoice$9 = "Suara";
17758  var prefDescRate$9 = "Spoken Description Rate";
17759  var prefCaptionRate$9 = "Spoken Caption Rate";
17760  var prefDescPitch$9 = "Nada";
17761  var prefDescPitch1$9 = "Sangat rendah";
17762  var prefDescPitch2$9 = "Rendah";
17763  var prefDescPitch3$9 = "Lalai";
17764  var prefDescPitch4$9 = "Tinggi";
17765  var prefDescPitch5$9 = "Sangat tinggi";
17766  var sampleDescriptionText$9 = "Laraskan tetapan untuk mendengar teks contoh ini.";
17767  var prefHighlight$9 = "Serlahkan transkrip semasa media dimainkan";
17768  var prefTabbable$9 = "Aktifkan transkrip dengan papan kekunci";
17769  var prefCaptionsFont$9 = "Fon";
17770  var prefCaptionsColor$9 = "Warna Teks";
17771  var prefCaptionsBGColor$9 = "Latar Belakang";
17772  var prefCaptionsSize$9 = "Saiz Fon";
17773  var prefCaptionsOpacity$9 = "Kelegapan";
17774  var prefCaptionsStyle$9 = "Gaya";
17775  var serif$9 = "serif";
17776  var sans$9 = "sans-serif";
17777  var cursive$9 = "cursive";
17778  var fantasy$9 = "fantasy";
17779  var monospace$9 = "monospace";
17780  var white$9 = "putih";
17781  var yellow$9 = "kuning";
17782  var green$9 = "hijau";
17783  var cyan$9 = "sian";
17784  var blue$9 = "biru";
17785  var magenta$9 = "magenta";
17786  var red$9 = "merah";
17787  var black$9 = "hitam";
17788  var transparent$9 = "telus";
17789  var solid$9 = "pepejal";
17790  var captionsStylePopOn$9 = "Pop-on";
17791  var captionsStyleRollUp$9 = "Roll-up";
17792  var prefCaptionsPosition$9 = "Kedudukan";
17793  var captionsPositionOverlay$9 = "Lapisan atas";
17794  var captionsPositionBelow$9 = "Di bawah video";
17795  var sampleCaptionText$9 = "Teks contoh sarikata";
17796  var prefSuccess$9 = "Perubahan anda telah disimpan.";
17797  var prefNoChange$9 = "Anda tidak membuat sebarang perubahan.";
17798  var save$9 = "Simpan";
17799  var cancel$9 = "Batal";
17800  var dismissButton$9 = "Dismiss";
17801  var windowButtonLabel$9 = "Pilihan tetingkap";
17802  var windowMove$9 = "Alih";
17803  var windowMoveLeft$9 = "Window moved left";
17804  var windowMoveRight$9 = "Window moved right";
17805  var windowMoveUp$9 = "Window moved up";
17806  var windowMoveDown$9 = "Window moved down";
17807  var windowMoveStopped$9 = "Window move stopped";
17808  var transcriptControls$9 = "Transcript Window Controls";
17809  var signControls$9 = "Sign Language Window Controls";
17810  var windowMoveAlert$9 = "Seret atau gunakan kekunci anak panah untuk mengalih tetingkap; Enter untuk berhenti";
17811  var windowResize$9 = "Ubah saiz";
17812  var windowResizeHeading$9 = "Ubah Saiz Tetingkap";
17813  var closeButtonLabel$9 = "Tutup";
17814  var width$9 = "Lebar";
17815  var height$9 = "Tinggi";
17816  var resultsSummary1$9 = "Anda mencari:";
17817  var resultsSummary2$9 = "Ditemui %1 item sepadan.";
17818  var resultsSummary3$9 = "Klik masa yang berkaitan dengan mana-mana item untuk mainkan video dari titik itu.";
17819  var noResultsFound$9 = "Tiada hasil ditemui.";
17820  var searchButtonLabel$9 = "Main pada %1";
17821  var hour$9 = "jam";
17822  var minute$9 = "minit";
17823  var second$9 = "saat";
17824  var hours$9 = "jam";
17825  var minutes$9 = "minit";
17826  var seconds$9 = "saat";
17827  var vtsHeading$9 = "Video Transcript Sorter";
17828  var vtsInstructions1$9 = "Use the Video Transcript Sorter to modify text tracks:";
17829  var vtsInstructions2$9 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
17830  var vtsInstructions3$9 = "Modify content or start/end times (all are directly editable within the table).";
17831  var vtsInstructions4$9 = "Add new content, such as chapters or descriptions.";
17832  var vtsInstructions5$9 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
17833  var vtsSelectLanguage$9 = "Select a language";
17834  var vtsSave$9 = "Generate new .vtt content";
17835  var vtsReturn$9 = "Return to Editor";
17836  var vtsCancel$9 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
17837  var vtsRow$9 = "Row";
17838  var vtsKind$9 = "Kind";
17839  var vtsStart$9 = "Start";
17840  var vtsEnd$9 = "End";
17841  var vtsContent$9 = "Content";
17842  var vtsActions$9 = "Actions";
17843  var vtsNewRow$9 = "A new row %1 has been inserted.";
17844  var vtsDeletedRow$9 = "Row %1 has been deleted.";
17845  var vtsMovedRow$9 = "Row %1 has been moved %2 and is now Row %3.";
17846  var ms = {
17847  	playerHeading: playerHeading$9,
17848  	audioPlayer: audioPlayer$9,
17849  	videoPlayer: videoPlayer$9,
17850  	faster: faster$9,
17851  	slower: slower$9,
17852  	play: play$9,
17853  	pause: pause$9,
17854  	restart: restart$9,
17855  	prevTrack: prevTrack$9,
17856  	nextTrack: nextTrack$9,
17857  	rewind: rewind$9,
17858  	forward: forward$9,
17859  	captions: captions$9,
17860  	showCaptions: showCaptions$9,
17861  	hideCaptions: hideCaptions$9,
17862  	captionsOff: captionsOff$9,
17863  	showTranscript: showTranscript$9,
17864  	hideTranscript: hideTranscript$9,
17865  	turnOnDescriptions: turnOnDescriptions$9,
17866  	turnOffDescriptions: turnOffDescriptions$9,
17867  	chapters: chapters$9,
17868  	language: language$9,
17869  	sign: sign$9,
17870  	showSign: showSign$9,
17871  	hideSign: hideSign$9,
17872  	seekbarLabel: seekbarLabel$9,
17873  	mute: mute$9,
17874  	unmute: unmute$9,
17875  	volume: volume$9,
17876  	volumeUpDown: volumeUpDown$9,
17877  	preferences: preferences$9,
17878  	enterFullScreen: enterFullScreen$9,
17879  	exitFullScreen: exitFullScreen$9,
17880  	speed: speed$9,
17881  	spacebar: spacebar$9,
17882  	transcriptTitle: transcriptTitle$9,
17883  	lyricsTitle: lyricsTitle$9,
17884  	autoScroll: autoScroll$9,
17885  	statusPlaying: statusPlaying$9,
17886  	statusPaused: statusPaused$9,
17887  	statusStopped: statusStopped$9,
17888  	statusBuffering: statusBuffering$9,
17889  	statusEnd: statusEnd$9,
17890  	selectedTrack: selectedTrack$9,
17891  	alertDescribedVersion: alertDescribedVersion$9,
17892  	alertNonDescribedVersion: alertNonDescribedVersion$9,
17893  	prefMenuCaptions: prefMenuCaptions$9,
17894  	prefVoicedCaptions: prefVoicedCaptions$9,
17895  	prefMenuDescriptions: prefMenuDescriptions$9,
17896  	prefMenuKeyboard: prefMenuKeyboard$9,
17897  	prefMenuTranscript: prefMenuTranscript$9,
17898  	prefTitleCaptions: prefTitleCaptions$9,
17899  	prefTitleDescriptions: prefTitleDescriptions$9,
17900  	prefTitleKeyboard: prefTitleKeyboard$9,
17901  	prefTitleTranscript: prefTitleTranscript$9,
17902  	prefIntroDescription1: prefIntroDescription1$9,
17903  	prefDescription1: prefDescription1$9,
17904  	prefDescription2: prefDescription2$9,
17905  	prefDescription3: prefDescription3$9,
17906  	prefIntroDescriptionNone: prefIntroDescriptionNone$9,
17907  	prefIntroDescription3: prefIntroDescription3$9,
17908  	prefIntroDescription4: prefIntroDescription4$9,
17909  	prefIntroKeyboard1: prefIntroKeyboard1$9,
17910  	prefIntroKeyboard2: prefIntroKeyboard2$9,
17911  	prefIntroKeyboard3: prefIntroKeyboard3$9,
17912  	prefHeadingKeyboard1: prefHeadingKeyboard1$9,
17913  	prefHeadingKeyboard2: prefHeadingKeyboard2$9,
17914  	prefHeadingDescription: prefHeadingDescription$9,
17915  	prefHeadingTextDescription: prefHeadingTextDescription$9,
17916  	prefAltKey: prefAltKey$9,
17917  	prefCtrlKey: prefCtrlKey$9,
17918  	prefShiftKey: prefShiftKey$9,
17919  	prefNoKeyShortcuts: prefNoKeyShortcuts$9,
17920  	escapeKey: escapeKey$9,
17921  	escapeKeyFunction: escapeKeyFunction$9,
17922  	prefDescPause: prefDescPause$9,
17923  	prefDescVisible: prefDescVisible$9,
17924  	prefDescVoice: prefDescVoice$9,
17925  	prefDescRate: prefDescRate$9,
17926  	prefCaptionRate: prefCaptionRate$9,
17927  	prefDescPitch: prefDescPitch$9,
17928  	prefDescPitch1: prefDescPitch1$9,
17929  	prefDescPitch2: prefDescPitch2$9,
17930  	prefDescPitch3: prefDescPitch3$9,
17931  	prefDescPitch4: prefDescPitch4$9,
17932  	prefDescPitch5: prefDescPitch5$9,
17933  	sampleDescriptionText: sampleDescriptionText$9,
17934  	prefHighlight: prefHighlight$9,
17935  	prefTabbable: prefTabbable$9,
17936  	prefCaptionsFont: prefCaptionsFont$9,
17937  	prefCaptionsColor: prefCaptionsColor$9,
17938  	prefCaptionsBGColor: prefCaptionsBGColor$9,
17939  	prefCaptionsSize: prefCaptionsSize$9,
17940  	prefCaptionsOpacity: prefCaptionsOpacity$9,
17941  	prefCaptionsStyle: prefCaptionsStyle$9,
17942  	serif: serif$9,
17943  	sans: sans$9,
17944  	cursive: cursive$9,
17945  	fantasy: fantasy$9,
17946  	monospace: monospace$9,
17947  	white: white$9,
17948  	yellow: yellow$9,
17949  	green: green$9,
17950  	cyan: cyan$9,
17951  	blue: blue$9,
17952  	magenta: magenta$9,
17953  	red: red$9,
17954  	black: black$9,
17955  	transparent: transparent$9,
17956  	solid: solid$9,
17957  	captionsStylePopOn: captionsStylePopOn$9,
17958  	captionsStyleRollUp: captionsStyleRollUp$9,
17959  	prefCaptionsPosition: prefCaptionsPosition$9,
17960  	captionsPositionOverlay: captionsPositionOverlay$9,
17961  	captionsPositionBelow: captionsPositionBelow$9,
17962  	sampleCaptionText: sampleCaptionText$9,
17963  	prefSuccess: prefSuccess$9,
17964  	prefNoChange: prefNoChange$9,
17965  	save: save$9,
17966  	cancel: cancel$9,
17967  	dismissButton: dismissButton$9,
17968  	windowButtonLabel: windowButtonLabel$9,
17969  	windowMove: windowMove$9,
17970  	windowMoveLeft: windowMoveLeft$9,
17971  	windowMoveRight: windowMoveRight$9,
17972  	windowMoveUp: windowMoveUp$9,
17973  	windowMoveDown: windowMoveDown$9,
17974  	windowMoveStopped: windowMoveStopped$9,
17975  	transcriptControls: transcriptControls$9,
17976  	signControls: signControls$9,
17977  	windowMoveAlert: windowMoveAlert$9,
17978  	windowResize: windowResize$9,
17979  	windowResizeHeading: windowResizeHeading$9,
17980  	closeButtonLabel: closeButtonLabel$9,
17981  	width: width$9,
17982  	height: height$9,
17983  	resultsSummary1: resultsSummary1$9,
17984  	resultsSummary2: resultsSummary2$9,
17985  	resultsSummary3: resultsSummary3$9,
17986  	noResultsFound: noResultsFound$9,
17987  	searchButtonLabel: searchButtonLabel$9,
17988  	hour: hour$9,
17989  	minute: minute$9,
17990  	second: second$9,
17991  	hours: hours$9,
17992  	minutes: minutes$9,
17993  	seconds: seconds$9,
17994  	vtsHeading: vtsHeading$9,
17995  	vtsInstructions1: vtsInstructions1$9,
17996  	vtsInstructions2: vtsInstructions2$9,
17997  	vtsInstructions3: vtsInstructions3$9,
17998  	vtsInstructions4: vtsInstructions4$9,
17999  	vtsInstructions5: vtsInstructions5$9,
18000  	vtsSelectLanguage: vtsSelectLanguage$9,
18001  	vtsSave: vtsSave$9,
18002  	vtsReturn: vtsReturn$9,
18003  	vtsCancel: vtsCancel$9,
18004  	vtsRow: vtsRow$9,
18005  	vtsKind: vtsKind$9,
18006  	vtsStart: vtsStart$9,
18007  	vtsEnd: vtsEnd$9,
18008  	vtsContent: vtsContent$9,
18009  	vtsActions: vtsActions$9,
18010  	vtsNewRow: vtsNewRow$9,
18011  	vtsDeletedRow: vtsDeletedRow$9,
18012  	vtsMovedRow: vtsMovedRow$9
18013  };
18014
18015  var playerHeading$8 = "Mediespiller";
18016  var audioPlayer$8 = "Audio player";
18017  var videoPlayer$8 = "Video player";
18018  var faster$8 = "Raskere";
18019  var slower$8 = "Saktere";
18020  var play$8 = "Spill av";
18021  var pause$8 = "Pause";
18022  var restart$8 = "Start på nytt";
18023  var prevTrack$8 = "Forrige spor";
18024  var nextTrack$8 = "Neste spor";
18025  var rewind$8 = "Spol tilbake";
18026  var forward$8 = "Spol fremover";
18027  var captions$8 = "Undertekster";
18028  var showCaptions$8 = "Vis undertekster";
18029  var hideCaptions$8 = "Skjul undertekster";
18030  var captionsOff$8 = "Slå av undertekster";
18031  var showTranscript$8 = "Vis transkripsjon";
18032  var hideTranscript$8 = "Skjul transkripsjon";
18033  var turnOnDescriptions$8 = "Slå på synstolking";
18034  var turnOffDescriptions$8 = "Slå av synstolking";
18035  var chapters$8 = "Kapitler";
18036  var language$8 = "Språk";
18037  var sign$8 = "Tegnspråk";
18038  var showSign$8 = "Vis tegnspråk";
18039  var hideSign$8 = "Skjul tegnspråk";
18040  var seekbarLabel$8 = "tidslinje";
18041  var mute$8 = "Lyd av";
18042  var unmute$8 = "Lyd på";
18043  var volume$8 = "Volum";
18044  var volumeUpDown$8 = "Volumkontroll";
18045  var preferences$8 = "Innstillinger";
18046  var enterFullScreen$8 = "Vis fullskjerm";
18047  var exitFullScreen$8 = "Avslutt fullskjerm";
18048  var speed$8 = "Hastighet";
18049  var spacebar$8 = "ordskiller";
18050  var transcriptTitle$8 = "Transkripsjon";
18051  var lyricsTitle$8 = "Teksting, verselinjer";
18052  var autoScroll$8 = "Automatisk rulling";
18053  var statusPlaying$8 = "Spiller av";
18054  var statusPaused$8 = "Satt på pause";
18055  var statusStopped$8 = "Stoppet";
18056  var statusBuffering$8 = "Mottar data";
18057  var statusEnd$8 = "Slutt på sporet";
18058  var selectedTrack$8 = "Valgt spor";
18059  var alertDescribedVersion$8 = "Bruker videoversjon med synstolking";
18060  var alertNonDescribedVersion$8 = "Bruker videoversjon uten synstolking";
18061  var prefMenuCaptions$8 = "Undertekster";
18062  var prefVoicedCaptions$8 = "Spoken Captions";
18063  var prefMenuDescriptions$8 = "Synstolking";
18064  var prefMenuKeyboard$8 = "Tastatur";
18065  var prefMenuTranscript$8 = "Transkripsjon";
18066  var prefTitleCaptions$8 = "Undertekstinnstillinger";
18067  var prefTitleDescriptions$8 = "Synstolkingsinnstillinger";
18068  var prefTitleKeyboard$8 = "Tastaturinnstillinger";
18069  var prefTitleTranscript$8 = "Transkripsjonsinnstillinger";
18070  var prefIntroDescription1$8 = "Denne mediespilleren støtter lydbeskrivelse på to måter: ";
18071  var prefDescription1$8 = "Denne videoen har en alternativ synstolket versjon, tekstbasert synstolking.";
18072  var prefDescription2$8 = "Denne videoen har alternativ synstolket versjon av video.";
18073  var prefDescription3$8 = "Denne videoen har tekstbasert synstolket versjon opplest av skjermleser.";
18074  var prefIntroDescriptionNone$8 = "Denne videoen har ikke synstolking i noen av formatene.";
18075  var prefIntroDescription3$8 = "Bruk følgende skjema for å gjøre dine valg angående tekstbasert synstolking.";
18076  var prefIntroDescription4$8 = "Etter at du har lagret dine valg kan synstolking slås av og på med synstolkingsknappen.";
18077  var prefIntroKeyboard1$8 = "Mediespilleren på denne nettsiden kan styres fra hvor som helst på siden ved hjelp av tastatursnarveier (se liste nedenfor).";
18078  var prefIntroKeyboard2$8 = "Metataster (Shift, Alt, og Control) kan legges til nedenfor.";
18079  var prefIntroKeyboard3$8 = "NB! Noen tastekombinasjoner kan komme i konflikt med taster som brukes av nettleseren din og/eller andre programmer. Prøv forskjellige tastekombinasjoner for å finne de som fungerer for deg.";
18080  var prefHeadingKeyboard1$8 = "Metataster som brukes til tastekombinasjoner";
18081  var prefHeadingKeyboard2$8 = "Nåværende tastekombinasjoner";
18082  var prefHeadingDescription$8 = "Synstolking";
18083  var prefHeadingTextDescription$8 = "Tekstbasert synstolking";
18084  var prefAltKey$8 = "Alt";
18085  var prefCtrlKey$8 = "Control";
18086  var prefShiftKey$8 = "Shift";
18087  var prefNoKeyShortcuts$8 = "Disable keyboard shortcuts";
18088  var escapeKey$8 = "Escape";
18089  var escapeKeyFunction$8 = "Lukk dialogboks eller popup-meny";
18090  var prefDescPause$8 = "Sett video automatisk på pause når synstolking starter";
18091  var prefDescVisible$8 = "Vis synstolking";
18092  var prefDescVoice$8 = "Stemme";
18093  var prefDescRate$8 = "Spoken Description Rate";
18094  var prefCaptionRate$8 = "Spoken Caption Rate";
18095  var prefDescPitch$8 = "Toneleie";
18096  var prefDescPitch1$8 = "Meget lavt";
18097  var prefDescPitch2$8 = "Lavt";
18098  var prefDescPitch3$8 = "Normalt";
18099  var prefDescPitch4$8 = "Høyt";
18100  var prefDescPitch5$8 = "Meget høyt";
18101  var sampleDescriptionText$8 = "Juster innstillingene for å høre denne eksempelteksten.";
18102  var prefHighlight$8 = "Marker det som leses i transkripsjon ved avspilling";
18103  var prefTabbable$8 = "Tastaturaktivert transkripsjon";
18104  var prefCaptionsFont$8 = "Skrifttype";
18105  var prefCaptionsColor$8 = "Skriftfarge";
18106  var prefCaptionsBGColor$8 = "Bakgrunn";
18107  var prefCaptionsSize$8 = "Skriftstørrelse";
18108  var prefCaptionsOpacity$8 = "Ugjennomsiktighet";
18109  var prefCaptionsStyle$8 = "Stil";
18110  var serif$8 = "serif";
18111  var sans$8 = "sans-serif";
18112  var cursive$8 = "kursiv";
18113  var fantasy$8 = "fantasi";
18114  var monospace$8 = "ikke proporsjonal";
18115  var white$8 = "hvit";
18116  var yellow$8 = "gul";
18117  var green$8 = "grønn";
18118  var cyan$8 = "cyan";
18119  var blue$8 = "blå";
18120  var magenta$8 = "magenta";
18121  var red$8 = "rød";
18122  var black$8 = "sort";
18123  var transparent$8 = "gjennomsiktig";
18124  var solid$8 = "ugjennomsiktig";
18125  var captionsStylePopOn$8 = "Dukker opp";
18126  var captionsStyleRollUp$8 = "Ruller opp";
18127  var prefCaptionsPosition$8 = "Posisjon";
18128  var captionsPositionOverlay$8 = "Legg oppå video";
18129  var captionsPositionBelow$8 = "Legg under video";
18130  var sampleCaptionText$8 = "Eksempel på undertekst";
18131  var prefSuccess$8 = "Dine endringer er lagret.";
18132  var prefNoChange$8 = "Du gjorde ingen endringer.";
18133  var save$8 = "Lagre";
18134  var cancel$8 = "Avbryt";
18135  var dismissButton$8 = "Dismiss";
18136  var windowButtonLabel$8 = "Vindusinnstillinger";
18137  var windowMove$8 = "Flytt";
18138  var windowMoveLeft$8 = "Window moved left";
18139  var windowMoveRight$8 = "Window moved right";
18140  var windowMoveUp$8 = "Window moved up";
18141  var windowMoveDown$8 = "Window moved down";
18142  var windowMoveStopped$8 = "Window move stopped";
18143  var transcriptControls$8 = "Transcript Window Controls";
18144  var signControls$8 = "Sign Language Window Controls";
18145  var windowMoveAlert$8 = "Dra eller bruk piltaster for å flytte vinduet; Enter-tast for å stoppe";
18146  var windowResize$8 = "Endre størrelse";
18147  var windowResizeHeading$8 = "Endre vindusstørrelse";
18148  var closeButtonLabel$8 = "Lukk";
18149  var width$8 = "Bredde";
18150  var height$8 = "Høyde";
18151  var resultsSummary1$8 = "Du søkte etter:";
18152  var resultsSummary2$8 = "Funnet %1 passende treff.";
18153  var resultsSummary3$8 = "Klikk tidspunkt på aktuelt treff for å avspille video derfra.";
18154  var noResultsFound$8 = "Ingen treff funnet.";
18155  var searchButtonLabel$8 = "Spill av fra %1";
18156  var hour$8 = "time";
18157  var minute$8 = "minutt";
18158  var second$8 = "sekund";
18159  var hours$8 = "timer";
18160  var minutes$8 = "minutter";
18161  var seconds$8 = "sekunder";
18162  var vtsHeading$8 = "Video Transcript Sorter";
18163  var vtsInstructions1$8 = "Use the Video Transcript Sorter to modify text tracks:";
18164  var vtsInstructions2$8 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
18165  var vtsInstructions3$8 = "Modify content or start/end times (all are directly editable within the table).";
18166  var vtsInstructions4$8 = "Add new content, such as chapters or descriptions.";
18167  var vtsInstructions5$8 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
18168  var vtsSelectLanguage$8 = "Select a language";
18169  var vtsSave$8 = "Generate new .vtt content";
18170  var vtsReturn$8 = "Return to Editor";
18171  var vtsCancel$8 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
18172  var vtsRow$8 = "Row";
18173  var vtsKind$8 = "Kind";
18174  var vtsStart$8 = "Start";
18175  var vtsEnd$8 = "End";
18176  var vtsContent$8 = "Content";
18177  var vtsActions$8 = "Actions";
18178  var vtsNewRow$8 = "A new row %1 has been inserted.";
18179  var vtsDeletedRow$8 = "Row %1 has been deleted.";
18180  var vtsMovedRow$8 = "Row %1 has been moved %2 and is now Row %3.";
18181  var nb = {
18182  	playerHeading: playerHeading$8,
18183  	audioPlayer: audioPlayer$8,
18184  	videoPlayer: videoPlayer$8,
18185  	faster: faster$8,
18186  	slower: slower$8,
18187  	play: play$8,
18188  	pause: pause$8,
18189  	restart: restart$8,
18190  	prevTrack: prevTrack$8,
18191  	nextTrack: nextTrack$8,
18192  	rewind: rewind$8,
18193  	forward: forward$8,
18194  	captions: captions$8,
18195  	showCaptions: showCaptions$8,
18196  	hideCaptions: hideCaptions$8,
18197  	captionsOff: captionsOff$8,
18198  	showTranscript: showTranscript$8,
18199  	hideTranscript: hideTranscript$8,
18200  	turnOnDescriptions: turnOnDescriptions$8,
18201  	turnOffDescriptions: turnOffDescriptions$8,
18202  	chapters: chapters$8,
18203  	language: language$8,
18204  	sign: sign$8,
18205  	showSign: showSign$8,
18206  	hideSign: hideSign$8,
18207  	seekbarLabel: seekbarLabel$8,
18208  	mute: mute$8,
18209  	unmute: unmute$8,
18210  	volume: volume$8,
18211  	volumeUpDown: volumeUpDown$8,
18212  	preferences: preferences$8,
18213  	enterFullScreen: enterFullScreen$8,
18214  	exitFullScreen: exitFullScreen$8,
18215  	speed: speed$8,
18216  	spacebar: spacebar$8,
18217  	transcriptTitle: transcriptTitle$8,
18218  	lyricsTitle: lyricsTitle$8,
18219  	autoScroll: autoScroll$8,
18220  	statusPlaying: statusPlaying$8,
18221  	statusPaused: statusPaused$8,
18222  	statusStopped: statusStopped$8,
18223  	statusBuffering: statusBuffering$8,
18224  	statusEnd: statusEnd$8,
18225  	selectedTrack: selectedTrack$8,
18226  	alertDescribedVersion: alertDescribedVersion$8,
18227  	alertNonDescribedVersion: alertNonDescribedVersion$8,
18228  	prefMenuCaptions: prefMenuCaptions$8,
18229  	prefVoicedCaptions: prefVoicedCaptions$8,
18230  	prefMenuDescriptions: prefMenuDescriptions$8,
18231  	prefMenuKeyboard: prefMenuKeyboard$8,
18232  	prefMenuTranscript: prefMenuTranscript$8,
18233  	prefTitleCaptions: prefTitleCaptions$8,
18234  	prefTitleDescriptions: prefTitleDescriptions$8,
18235  	prefTitleKeyboard: prefTitleKeyboard$8,
18236  	prefTitleTranscript: prefTitleTranscript$8,
18237  	prefIntroDescription1: prefIntroDescription1$8,
18238  	prefDescription1: prefDescription1$8,
18239  	prefDescription2: prefDescription2$8,
18240  	prefDescription3: prefDescription3$8,
18241  	prefIntroDescriptionNone: prefIntroDescriptionNone$8,
18242  	prefIntroDescription3: prefIntroDescription3$8,
18243  	prefIntroDescription4: prefIntroDescription4$8,
18244  	prefIntroKeyboard1: prefIntroKeyboard1$8,
18245  	prefIntroKeyboard2: prefIntroKeyboard2$8,
18246  	prefIntroKeyboard3: prefIntroKeyboard3$8,
18247  	prefHeadingKeyboard1: prefHeadingKeyboard1$8,
18248  	prefHeadingKeyboard2: prefHeadingKeyboard2$8,
18249  	prefHeadingDescription: prefHeadingDescription$8,
18250  	prefHeadingTextDescription: prefHeadingTextDescription$8,
18251  	prefAltKey: prefAltKey$8,
18252  	prefCtrlKey: prefCtrlKey$8,
18253  	prefShiftKey: prefShiftKey$8,
18254  	prefNoKeyShortcuts: prefNoKeyShortcuts$8,
18255  	escapeKey: escapeKey$8,
18256  	escapeKeyFunction: escapeKeyFunction$8,
18257  	prefDescPause: prefDescPause$8,
18258  	prefDescVisible: prefDescVisible$8,
18259  	prefDescVoice: prefDescVoice$8,
18260  	prefDescRate: prefDescRate$8,
18261  	prefCaptionRate: prefCaptionRate$8,
18262  	prefDescPitch: prefDescPitch$8,
18263  	prefDescPitch1: prefDescPitch1$8,
18264  	prefDescPitch2: prefDescPitch2$8,
18265  	prefDescPitch3: prefDescPitch3$8,
18266  	prefDescPitch4: prefDescPitch4$8,
18267  	prefDescPitch5: prefDescPitch5$8,
18268  	sampleDescriptionText: sampleDescriptionText$8,
18269  	prefHighlight: prefHighlight$8,
18270  	prefTabbable: prefTabbable$8,
18271  	prefCaptionsFont: prefCaptionsFont$8,
18272  	prefCaptionsColor: prefCaptionsColor$8,
18273  	prefCaptionsBGColor: prefCaptionsBGColor$8,
18274  	prefCaptionsSize: prefCaptionsSize$8,
18275  	prefCaptionsOpacity: prefCaptionsOpacity$8,
18276  	prefCaptionsStyle: prefCaptionsStyle$8,
18277  	serif: serif$8,
18278  	sans: sans$8,
18279  	cursive: cursive$8,
18280  	fantasy: fantasy$8,
18281  	monospace: monospace$8,
18282  	white: white$8,
18283  	yellow: yellow$8,
18284  	green: green$8,
18285  	cyan: cyan$8,
18286  	blue: blue$8,
18287  	magenta: magenta$8,
18288  	red: red$8,
18289  	black: black$8,
18290  	transparent: transparent$8,
18291  	solid: solid$8,
18292  	captionsStylePopOn: captionsStylePopOn$8,
18293  	captionsStyleRollUp: captionsStyleRollUp$8,
18294  	prefCaptionsPosition: prefCaptionsPosition$8,
18295  	captionsPositionOverlay: captionsPositionOverlay$8,
18296  	captionsPositionBelow: captionsPositionBelow$8,
18297  	sampleCaptionText: sampleCaptionText$8,
18298  	prefSuccess: prefSuccess$8,
18299  	prefNoChange: prefNoChange$8,
18300  	save: save$8,
18301  	cancel: cancel$8,
18302  	dismissButton: dismissButton$8,
18303  	windowButtonLabel: windowButtonLabel$8,
18304  	windowMove: windowMove$8,
18305  	windowMoveLeft: windowMoveLeft$8,
18306  	windowMoveRight: windowMoveRight$8,
18307  	windowMoveUp: windowMoveUp$8,
18308  	windowMoveDown: windowMoveDown$8,
18309  	windowMoveStopped: windowMoveStopped$8,
18310  	transcriptControls: transcriptControls$8,
18311  	signControls: signControls$8,
18312  	windowMoveAlert: windowMoveAlert$8,
18313  	windowResize: windowResize$8,
18314  	windowResizeHeading: windowResizeHeading$8,
18315  	closeButtonLabel: closeButtonLabel$8,
18316  	width: width$8,
18317  	height: height$8,
18318  	resultsSummary1: resultsSummary1$8,
18319  	resultsSummary2: resultsSummary2$8,
18320  	resultsSummary3: resultsSummary3$8,
18321  	noResultsFound: noResultsFound$8,
18322  	searchButtonLabel: searchButtonLabel$8,
18323  	hour: hour$8,
18324  	minute: minute$8,
18325  	second: second$8,
18326  	hours: hours$8,
18327  	minutes: minutes$8,
18328  	seconds: seconds$8,
18329  	vtsHeading: vtsHeading$8,
18330  	vtsInstructions1: vtsInstructions1$8,
18331  	vtsInstructions2: vtsInstructions2$8,
18332  	vtsInstructions3: vtsInstructions3$8,
18333  	vtsInstructions4: vtsInstructions4$8,
18334  	vtsInstructions5: vtsInstructions5$8,
18335  	vtsSelectLanguage: vtsSelectLanguage$8,
18336  	vtsSave: vtsSave$8,
18337  	vtsReturn: vtsReturn$8,
18338  	vtsCancel: vtsCancel$8,
18339  	vtsRow: vtsRow$8,
18340  	vtsKind: vtsKind$8,
18341  	vtsStart: vtsStart$8,
18342  	vtsEnd: vtsEnd$8,
18343  	vtsContent: vtsContent$8,
18344  	vtsActions: vtsActions$8,
18345  	vtsNewRow: vtsNewRow$8,
18346  	vtsDeletedRow: vtsDeletedRow$8,
18347  	vtsMovedRow: vtsMovedRow$8
18348  };
18349
18350  var playerHeading$7 = "Mediaspeler";
18351  var audioPlayer$7 = "Audiospeler";
18352  var videoPlayer$7 = "Videospeler";
18353  var faster$7 = "Sneller";
18354  var slower$7 = "Langzamer";
18355  var play$7 = "Afspelen";
18356  var pause$7 = "Pauzeren";
18357  var restart$7 = "Herstarten";
18358  var prevTrack$7 = "Vorige track";
18359  var nextTrack$7 = "Volgend track";
18360  var rewind$7 = "Terug";
18361  var forward$7 = "Verder";
18362  var captions$7 = "Ondertiteling";
18363  var showCaptions$7 = "Toon ondertiteling";
18364  var hideCaptions$7 = "Verberg ondertiteling";
18365  var captionsOff$7 = "Ondertiteling uit";
18366  var showTranscript$7 = "Toon transcript";
18367  var hideTranscript$7 = "Verberg transcript";
18368  var turnOnDescriptions$7 = "Beschrijvingen aanzetten";
18369  var turnOffDescriptions$7 = "Beschrijvingen uitzetten";
18370  var chapters$7 = "Hoofdstukken";
18371  var language$7 = "Taal";
18372  var sign$7 = "Gebarentaal";
18373  var showSign$7 = "Toon gebarentaal";
18374  var seekbarLabel$7 = "tijdlijn";
18375  var hideSign$7 = "Verberg gebarentaal";
18376  var mute$7 = "Dempen";
18377  var unmute$7 = "Dempen uit";
18378  var volume$7 = "Volume";
18379  var volumeUpDown$7 = "Volume hoger lager";
18380  var preferences$7 = "Voorkeuren";
18381  var enterFullScreen$7 = "Ga naar volledig scherm";
18382  var exitFullScreen$7 = "Verlaat volledig scherm";
18383  var speed$7 = "Snelheid";
18384  var spacebar$7 = "spatietoets";
18385  var transcriptTitle$7 = "Transcript";
18386  var lyricsTitle$7 = "Tekst";
18387  var autoScroll$7 = "Auto-scroll";
18388  var statusPlaying$7 = "Aan het spelen";
18389  var statusPaused$7 = "Gepauzeerd";
18390  var statusStopped$7 = "Gestopt";
18391  var statusBuffering$7 = "Aan het bufferen";
18392  var statusEnd$7 = "Einde van track";
18393  var selectedTrack$7 = "Geselecteerde track";
18394  var alertDescribedVersion$7 = "Versie met audiobeschrijving wordt gebruikt";
18395  var alertNonDescribedVersion$7 = "Versie zonder audiobeschrijving wordt gebruikt";
18396  var prefMenuCaptions$7 = "Ondertiteling";
18397  var prefVoicedCaptions$7 = "Spoken Captions";
18398  var prefMenuDescriptions$7 = "Beschrijvingen";
18399  var prefMenuKeyboard$7 = "Toetsenbord";
18400  var prefMenuTranscript$7 = "Transcript";
18401  var prefTitleCaptions$7 = "Voorkeuren ondertiteling";
18402  var prefTitleDescriptions$7 = "Voorkeuren audiobeschrijving";
18403  var prefTitleKeyboard$7 = "Voorkeuren toetsenbord";
18404  var prefTitleTranscript$7 = "Voorkeuren transcript";
18405  var prefIntroDescription1$7 = "Deze videospeler ondersteund audiobeschrijving op twee manieren: ";
18406  var prefDescription1$7 = "De huidige video heeft een alternatief beschreven versie, op tekst gebaseerde beschrijving.";
18407  var prefDescription2$7 = "De huidige video heeft Alternatieve beschreven versie van de video.";
18408  var prefDescription3$7 = "De huidige video heeft op tekst gebaseerde beschrijving, uitgesproken door de schermlezer.";
18409  var prefIntroDescriptionNone$7 = "De huidige video heeft in beide formaten geen audiobeschrijving.";
18410  var prefIntroDescription3$7 = "Gebruik het volgende formulier om je voorkeuren gerelateerd aan tekst-gebaseerde audiobeschrijving in te stellen.";
18411  var prefIntroDescription4$7 = "Na het opslaan van je instellingen, kan audiobeschrijving aan of uit gezet worden met de Beschrijving-knop.";
18412  var prefIntroKeyboard1$7 = "De mediaspeler op deze pagina kan vanaf elke plek binnen de pagina bestuurd worden met de toetsenbord sneltoetsen (zie de lijst hieronder).";
18413  var prefIntroKeyboard2$7 = "Mutatie knoppen (Shift, Alt, and Control) kunnen hier onder toegekend worden.";
18414  var prefIntroKeyboard3$7 = "LET OP: Sommige toetsencombinaties kunnen conflicteren met de toetsen die gebruikt worden door de browser en/of andere software applicaties. Probeer verscheidene combinaties van de mutatie knoppen om een combinatie te vinden die voor jou werkt.";
18415  var prefHeadingKeyboard1$7 = "Mutatie knoppen gebruikt voor sneltoetsen";
18416  var prefHeadingKeyboard2$7 = "Huidige toetsenbord sneltoetsen";
18417  var prefHeadingDescription$7 = "Audiobeschrijving";
18418  var prefHeadingTextDescription$7 = "Tekst-gebaseerde audiobeschrijving";
18419  var prefAltKey$7 = "Alt";
18420  var prefCtrlKey$7 = "Control";
18421  var prefShiftKey$7 = "Shift";
18422  var prefNoKeyShortcuts$7 = "Schakel sneltoetsen uit";
18423  var escapeKey$7 = "Escape";
18424  var escapeKeyFunction$7 = "Sluit huidige dialoog of pop-up menu";
18425  var prefDescPause$7 = "Pauzeer de video automatisch als de beschrijving aan wordt gezet";
18426  var prefDescVisible$7 = "Als er een tekst-gebaseerde beschrijving is, maak deze dan zichtbaar";
18427  var prefDescVoice$7 = "Stem";
18428  var prefDescRate$7 = "Spoken Description Rate";
18429  var prefCaptionRate$7 = "Spoken Caption Rate";
18430  var prefDescPitch$7 = "Toonhoogte";
18431  var prefDescPitch1$7 = "Zeer laag";
18432  var prefDescPitch2$7 = "Laag";
18433  var prefDescPitch3$7 = "Normaal";
18434  var prefDescPitch4$7 = "Hoog";
18435  var prefDescPitch5$7 = "Zeer hoog";
18436  var sampleDescriptionText$7 = "Pas de instellingen aan om deze voorbeeldtekst te beluisteren.";
18437  var prefHighlight$7 = "Highlight transcript terwijl media speelt";
18438  var prefTabbable$7 = "Maak transcript bedienbaar met toetsenbord";
18439  var prefCaptionsFont$7 = "Lettertype";
18440  var prefCaptionsColor$7 = "Tekstkleur";
18441  var prefCaptionsBGColor$7 = "Achtergrondkleur";
18442  var prefCaptionsSize$7 = "Grootte lettertype ";
18443  var prefCaptionsOpacity$7 = "Ondoorzichtigheid";
18444  var prefCaptionsStyle$7 = "Stijl";
18445  var serif$7 = "serif";
18446  var sans$7 = "sans-serif";
18447  var cursive$7 = "cursief";
18448  var fantasy$7 = "fantasy";
18449  var monospace$7 = "monospace";
18450  var white$7 = "wit";
18451  var yellow$7 = "geel";
18452  var green$7 = "groen";
18453  var cyan$7 = "cyaan";
18454  var blue$7 = "blauw";
18455  var magenta$7 = "magenta";
18456  var red$7 = "rood";
18457  var black$7 = "zwart";
18458  var transparent$7 = "transparant";
18459  var solid$7 = "solide";
18460  var captionsStylePopOn$7 = "Verschijn";
18461  var captionsStyleRollUp$7 = "Rol omhoog";
18462  var prefCaptionsPosition$7 = "Positie";
18463  var captionsPositionOverlay$7 = "Overlay";
18464  var captionsPositionBelow$7 = "Onder video";
18465  var sampleCaptionText$7 = "Voorbeeld ondertiteling";
18466  var prefSuccess$7 = "Je wijzigingen zijn opgeslagen.";
18467  var prefNoChange$7 = "Je hebt geen wijzigingen gemaakt.";
18468  var save$7 = "Opslaan";
18469  var cancel$7 = "Annuleren";
18470  var dismissButton$7 = "Verwijder";
18471  var windowButtonLabel$7 = "Instellingen venster";
18472  var windowMove$7 = "Verplaats";
18473  var windowMoveLeft$7 = "Venster naar links verschoven";
18474  var windowMoveRight$7 = "Venster naar rechts verschoven";
18475  var windowMoveUp$7 = "Venster omhoog geschoven.";
18476  var windowMoveDown$7 = "Venster omlaag geschoven.";
18477  var windowMoveStopped$7 = "Verplaatsing venster gestopt";
18478  var transcriptControls$7 = "Bediening venster transcript";
18479  var signControls$7 = "Bediening venster gebarentaal";
18480  var windowMoveAlert$7 = "Versleep of gebruik de pijltjestoetsen om te verplaatsen. Druk op Enter om te stoppen.";
18481  var windowResize$7 = "Verkleinen of vergroten";
18482  var windowResizeHeading$7 = "Verander grootte van venster";
18483  var closeButtonLabel$7 = "Sluit";
18484  var width$7 = "Breedte";
18485  var height$7 = "Hoogte";
18486  var resultsSummary1$7 = "Je zocht naar:";
18487  var resultsSummary2$7 = "Er zijn %1 overeenkomende items gevonden.";
18488  var resultsSummary3$7 = "Klik op de tijd die bij een item staat om de video vanaf dat punt af te spelen.";
18489  var noResultsFound$7 = "Geen resultaten gevonden.";
18490  var searchButtonLabel$7 = "Speel af op %1";
18491  var hour$7 = "uur";
18492  var minute$7 = "minuut";
18493  var second$7 = "seconde";
18494  var hours$7 = "hours";
18495  var minutes$7 = "minuten";
18496  var seconds$7 = "seconden";
18497  var vtsHeading$7 = "Video Transcript Sorter";
18498  var vtsInstructions1$7 = "Gebruik de Video Transcript Sorter om teksttracks te wijzigen:";
18499  var vtsInstructions2$7 = "Herschik hoofdstukken, beschrijvingen, bijschriften en/of ondertiteling zodat ze in de juiste volgorde verschijnen in het automatisch gegenereerde transcript van Able Player.";
18500  var vtsInstructions3$7 = "Wijzig de inhoud of de begin-/eindtijden (alles is direct bewerkbaar in de tabel).";
18501  var vtsInstructions4$7 = "Voeg nieuwe inhoud toe, zoals hoofdstukken of beschrijvingen.";
18502  var vtsInstructions5$7 = "Klik na het bewerken op de knop 'Wijzigingen opslaan' om nieuwe inhoud te genereren voor alle relevante getimede tekstbestanden. De nieuwe tekst kan worden gekopieerd en geplakt in nieuwe WebVTT-bestanden.";
18503  var vtsSelectLanguage$7 = "Selecteer een taal";
18504  var vtsSave$7 = "Genereer nieuwe .vtt inhoud";
18505  var vtsReturn$7 = "Terug naar de editor";
18506  var vtsCancel$7 = "Opslaan annuleren. Alle bewerkingen die je hebt gedaan, zijn hersteld in de VTS-tabel";
18507  var vtsRow$7 = "Rij";
18508  var vtsKind$7 = "Sorteer";
18509  var vtsStart$7 = "Start";
18510  var vtsEnd$7 = "Eind";
18511  var vtsContent$7 = "Inhoud";
18512  var vtsActions$7 = "Acties";
18513  var vtsNewRow$7 = "Er is een nieuwe rij %1 ingevoegd.";
18514  var vtsDeletedRow$7 = "Rij %1 is verwijderd.";
18515  var vtsMovedRow$7 = "Rij %1 is verplaatst naar %2 en is nu rij %3.";
18516  var nl = {
18517  	playerHeading: playerHeading$7,
18518  	audioPlayer: audioPlayer$7,
18519  	videoPlayer: videoPlayer$7,
18520  	faster: faster$7,
18521  	slower: slower$7,
18522  	play: play$7,
18523  	pause: pause$7,
18524  	restart: restart$7,
18525  	prevTrack: prevTrack$7,
18526  	nextTrack: nextTrack$7,
18527  	rewind: rewind$7,
18528  	forward: forward$7,
18529  	captions: captions$7,
18530  	showCaptions: showCaptions$7,
18531  	hideCaptions: hideCaptions$7,
18532  	captionsOff: captionsOff$7,
18533  	showTranscript: showTranscript$7,
18534  	hideTranscript: hideTranscript$7,
18535  	turnOnDescriptions: turnOnDescriptions$7,
18536  	turnOffDescriptions: turnOffDescriptions$7,
18537  	chapters: chapters$7,
18538  	language: language$7,
18539  	sign: sign$7,
18540  	showSign: showSign$7,
18541  	seekbarLabel: seekbarLabel$7,
18542  	hideSign: hideSign$7,
18543  	mute: mute$7,
18544  	unmute: unmute$7,
18545  	volume: volume$7,
18546  	volumeUpDown: volumeUpDown$7,
18547  	preferences: preferences$7,
18548  	enterFullScreen: enterFullScreen$7,
18549  	exitFullScreen: exitFullScreen$7,
18550  	speed: speed$7,
18551  	spacebar: spacebar$7,
18552  	transcriptTitle: transcriptTitle$7,
18553  	lyricsTitle: lyricsTitle$7,
18554  	autoScroll: autoScroll$7,
18555  	statusPlaying: statusPlaying$7,
18556  	statusPaused: statusPaused$7,
18557  	statusStopped: statusStopped$7,
18558  	statusBuffering: statusBuffering$7,
18559  	statusEnd: statusEnd$7,
18560  	selectedTrack: selectedTrack$7,
18561  	alertDescribedVersion: alertDescribedVersion$7,
18562  	alertNonDescribedVersion: alertNonDescribedVersion$7,
18563  	prefMenuCaptions: prefMenuCaptions$7,
18564  	prefVoicedCaptions: prefVoicedCaptions$7,
18565  	prefMenuDescriptions: prefMenuDescriptions$7,
18566  	prefMenuKeyboard: prefMenuKeyboard$7,
18567  	prefMenuTranscript: prefMenuTranscript$7,
18568  	prefTitleCaptions: prefTitleCaptions$7,
18569  	prefTitleDescriptions: prefTitleDescriptions$7,
18570  	prefTitleKeyboard: prefTitleKeyboard$7,
18571  	prefTitleTranscript: prefTitleTranscript$7,
18572  	prefIntroDescription1: prefIntroDescription1$7,
18573  	prefDescription1: prefDescription1$7,
18574  	prefDescription2: prefDescription2$7,
18575  	prefDescription3: prefDescription3$7,
18576  	prefIntroDescriptionNone: prefIntroDescriptionNone$7,
18577  	prefIntroDescription3: prefIntroDescription3$7,
18578  	prefIntroDescription4: prefIntroDescription4$7,
18579  	prefIntroKeyboard1: prefIntroKeyboard1$7,
18580  	prefIntroKeyboard2: prefIntroKeyboard2$7,
18581  	prefIntroKeyboard3: prefIntroKeyboard3$7,
18582  	prefHeadingKeyboard1: prefHeadingKeyboard1$7,
18583  	prefHeadingKeyboard2: prefHeadingKeyboard2$7,
18584  	prefHeadingDescription: prefHeadingDescription$7,
18585  	prefHeadingTextDescription: prefHeadingTextDescription$7,
18586  	prefAltKey: prefAltKey$7,
18587  	prefCtrlKey: prefCtrlKey$7,
18588  	prefShiftKey: prefShiftKey$7,
18589  	prefNoKeyShortcuts: prefNoKeyShortcuts$7,
18590  	escapeKey: escapeKey$7,
18591  	escapeKeyFunction: escapeKeyFunction$7,
18592  	prefDescPause: prefDescPause$7,
18593  	prefDescVisible: prefDescVisible$7,
18594  	prefDescVoice: prefDescVoice$7,
18595  	prefDescRate: prefDescRate$7,
18596  	prefCaptionRate: prefCaptionRate$7,
18597  	prefDescPitch: prefDescPitch$7,
18598  	prefDescPitch1: prefDescPitch1$7,
18599  	prefDescPitch2: prefDescPitch2$7,
18600  	prefDescPitch3: prefDescPitch3$7,
18601  	prefDescPitch4: prefDescPitch4$7,
18602  	prefDescPitch5: prefDescPitch5$7,
18603  	sampleDescriptionText: sampleDescriptionText$7,
18604  	prefHighlight: prefHighlight$7,
18605  	prefTabbable: prefTabbable$7,
18606  	prefCaptionsFont: prefCaptionsFont$7,
18607  	prefCaptionsColor: prefCaptionsColor$7,
18608  	prefCaptionsBGColor: prefCaptionsBGColor$7,
18609  	prefCaptionsSize: prefCaptionsSize$7,
18610  	prefCaptionsOpacity: prefCaptionsOpacity$7,
18611  	prefCaptionsStyle: prefCaptionsStyle$7,
18612  	serif: serif$7,
18613  	sans: sans$7,
18614  	cursive: cursive$7,
18615  	fantasy: fantasy$7,
18616  	monospace: monospace$7,
18617  	white: white$7,
18618  	yellow: yellow$7,
18619  	green: green$7,
18620  	cyan: cyan$7,
18621  	blue: blue$7,
18622  	magenta: magenta$7,
18623  	red: red$7,
18624  	black: black$7,
18625  	transparent: transparent$7,
18626  	solid: solid$7,
18627  	captionsStylePopOn: captionsStylePopOn$7,
18628  	captionsStyleRollUp: captionsStyleRollUp$7,
18629  	prefCaptionsPosition: prefCaptionsPosition$7,
18630  	captionsPositionOverlay: captionsPositionOverlay$7,
18631  	captionsPositionBelow: captionsPositionBelow$7,
18632  	sampleCaptionText: sampleCaptionText$7,
18633  	prefSuccess: prefSuccess$7,
18634  	prefNoChange: prefNoChange$7,
18635  	save: save$7,
18636  	cancel: cancel$7,
18637  	dismissButton: dismissButton$7,
18638  	windowButtonLabel: windowButtonLabel$7,
18639  	windowMove: windowMove$7,
18640  	windowMoveLeft: windowMoveLeft$7,
18641  	windowMoveRight: windowMoveRight$7,
18642  	windowMoveUp: windowMoveUp$7,
18643  	windowMoveDown: windowMoveDown$7,
18644  	windowMoveStopped: windowMoveStopped$7,
18645  	transcriptControls: transcriptControls$7,
18646  	signControls: signControls$7,
18647  	windowMoveAlert: windowMoveAlert$7,
18648  	windowResize: windowResize$7,
18649  	windowResizeHeading: windowResizeHeading$7,
18650  	closeButtonLabel: closeButtonLabel$7,
18651  	width: width$7,
18652  	height: height$7,
18653  	resultsSummary1: resultsSummary1$7,
18654  	resultsSummary2: resultsSummary2$7,
18655  	resultsSummary3: resultsSummary3$7,
18656  	noResultsFound: noResultsFound$7,
18657  	searchButtonLabel: searchButtonLabel$7,
18658  	hour: hour$7,
18659  	minute: minute$7,
18660  	second: second$7,
18661  	hours: hours$7,
18662  	minutes: minutes$7,
18663  	seconds: seconds$7,
18664  	vtsHeading: vtsHeading$7,
18665  	vtsInstructions1: vtsInstructions1$7,
18666  	vtsInstructions2: vtsInstructions2$7,
18667  	vtsInstructions3: vtsInstructions3$7,
18668  	vtsInstructions4: vtsInstructions4$7,
18669  	vtsInstructions5: vtsInstructions5$7,
18670  	vtsSelectLanguage: vtsSelectLanguage$7,
18671  	vtsSave: vtsSave$7,
18672  	vtsReturn: vtsReturn$7,
18673  	vtsCancel: vtsCancel$7,
18674  	vtsRow: vtsRow$7,
18675  	vtsKind: vtsKind$7,
18676  	vtsStart: vtsStart$7,
18677  	vtsEnd: vtsEnd$7,
18678  	vtsContent: vtsContent$7,
18679  	vtsActions: vtsActions$7,
18680  	vtsNewRow: vtsNewRow$7,
18681  	vtsDeletedRow: vtsDeletedRow$7,
18682  	vtsMovedRow: vtsMovedRow$7
18683  };
18684
18685  var playerHeading$6 = "Odtwarzacz mediów";
18686  var audioPlayer$6 = "Audio player";
18687  var videoPlayer$6 = "Video player";
18688  var faster$6 = "Szybciej";
18689  var slower$6 = "Wolniej";
18690  var play$6 = "Odtwarzaj";
18691  var pause$6 = "Pauza";
18692  var restart$6 = "Restart";
18693  var prevTrack$6 = "Poprzednia ścieżka";
18694  var nextTrack$6 = "Następna ścieżka";
18695  var rewind$6 = "Przewiń w tył";
18696  var forward$6 = "Przewiń w przód";
18697  var captions$6 = "Napisy";
18698  var showCaptions$6 = "Pokaż napisy";
18699  var hideCaptions$6 = "Ukryj napisy";
18700  var captionsOff$6 = "Wyłącz napisy";
18701  var showTranscript$6 = "Pokaż transkrypcję";
18702  var hideTranscript$6 = "Ukryj transkrypcję";
18703  var turnOnDescriptions$6 = "Włącz opis audio";
18704  var turnOffDescriptions$6 = "Wyłącz opis audio";
18705  var chapters$6 = "Rozdziały";
18706  var language$6 = "Język";
18707  var sign$6 = "Język migowy";
18708  var showSign$6 = "Pokaż język migowy";
18709  var hideSign$6 = "Ukryj język migowy";
18710  var seekbarLabel$6 = "Linia czasu";
18711  var mute$6 = "Wycisz";
18712  var unmute$6 = "Pogłośnij";
18713  var volume$6 = "Głośność";
18714  var volumeUpDown$6 = "Głośniej/Ciszej";
18715  var preferences$6 = "Preferencje";
18716  var enterFullScreen$6 = "Wejdź na pełny ekran";
18717  var exitFullScreen$6 = "Wyjdź z pełnego ekranu";
18718  var speed$6 = "Tempo";
18719  var spacebar$6 = "spacja";
18720  var transcriptTitle$6 = "Transkrypcja";
18721  var lyricsTitle$6 = "Tekst";
18722  var autoScroll$6 = "Auto przewijanie";
18723  var statusPlaying$6 = "Odtwarzanie";
18724  var statusPaused$6 = "Wstrzymane";
18725  var statusStopped$6 = "Zatrzymane";
18726  var statusBuffering$6 = "Buforowanie";
18727  var statusEnd$6 = "Koniec ścieżki";
18728  var selectedTrack$6 = "Wybrana ścieżka";
18729  var alertDescribedVersion$6 = "Użyj wersji filmu z opisem audio";
18730  var alertNonDescribedVersion$6 = "Użyj wersji filmu bez opisu audio";
18731  var prefMenuCaptions$6 = "Napisy";
18732  var prefVoicedCaptions$6 = "Spoken Captions";
18733  var prefMenuDescriptions$6 = "Audiodeskrypcja";
18734  var prefMenuKeyboard$6 = "Klawiatura";
18735  var prefMenuTranscript$6 = "Transkrypcja";
18736  var prefTitleCaptions$6 = "Preferencje napisów";
18737  var prefTitleDescriptions$6 = "Preferencje audiodeskrypcji";
18738  var prefTitleKeyboard$6 = "Preferencje klawiatury";
18739  var prefTitleTranscript$6 = "Preferencje transkrypcji";
18740  var prefIntroDescription1$6 = "Ten odtwarzacz mediów obsługuje audiodeskrypcję na dwa sposoby: ";
18741  var prefDescription1$6 = "Bieżący film ma alternatywna wersja opisu audio, tekst opisu audio.";
18742  var prefDescription2$6 = "Bieżący film ma alternatywna wersja wideo z opisem audio.";
18743  var prefDescription3$6 = "Bieżący film ma tekst opisu audio, ogłaszany przez czytnik ekranu.";
18744  var prefIntroDescriptionNone$6 = "Bieżący film nie ma audiodeskprycji.";
18745  var prefIntroDescription3$6 = "Użyj formularza poniższej, aby określić ustawienia odtwarzania tekstowego opisu audio";
18746  var prefIntroDescription4$6 = "Po zapisaniu ustawień opisu audio można włączać/wyłączać za pomocą przycisku Audiodeskrypcja.";
18747  var prefIntroKeyboard1$6 = "Odtwarzaczem mediów na tej stronie można sterować z dowolnego miejsca na stronie za pomocą skrótów klawiaturowych (lista poniżej).";
18748  var prefIntroKeyboard2$6 = "Klawisze modyfikujące (Shift, Alt i Control) można przypisać poniżej";
18749  var prefIntroKeyboard3$6 = "UWAGA: Niektóre kombinacje klawiszy mogą kolidować z klawiszami używanymi przez przeglądarkę i/lub inne aplikacje. Wypróbuj różne kombinacje klawiszy modyfikujących, aby znaleźć taką, która będzie dla Ciebie odpowiednia.";
18750  var prefHeadingKeyboard1$6 = "Klawisze modyfikujące używane do skrótów";
18751  var prefHeadingKeyboard2$6 = "Bieżące skróty klawiaturowe";
18752  var prefHeadingDescription$6 = "Audiodeskrypcja";
18753  var prefHeadingTextDescription$6 = "Tekst oparty na audiodeskrypcji";
18754  var prefAltKey$6 = "Alt";
18755  var prefCtrlKey$6 = "Control";
18756  var prefShiftKey$6 = "Shift";
18757  var prefNoKeyShortcuts$6 = "Disable keyboard shortcuts";
18758  var escapeKey$6 = "Escape";
18759  var escapeKeyFunction$6 = "Zamknij bieżące okno dialogowe lub menu podręczne";
18760  var prefDescPause$6 = "Automatycznie wstrzymuj wideo po rozpoczęciu audiodeskrypcji";
18761  var prefDescVisible$6 = "Wyświetlaj opis audio";
18762  var prefDescVoice$6 = "Głos";
18763  var prefDescRate$6 = "Spoken Description Rate";
18764  var prefCaptionRate$6 = "Spoken Caption Rate";
18765  var prefDescPitch$6 = "Wysokość";
18766  var prefDescPitch1$6 = "Bardzo niska";
18767  var prefDescPitch2$6 = "Niska";
18768  var prefDescPitch3$6 = "Domyślna";
18769  var prefDescPitch4$6 = "Wysoka";
18770  var prefDescPitch5$6 = "Bardzo wysoko";
18771  var sampleDescriptionText$6 = "Edytuj ustawienia, aby odczytać ten tekst na głos.";
18772  var prefHighlight$6 = "Podświetl transkrypcję podczas odtwarzania mediów";
18773  var prefTabbable$6 = "Włącz transkrypcję za pomocą klawiatury";
18774  var prefCaptionsFont$6 = "Czcionka";
18775  var prefCaptionsColor$6 = "Kolor tekstu";
18776  var prefCaptionsBGColor$6 = "Kolor tła";
18777  var prefCaptionsSize$6 = "Rozmiar czcionki";
18778  var prefCaptionsOpacity$6 = "Krycie";
18779  var prefCaptionsStyle$6 = "Styl";
18780  var serif$6 = "szeryfowa";
18781  var sans$6 = "bezszeryfowa";
18782  var cursive$6 = "pochyła";
18783  var fantasy$6 = "fantazyjna";
18784  var monospace$6 = "stała szerokość";
18785  var white$6 = "biały";
18786  var yellow$6 = "żółty";
18787  var green$6 = "zielony";
18788  var cyan$6 = "cyjan";
18789  var blue$6 = "niebieski";
18790  var magenta$6 = "magenta";
18791  var red$6 = "czerwony";
18792  var black$6 = "czarny";
18793  var transparent$6 = "transparentne";
18794  var solid$6 = "solidne";
18795  var captionsStylePopOn$6 = "Rozwiń";
18796  var captionsStyleRollUp$6 = "Zwiń";
18797  var prefCaptionsPosition$6 = "Pozycja";
18798  var captionsPositionOverlay$6 = "Nakładka";
18799  var captionsPositionBelow$6 = "Poniżej wideo";
18800  var sampleCaptionText$6 = "Przykładowy tekst napisu";
18801  var prefSuccess$6 = "Zmiany zostały zapisane.";
18802  var prefNoChange$6 = "Nie wprowadzono żadnych zmian.";
18803  var save$6 = "Zapisz";
18804  var cancel$6 = "Anuluj";
18805  var dismissButton$6 = "Dismiss";
18806  var windowButtonLabel$6 = "Opcje okna";
18807  var windowMove$6 = "Przenieś";
18808  var windowMoveLeft$6 = "Window moved left";
18809  var windowMoveRight$6 = "Window moved right";
18810  var windowMoveUp$6 = "Window moved up";
18811  var windowMoveDown$6 = "Window moved down";
18812  var windowMoveStopped$6 = "Window move stopped";
18813  var transcriptControls$6 = "Transcript Window Controls";
18814  var signControls$6 = "Sign Language Window Controls";
18815  var windowMoveAlert$6 = "Przeciągnij lub użyj strzałek, aby przesunąć okno; Enter, aby zatrzymać";
18816  var windowResize$6 = "Zmień rozmiar";
18817  var windowResizeHeading$6 = "Zmień rozmiar okna";
18818  var closeButtonLabel$6 = "Zamknij";
18819  var width$6 = "Szerokość";
18820  var height$6 = "Wysokość";
18821  var resultsSummary1$6 = "Szukano:";
18822  var resultsSummary2$6 = "Znaleziono %1 pasujące elementy.";
18823  var resultsSummary3$6 = "Kliknij czas powiązany z dowolnym elementem, aby odtworzyć wideo od tego momentu.";
18824  var noResultsFound$6 = "Nie znaleziono żadnych wyników";
18825  var searchButtonLabel$6 = "Odtwarzaj od %1";
18826  var hour$6 = "godzina";
18827  var minute$6 = "minuta";
18828  var second$6 = "sekunda";
18829  var hours$6 = "godziny";
18830  var minutes$6 = "minuty";
18831  var seconds$6 = "sekundy";
18832  var vtsHeading$6 = "Video Transcript Sorter";
18833  var vtsInstructions1$6 = "Use the Video Transcript Sorter to modify text tracks:";
18834  var vtsInstructions2$6 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
18835  var vtsInstructions3$6 = "Modify content or start/end times (all are directly editable within the table).";
18836  var vtsInstructions4$6 = "Add new content, such as chapters or descriptions.";
18837  var vtsInstructions5$6 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
18838  var vtsSelectLanguage$6 = "Select a language";
18839  var vtsSave$6 = "Generate new .vtt content";
18840  var vtsReturn$6 = "Return to Editor";
18841  var vtsCancel$6 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
18842  var vtsRow$6 = "Row";
18843  var vtsKind$6 = "Kind";
18844  var vtsStart$6 = "Start";
18845  var vtsEnd$6 = "End";
18846  var vtsContent$6 = "Content";
18847  var vtsActions$6 = "Actions";
18848  var vtsNewRow$6 = "A new row %1 has been inserted.";
18849  var vtsDeletedRow$6 = "Row %1 has been deleted.";
18850  var vtsMovedRow$6 = "Row %1 has been moved %2 and is now Row %3.";
18851  var pl = {
18852  	playerHeading: playerHeading$6,
18853  	audioPlayer: audioPlayer$6,
18854  	videoPlayer: videoPlayer$6,
18855  	faster: faster$6,
18856  	slower: slower$6,
18857  	play: play$6,
18858  	pause: pause$6,
18859  	restart: restart$6,
18860  	prevTrack: prevTrack$6,
18861  	nextTrack: nextTrack$6,
18862  	rewind: rewind$6,
18863  	forward: forward$6,
18864  	captions: captions$6,
18865  	showCaptions: showCaptions$6,
18866  	hideCaptions: hideCaptions$6,
18867  	captionsOff: captionsOff$6,
18868  	showTranscript: showTranscript$6,
18869  	hideTranscript: hideTranscript$6,
18870  	turnOnDescriptions: turnOnDescriptions$6,
18871  	turnOffDescriptions: turnOffDescriptions$6,
18872  	chapters: chapters$6,
18873  	language: language$6,
18874  	sign: sign$6,
18875  	showSign: showSign$6,
18876  	hideSign: hideSign$6,
18877  	seekbarLabel: seekbarLabel$6,
18878  	mute: mute$6,
18879  	unmute: unmute$6,
18880  	volume: volume$6,
18881  	volumeUpDown: volumeUpDown$6,
18882  	preferences: preferences$6,
18883  	enterFullScreen: enterFullScreen$6,
18884  	exitFullScreen: exitFullScreen$6,
18885  	speed: speed$6,
18886  	spacebar: spacebar$6,
18887  	transcriptTitle: transcriptTitle$6,
18888  	lyricsTitle: lyricsTitle$6,
18889  	autoScroll: autoScroll$6,
18890  	statusPlaying: statusPlaying$6,
18891  	statusPaused: statusPaused$6,
18892  	statusStopped: statusStopped$6,
18893  	statusBuffering: statusBuffering$6,
18894  	statusEnd: statusEnd$6,
18895  	selectedTrack: selectedTrack$6,
18896  	alertDescribedVersion: alertDescribedVersion$6,
18897  	alertNonDescribedVersion: alertNonDescribedVersion$6,
18898  	prefMenuCaptions: prefMenuCaptions$6,
18899  	prefVoicedCaptions: prefVoicedCaptions$6,
18900  	prefMenuDescriptions: prefMenuDescriptions$6,
18901  	prefMenuKeyboard: prefMenuKeyboard$6,
18902  	prefMenuTranscript: prefMenuTranscript$6,
18903  	prefTitleCaptions: prefTitleCaptions$6,
18904  	prefTitleDescriptions: prefTitleDescriptions$6,
18905  	prefTitleKeyboard: prefTitleKeyboard$6,
18906  	prefTitleTranscript: prefTitleTranscript$6,
18907  	prefIntroDescription1: prefIntroDescription1$6,
18908  	prefDescription1: prefDescription1$6,
18909  	prefDescription2: prefDescription2$6,
18910  	prefDescription3: prefDescription3$6,
18911  	prefIntroDescriptionNone: prefIntroDescriptionNone$6,
18912  	prefIntroDescription3: prefIntroDescription3$6,
18913  	prefIntroDescription4: prefIntroDescription4$6,
18914  	prefIntroKeyboard1: prefIntroKeyboard1$6,
18915  	prefIntroKeyboard2: prefIntroKeyboard2$6,
18916  	prefIntroKeyboard3: prefIntroKeyboard3$6,
18917  	prefHeadingKeyboard1: prefHeadingKeyboard1$6,
18918  	prefHeadingKeyboard2: prefHeadingKeyboard2$6,
18919  	prefHeadingDescription: prefHeadingDescription$6,
18920  	prefHeadingTextDescription: prefHeadingTextDescription$6,
18921  	prefAltKey: prefAltKey$6,
18922  	prefCtrlKey: prefCtrlKey$6,
18923  	prefShiftKey: prefShiftKey$6,
18924  	prefNoKeyShortcuts: prefNoKeyShortcuts$6,
18925  	escapeKey: escapeKey$6,
18926  	escapeKeyFunction: escapeKeyFunction$6,
18927  	prefDescPause: prefDescPause$6,
18928  	prefDescVisible: prefDescVisible$6,
18929  	prefDescVoice: prefDescVoice$6,
18930  	prefDescRate: prefDescRate$6,
18931  	prefCaptionRate: prefCaptionRate$6,
18932  	prefDescPitch: prefDescPitch$6,
18933  	prefDescPitch1: prefDescPitch1$6,
18934  	prefDescPitch2: prefDescPitch2$6,
18935  	prefDescPitch3: prefDescPitch3$6,
18936  	prefDescPitch4: prefDescPitch4$6,
18937  	prefDescPitch5: prefDescPitch5$6,
18938  	sampleDescriptionText: sampleDescriptionText$6,
18939  	prefHighlight: prefHighlight$6,
18940  	prefTabbable: prefTabbable$6,
18941  	prefCaptionsFont: prefCaptionsFont$6,
18942  	prefCaptionsColor: prefCaptionsColor$6,
18943  	prefCaptionsBGColor: prefCaptionsBGColor$6,
18944  	prefCaptionsSize: prefCaptionsSize$6,
18945  	prefCaptionsOpacity: prefCaptionsOpacity$6,
18946  	prefCaptionsStyle: prefCaptionsStyle$6,
18947  	serif: serif$6,
18948  	sans: sans$6,
18949  	cursive: cursive$6,
18950  	fantasy: fantasy$6,
18951  	monospace: monospace$6,
18952  	white: white$6,
18953  	yellow: yellow$6,
18954  	green: green$6,
18955  	cyan: cyan$6,
18956  	blue: blue$6,
18957  	magenta: magenta$6,
18958  	red: red$6,
18959  	black: black$6,
18960  	transparent: transparent$6,
18961  	solid: solid$6,
18962  	captionsStylePopOn: captionsStylePopOn$6,
18963  	captionsStyleRollUp: captionsStyleRollUp$6,
18964  	prefCaptionsPosition: prefCaptionsPosition$6,
18965  	captionsPositionOverlay: captionsPositionOverlay$6,
18966  	captionsPositionBelow: captionsPositionBelow$6,
18967  	sampleCaptionText: sampleCaptionText$6,
18968  	prefSuccess: prefSuccess$6,
18969  	prefNoChange: prefNoChange$6,
18970  	save: save$6,
18971  	cancel: cancel$6,
18972  	dismissButton: dismissButton$6,
18973  	windowButtonLabel: windowButtonLabel$6,
18974  	windowMove: windowMove$6,
18975  	windowMoveLeft: windowMoveLeft$6,
18976  	windowMoveRight: windowMoveRight$6,
18977  	windowMoveUp: windowMoveUp$6,
18978  	windowMoveDown: windowMoveDown$6,
18979  	windowMoveStopped: windowMoveStopped$6,
18980  	transcriptControls: transcriptControls$6,
18981  	signControls: signControls$6,
18982  	windowMoveAlert: windowMoveAlert$6,
18983  	windowResize: windowResize$6,
18984  	windowResizeHeading: windowResizeHeading$6,
18985  	closeButtonLabel: closeButtonLabel$6,
18986  	width: width$6,
18987  	height: height$6,
18988  	resultsSummary1: resultsSummary1$6,
18989  	resultsSummary2: resultsSummary2$6,
18990  	resultsSummary3: resultsSummary3$6,
18991  	noResultsFound: noResultsFound$6,
18992  	searchButtonLabel: searchButtonLabel$6,
18993  	hour: hour$6,
18994  	minute: minute$6,
18995  	second: second$6,
18996  	hours: hours$6,
18997  	minutes: minutes$6,
18998  	seconds: seconds$6,
18999  	vtsHeading: vtsHeading$6,
19000  	vtsInstructions1: vtsInstructions1$6,
19001  	vtsInstructions2: vtsInstructions2$6,
19002  	vtsInstructions3: vtsInstructions3$6,
19003  	vtsInstructions4: vtsInstructions4$6,
19004  	vtsInstructions5: vtsInstructions5$6,
19005  	vtsSelectLanguage: vtsSelectLanguage$6,
19006  	vtsSave: vtsSave$6,
19007  	vtsReturn: vtsReturn$6,
19008  	vtsCancel: vtsCancel$6,
19009  	vtsRow: vtsRow$6,
19010  	vtsKind: vtsKind$6,
19011  	vtsStart: vtsStart$6,
19012  	vtsEnd: vtsEnd$6,
19013  	vtsContent: vtsContent$6,
19014  	vtsActions: vtsActions$6,
19015  	vtsNewRow: vtsNewRow$6,
19016  	vtsDeletedRow: vtsDeletedRow$6,
19017  	vtsMovedRow: vtsMovedRow$6
19018  };
19019
19020  var playerHeading$5 = "Reprodutor de mídias";
19021  var audioPlayer$5 = "Audio player";
19022  var videoPlayer$5 = "Video player";
19023  var faster$5 = "Rápido";
19024  var slower$5 = "Lento";
19025  var play$5 = "Reproduzir";
19026  var pause$5 = "Pausar";
19027  var restart$5 = "Reiniciar";
19028  var prevTrack$5 = "Faixa anterior";
19029  var nextTrack$5 = "Próxima faixa";
19030  var rewind$5 = "Retroceder";
19031  var forward$5 = "Avançar";
19032  var captions$5 = "Legendas";
19033  var showCaptions$5 = "Mostrar legendas";
19034  var hideCaptions$5 = "Ocultar legendas";
19035  var captionsOff$5 = "Legendas desligadas";
19036  var showTranscript$5 = "Mostrar transcrição";
19037  var hideTranscript$5 = "Ocultar transcrição";
19038  var turnOnDescriptions$5 = "Habilitar descrições";
19039  var turnOffDescriptions$5 = "Desabilitar descrições";
19040  var chapters$5 = "Capítulos";
19041  var language$5 = "Idioma";
19042  var sign$5 = "Língua de sinais";
19043  var showSign$5 = "Mostrar língua de sinais";
19044  var hideSign$5 = "Ocultar língua de sinais";
19045  var seekbarLabel$5 = "Linha do tempo";
19046  var mute$5 = "Silenciar";
19047  var unmute$5 = "Ativar som";
19048  var volume$5 = "Volume";
19049  var volumeUpDown$5 = "Ampliar/reduzir volume";
19050  var preferences$5 = "Preferências";
19051  var enterFullScreen$5 = "Entrar em tela cheia";
19052  var exitFullScreen$5 = "Sair da tela cheia";
19053  var speed$5 = "Velocidade";
19054  var spacebar$5 = "barra de espaço";
19055  var transcriptTitle$5 = "Transcrição";
19056  var lyricsTitle$5 = "Letras";
19057  var autoScroll$5 = "Rolagem automática";
19058  var statusPlaying$5 = "Reproduzindo";
19059  var statusPaused$5 = "Pausado";
19060  var statusStopped$5 = "Parado";
19061  var statusBuffering$5 = "Carregando";
19062  var statusEnd$5 = "Fim da faixa";
19063  var selectedTrack$5 = "Faixa Selecionada";
19064  var alertDescribedVersion$5 = "Usando a versão com audiodescrição deste vídeo";
19065  var alertNonDescribedVersion$5 = "Utilizando a versão sem audiodescrição desse vídeo";
19066  var prefMenuCaptions$5 = "Capítulos";
19067  var prefVoicedCaptions$5 = "Spoken Captions";
19068  var prefMenuDescriptions$5 = "Descrições";
19069  var prefMenuKeyboard$5 = "Teclado";
19070  var prefMenuTranscript$5 = "Transcrição";
19071  var prefTitleCaptions$5 = "Preferências de Legenda";
19072  var prefTitleDescriptions$5 = "Preferências da Audiodescrição";
19073  var prefTitleKeyboard$5 = "Preferências do Teclado";
19074  var prefTitleTranscript$5 = "Preferências da Transcrição";
19075  var prefIntroDescription1$5 = "Esse reprodutor de mídias suportam audiodescrição de duas maneiras: ";
19076  var prefDescription1$5 = "O vídeo atual está uma versão alternativa de descrição, texto baseado em descrição.";
19077  var prefDescription2$5 = "O vídeo atual está versão alternativa de descrição do vídeo.";
19078  var prefDescription3$5 = "O vídeo atual está texto baseado em descrição, anunciado pelo leitor de tela.";
19079  var prefIntroDescriptionNone$5 = "O vídeo atual não possui audiodescrição em nenhum formato.";
19080  var prefIntroDescription3$5 = "Use o formulário a seguir para definir suas preferências relacionadas à texto baseado em audiodescrição.";
19081  var prefIntroDescription4$5 = "Depois que salvar suas configurações, a audiodescrição poderá ser ligada/desligada usando o botão Descrição.";
19082  var prefIntroKeyboard1$5 = "O reprodutor de mídias desse página web pode ser operado de qualquer lugar nessa página, usando os atalhos de teclado (veja a lista abaixo).";
19083  var prefIntroKeyboard2$5 = "Modificar teclas (Shift, Alt and Control) podem ser gravadas abaixo.";
19084  var prefIntroKeyboard3$5 = "OBSERVE: Algumas combinações de teclado podem entrar em conflito com atalhos usados pelo seu navegador e/ou outro software. Teste várias combinações de atalhos de teclado para encontrar uma que funcione bem para você.";
19085  var prefHeadingKeyboard1$5 = "Teclas modificadas dos atalhos";
19086  var prefHeadingKeyboard2$5 = "Atual atalho de teclado";
19087  var prefHeadingDescription$5 = "Audiodescrição";
19088  var prefHeadingTextDescription$5 = "Texto baseado em audiodescrição";
19089  var prefAltKey$5 = "Alt";
19090  var prefCtrlKey$5 = "Control";
19091  var prefShiftKey$5 = "Shift";
19092  var prefNoKeyShortcuts$5 = "Disable keyboard shortcuts";
19093  var escapeKey$5 = "ESC";
19094  var escapeKeyFunction$5 = "Fechar diálogo ou caixa pop-up atual";
19095  var prefDescPause$5 = "Automaticamente pausar um vídeo quando a descrição iniciar";
19096  var prefDescVisible$5 = "Fazer com que a descrição esteja visível";
19097  var prefDescVoice$5 = "Voz";
19098  var prefDescRate$5 = "Spoken Description Rate";
19099  var prefCaptionRate$5 = "Spoken Caption Rate";
19100  var prefDescPitch$5 = "Tom";
19101  var prefDescPitch1$5 = "Muito baixo";
19102  var prefDescPitch2$5 = "Baixo";
19103  var prefDescPitch3$5 = "Padrão";
19104  var prefDescPitch4$5 = "Alto";
19105  var prefDescPitch5$5 = "Muito alto";
19106  var sampleDescriptionText$5 = "Ajuste as configurações para ouvir este exemplo de texto.";
19107  var prefHighlight$5 = "Destacar a descrição conforme a mídia for reproduzida";
19108  var prefTabbable$5 = "Transcrição de teclado habilitada";
19109  var prefCaptionsFont$5 = "Fonte";
19110  var prefCaptionsColor$5 = "Cor do Texto";
19111  var prefCaptionsBGColor$5 = "Cor do plano de Fundo";
19112  var prefCaptionsSize$5 = "Tamanho da Fonte";
19113  var prefCaptionsOpacity$5 = "transparência";
19114  var prefCaptionsStyle$5 = "Estilo";
19115  var serif$5 = "serifada";
19116  var sans$5 = "sem serifa";
19117  var cursive$5 = "cursiva";
19118  var fantasy$5 = "fantasiosa";
19119  var monospace$5 = "monoespaçada";
19120  var white$5 = "branca";
19121  var yellow$5 = "amarela";
19122  var green$5 = "verde";
19123  var cyan$5 = "ciano";
19124  var blue$5 = "azul";
19125  var magenta$5 = "magenta";
19126  var red$5 = "vermelha";
19127  var black$5 = "preta";
19128  var transparent$5 = "transparente";
19129  var solid$5 = "sólida";
19130  var captionsStylePopOn$5 = "Aparecer";
19131  var captionsStyleRollUp$5 = "Rolar pra cima";
19132  var prefCaptionsPosition$5 = "Posição";
19133  var captionsPositionOverlay$5 = "Sobrepôr";
19134  var captionsPositionBelow$5 = "Abaixo do vídeo";
19135  var sampleCaptionText$5 = "Um texto de exemplo para legendas";
19136  var prefSuccess$5 = "Suas alterações foram salvas";
19137  var prefNoChange$5 = "Você não fez nenhuma alteração";
19138  var save$5 = "Salvar";
19139  var cancel$5 = "Cancelar";
19140  var dismissButton$5 = "Dismiss";
19141  var windowButtonLabel$5 = "Opções de janela";
19142  var windowMove$5 = "Mover";
19143  var windowMoveLeft$5 = "Window moved left";
19144  var windowMoveRight$5 = "Window moved right";
19145  var windowMoveUp$5 = "Window moved up";
19146  var windowMoveDown$5 = "Window moved down";
19147  var windowMoveStopped$5 = "Window move stopped";
19148  var transcriptControls$5 = "Transcript Window Controls";
19149  var signControls$5 = "Sign Language Window Controls";
19150  var windowMoveAlert$5 = "Arraste ou use as setas do seu teclado para mover; Enter para parar";
19151  var windowResize$5 = "Redimensionar";
19152  var windowResizeHeading$5 = "Redimensionar Janela";
19153  var closeButtonLabel$5 = "Fechar";
19154  var width$5 = "Largura";
19155  var height$5 = "Altura";
19156  var resultsSummary1$5 = "Você buscou por:";
19157  var resultsSummary2$5 = "Encontrado %1 itens correspondentes";
19158  var resultsSummary3$5 = "Ative o item de tempo associado para reproduzir o vídeo a partir desse ponto.";
19159  var noResultsFound$5 = "Nenhum resultado encontrado";
19160  var searchButtonLabel$5 = "Tocar no %1";
19161  var hour$5 = "hora";
19162  var minute$5 = "minuto";
19163  var second$5 = "segundos";
19164  var hours$5 = "horas";
19165  var minutes$5 = "minutos";
19166  var seconds$5 = "segundos";
19167  var vtsHeading$5 = "Video Transcript Sorter";
19168  var vtsInstructions1$5 = "Use the Video Transcript Sorter to modify text tracks:";
19169  var vtsInstructions2$5 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
19170  var vtsInstructions3$5 = "Modify content or start/end times (all are directly editable within the table).";
19171  var vtsInstructions4$5 = "Add new content, such as chapters or descriptions.";
19172  var vtsInstructions5$5 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
19173  var vtsSelectLanguage$5 = "Select a language";
19174  var vtsSave$5 = "Generate new .vtt content";
19175  var vtsReturn$5 = "Return to Editor";
19176  var vtsCancel$5 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
19177  var vtsRow$5 = "Row";
19178  var vtsKind$5 = "Kind";
19179  var vtsStart$5 = "Start";
19180  var vtsEnd$5 = "End";
19181  var vtsContent$5 = "Content";
19182  var vtsActions$5 = "Actions";
19183  var vtsNewRow$5 = "A new row %1 has been inserted.";
19184  var vtsDeletedRow$5 = "Row %1 has been deleted.";
19185  var vtsMovedRow$5 = "Row %1 has been moved %2 and is now Row %3.";
19186  var pt_br = {
19187  	playerHeading: playerHeading$5,
19188  	audioPlayer: audioPlayer$5,
19189  	videoPlayer: videoPlayer$5,
19190  	faster: faster$5,
19191  	slower: slower$5,
19192  	play: play$5,
19193  	pause: pause$5,
19194  	restart: restart$5,
19195  	prevTrack: prevTrack$5,
19196  	nextTrack: nextTrack$5,
19197  	rewind: rewind$5,
19198  	forward: forward$5,
19199  	captions: captions$5,
19200  	showCaptions: showCaptions$5,
19201  	hideCaptions: hideCaptions$5,
19202  	captionsOff: captionsOff$5,
19203  	showTranscript: showTranscript$5,
19204  	hideTranscript: hideTranscript$5,
19205  	turnOnDescriptions: turnOnDescriptions$5,
19206  	turnOffDescriptions: turnOffDescriptions$5,
19207  	chapters: chapters$5,
19208  	language: language$5,
19209  	sign: sign$5,
19210  	showSign: showSign$5,
19211  	hideSign: hideSign$5,
19212  	seekbarLabel: seekbarLabel$5,
19213  	mute: mute$5,
19214  	unmute: unmute$5,
19215  	volume: volume$5,
19216  	volumeUpDown: volumeUpDown$5,
19217  	preferences: preferences$5,
19218  	enterFullScreen: enterFullScreen$5,
19219  	exitFullScreen: exitFullScreen$5,
19220  	speed: speed$5,
19221  	spacebar: spacebar$5,
19222  	transcriptTitle: transcriptTitle$5,
19223  	lyricsTitle: lyricsTitle$5,
19224  	autoScroll: autoScroll$5,
19225  	statusPlaying: statusPlaying$5,
19226  	statusPaused: statusPaused$5,
19227  	statusStopped: statusStopped$5,
19228  	statusBuffering: statusBuffering$5,
19229  	statusEnd: statusEnd$5,
19230  	selectedTrack: selectedTrack$5,
19231  	alertDescribedVersion: alertDescribedVersion$5,
19232  	alertNonDescribedVersion: alertNonDescribedVersion$5,
19233  	prefMenuCaptions: prefMenuCaptions$5,
19234  	prefVoicedCaptions: prefVoicedCaptions$5,
19235  	prefMenuDescriptions: prefMenuDescriptions$5,
19236  	prefMenuKeyboard: prefMenuKeyboard$5,
19237  	prefMenuTranscript: prefMenuTranscript$5,
19238  	prefTitleCaptions: prefTitleCaptions$5,
19239  	prefTitleDescriptions: prefTitleDescriptions$5,
19240  	prefTitleKeyboard: prefTitleKeyboard$5,
19241  	prefTitleTranscript: prefTitleTranscript$5,
19242  	prefIntroDescription1: prefIntroDescription1$5,
19243  	prefDescription1: prefDescription1$5,
19244  	prefDescription2: prefDescription2$5,
19245  	prefDescription3: prefDescription3$5,
19246  	prefIntroDescriptionNone: prefIntroDescriptionNone$5,
19247  	prefIntroDescription3: prefIntroDescription3$5,
19248  	prefIntroDescription4: prefIntroDescription4$5,
19249  	prefIntroKeyboard1: prefIntroKeyboard1$5,
19250  	prefIntroKeyboard2: prefIntroKeyboard2$5,
19251  	prefIntroKeyboard3: prefIntroKeyboard3$5,
19252  	prefHeadingKeyboard1: prefHeadingKeyboard1$5,
19253  	prefHeadingKeyboard2: prefHeadingKeyboard2$5,
19254  	prefHeadingDescription: prefHeadingDescription$5,
19255  	prefHeadingTextDescription: prefHeadingTextDescription$5,
19256  	prefAltKey: prefAltKey$5,
19257  	prefCtrlKey: prefCtrlKey$5,
19258  	prefShiftKey: prefShiftKey$5,
19259  	prefNoKeyShortcuts: prefNoKeyShortcuts$5,
19260  	escapeKey: escapeKey$5,
19261  	escapeKeyFunction: escapeKeyFunction$5,
19262  	prefDescPause: prefDescPause$5,
19263  	prefDescVisible: prefDescVisible$5,
19264  	prefDescVoice: prefDescVoice$5,
19265  	prefDescRate: prefDescRate$5,
19266  	prefCaptionRate: prefCaptionRate$5,
19267  	prefDescPitch: prefDescPitch$5,
19268  	prefDescPitch1: prefDescPitch1$5,
19269  	prefDescPitch2: prefDescPitch2$5,
19270  	prefDescPitch3: prefDescPitch3$5,
19271  	prefDescPitch4: prefDescPitch4$5,
19272  	prefDescPitch5: prefDescPitch5$5,
19273  	sampleDescriptionText: sampleDescriptionText$5,
19274  	prefHighlight: prefHighlight$5,
19275  	prefTabbable: prefTabbable$5,
19276  	prefCaptionsFont: prefCaptionsFont$5,
19277  	prefCaptionsColor: prefCaptionsColor$5,
19278  	prefCaptionsBGColor: prefCaptionsBGColor$5,
19279  	prefCaptionsSize: prefCaptionsSize$5,
19280  	prefCaptionsOpacity: prefCaptionsOpacity$5,
19281  	prefCaptionsStyle: prefCaptionsStyle$5,
19282  	serif: serif$5,
19283  	sans: sans$5,
19284  	cursive: cursive$5,
19285  	fantasy: fantasy$5,
19286  	monospace: monospace$5,
19287  	white: white$5,
19288  	yellow: yellow$5,
19289  	green: green$5,
19290  	cyan: cyan$5,
19291  	blue: blue$5,
19292  	magenta: magenta$5,
19293  	red: red$5,
19294  	black: black$5,
19295  	transparent: transparent$5,
19296  	solid: solid$5,
19297  	captionsStylePopOn: captionsStylePopOn$5,
19298  	captionsStyleRollUp: captionsStyleRollUp$5,
19299  	prefCaptionsPosition: prefCaptionsPosition$5,
19300  	captionsPositionOverlay: captionsPositionOverlay$5,
19301  	captionsPositionBelow: captionsPositionBelow$5,
19302  	sampleCaptionText: sampleCaptionText$5,
19303  	prefSuccess: prefSuccess$5,
19304  	prefNoChange: prefNoChange$5,
19305  	save: save$5,
19306  	cancel: cancel$5,
19307  	dismissButton: dismissButton$5,
19308  	windowButtonLabel: windowButtonLabel$5,
19309  	windowMove: windowMove$5,
19310  	windowMoveLeft: windowMoveLeft$5,
19311  	windowMoveRight: windowMoveRight$5,
19312  	windowMoveUp: windowMoveUp$5,
19313  	windowMoveDown: windowMoveDown$5,
19314  	windowMoveStopped: windowMoveStopped$5,
19315  	transcriptControls: transcriptControls$5,
19316  	signControls: signControls$5,
19317  	windowMoveAlert: windowMoveAlert$5,
19318  	windowResize: windowResize$5,
19319  	windowResizeHeading: windowResizeHeading$5,
19320  	closeButtonLabel: closeButtonLabel$5,
19321  	width: width$5,
19322  	height: height$5,
19323  	resultsSummary1: resultsSummary1$5,
19324  	resultsSummary2: resultsSummary2$5,
19325  	resultsSummary3: resultsSummary3$5,
19326  	noResultsFound: noResultsFound$5,
19327  	searchButtonLabel: searchButtonLabel$5,
19328  	hour: hour$5,
19329  	minute: minute$5,
19330  	second: second$5,
19331  	hours: hours$5,
19332  	minutes: minutes$5,
19333  	seconds: seconds$5,
19334  	vtsHeading: vtsHeading$5,
19335  	vtsInstructions1: vtsInstructions1$5,
19336  	vtsInstructions2: vtsInstructions2$5,
19337  	vtsInstructions3: vtsInstructions3$5,
19338  	vtsInstructions4: vtsInstructions4$5,
19339  	vtsInstructions5: vtsInstructions5$5,
19340  	vtsSelectLanguage: vtsSelectLanguage$5,
19341  	vtsSave: vtsSave$5,
19342  	vtsReturn: vtsReturn$5,
19343  	vtsCancel: vtsCancel$5,
19344  	vtsRow: vtsRow$5,
19345  	vtsKind: vtsKind$5,
19346  	vtsStart: vtsStart$5,
19347  	vtsEnd: vtsEnd$5,
19348  	vtsContent: vtsContent$5,
19349  	vtsActions: vtsActions$5,
19350  	vtsNewRow: vtsNewRow$5,
19351  	vtsDeletedRow: vtsDeletedRow$5,
19352  	vtsMovedRow: vtsMovedRow$5
19353  };
19354
19355  var playerHeading$4 = "Leitor multimédia";
19356  var audioPlayer$4 = "Audio player";
19357  var videoPlayer$4 = "Video player";
19358  var faster$4 = "Mais rápido";
19359  var slower$4 = "Mais lento";
19360  var play$4 = "Reproduzir";
19361  var pause$4 = "Pausa";
19362  var restart$4 = "Reiniciar";
19363  var prevTrack$4 = "Faixa anterior";
19364  var nextTrack$4 = "Faixa seguinte";
19365  var rewind$4 = "Retroceder";
19366  var forward$4 = "Avançar";
19367  var captions$4 = "Legendas";
19368  var showCaptions$4 = "Mostrar legendas";
19369  var hideCaptions$4 = "Esconder legendas";
19370  var captionsOff$4 = "Desativar legendas";
19371  var showTranscript$4 = "Mostrar transcrição";
19372  var hideTranscript$4 = "Esconder transcrição";
19373  var turnOnDescriptions$4 = "Ativar descrições";
19374  var turnOffDescriptions$4 = "Desativar descrições";
19375  var chapters$4 = "Capítulos";
19376  var language$4 = "Idioma";
19377  var sign$4 = "Linguagem gestual";
19378  var showSign$4 = "Mostrar linguagem gestual";
19379  var hideSign$4 = "Esconder linguagem gestual";
19380  var seekbarLabel$4 = "linha temporal";
19381  var mute$4 = "Desativar som";
19382  var unmute$4 = "Ativar som";
19383  var volume$4 = "Volume";
19384  var volumeUpDown$4 = "Volume up down";
19385  var preferences$4 = "Preferências";
19386  var enterFullScreen$4 = "Ativar a vista de ecrã inteiro";
19387  var exitFullScreen$4 = "Sair da vista de ecrã inteiro";
19388  var speed$4 = "Velocidade";
19389  var spacebar$4 = "barra de espaço";
19390  var transcriptTitle$4 = "Transcrição";
19391  var lyricsTitle$4 = "Letra";
19392  var autoScroll$4 = "Scroll automático";
19393  var statusPlaying$4 = "A reproduzir";
19394  var statusPaused$4 = "Pausado";
19395  var statusStopped$4 = "Parado";
19396  var statusBuffering$4 = "A carregar";
19397  var statusEnd$4 = "Fim da faixa";
19398  var selectedTrack$4 = "Faixa selecionada";
19399  var alertDescribedVersion$4 = "A usar versão de áudio descrita deste vídeo";
19400  var alertNonDescribedVersion$4 = "A usar versão não descrita deste vídeo";
19401  var prefMenuCaptions$4 = "Legendas";
19402  var prefVoicedCaptions$4 = "Spoken Captions";
19403  var prefMenuDescriptions$4 = "Descrições";
19404  var prefMenuKeyboard$4 = "Teclado";
19405  var prefMenuTranscript$4 = "Transcrição";
19406  var prefTitleCaptions$4 = "Preferências das Legendas";
19407  var prefTitleDescriptions$4 = "Preferências da Descrição de Aúdio";
19408  var prefTitleKeyboard$4 = "Preferências de Peclado";
19409  var prefTitleTranscript$4 = "Preferências de Transcrição";
19410  var prefIntroDescription1$4 = "Este leitor multimédia suporta descrição de áudio de duas formas: ";
19411  var prefDescription1$4 = "O vídeo atual tem uma versão descrita alternativa, descrição à base de texto.";
19412  var prefDescription2$4 = "O vídeo atual tem versão descrita alernativa do vídeo.";
19413  var prefDescription3$4 = "O vídeo atual tem descrição à base de texto, anunciada pelo leitor de ecrãs.";
19414  var prefIntroDescriptionNone$4 = "O vídeo atual não tem descrição de áudio em nenhum formato.";
19415  var prefIntroDescription3$4 = "Utiliza o seguinte formulário para definir as tuas preferências relacionadas com descrição de áudio à base de texto.";
19416  var prefIntroDescription4$4 = "Depois de guardar as tuas definições, descrição de áudio pode ser alternada on/off usando o botão Descrição.";
19417  var prefIntroKeyboard1$4 = "O leitor multimédia nesta página pode ser operado de qualquer lugar na página utilizando os atalhos de teclado (vê em baixo a lista).";
19418  var prefIntroKeyboard2$4 = "Teclas modificadoras (Shift, Alt, and Control) podem ser atribuídas em baixo.";
19419  var prefIntroKeyboard3$4 = "NOTA: Algumas combinações de teclas podem entrar em conflito com teclas usadas pelo teu navegador e/ou outras aplicações de software. Tenta várias combinações de teclas modificadoras para encontrar uma que funciona para ti.";
19420  var prefHeadingKeyboard1$4 = "Teclas modificadoras usadas para atalhos";
19421  var prefHeadingKeyboard2$4 = "Atalhos de teclado em uso";
19422  var prefHeadingDescription$4 = "Descrição de áudio";
19423  var prefHeadingTextDescription$4 = "Descrição de áudio à base de texto";
19424  var prefAltKey$4 = "Alt";
19425  var prefCtrlKey$4 = "Control";
19426  var prefShiftKey$4 = "Shift";
19427  var prefNoKeyShortcuts$4 = "Disable keyboard shortcuts";
19428  var escapeKey$4 = "Escape";
19429  var escapeKeyFunction$4 = "Fecha o diálogo ou menu popup";
19430  var prefDescPause$4 = "Pausar vídeo automaticamente quando a descrição começa";
19431  var prefDescVisible$4 = "Torna a descrição visível";
19432  var prefDescVoice$4 = "Voz";
19433  var prefDescRate$4 = "Spoken Description Rate";
19434  var prefCaptionRate$4 = "Spoken Caption Rate";
19435  var prefDescPitch$4 = "Tom";
19436  var prefDescPitch1$4 = "Muito baixo";
19437  var prefDescPitch2$4 = "Baixo";
19438  var prefDescPitch3$4 = "Padrão";
19439  var prefDescPitch4$4 = "Alto";
19440  var prefDescPitch5$4 = "Muito Alto";
19441  var sampleDescriptionText$4 = "Ajusta as preferências para ouvir esta amostra de texto.";
19442  var prefHighlight$4 = "Destacar transcrição enquanto o leito está a reproduzir.";
19443  var prefTabbable$4 = "Transcrição ativada por teclado";
19444  var prefCaptionsFont$4 = "Tipo de letra";
19445  var prefCaptionsColor$4 = "Cor de texto";
19446  var prefCaptionsBGColor$4 = "Fundo";
19447  var prefCaptionsSize$4 = "Tamanho de letra";
19448  var prefCaptionsOpacity$4 = "Opacidade";
19449  var prefCaptionsStyle$4 = "Estilo";
19450  var serif$4 = "serifada";
19451  var sans$4 = "sem serifa";
19452  var cursive$4 = "cursivo";
19453  var fantasy$4 = "fantasia";
19454  var monospace$4 = "mono-espaçada";
19455  var white$4 = "branco";
19456  var yellow$4 = "amarelo";
19457  var green$4 = "verde";
19458  var cyan$4 = "ciano";
19459  var blue$4 = "azul";
19460  var magenta$4 = "magenta";
19461  var red$4 = "vermelho";
19462  var black$4 = "preto";
19463  var transparent$4 = "transparente";
19464  var solid$4 = "sólido";
19465  var captionsStylePopOn$4 = "Pop-on";
19466  var captionsStyleRollUp$4 = "Roll-up";
19467  var prefCaptionsPosition$4 = "Posição";
19468  var captionsPositionOverlay$4 = "Sobreposição";
19469  var captionsPositionBelow$4 = "Por baixo do vídeo";
19470  var sampleCaptionText$4 = "Amostra texto de legenda";
19471  var prefSuccess$4 = "As tuas alterações foram guardadas.";
19472  var prefNoChange$4 = "Não fizeste nenhuma alteração.";
19473  var save$4 = "Guardar";
19474  var cancel$4 = "Cancelar";
19475  var dismissButton$4 = "Dismiss";
19476  var windowButtonLabel$4 = "Opções de janela";
19477  var windowMove$4 = "Mover";
19478  var windowMoveLeft$4 = "Window moved left";
19479  var windowMoveRight$4 = "Window moved right";
19480  var windowMoveUp$4 = "Window moved up";
19481  var windowMoveDown$4 = "Window moved down";
19482  var windowMoveStopped$4 = "Window move stopped";
19483  var transcriptControls$4 = "Transcript Window Controls";
19484  var signControls$4 = "Sign Language Window Controls";
19485  var windowMoveAlert$4 = "Arrasta ou usa as teclas de seta para mover a janela; Enter para parar.";
19486  var windowResize$4 = "Redimencionar";
19487  var windowResizeHeading$4 = "Redimencionar janela";
19488  var closeButtonLabel$4 = "Fechar";
19489  var width$4 = "Largura";
19490  var height$4 = "Altura";
19491  var resultsSummary1$4 = "Pesquisaste por:";
19492  var resultsSummary2$4 = "Encontrado %1 itens correspondentes.";
19493  var resultsSummary3$4 = "Clica no tempo associado a qualquer item para reproduzir o vídeo a partir daquele ponto.";
19494  var noResultsFound$4 = "Nenhum resultado encontrado.";
19495  var searchButtonLabel$4 = "Reproduzir a partir de %1";
19496  var hour$4 = "hora";
19497  var minute$4 = "minuto";
19498  var second$4 = "segundo";
19499  var hours$4 = "horas";
19500  var minutes$4 = "minutos";
19501  var seconds$4 = "segundos";
19502  var vtsHeading$4 = "Video Transcript Sorter";
19503  var vtsInstructions1$4 = "Use the Video Transcript Sorter to modify text tracks:";
19504  var vtsInstructions2$4 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
19505  var vtsInstructions3$4 = "Modify content or start/end times (all are directly editable within the table).";
19506  var vtsInstructions4$4 = "Add new content, such as chapters or descriptions.";
19507  var vtsInstructions5$4 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
19508  var vtsSelectLanguage$4 = "Select a language";
19509  var vtsSave$4 = "Generate new .vtt content";
19510  var vtsReturn$4 = "Return to Editor";
19511  var vtsCancel$4 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
19512  var vtsRow$4 = "Row";
19513  var vtsKind$4 = "Kind";
19514  var vtsStart$4 = "Start";
19515  var vtsEnd$4 = "End";
19516  var vtsContent$4 = "Content";
19517  var vtsActions$4 = "Actions";
19518  var vtsNewRow$4 = "A new row %1 has been inserted.";
19519  var vtsDeletedRow$4 = "Row %1 has been deleted.";
19520  var vtsMovedRow$4 = "Row %1 has been moved %2 and is now Row %3.";
19521  var pt = {
19522  	playerHeading: playerHeading$4,
19523  	audioPlayer: audioPlayer$4,
19524  	videoPlayer: videoPlayer$4,
19525  	faster: faster$4,
19526  	slower: slower$4,
19527  	play: play$4,
19528  	pause: pause$4,
19529  	restart: restart$4,
19530  	prevTrack: prevTrack$4,
19531  	nextTrack: nextTrack$4,
19532  	rewind: rewind$4,
19533  	forward: forward$4,
19534  	captions: captions$4,
19535  	showCaptions: showCaptions$4,
19536  	hideCaptions: hideCaptions$4,
19537  	captionsOff: captionsOff$4,
19538  	showTranscript: showTranscript$4,
19539  	hideTranscript: hideTranscript$4,
19540  	turnOnDescriptions: turnOnDescriptions$4,
19541  	turnOffDescriptions: turnOffDescriptions$4,
19542  	chapters: chapters$4,
19543  	language: language$4,
19544  	sign: sign$4,
19545  	showSign: showSign$4,
19546  	hideSign: hideSign$4,
19547  	seekbarLabel: seekbarLabel$4,
19548  	mute: mute$4,
19549  	unmute: unmute$4,
19550  	volume: volume$4,
19551  	volumeUpDown: volumeUpDown$4,
19552  	preferences: preferences$4,
19553  	enterFullScreen: enterFullScreen$4,
19554  	exitFullScreen: exitFullScreen$4,
19555  	speed: speed$4,
19556  	spacebar: spacebar$4,
19557  	transcriptTitle: transcriptTitle$4,
19558  	lyricsTitle: lyricsTitle$4,
19559  	autoScroll: autoScroll$4,
19560  	statusPlaying: statusPlaying$4,
19561  	statusPaused: statusPaused$4,
19562  	statusStopped: statusStopped$4,
19563  	statusBuffering: statusBuffering$4,
19564  	statusEnd: statusEnd$4,
19565  	selectedTrack: selectedTrack$4,
19566  	alertDescribedVersion: alertDescribedVersion$4,
19567  	alertNonDescribedVersion: alertNonDescribedVersion$4,
19568  	prefMenuCaptions: prefMenuCaptions$4,
19569  	prefVoicedCaptions: prefVoicedCaptions$4,
19570  	prefMenuDescriptions: prefMenuDescriptions$4,
19571  	prefMenuKeyboard: prefMenuKeyboard$4,
19572  	prefMenuTranscript: prefMenuTranscript$4,
19573  	prefTitleCaptions: prefTitleCaptions$4,
19574  	prefTitleDescriptions: prefTitleDescriptions$4,
19575  	prefTitleKeyboard: prefTitleKeyboard$4,
19576  	prefTitleTranscript: prefTitleTranscript$4,
19577  	prefIntroDescription1: prefIntroDescription1$4,
19578  	prefDescription1: prefDescription1$4,
19579  	prefDescription2: prefDescription2$4,
19580  	prefDescription3: prefDescription3$4,
19581  	prefIntroDescriptionNone: prefIntroDescriptionNone$4,
19582  	prefIntroDescription3: prefIntroDescription3$4,
19583  	prefIntroDescription4: prefIntroDescription4$4,
19584  	prefIntroKeyboard1: prefIntroKeyboard1$4,
19585  	prefIntroKeyboard2: prefIntroKeyboard2$4,
19586  	prefIntroKeyboard3: prefIntroKeyboard3$4,
19587  	prefHeadingKeyboard1: prefHeadingKeyboard1$4,
19588  	prefHeadingKeyboard2: prefHeadingKeyboard2$4,
19589  	prefHeadingDescription: prefHeadingDescription$4,
19590  	prefHeadingTextDescription: prefHeadingTextDescription$4,
19591  	prefAltKey: prefAltKey$4,
19592  	prefCtrlKey: prefCtrlKey$4,
19593  	prefShiftKey: prefShiftKey$4,
19594  	prefNoKeyShortcuts: prefNoKeyShortcuts$4,
19595  	escapeKey: escapeKey$4,
19596  	escapeKeyFunction: escapeKeyFunction$4,
19597  	prefDescPause: prefDescPause$4,
19598  	prefDescVisible: prefDescVisible$4,
19599  	prefDescVoice: prefDescVoice$4,
19600  	prefDescRate: prefDescRate$4,
19601  	prefCaptionRate: prefCaptionRate$4,
19602  	prefDescPitch: prefDescPitch$4,
19603  	prefDescPitch1: prefDescPitch1$4,
19604  	prefDescPitch2: prefDescPitch2$4,
19605  	prefDescPitch3: prefDescPitch3$4,
19606  	prefDescPitch4: prefDescPitch4$4,
19607  	prefDescPitch5: prefDescPitch5$4,
19608  	sampleDescriptionText: sampleDescriptionText$4,
19609  	prefHighlight: prefHighlight$4,
19610  	prefTabbable: prefTabbable$4,
19611  	prefCaptionsFont: prefCaptionsFont$4,
19612  	prefCaptionsColor: prefCaptionsColor$4,
19613  	prefCaptionsBGColor: prefCaptionsBGColor$4,
19614  	prefCaptionsSize: prefCaptionsSize$4,
19615  	prefCaptionsOpacity: prefCaptionsOpacity$4,
19616  	prefCaptionsStyle: prefCaptionsStyle$4,
19617  	serif: serif$4,
19618  	sans: sans$4,
19619  	cursive: cursive$4,
19620  	fantasy: fantasy$4,
19621  	monospace: monospace$4,
19622  	white: white$4,
19623  	yellow: yellow$4,
19624  	green: green$4,
19625  	cyan: cyan$4,
19626  	blue: blue$4,
19627  	magenta: magenta$4,
19628  	red: red$4,
19629  	black: black$4,
19630  	transparent: transparent$4,
19631  	solid: solid$4,
19632  	captionsStylePopOn: captionsStylePopOn$4,
19633  	captionsStyleRollUp: captionsStyleRollUp$4,
19634  	prefCaptionsPosition: prefCaptionsPosition$4,
19635  	captionsPositionOverlay: captionsPositionOverlay$4,
19636  	captionsPositionBelow: captionsPositionBelow$4,
19637  	sampleCaptionText: sampleCaptionText$4,
19638  	prefSuccess: prefSuccess$4,
19639  	prefNoChange: prefNoChange$4,
19640  	save: save$4,
19641  	cancel: cancel$4,
19642  	dismissButton: dismissButton$4,
19643  	windowButtonLabel: windowButtonLabel$4,
19644  	windowMove: windowMove$4,
19645  	windowMoveLeft: windowMoveLeft$4,
19646  	windowMoveRight: windowMoveRight$4,
19647  	windowMoveUp: windowMoveUp$4,
19648  	windowMoveDown: windowMoveDown$4,
19649  	windowMoveStopped: windowMoveStopped$4,
19650  	transcriptControls: transcriptControls$4,
19651  	signControls: signControls$4,
19652  	windowMoveAlert: windowMoveAlert$4,
19653  	windowResize: windowResize$4,
19654  	windowResizeHeading: windowResizeHeading$4,
19655  	closeButtonLabel: closeButtonLabel$4,
19656  	width: width$4,
19657  	height: height$4,
19658  	resultsSummary1: resultsSummary1$4,
19659  	resultsSummary2: resultsSummary2$4,
19660  	resultsSummary3: resultsSummary3$4,
19661  	noResultsFound: noResultsFound$4,
19662  	searchButtonLabel: searchButtonLabel$4,
19663  	hour: hour$4,
19664  	minute: minute$4,
19665  	second: second$4,
19666  	hours: hours$4,
19667  	minutes: minutes$4,
19668  	seconds: seconds$4,
19669  	vtsHeading: vtsHeading$4,
19670  	vtsInstructions1: vtsInstructions1$4,
19671  	vtsInstructions2: vtsInstructions2$4,
19672  	vtsInstructions3: vtsInstructions3$4,
19673  	vtsInstructions4: vtsInstructions4$4,
19674  	vtsInstructions5: vtsInstructions5$4,
19675  	vtsSelectLanguage: vtsSelectLanguage$4,
19676  	vtsSave: vtsSave$4,
19677  	vtsReturn: vtsReturn$4,
19678  	vtsCancel: vtsCancel$4,
19679  	vtsRow: vtsRow$4,
19680  	vtsKind: vtsKind$4,
19681  	vtsStart: vtsStart$4,
19682  	vtsEnd: vtsEnd$4,
19683  	vtsContent: vtsContent$4,
19684  	vtsActions: vtsActions$4,
19685  	vtsNewRow: vtsNewRow$4,
19686  	vtsDeletedRow: vtsDeletedRow$4,
19687  	vtsMovedRow: vtsMovedRow$4
19688  };
19689
19690  var __author__ = "Slovak translation by Radoslav Ďurač (@rraddatch)";
19691  var playerHeading$3 = "Prehrávač médií";
19692  var audioPlayer$3 = "Prehrávač zvuku";
19693  var videoPlayer$3 = "Prehrávač videa";
19694  var faster$3 = "Rýchlejšie";
19695  var slower$3 = "Pomalšie";
19696  var play$3 = "Prehrať";
19697  var pause$3 = "Pozastaviť";
19698  var restart$3 = "Prehrať odznova";
19699  var prevTrack$3 = "Predchádzajúca stopa";
19700  var nextTrack$3 = "Nasledujúca stopa";
19701  var rewind$3 = "Pretočiť dozadu";
19702  var forward$3 = "Pretočiť dopredu";
19703  var captions$3 = "Skryté titulky";
19704  var showCaptions$3 = "Zobraziť skryté titulky";
19705  var hideCaptions$3 = "Vypnúť skryté titulky";
19706  var captionsOff$3 = "Vypnúť skryté titulky";
19707  var showTranscript$3 = "Zobraziť prepis";
19708  var hideTranscript$3 = "Skryť prepis";
19709  var turnOnDescriptions$3 = "Zapnúť audiokomentár";
19710  var turnOffDescriptions$3 = "Vypnúť audiokomentár";
19711  var chapters$3 = "Kapitoly";
19712  var language$3 = "Jazyk";
19713  var sign$3 = "Posunkový jazyk";
19714  var showSign$3 = "Zobraziť posunkový jazyk";
19715  var hideSign$3 = "Skryť posunkový jazyk";
19716  var seekbarLabel$3 = "časová os";
19717  var mute$3 = "Stlmiť";
19718  var unmute$3 = "Zrušiť stlmenie";
19719  var volume$3 = "Hlasitosť";
19720  var volumeUpDown$3 = "Hlasitosť hore dole";
19721  var preferences$3 = "Nastavenia";
19722  var enterFullScreen$3 = "Prejsť na celú obrazovku";
19723  var exitFullScreen$3 = "Ukončiť celú obrazovku";
19724  var speed$3 = "Rýchlosť";
19725  var spacebar$3 = "medzerník";
19726  var transcriptTitle$3 = "Prepis";
19727  var lyricsTitle$3 = "Text piesne";
19728  var autoScroll$3 = "Automatické posúvanie";
19729  var statusPlaying$3 = "Prehráva sa";
19730  var statusPaused$3 = "Pozastavené";
19731  var statusStopped$3 = "Zastavené";
19732  var statusBuffering$3 = "Načítava sa";
19733  var statusEnd$3 = "Koniec stopy";
19734  var selectedTrack$3 = "Vybraná stopa";
19735  var alertDescribedVersion$3 = "Používa sa verzia videa s audiokomentárom";
19736  var alertNonDescribedVersion$3 = "Používa sa verzia videa bez audiokomentára";
19737  var prefMenuCaptions$3 = "Skryté titulky";
19738  var prefVoicedCaptions$3 = "Hovorené skryté titulky";
19739  var prefMenuDescriptions$3 = "Audiokomentár";
19740  var prefMenuKeyboard$3 = "Klávesnica";
19741  var prefMenuTranscript$3 = "Prepis";
19742  var prefTitleCaptions$3 = "Nastavenia skrytých titulkov";
19743  var prefTitleDescriptions$3 = "Nastavenia audiokomentára";
19744  var prefTitleKeyboard$3 = "Nastavenia klávesnice";
19745  var prefTitleTranscript$3 = "Nastavenia prepisu";
19746  var prefIntroDescription1$3 = "Tento prehrávač médií podporuje audiokomentár dvoma spôsobmi: ";
19747  var prefDescription1$3 = "Aktuálne video má alternatívnu verziu s audiokomentárom a textový audiokomentár oznamovaný čítačkou obrazovky.";
19748  var prefDescription2$3 = "Aktuálne video má textový audiokomentár.";
19749  var prefDescription3$3 = "Aktuálne video má alternatívnu verziu s audiokomentárom.";
19750  var prefIntroDescriptionNone$3 = "Aktuálne video nemá audiokomentár v žiadnom formáte.";
19751  var prefIntroDescription3$3 = "Pomocou nasledujúceho formulára nastavte svoje predvoľby týkajúce sa textového audiokomentára.";
19752  var prefIntroDescription4$3 = "Po uložení nastavení je možné audiokomentár zapnúť/vypnúť pomocou príslušného tlačidla.";
19753  var prefIntroKeyboard1$3 = "Prehrávač médií na tejto webovej stránke možno ovládať odkiaľkoľvek na stránke pomocou klávesových skratiek (zoznam nájdete nižšie).";
19754  var prefIntroKeyboard2$3 = "Modifikačné klávesy (Shift, Alt a Control) je možné priradiť nižšie.";
19755  var prefIntroKeyboard3$3 = "POZNÁMKA: Niektoré kombinácie klávesov môžu byť v konflikte s klávesmi používanými vaším prehliadačom a/alebo inými softvérovými aplikáciami. Vyskúšajte rôzne kombinácie modifikačných klávesov, aby ste našli tú, ktorá vám vyhovuje.";
19756  var prefHeadingKeyboard1$3 = "Modifikačné klávesy používané pre skratky";
19757  var prefHeadingKeyboard2$3 = "Aktuálne klávesové skratky";
19758  var prefHeadingDescription$3 = "Audiokomentár";
19759  var prefHeadingTextDescription$3 = "Textový audiokomentár";
19760  var prefAltKey$3 = "Alt";
19761  var prefCtrlKey$3 = "Control";
19762  var prefShiftKey$3 = "Shift";
19763  var prefNoKeyShortcuts$3 = "Zakázať klávesové skratky";
19764  var escapeKey$3 = "Escape";
19765  var escapeKeyFunction$3 = "Zatvoriť aktuálny dialóg alebo vyskakovacie menu";
19766  var prefDescPause$3 = "Automaticky pozastaviť video, keď sa spustí audiokomentár";
19767  var prefDescVisible$3 = "Zobraziť audiokomentár";
19768  var prefDescVoice$3 = "Hlas";
19769  var prefDescRate$3 = "Rýchlosť hovoreného audiokomentára";
19770  var prefCaptionRate$3 = "Rýchlosť hovorených skrytých titulkov";
19771  var prefDescPitch$3 = "Výška tónu";
19772  var prefDescPitch1$3 = "Veľmi nízka";
19773  var prefDescPitch2$3 = "Nízka";
19774  var prefDescPitch3$3 = "Predvolená";
19775  var prefDescPitch4$3 = "Vysoká";
19776  var prefDescPitch5$3 = "Veľmi vysoká";
19777  var sampleDescriptionText$3 = "Upravte nastavenia, aby ste počuli tento vzorový text.";
19778  var prefHighlight$3 = "Zvýrazniť prepis počas prehrávania médií";
19779  var prefTabbable$3 = "Povoliť ovládanie prepisu klávesnicou";
19780  var prefCaptionsFont$3 = "Písmo";
19781  var prefCaptionsColor$3 = "Farba textu";
19782  var prefCaptionsBGColor$3 = "Pozadie";
19783  var prefCaptionsSize$3 = "Veľkosť písma";
19784  var prefCaptionsOpacity$3 = "Nepriehľadnosť";
19785  var prefCaptionsStyle$3 = "Štýl";
19786  var serif$3 = "pätkové (serif)";
19787  var sans$3 = "bezpätkové (sans-serif)";
19788  var cursive$3 = "kurzíva";
19789  var fantasy$3 = "ozdobné";
19790  var monospace$3 = "stála šírka (monospace)";
19791  var white$3 = "biela";
19792  var yellow$3 = "žltá";
19793  var green$3 = "zelená";
19794  var cyan$3 = "azúrová";
19795  var blue$3 = "modrá";
19796  var magenta$3 = "fialová";
19797  var red$3 = "červená";
19798  var black$3 = "čierna";
19799  var transparent$3 = "priehľadné";
19800  var solid$3 = "nepriehľadné";
19801  var captionsStylePopOn$3 = "Pop-on";
19802  var captionsStyleRollUp$3 = "Roll-up";
19803  var prefCaptionsPosition$3 = "Pozícia";
19804  var captionsPositionOverlay$3 = "Prekrytie";
19805  var captionsPositionBelow$3 = "Pod videom";
19806  var sampleCaptionText$3 = "Vzorový text skrytých titulkov";
19807  var prefSuccess$3 = "Vaše zmeny boli uložené.";
19808  var prefNoChange$3 = "Neurobili ste žiadne zmeny.";
19809  var save$3 = "Uložiť";
19810  var cancel$3 = "Zrušiť";
19811  var dismissButton$3 = "Zatvoriť";
19812  var windowButtonLabel$3 = "Možnosti okna";
19813  var windowMove$3 = "Presunúť";
19814  var windowMoveLeft$3 = "Okno sa presunulo doľava";
19815  var windowMoveRight$3 = "Okno sa presunulo doprava";
19816  var windowMoveUp$3 = "Okno sa presunulo hore";
19817  var windowMoveDown$3 = "Okno sa presunulo dole";
19818  var windowMoveStopped$3 = "Presúvanie okna zastavené";
19819  var transcriptControls$3 = "Ovládacie prvky okna prepisu";
19820  var signControls$3 = "Ovládacie prvky okna posunkového jazyka";
19821  var windowMoveAlert$3 = "Potiahnite alebo použite šípky na presun okna; stlačením Enter zastavte";
19822  var windowResize$3 = "Zmeniť veľkosť";
19823  var windowResizeHeading$3 = "Zmeniť veľkosť okna";
19824  var closeButtonLabel$3 = "Zatvoriť";
19825  var width$3 = "Šírka";
19826  var height$3 = "Výška";
19827  var resultsSummary1$3 = "Hľadali ste:";
19828  var resultsSummary2$3 = "Nájdených %1 zodpovedajúcich položiek.";
19829  var resultsSummary3$3 = "Kliknutím na čas priradený k ľubovoľnej položke prehráte video od daného bodu.";
19830  var noResultsFound$3 = "Nenašli sa žiadne výsledky.";
19831  var searchButtonLabel$3 = "Prehrať od %1";
19832  var hour$3 = "hodina";
19833  var minute$3 = "minúta";
19834  var second$3 = "sekunda";
19835  var hours$3 = "hodiny";
19836  var minutes$3 = "minúty";
19837  var seconds$3 = "sekundy";
19838  var vtsHeading$3 = "Triedič prepisu videa";
19839  var vtsInstructions1$3 = "Použite Triedič prepisu videa na úpravu textových stôp:";
19840  var vtsInstructions2$3 = "Zmeňte poradie kapitol, audiokomentárov, titulkov a/alebo podtitulkov tak, aby sa zobrazovali v správnom poradí v automaticky generovanom prepise Able Player.";
19841  var vtsInstructions3$3 = "Upravte obsah alebo časy začiatku/konca (všetky sú priamo upraviteľné v tabuľke).";
19842  var vtsInstructions4$3 = "Pridajte nový obsah, napríklad kapitoly alebo audiokomentáre.";
19843  var vtsInstructions5$3 = "Po úprave kliknite na tlačidlo \"Uložiť zmeny\", čím vygenerujete nový obsah pre všetky príslušné časované textové súbory. Nový text možno skopírovať a vložiť do nových súborov WebVTT.";
19844  var vtsSelectLanguage$3 = "Vyberte jazyk";
19845  var vtsSave$3 = "Vygenerovať nový obsah .vtt";
19846  var vtsReturn$3 = "Vrátiť sa do editora";
19847  var vtsCancel$3 = "Ukladanie sa ruší. Všetky vykonané úpravy boli v tabuľke VTS obnovené.";
19848  var vtsRow$3 = "Riadok";
19849  var vtsKind$3 = "Typ";
19850  var vtsStart$3 = "Začiatok";
19851  var vtsEnd$3 = "Koniec";
19852  var vtsContent$3 = "Obsah";
19853  var vtsActions$3 = "Akcie";
19854  var vtsNewRow$3 = "Bol vložený nový riadok %1.";
19855  var vtsDeletedRow$3 = "Riadok %1 bol odstránený.";
19856  var vtsMovedRow$3 = "Riadok %1 bol presunutý %2 a teraz je riadkom %3.";
19857  var sk = {
19858  	__author__: __author__,
19859  	playerHeading: playerHeading$3,
19860  	audioPlayer: audioPlayer$3,
19861  	videoPlayer: videoPlayer$3,
19862  	faster: faster$3,
19863  	slower: slower$3,
19864  	play: play$3,
19865  	pause: pause$3,
19866  	restart: restart$3,
19867  	prevTrack: prevTrack$3,
19868  	nextTrack: nextTrack$3,
19869  	rewind: rewind$3,
19870  	forward: forward$3,
19871  	captions: captions$3,
19872  	showCaptions: showCaptions$3,
19873  	hideCaptions: hideCaptions$3,
19874  	captionsOff: captionsOff$3,
19875  	showTranscript: showTranscript$3,
19876  	hideTranscript: hideTranscript$3,
19877  	turnOnDescriptions: turnOnDescriptions$3,
19878  	turnOffDescriptions: turnOffDescriptions$3,
19879  	chapters: chapters$3,
19880  	language: language$3,
19881  	sign: sign$3,
19882  	showSign: showSign$3,
19883  	hideSign: hideSign$3,
19884  	seekbarLabel: seekbarLabel$3,
19885  	mute: mute$3,
19886  	unmute: unmute$3,
19887  	volume: volume$3,
19888  	volumeUpDown: volumeUpDown$3,
19889  	preferences: preferences$3,
19890  	enterFullScreen: enterFullScreen$3,
19891  	exitFullScreen: exitFullScreen$3,
19892  	speed: speed$3,
19893  	spacebar: spacebar$3,
19894  	transcriptTitle: transcriptTitle$3,
19895  	lyricsTitle: lyricsTitle$3,
19896  	autoScroll: autoScroll$3,
19897  	statusPlaying: statusPlaying$3,
19898  	statusPaused: statusPaused$3,
19899  	statusStopped: statusStopped$3,
19900  	statusBuffering: statusBuffering$3,
19901  	statusEnd: statusEnd$3,
19902  	selectedTrack: selectedTrack$3,
19903  	alertDescribedVersion: alertDescribedVersion$3,
19904  	alertNonDescribedVersion: alertNonDescribedVersion$3,
19905  	prefMenuCaptions: prefMenuCaptions$3,
19906  	prefVoicedCaptions: prefVoicedCaptions$3,
19907  	prefMenuDescriptions: prefMenuDescriptions$3,
19908  	prefMenuKeyboard: prefMenuKeyboard$3,
19909  	prefMenuTranscript: prefMenuTranscript$3,
19910  	prefTitleCaptions: prefTitleCaptions$3,
19911  	prefTitleDescriptions: prefTitleDescriptions$3,
19912  	prefTitleKeyboard: prefTitleKeyboard$3,
19913  	prefTitleTranscript: prefTitleTranscript$3,
19914  	prefIntroDescription1: prefIntroDescription1$3,
19915  	prefDescription1: prefDescription1$3,
19916  	prefDescription2: prefDescription2$3,
19917  	prefDescription3: prefDescription3$3,
19918  	prefIntroDescriptionNone: prefIntroDescriptionNone$3,
19919  	prefIntroDescription3: prefIntroDescription3$3,
19920  	prefIntroDescription4: prefIntroDescription4$3,
19921  	prefIntroKeyboard1: prefIntroKeyboard1$3,
19922  	prefIntroKeyboard2: prefIntroKeyboard2$3,
19923  	prefIntroKeyboard3: prefIntroKeyboard3$3,
19924  	prefHeadingKeyboard1: prefHeadingKeyboard1$3,
19925  	prefHeadingKeyboard2: prefHeadingKeyboard2$3,
19926  	prefHeadingDescription: prefHeadingDescription$3,
19927  	prefHeadingTextDescription: prefHeadingTextDescription$3,
19928  	prefAltKey: prefAltKey$3,
19929  	prefCtrlKey: prefCtrlKey$3,
19930  	prefShiftKey: prefShiftKey$3,
19931  	prefNoKeyShortcuts: prefNoKeyShortcuts$3,
19932  	escapeKey: escapeKey$3,
19933  	escapeKeyFunction: escapeKeyFunction$3,
19934  	prefDescPause: prefDescPause$3,
19935  	prefDescVisible: prefDescVisible$3,
19936  	prefDescVoice: prefDescVoice$3,
19937  	prefDescRate: prefDescRate$3,
19938  	prefCaptionRate: prefCaptionRate$3,
19939  	prefDescPitch: prefDescPitch$3,
19940  	prefDescPitch1: prefDescPitch1$3,
19941  	prefDescPitch2: prefDescPitch2$3,
19942  	prefDescPitch3: prefDescPitch3$3,
19943  	prefDescPitch4: prefDescPitch4$3,
19944  	prefDescPitch5: prefDescPitch5$3,
19945  	sampleDescriptionText: sampleDescriptionText$3,
19946  	prefHighlight: prefHighlight$3,
19947  	prefTabbable: prefTabbable$3,
19948  	prefCaptionsFont: prefCaptionsFont$3,
19949  	prefCaptionsColor: prefCaptionsColor$3,
19950  	prefCaptionsBGColor: prefCaptionsBGColor$3,
19951  	prefCaptionsSize: prefCaptionsSize$3,
19952  	prefCaptionsOpacity: prefCaptionsOpacity$3,
19953  	prefCaptionsStyle: prefCaptionsStyle$3,
19954  	serif: serif$3,
19955  	sans: sans$3,
19956  	cursive: cursive$3,
19957  	fantasy: fantasy$3,
19958  	monospace: monospace$3,
19959  	white: white$3,
19960  	yellow: yellow$3,
19961  	green: green$3,
19962  	cyan: cyan$3,
19963  	blue: blue$3,
19964  	magenta: magenta$3,
19965  	red: red$3,
19966  	black: black$3,
19967  	transparent: transparent$3,
19968  	solid: solid$3,
19969  	captionsStylePopOn: captionsStylePopOn$3,
19970  	captionsStyleRollUp: captionsStyleRollUp$3,
19971  	prefCaptionsPosition: prefCaptionsPosition$3,
19972  	captionsPositionOverlay: captionsPositionOverlay$3,
19973  	captionsPositionBelow: captionsPositionBelow$3,
19974  	sampleCaptionText: sampleCaptionText$3,
19975  	prefSuccess: prefSuccess$3,
19976  	prefNoChange: prefNoChange$3,
19977  	save: save$3,
19978  	cancel: cancel$3,
19979  	dismissButton: dismissButton$3,
19980  	windowButtonLabel: windowButtonLabel$3,
19981  	windowMove: windowMove$3,
19982  	windowMoveLeft: windowMoveLeft$3,
19983  	windowMoveRight: windowMoveRight$3,
19984  	windowMoveUp: windowMoveUp$3,
19985  	windowMoveDown: windowMoveDown$3,
19986  	windowMoveStopped: windowMoveStopped$3,
19987  	transcriptControls: transcriptControls$3,
19988  	signControls: signControls$3,
19989  	windowMoveAlert: windowMoveAlert$3,
19990  	windowResize: windowResize$3,
19991  	windowResizeHeading: windowResizeHeading$3,
19992  	closeButtonLabel: closeButtonLabel$3,
19993  	width: width$3,
19994  	height: height$3,
19995  	resultsSummary1: resultsSummary1$3,
19996  	resultsSummary2: resultsSummary2$3,
19997  	resultsSummary3: resultsSummary3$3,
19998  	noResultsFound: noResultsFound$3,
19999  	searchButtonLabel: searchButtonLabel$3,
20000  	hour: hour$3,
20001  	minute: minute$3,
20002  	second: second$3,
20003  	hours: hours$3,
20004  	minutes: minutes$3,
20005  	seconds: seconds$3,
20006  	vtsHeading: vtsHeading$3,
20007  	vtsInstructions1: vtsInstructions1$3,
20008  	vtsInstructions2: vtsInstructions2$3,
20009  	vtsInstructions3: vtsInstructions3$3,
20010  	vtsInstructions4: vtsInstructions4$3,
20011  	vtsInstructions5: vtsInstructions5$3,
20012  	vtsSelectLanguage: vtsSelectLanguage$3,
20013  	vtsSave: vtsSave$3,
20014  	vtsReturn: vtsReturn$3,
20015  	vtsCancel: vtsCancel$3,
20016  	vtsRow: vtsRow$3,
20017  	vtsKind: vtsKind$3,
20018  	vtsStart: vtsStart$3,
20019  	vtsEnd: vtsEnd$3,
20020  	vtsContent: vtsContent$3,
20021  	vtsActions: vtsActions$3,
20022  	vtsNewRow: vtsNewRow$3,
20023  	vtsDeletedRow: vtsDeletedRow$3,
20024  	vtsMovedRow: vtsMovedRow$3
20025  };
20026
20027  var playerHeading$2 = "Mediaspelare";
20028  var audioPlayer$2 = "Audio player";
20029  var videoPlayer$2 = "Video player";
20030  var faster$2 = "Snabbare";
20031  var slower$2 = "LÃ¥ngsammare";
20032  var play$2 = "Spela upp";
20033  var pause$2 = "Pausa";
20034  var restart$2 = "Starta om";
20035  var prevTrack$2 = "Föregående spår";
20036  var nextTrack$2 = "Nästa spår";
20037  var rewind$2 = "Spola tillbaka";
20038  var forward$2 = "Spola framåt";
20039  var captions$2 = "Undertexter";
20040  var showCaptions$2 = "Visa undertexter";
20041  var hideCaptions$2 = "Göm undertexter";
20042  var captionsOff$2 = "Stäng av undertexter";
20043  var showTranscript$2 = "Visa transkript";
20044  var hideTranscript$2 = "Göm transkript";
20045  var turnOnDescriptions$2 = "Sätt på syntolkning";
20046  var turnOffDescriptions$2 = "Stäng av syntolkning";
20047  var chapters$2 = "Kapitel";
20048  var language$2 = "Språk";
20049  var sign$2 = "Teckenspråk";
20050  var showSign$2 = "Visa teckenspråk";
20051  var hideSign$2 = "Göm teckenspråk";
20052  var seekbarLabel$2 = "tidslinje";
20053  var mute$2 = "Slå av ljud";
20054  var unmute$2 = "Slå på ljud";
20055  var volume$2 = "Volym";
20056  var volumeUpDown$2 = "Volym up ner";
20057  var preferences$2 = "Preferenser";
20058  var enterFullScreen$2 = "Visa i fullskärmsläge";
20059  var exitFullScreen$2 = "Gå ur fullskärmsläge";
20060  var speed$2 = "Hastighet";
20061  var spacebar$2 = "mellanslag";
20062  var transcriptTitle$2 = "Transkript";
20063  var lyricsTitle$2 = "Lyrik";
20064  var autoScroll$2 = "Auto-scrolla";
20065  var statusPlaying$2 = "Spelar upp";
20066  var statusPaused$2 = "Pausad";
20067  var statusStopped$2 = "Stoppad";
20068  var statusBuffering$2 = "Buffrar";
20069  var statusEnd$2 = "Slut på spåret";
20070  var selectedTrack$2 = "Valt spår";
20071  var alertDescribedVersion$2 = "Använder syntolkad version av denna video";
20072  var alertNonDescribedVersion$2 = "Använder ej syntolkad version av denna video";
20073  var prefMenuCaptions$2 = "Undertexter";
20074  var prefVoicedCaptions$2 = "Spoken Captions";
20075  var prefMenuDescriptions$2 = "Syntolkning";
20076  var prefMenuKeyboard$2 = "Tangentbord";
20077  var prefMenuTranscript$2 = "Transkript";
20078  var prefTitleCaptions$2 = "Undertextprefenser";
20079  var prefTitleDescriptions$2 = "Syntolkningspreferenser";
20080  var prefTitleKeyboard$2 = "Tangentbordspreferenser";
20081  var prefTitleTranscript$2 = "Transkriptpreferenser";
20082  var prefIntroDescription1$2 = "Denna mediaspelare stöder syntolkning på två sätt: ";
20083  var prefDescription1$2 = "Följande video har en alternativ syntolkad version, textbaserad syntolkning.";
20084  var prefDescription2$2 = "Följande video har alternativ syntolkad version av videon.";
20085  var prefDescription3$2 = "Följande video har textbaserad syntolkning, uppläst av skärmläsare.";
20086  var prefIntroDescriptionNone$2 = "Nuvarande video har ingen syntolkning i något format.";
20087  var prefIntroDescription3$2 = "Använd följande formulär för att ställa in dina prefenser gällande textbaserad syntolkning.";
20088  var prefIntroDescription4$2 = "Efter du sparar dina prefenser, kan syntolkning växlas av och på med syntolkningsknappen.";
20089  var prefIntroKeyboard1$2 = "Mediaspelaren på denna webbsida kan styras från varsomhelst på sidan med hjälp av tangentbordsgenvägar (se nedan för en lista).";
20090  var prefIntroKeyboard2$2 = "Metatangenter (Shift, Alt, och Ctrl) kan tilldelas nedan.";
20091  var prefIntroKeyboard3$2 = "NOTERA: Vissa tangentkombinationer kan vara i konflikt med din webbläsare eller andra mjukvarors inställningar. Prova olika kombinationer av metatangeter för att hitta en som fungerar för dig.";
20092  var prefHeadingKeyboard1$2 = "Metatangenter använda till genvägar";
20093  var prefHeadingKeyboard2$2 = "Nuvarande tangentbordsgenvägar";
20094  var prefHeadingDescription$2 = "Syntolkning";
20095  var prefHeadingTextDescription$2 = "Textbaserad syntolkning";
20096  var prefAltKey$2 = "Alt";
20097  var prefCtrlKey$2 = "Ctrl";
20098  var prefShiftKey$2 = "Shift";
20099  var prefNoKeyShortcuts$2 = "Disable keyboard shortcuts";
20100  var escapeKey$2 = "Escape";
20101  var escapeKeyFunction$2 = "Stäng nuvarande dialog eller popupmeny";
20102  var prefDescPause$2 = "Pausa automatiskt video när syntolkning startar";
20103  var prefDescVisible$2 = "Synliggör syntolkning";
20104  var prefDescVoice$2 = "Röst";
20105  var prefDescRate$2 = "Spoken Description Rate";
20106  var prefCaptionRate$2 = "Spoken Caption Rate";
20107  var prefDescPitch$2 = "Tonhöjd";
20108  var prefDescPitch1$2 = "Mycket lågt";
20109  var prefDescPitch2$2 = "LÃ¥g";
20110  var prefDescPitch3$2 = "Normal";
20111  var prefDescPitch4$2 = "Högt";
20112  var prefDescPitch5$2 = "Mycket högt";
20113  var sampleDescriptionText$2 = "Justera inställningarna för att höra denna exempeltext.";
20114  var prefHighlight$2 = "Markera det som läses upp i transkriptet";
20115  var prefTabbable$2 = "Tangentbordsaktiverat transkript";
20116  var prefCaptionsFont$2 = "Typsnitt";
20117  var prefCaptionsColor$2 = "Textfärg";
20118  var prefCaptionsBGColor$2 = "Bakgrund";
20119  var prefCaptionsSize$2 = "Textstorlek";
20120  var prefCaptionsOpacity$2 = "Ogenomskinlighet";
20121  var prefCaptionsStyle$2 = "Stil";
20122  var serif$2 = "serif";
20123  var sans$2 = "sans-serif";
20124  var cursive$2 = "kursiv";
20125  var fantasy$2 = "fantasi";
20126  var monospace$2 = "monospace";
20127  var white$2 = "vit";
20128  var yellow$2 = "gul";
20129  var green$2 = "grön";
20130  var cyan$2 = "ljusblå";
20131  var blue$2 = "blå";
20132  var magenta$2 = "lila";
20133  var red$2 = "röd";
20134  var black$2 = "svart";
20135  var transparent$2 = "genomskinligt";
20136  var solid$2 = "massiv";
20137  var captionsStylePopOn$2 = "Dyker upp";
20138  var captionsStyleRollUp$2 = "Rullar upp";
20139  var prefCaptionsPosition$2 = "Position";
20140  var captionsPositionOverlay$2 = "Täck över videon";
20141  var captionsPositionBelow$2 = "Under videon";
20142  var sampleCaptionText$2 = "Exempel på undertext";
20143  var prefSuccess$2 = "Dina ändringar har blivit sparade.";
20144  var prefNoChange$2 = "Du gjorde inga ändringar.";
20145  var save$2 = "Spara";
20146  var cancel$2 = "Avbryt";
20147  var dismissButton$2 = "Dismiss";
20148  var windowButtonLabel$2 = "Fönsteralternativ";
20149  var windowMove$2 = "Flytta";
20150  var windowMoveLeft$2 = "Window moved left";
20151  var windowMoveRight$2 = "Window moved right";
20152  var windowMoveUp$2 = "Window moved up";
20153  var windowMoveDown$2 = "Window moved down";
20154  var windowMoveStopped$2 = "Window move stopped";
20155  var transcriptControls$2 = "Transcript Window Controls";
20156  var signControls$2 = "Sign Language Window Controls";
20157  var windowMoveAlert$2 = "Drag eller använd piltangenter för att flytta fönstret; Enter för att sluta";
20158  var windowResize$2 = "Ändra storlek";
20159  var windowResizeHeading$2 = "Ändra fönsterstorlek";
20160  var closeButtonLabel$2 = "Stäng";
20161  var width$2 = "Bredd";
20162  var height$2 = "Höjd";
20163  var resultsSummary1$2 = "Du sökte efter:";
20164  var resultsSummary2$2 = "Hittade %1 träffar.";
20165  var resultsSummary3$2 = "Klicka på tiden för en punkt för att spela video från den tiden.";
20166  var noResultsFound$2 = "Inga träffar hittade.";
20167  var searchButtonLabel$2 = "Spela från %1";
20168  var hour$2 = "timme";
20169  var minute$2 = "minut";
20170  var second$2 = "sekund";
20171  var hours$2 = "timmar";
20172  var minutes$2 = "minuter";
20173  var seconds$2 = "sekunder";
20174  var vtsHeading$2 = "Video Transcript Sorter";
20175  var vtsInstructions1$2 = "Use the Video Transcript Sorter to modify text tracks:";
20176  var vtsInstructions2$2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
20177  var vtsInstructions3$2 = "Modify content or start/end times (all are directly editable within the table).";
20178  var vtsInstructions4$2 = "Add new content, such as chapters or descriptions.";
20179  var vtsInstructions5$2 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
20180  var vtsSelectLanguage$2 = "Select a language";
20181  var vtsSave$2 = "Generate new .vtt content";
20182  var vtsReturn$2 = "Return to Editor";
20183  var vtsCancel$2 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
20184  var vtsRow$2 = "Row";
20185  var vtsKind$2 = "Kind";
20186  var vtsStart$2 = "Start";
20187  var vtsEnd$2 = "End";
20188  var vtsContent$2 = "Content";
20189  var vtsActions$2 = "Actions";
20190  var vtsNewRow$2 = "A new row %1 has been inserted.";
20191  var vtsDeletedRow$2 = "Row %1 has been deleted.";
20192  var vtsMovedRow$2 = "Row %1 has been moved %2 and is now Row %3.";
20193  var sv = {
20194  	playerHeading: playerHeading$2,
20195  	audioPlayer: audioPlayer$2,
20196  	videoPlayer: videoPlayer$2,
20197  	faster: faster$2,
20198  	slower: slower$2,
20199  	play: play$2,
20200  	pause: pause$2,
20201  	restart: restart$2,
20202  	prevTrack: prevTrack$2,
20203  	nextTrack: nextTrack$2,
20204  	rewind: rewind$2,
20205  	forward: forward$2,
20206  	captions: captions$2,
20207  	showCaptions: showCaptions$2,
20208  	hideCaptions: hideCaptions$2,
20209  	captionsOff: captionsOff$2,
20210  	showTranscript: showTranscript$2,
20211  	hideTranscript: hideTranscript$2,
20212  	turnOnDescriptions: turnOnDescriptions$2,
20213  	turnOffDescriptions: turnOffDescriptions$2,
20214  	chapters: chapters$2,
20215  	language: language$2,
20216  	sign: sign$2,
20217  	showSign: showSign$2,
20218  	hideSign: hideSign$2,
20219  	seekbarLabel: seekbarLabel$2,
20220  	mute: mute$2,
20221  	unmute: unmute$2,
20222  	volume: volume$2,
20223  	volumeUpDown: volumeUpDown$2,
20224  	preferences: preferences$2,
20225  	enterFullScreen: enterFullScreen$2,
20226  	exitFullScreen: exitFullScreen$2,
20227  	speed: speed$2,
20228  	spacebar: spacebar$2,
20229  	transcriptTitle: transcriptTitle$2,
20230  	lyricsTitle: lyricsTitle$2,
20231  	autoScroll: autoScroll$2,
20232  	statusPlaying: statusPlaying$2,
20233  	statusPaused: statusPaused$2,
20234  	statusStopped: statusStopped$2,
20235  	statusBuffering: statusBuffering$2,
20236  	statusEnd: statusEnd$2,
20237  	selectedTrack: selectedTrack$2,
20238  	alertDescribedVersion: alertDescribedVersion$2,
20239  	alertNonDescribedVersion: alertNonDescribedVersion$2,
20240  	prefMenuCaptions: prefMenuCaptions$2,
20241  	prefVoicedCaptions: prefVoicedCaptions$2,
20242  	prefMenuDescriptions: prefMenuDescriptions$2,
20243  	prefMenuKeyboard: prefMenuKeyboard$2,
20244  	prefMenuTranscript: prefMenuTranscript$2,
20245  	prefTitleCaptions: prefTitleCaptions$2,
20246  	prefTitleDescriptions: prefTitleDescriptions$2,
20247  	prefTitleKeyboard: prefTitleKeyboard$2,
20248  	prefTitleTranscript: prefTitleTranscript$2,
20249  	prefIntroDescription1: prefIntroDescription1$2,
20250  	prefDescription1: prefDescription1$2,
20251  	prefDescription2: prefDescription2$2,
20252  	prefDescription3: prefDescription3$2,
20253  	prefIntroDescriptionNone: prefIntroDescriptionNone$2,
20254  	prefIntroDescription3: prefIntroDescription3$2,
20255  	prefIntroDescription4: prefIntroDescription4$2,
20256  	prefIntroKeyboard1: prefIntroKeyboard1$2,
20257  	prefIntroKeyboard2: prefIntroKeyboard2$2,
20258  	prefIntroKeyboard3: prefIntroKeyboard3$2,
20259  	prefHeadingKeyboard1: prefHeadingKeyboard1$2,
20260  	prefHeadingKeyboard2: prefHeadingKeyboard2$2,
20261  	prefHeadingDescription: prefHeadingDescription$2,
20262  	prefHeadingTextDescription: prefHeadingTextDescription$2,
20263  	prefAltKey: prefAltKey$2,
20264  	prefCtrlKey: prefCtrlKey$2,
20265  	prefShiftKey: prefShiftKey$2,
20266  	prefNoKeyShortcuts: prefNoKeyShortcuts$2,
20267  	escapeKey: escapeKey$2,
20268  	escapeKeyFunction: escapeKeyFunction$2,
20269  	prefDescPause: prefDescPause$2,
20270  	prefDescVisible: prefDescVisible$2,
20271  	prefDescVoice: prefDescVoice$2,
20272  	prefDescRate: prefDescRate$2,
20273  	prefCaptionRate: prefCaptionRate$2,
20274  	prefDescPitch: prefDescPitch$2,
20275  	prefDescPitch1: prefDescPitch1$2,
20276  	prefDescPitch2: prefDescPitch2$2,
20277  	prefDescPitch3: prefDescPitch3$2,
20278  	prefDescPitch4: prefDescPitch4$2,
20279  	prefDescPitch5: prefDescPitch5$2,
20280  	sampleDescriptionText: sampleDescriptionText$2,
20281  	prefHighlight: prefHighlight$2,
20282  	prefTabbable: prefTabbable$2,
20283  	prefCaptionsFont: prefCaptionsFont$2,
20284  	prefCaptionsColor: prefCaptionsColor$2,
20285  	prefCaptionsBGColor: prefCaptionsBGColor$2,
20286  	prefCaptionsSize: prefCaptionsSize$2,
20287  	prefCaptionsOpacity: prefCaptionsOpacity$2,
20288  	prefCaptionsStyle: prefCaptionsStyle$2,
20289  	serif: serif$2,
20290  	sans: sans$2,
20291  	cursive: cursive$2,
20292  	fantasy: fantasy$2,
20293  	monospace: monospace$2,
20294  	white: white$2,
20295  	yellow: yellow$2,
20296  	green: green$2,
20297  	cyan: cyan$2,
20298  	blue: blue$2,
20299  	magenta: magenta$2,
20300  	red: red$2,
20301  	black: black$2,
20302  	transparent: transparent$2,
20303  	solid: solid$2,
20304  	captionsStylePopOn: captionsStylePopOn$2,
20305  	captionsStyleRollUp: captionsStyleRollUp$2,
20306  	prefCaptionsPosition: prefCaptionsPosition$2,
20307  	captionsPositionOverlay: captionsPositionOverlay$2,
20308  	captionsPositionBelow: captionsPositionBelow$2,
20309  	sampleCaptionText: sampleCaptionText$2,
20310  	prefSuccess: prefSuccess$2,
20311  	prefNoChange: prefNoChange$2,
20312  	save: save$2,
20313  	cancel: cancel$2,
20314  	dismissButton: dismissButton$2,
20315  	windowButtonLabel: windowButtonLabel$2,
20316  	windowMove: windowMove$2,
20317  	windowMoveLeft: windowMoveLeft$2,
20318  	windowMoveRight: windowMoveRight$2,
20319  	windowMoveUp: windowMoveUp$2,
20320  	windowMoveDown: windowMoveDown$2,
20321  	windowMoveStopped: windowMoveStopped$2,
20322  	transcriptControls: transcriptControls$2,
20323  	signControls: signControls$2,
20324  	windowMoveAlert: windowMoveAlert$2,
20325  	windowResize: windowResize$2,
20326  	windowResizeHeading: windowResizeHeading$2,
20327  	closeButtonLabel: closeButtonLabel$2,
20328  	width: width$2,
20329  	height: height$2,
20330  	resultsSummary1: resultsSummary1$2,
20331  	resultsSummary2: resultsSummary2$2,
20332  	resultsSummary3: resultsSummary3$2,
20333  	noResultsFound: noResultsFound$2,
20334  	searchButtonLabel: searchButtonLabel$2,
20335  	hour: hour$2,
20336  	minute: minute$2,
20337  	second: second$2,
20338  	hours: hours$2,
20339  	minutes: minutes$2,
20340  	seconds: seconds$2,
20341  	vtsHeading: vtsHeading$2,
20342  	vtsInstructions1: vtsInstructions1$2,
20343  	vtsInstructions2: vtsInstructions2$2,
20344  	vtsInstructions3: vtsInstructions3$2,
20345  	vtsInstructions4: vtsInstructions4$2,
20346  	vtsInstructions5: vtsInstructions5$2,
20347  	vtsSelectLanguage: vtsSelectLanguage$2,
20348  	vtsSave: vtsSave$2,
20349  	vtsReturn: vtsReturn$2,
20350  	vtsCancel: vtsCancel$2,
20351  	vtsRow: vtsRow$2,
20352  	vtsKind: vtsKind$2,
20353  	vtsStart: vtsStart$2,
20354  	vtsEnd: vtsEnd$2,
20355  	vtsContent: vtsContent$2,
20356  	vtsActions: vtsActions$2,
20357  	vtsNewRow: vtsNewRow$2,
20358  	vtsDeletedRow: vtsDeletedRow$2,
20359  	vtsMovedRow: vtsMovedRow$2
20360  };
20361
20362  var playerHeading$1 = "Medya Oynatıcı";
20363  var audioPlayer$1 = "Audio player";
20364  var videoPlayer$1 = "Video player";
20365  var faster$1 = "Hızlandır";
20366  var slower$1 = "Yavaşlat";
20367  var play$1 = "Oynat";
20368  var pause$1 = "Duraklat";
20369  var restart$1 = "Yeniden Oynat";
20370  var prevTrack$1 = "Önceki Parça";
20371  var nextTrack$1 = "Gelecek Parça";
20372  var rewind$1 = "Geri Sar";
20373  var forward$1 = "İleri Sar";
20374  var captions$1 = "Altyazılar";
20375  var showCaptions$1 = "Altyazıları Göster";
20376  var hideCaptions$1 = "Altyazıları Gizle";
20377  var captionsOff$1 = "Altyazıkarı Kapat";
20378  var showTranscript$1 = "Belgeyi Göster";
20379  var hideTranscript$1 = "HBelgeyi Gizle";
20380  var turnOnDescriptions$1 = "Açıklamaları Aç";
20381  var turnOffDescriptions$1 = "Açıklamaları Kapa";
20382  var chapters$1 = "Bölümler";
20383  var language$1 = "Dil";
20384  var sign$1 = "İşaret Dili";
20385  var showSign$1 = "İşaret Dilini Göster";
20386  var hideSign$1 = "İşaret Dilini Gizle";
20387  var seekbarLabel$1 = "Zaman Cetveli";
20388  var mute$1 = "Sessize Al";
20389  var unmute$1 = "Sesi Aç";
20390  var volume$1 = "Ses Ayarı";
20391  var volumeUpDown$1 = "Sesi yükselt veya alçalt";
20392  var preferences$1 = "Seçenekler";
20393  var enterFullScreen$1 = "Tam Ekranı Etkinleştir";
20394  var exitFullScreen$1 = "Tam Ekranı Kapa";
20395  var speed$1 = "Hız";
20396  var spacebar$1 = "boşluk tuşu";
20397  var transcriptTitle$1 = "Belge";
20398  var lyricsTitle$1 = "Sözler";
20399  var autoScroll$1 = "Otomatik Kaydırma";
20400  var statusPlaying$1 = "Yürütülüyor";
20401  var statusPaused$1 = "Duraklat";
20402  var statusStopped$1 = "Durdur";
20403  var statusBuffering$1 = "Arabelleğe Alınıyor";
20404  var statusEnd$1 = "Parça sonlandı";
20405  var selectedTrack$1 = "Seçilen Parça";
20406  var alertDescribedVersion$1 = "Tanımlı video versiyonu için ses kullanılıyor";
20407  var alertNonDescribedVersion$1 = "Tanımlanmamış video versiyonu için kullanılıyor";
20408  var prefMenuCaptions$1 = "Altyazılar";
20409  var prefVoicedCaptions$1 = "Spoken Captions";
20410  var prefMenuDescriptions$1 = "Açıklamalar";
20411  var prefMenuKeyboard$1 = "Klavye";
20412  var prefMenuTranscript$1 = "Belgeler";
20413  var prefTitleCaptions$1 = "Altyazı Seçenekleri";
20414  var prefTitleDescriptions$1 = "Ses Açıklama Ayarları";
20415  var prefTitleKeyboard$1 = "Klavye Tercihleri";
20416  var prefTitleTranscript$1 = "Belge Tercihleri";
20417  var prefIntroDescription1$1 = "Bu video oynatıcı iki farklı ses açıklaması destekler: ";
20418  var prefDescription1$1 = "Şu anki video'da mevcut alternatif tanımlı versiyon, metin tabanlı açıklama.";
20419  var prefDescription2$1 = "Şu anki video'da mevcut Video versiyonu için tanımlı alternatifler.";
20420  var prefDescription3$1 = "Şu anki video'da mevcut metin tabanlı açıklama, ekran okuyucusu tarafından duyurulan.";
20421  var prefIntroDescriptionNone$1 = "Şu anki video'da her iki format için de ses açıklaması mevcut değil.";
20422  var prefIntroDescription3$1 = "Ses açıklamalarını tercihlerinle ilişkilendirmek için belirtilen biçimi kullan.";
20423  var prefIntroDescription4$1 = "Tercihlerini kaydettikten sonra, ses açıklamasını Açıklama butonu aracılığıyla açıp kapatabilirsin.";
20424  var prefIntroKeyboard1$1 = "Bu web sayfasındaki medya oynatıcıyı, klavye kısayolları yardımıyla, sayfanın herhangi bir kısmından da yönetebilirsin (Liste için Aşağıyı İncele).";
20425  var prefIntroKeyboard2$1 = "Niteleme Tuşları (Shift, Alt, ve Control) aşağınan atanabilir.";
20426  var prefIntroKeyboard3$1 = "NOT: Bazı tuş kombinasyonları, tarayıcında veya farklı bir programda kullandığın tuşlarla çakışabilir. Uygun niteleme tuşlarını oluşturmak için farklı tuş kombinasyonları kullanmayı dene.";
20427  var prefHeadingKeyboard1$1 = "Niteleme Tuşları kısayollar için kullanılır";
20428  var prefHeadingKeyboard2$1 = "Şu anki klavye kısayolları";
20429  var prefHeadingDescription$1 = "Ses açıklaması";
20430  var prefHeadingTextDescription$1 = "Metin tabanlı ses açıklaması";
20431  var prefAltKey$1 = "Alt";
20432  var prefCtrlKey$1 = "Control";
20433  var prefShiftKey$1 = "Shift";
20434  var prefNoKeyShortcuts$1 = "Disable keyboard shortcuts";
20435  var escapeKey$1 = "Escape";
20436  var escapeKeyFunction$1 = "Şu anki diyalogu veya açılır menüyü kapa";
20437  var prefDescPause$1 = "Açıklama başladığında video'yu otomatik olarak durdur";
20438  var prefDescVisible$1 = "Açıklamayı görünür yap";
20439  var prefDescVoice$1 = "Voice";
20440  var prefDescRate$1 = "Spoken Description Rate";
20441  var prefCaptionRate$1 = "Spoken Caption Rate";
20442  var prefDescPitch$1 = "Pitch";
20443  var prefDescPitch1$1 = "Very low";
20444  var prefDescPitch2$1 = "Low";
20445  var prefDescPitch3$1 = "Default";
20446  var prefDescPitch4$1 = "High";
20447  var prefDescPitch5$1 = "Very high";
20448  var sampleDescriptionText$1 = "Adjust settings to hear this sample text.";
20449  var prefHighlight$1 = "Medya'daki belgeyi öne çıkar";
20450  var prefTabbable$1 = "Klavye ile aktif edilen belge";
20451  var prefCaptionsFont$1 = "Yazı Tipi";
20452  var prefCaptionsColor$1 = "Metin Rengi";
20453  var prefCaptionsBGColor$1 = "Arkaplan";
20454  var prefCaptionsSize$1 = "Yazı Boyutu";
20455  var prefCaptionsOpacity$1 = "Opasite";
20456  var prefCaptionsStyle$1 = "Stil";
20457  var serif$1 = "serif";
20458  var sans$1 = "sans-serif";
20459  var cursive$1 = "el yazısı";
20460  var fantasy$1 = "fantezi";
20461  var monospace$1 = "tek hacimli";
20462  var white$1 = "beyaz";
20463  var yellow$1 = "sarı";
20464  var green$1 = "yeşik";
20465  var cyan$1 = "açık mavi";
20466  var blue$1 = "mavi";
20467  var magenta$1 = "mor";
20468  var red$1 = "kırmızı";
20469  var black$1 = "siyah";
20470  var transparent$1 = "transparan";
20471  var solid$1 = "düz renk";
20472  var captionsStylePopOn$1 = "Pop-on";
20473  var captionsStyleRollUp$1 = "Roll-up";
20474  var prefCaptionsPosition$1 = "Pozisyon";
20475  var captionsPositionOverlay$1 = "Kaplama";
20476  var captionsPositionBelow$1 = "Video'nun altında";
20477  var sampleCaptionText$1 = "Örnek altyaı metni";
20478  var prefSuccess$1 = "Değişikliklerin kaydedildi.";
20479  var prefNoChange$1 = "Hiçbir değişiklik yapmadın.";
20480  var save$1 = "Kaydet";
20481  var cancel$1 = "İptal Et";
20482  var dismissButton$1 = "Dismiss";
20483  var windowButtonLabel$1 = "Pencere Seçenekleri";
20484  var windowMove$1 = "Hareket";
20485  var windowMoveLeft$1 = "Window moved left";
20486  var windowMoveRight$1 = "Window moved right";
20487  var windowMoveUp$1 = "Window moved up";
20488  var windowMoveDown$1 = "Window moved down";
20489  var windowMoveStopped$1 = "Window move stopped";
20490  var transcriptControls$1 = "Transcript Window Controls";
20491  var signControls$1 = "Sign Language Window Controls";
20492  var windowMoveAlert$1 = "Pencereyi hareket ettirmek için mouse'unu sürü veya ok tuşlarını kullan; Durdurmak için Enter'a bas";
20493  var windowResize$1 = "Yeniden Boyutlandır";
20494  var windowResizeHeading$1 = "Pencereyi yeniden Boyutlandır";
20495  var closeButtonLabel$1 = "Kapat";
20496  var width$1 = "En";
20497  var height$1 = "Boy";
20498  var resultsSummary1$1 = "You searched for:";
20499  var resultsSummary2$1 = "Found %1 matching items.";
20500  var resultsSummary3$1 = "Click the time associated with any item to play the video from that point.";
20501  var noResultsFound$1 = "No results found.";
20502  var searchButtonLabel$1 = "Play at %1";
20503  var hour$1 = "hour";
20504  var minute$1 = "minute";
20505  var second$1 = "second";
20506  var hours$1 = "hours";
20507  var minutes$1 = "minutes";
20508  var seconds$1 = "seconds";
20509  var vtsHeading$1 = "Video Transcript Sorter";
20510  var vtsInstructions1$1 = "Use the Video Transcript Sorter to modify text tracks:";
20511  var vtsInstructions2$1 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
20512  var vtsInstructions3$1 = "Modify content or start/end times (all are directly editable within the table).";
20513  var vtsInstructions4$1 = "Add new content, such as chapters or descriptions.";
20514  var vtsInstructions5$1 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
20515  var vtsSelectLanguage$1 = "Select a language";
20516  var vtsSave$1 = "Generate new .vtt content";
20517  var vtsReturn$1 = "Return to Editor";
20518  var vtsCancel$1 = "Cancelling saving. Any edits you made have been restored in the VTS table.";
20519  var vtsRow$1 = "Row";
20520  var vtsKind$1 = "Kind";
20521  var vtsStart$1 = "Start";
20522  var vtsEnd$1 = "End";
20523  var vtsContent$1 = "Content";
20524  var vtsActions$1 = "Actions";
20525  var vtsNewRow$1 = "A new row %1 has been inserted.";
20526  var vtsDeletedRow$1 = "Row %1 has been deleted.";
20527  var vtsMovedRow$1 = "Row %1 has been moved %2 and is now Row %3.";
20528  var tr = {
20529  	playerHeading: playerHeading$1,
20530  	audioPlayer: audioPlayer$1,
20531  	videoPlayer: videoPlayer$1,
20532  	faster: faster$1,
20533  	slower: slower$1,
20534  	play: play$1,
20535  	pause: pause$1,
20536  	restart: restart$1,
20537  	prevTrack: prevTrack$1,
20538  	nextTrack: nextTrack$1,
20539  	rewind: rewind$1,
20540  	forward: forward$1,
20541  	captions: captions$1,
20542  	showCaptions: showCaptions$1,
20543  	hideCaptions: hideCaptions$1,
20544  	captionsOff: captionsOff$1,
20545  	showTranscript: showTranscript$1,
20546  	hideTranscript: hideTranscript$1,
20547  	turnOnDescriptions: turnOnDescriptions$1,
20548  	turnOffDescriptions: turnOffDescriptions$1,
20549  	chapters: chapters$1,
20550  	language: language$1,
20551  	sign: sign$1,
20552  	showSign: showSign$1,
20553  	hideSign: hideSign$1,
20554  	seekbarLabel: seekbarLabel$1,
20555  	mute: mute$1,
20556  	unmute: unmute$1,
20557  	volume: volume$1,
20558  	volumeUpDown: volumeUpDown$1,
20559  	preferences: preferences$1,
20560  	enterFullScreen: enterFullScreen$1,
20561  	exitFullScreen: exitFullScreen$1,
20562  	speed: speed$1,
20563  	spacebar: spacebar$1,
20564  	transcriptTitle: transcriptTitle$1,
20565  	lyricsTitle: lyricsTitle$1,
20566  	autoScroll: autoScroll$1,
20567  	statusPlaying: statusPlaying$1,
20568  	statusPaused: statusPaused$1,
20569  	statusStopped: statusStopped$1,
20570  	statusBuffering: statusBuffering$1,
20571  	statusEnd: statusEnd$1,
20572  	selectedTrack: selectedTrack$1,
20573  	alertDescribedVersion: alertDescribedVersion$1,
20574  	alertNonDescribedVersion: alertNonDescribedVersion$1,
20575  	prefMenuCaptions: prefMenuCaptions$1,
20576  	prefVoicedCaptions: prefVoicedCaptions$1,
20577  	prefMenuDescriptions: prefMenuDescriptions$1,
20578  	prefMenuKeyboard: prefMenuKeyboard$1,
20579  	prefMenuTranscript: prefMenuTranscript$1,
20580  	prefTitleCaptions: prefTitleCaptions$1,
20581  	prefTitleDescriptions: prefTitleDescriptions$1,
20582  	prefTitleKeyboard: prefTitleKeyboard$1,
20583  	prefTitleTranscript: prefTitleTranscript$1,
20584  	prefIntroDescription1: prefIntroDescription1$1,
20585  	prefDescription1: prefDescription1$1,
20586  	prefDescription2: prefDescription2$1,
20587  	prefDescription3: prefDescription3$1,
20588  	prefIntroDescriptionNone: prefIntroDescriptionNone$1,
20589  	prefIntroDescription3: prefIntroDescription3$1,
20590  	prefIntroDescription4: prefIntroDescription4$1,
20591  	prefIntroKeyboard1: prefIntroKeyboard1$1,
20592  	prefIntroKeyboard2: prefIntroKeyboard2$1,
20593  	prefIntroKeyboard3: prefIntroKeyboard3$1,
20594  	prefHeadingKeyboard1: prefHeadingKeyboard1$1,
20595  	prefHeadingKeyboard2: prefHeadingKeyboard2$1,
20596  	prefHeadingDescription: prefHeadingDescription$1,
20597  	prefHeadingTextDescription: prefHeadingTextDescription$1,
20598  	prefAltKey: prefAltKey$1,
20599  	prefCtrlKey: prefCtrlKey$1,
20600  	prefShiftKey: prefShiftKey$1,
20601  	prefNoKeyShortcuts: prefNoKeyShortcuts$1,
20602  	escapeKey: escapeKey$1,
20603  	escapeKeyFunction: escapeKeyFunction$1,
20604  	prefDescPause: prefDescPause$1,
20605  	prefDescVisible: prefDescVisible$1,
20606  	prefDescVoice: prefDescVoice$1,
20607  	prefDescRate: prefDescRate$1,
20608  	prefCaptionRate: prefCaptionRate$1,
20609  	prefDescPitch: prefDescPitch$1,
20610  	prefDescPitch1: prefDescPitch1$1,
20611  	prefDescPitch2: prefDescPitch2$1,
20612  	prefDescPitch3: prefDescPitch3$1,
20613  	prefDescPitch4: prefDescPitch4$1,
20614  	prefDescPitch5: prefDescPitch5$1,
20615  	sampleDescriptionText: sampleDescriptionText$1,
20616  	prefHighlight: prefHighlight$1,
20617  	prefTabbable: prefTabbable$1,
20618  	prefCaptionsFont: prefCaptionsFont$1,
20619  	prefCaptionsColor: prefCaptionsColor$1,
20620  	prefCaptionsBGColor: prefCaptionsBGColor$1,
20621  	prefCaptionsSize: prefCaptionsSize$1,
20622  	prefCaptionsOpacity: prefCaptionsOpacity$1,
20623  	prefCaptionsStyle: prefCaptionsStyle$1,
20624  	serif: serif$1,
20625  	sans: sans$1,
20626  	cursive: cursive$1,
20627  	fantasy: fantasy$1,
20628  	monospace: monospace$1,
20629  	white: white$1,
20630  	yellow: yellow$1,
20631  	green: green$1,
20632  	cyan: cyan$1,
20633  	blue: blue$1,
20634  	magenta: magenta$1,
20635  	red: red$1,
20636  	black: black$1,
20637  	transparent: transparent$1,
20638  	solid: solid$1,
20639  	captionsStylePopOn: captionsStylePopOn$1,
20640  	captionsStyleRollUp: captionsStyleRollUp$1,
20641  	prefCaptionsPosition: prefCaptionsPosition$1,
20642  	captionsPositionOverlay: captionsPositionOverlay$1,
20643  	captionsPositionBelow: captionsPositionBelow$1,
20644  	sampleCaptionText: sampleCaptionText$1,
20645  	prefSuccess: prefSuccess$1,
20646  	prefNoChange: prefNoChange$1,
20647  	save: save$1,
20648  	cancel: cancel$1,
20649  	dismissButton: dismissButton$1,
20650  	windowButtonLabel: windowButtonLabel$1,
20651  	windowMove: windowMove$1,
20652  	windowMoveLeft: windowMoveLeft$1,
20653  	windowMoveRight: windowMoveRight$1,
20654  	windowMoveUp: windowMoveUp$1,
20655  	windowMoveDown: windowMoveDown$1,
20656  	windowMoveStopped: windowMoveStopped$1,
20657  	transcriptControls: transcriptControls$1,
20658  	signControls: signControls$1,
20659  	windowMoveAlert: windowMoveAlert$1,
20660  	windowResize: windowResize$1,
20661  	windowResizeHeading: windowResizeHeading$1,
20662  	closeButtonLabel: closeButtonLabel$1,
20663  	width: width$1,
20664  	height: height$1,
20665  	resultsSummary1: resultsSummary1$1,
20666  	resultsSummary2: resultsSummary2$1,
20667  	resultsSummary3: resultsSummary3$1,
20668  	noResultsFound: noResultsFound$1,
20669  	searchButtonLabel: searchButtonLabel$1,
20670  	hour: hour$1,
20671  	minute: minute$1,
20672  	second: second$1,
20673  	hours: hours$1,
20674  	minutes: minutes$1,
20675  	seconds: seconds$1,
20676  	vtsHeading: vtsHeading$1,
20677  	vtsInstructions1: vtsInstructions1$1,
20678  	vtsInstructions2: vtsInstructions2$1,
20679  	vtsInstructions3: vtsInstructions3$1,
20680  	vtsInstructions4: vtsInstructions4$1,
20681  	vtsInstructions5: vtsInstructions5$1,
20682  	vtsSelectLanguage: vtsSelectLanguage$1,
20683  	vtsSave: vtsSave$1,
20684  	vtsReturn: vtsReturn$1,
20685  	vtsCancel: vtsCancel$1,
20686  	vtsRow: vtsRow$1,
20687  	vtsKind: vtsKind$1,
20688  	vtsStart: vtsStart$1,
20689  	vtsEnd: vtsEnd$1,
20690  	vtsContent: vtsContent$1,
20691  	vtsActions: vtsActions$1,
20692  	vtsNewRow: vtsNewRow$1,
20693  	vtsDeletedRow: vtsDeletedRow$1,
20694  	vtsMovedRow: vtsMovedRow$1
20695  };
20696
20697  var playerHeading = "媒體播放器";
20698  var audioPlayer = "Audio player";
20699  var videoPlayer = "Video player";
20700  var faster = "加快";
20701  var slower = "減慢";
20702  var play = "播放";
20703  var pause = "暫停";
20704  var restart = "從頭開始";
20705  var prevTrack = "前一軌";
20706  var nextTrack = "後一軌";
20707  var rewind = "倒回";
20708  var forward = "快轉";
20709  var captions = "字幕";
20710  var showCaptions = "顯示字幕";
20711  var hideCaptions = "隱藏字幕";
20712  var captionsOff = "字幕關閉";
20713  var showTranscript = "顯示逐字稿";
20714  var hideTranscript = "隱藏逐字稿";
20715  var turnOnDescriptions = "開啟口述影像";
20716  var turnOffDescriptions = "關閉口述影像";
20717  var chapters = "章節";
20718  var language = "語言";
20719  var sign = "手語";
20720  var showSign = "顯示手語";
20721  var hideSign = "隱藏手語";
20722  var seekbarLabel = "時間軸";
20723  var mute = "靜音";
20724  var unmute = "取消靜音";
20725  var volume = "音量";
20726  var volumeUpDown = "音量增減";
20727  var preferences = "偏好設定";
20728  var enterFullScreen = "進入全螢幕模式";
20729  var exitFullScreen = "離開全螢幕模式";
20730  var speed = "播放速度";
20731  var spacebar = "空白鍵";
20732  var transcriptTitle = "逐字稿";
20733  var lyricsTitle = "歌詞";
20734  var autoScroll = "自動捲動";
20735  var statusPlaying = "正在播放";
20736  var statusPaused = "已暫停";
20737  var statusStopped = "已停止";
20738  var statusBuffering = "正在緩衝";
20739  var statusEnd = "軌段結束";
20740  var selectedTrack = "已選軌段";
20741  var alertDescribedVersion = "正在使用本影片的口述影像版本";
20742  var alertNonDescribedVersion = "正在使用本影片的非口述影像版本";
20743  var prefMenuCaptions = "字幕";
20744  var prefVoicedCaptions = "Spoken Captions";
20745  var prefMenuDescriptions = "口述影像";
20746  var prefMenuKeyboard = "鍵盤";
20747  var prefMenuTranscript = "逐字稿";
20748  var prefTitleCaptions = "字幕偏好設定";
20749  var prefTitleDescriptions = "口述影像偏好設定";
20750  var prefTitleKeyboard = "鍵盤偏好設定";
20751  var prefTitleTranscript = "逐字稿偏好設定";
20752  var prefIntroDescription1 = "本媒體播放器支援兩種支援口述影像格式:";
20753  var prefDescription1 = "目前的影片有 錄製口述影像的替代版本 文字式口述影像";
20754  var prefDescription2 = "目前的影片有 影片的口述影像替代版本";
20755  var prefDescription3 = "目前的影片有 文字式口述影像,需搭配螢幕報讀軟體";
20756  var prefIntroDescriptionNone = "目前的影片兩種格式都沒提供。";
20757  var prefIntroDescription3 = "請使用下列表單設定口述影像的相關偏好。";
20758  var prefIntroDescription4 = "儲存設定後,口述影像功能可以由口述影像按鈕切換開關。";
20759  var prefIntroKeyboard1 = "不論您身處網頁何處,都可以運用下列鍵盤快速鍵操作頁面中的這個媒體播放器(快速鍵清單請見底下)。";
20760  var prefIntroKeyboard2 = "以下都可以指派組合鍵(Shift、Alt、Ctrl)。";
20761  var prefIntroKeyboard3 = "請注意:有些快速鍵組合可能會跟您的瀏覽器或其他應用程式相衝突。請自行多加嘗試,確認哪些按鍵組合可以使用。";
20762  var prefHeadingKeyboard1 = "用於快速鍵的組合鍵";
20763  var prefHeadingKeyboard2 = "目前的快速鍵";
20764  var prefHeadingDescription = "口述影像";
20765  var prefHeadingTextDescription = "文字式口述影像";
20766  var prefAltKey = "Alt";
20767  var prefCtrlKey = "Ctrl";
20768  var prefShiftKey = "Shift";
20769  var prefNoKeyShortcuts = "Disable keyboard shortcuts";
20770  var escapeKey = "Esc";
20771  var escapeKeyFunction = "關閉目前的對話視窗或彈出式選單";
20772  var prefDescPause = "開始口述時自動暫停影片";
20773  var prefDescVisible = "同時以視覺方式呈現口述影像";
20774  var prefDescVoice = "語音";
20775  var prefDescRate = "Spoken Description Rate";
20776  var prefCaptionRate = "Spoken Caption Rate";
20777  var prefDescPitch = "音調";
20778  var prefDescPitch1 = "非常低沈";
20779  var prefDescPitch2 = "低沈";
20780  var prefDescPitch3 = "預設";
20781  var prefDescPitch4 = "高亢";
20782  var prefDescPitch5 = "非常高亢";
20783  var sampleDescriptionText = "調整設定以聆聽這段範例文字。";
20784  var prefHighlight = "播放媒體內容時凸顯相對應的逐字稿段落";
20785  var prefTabbable = "啟用鍵盤的逐字稿";
20786  var prefCaptionsFont = "字體變化";
20787  var prefCaptionsColor = "文字色彩";
20788  var prefCaptionsBGColor = "背景";
20789  var prefCaptionsSize = "文字尺寸";
20790  var prefCaptionsOpacity = "不透明度";
20791  var prefCaptionsStyle = "字型樣式";
20792  var serif = "襯線字型";
20793  var sans = "無襯線字型";
20794  var cursive = "手寫字型";
20795  var fantasy = "華麗字型";
20796  var monospace = "等寬字型";
20797  var white = "白色";
20798  var yellow = "黃色";
20799  var green = "綠色";
20800  var cyan = "青色";
20801  var blue = "藍色";
20802  var magenta = "洋紅色";
20803  var red = "紅色";
20804  var black = "黑色";
20805  var transparent = "透明";
20806  var solid = "不透明";
20807  var captionsStylePopOn = "彈出";
20808  var captionsStyleRollUp = "往上捲動";
20809  var prefCaptionsPosition = "位置";
20810  var captionsPositionOverlay = "覆蓋";
20811  var captionsPositionBelow = "在影片之下";
20812  var sampleCaptionText = "字幕範例";
20813  var prefSuccess = "已儲存您提出的變動。";
20814  var prefNoChange = "您並未提出任何變動。";
20815  var save = "儲存";
20816  var cancel = "取消";
20817  var dismissButton = "Dismiss";
20818  var windowButtonLabel = "視窗選項";
20819  var windowMove = "移動";
20820  var windowMoveLeft = "Window moved left";
20821  var windowMoveRight = "Window moved right";
20822  var windowMoveUp = "Window moved up";
20823  var windowMoveDown = "Window moved down";
20824  var windowMoveStopped = "Window move stopped";
20825  var transcriptControls = "Transcript Window Controls";
20826  var signControls = "Sign Language Window Controls";
20827  var windowMoveAlert = "拖曳視窗或以方向鍵移動視窗;確定後按 Enter";
20828  var windowResize = "變更大小";
20829  var windowResizeHeading = "變更視窗大小";
20830  var closeButtonLabel = "閉";
20831  var width = "寬度";
20832  var height = "高度";
20833  var resultsSummary1 = "You searched for:";
20834  var resultsSummary2 = "Found %1 matching items.";
20835  var resultsSummary3 = "Click the time associated with any item to play the video from that point.";
20836  var noResultsFound = "No results found.";
20837  var searchButtonLabel = "Play at %1";
20838  var hour = "hour";
20839  var minute = "minute";
20840  var second = "second";
20841  var hours = "hours";
20842  var minutes = "minutes";
20843  var seconds = "seconds";
20844  var vtsHeading = "Video Transcript Sorter";
20845  var vtsInstructions1 = "Use the Video Transcript Sorter to modify text tracks:";
20846  var vtsInstructions2 = "Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player's auto-generated transcript.";
20847  var vtsInstructions3 = "Modify content or start/end times (all are directly editable within the table).";
20848  var vtsInstructions4 = "Add new content, such as chapters or descriptions.";
20849  var vtsInstructions5 = "After editing, click the \"Save Changes\" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.";
20850  var vtsSelectLanguage = "Select a language";
20851  var vtsSave = "Generate new .vtt content";
20852  var vtsReturn = "Return to Editor";
20853  var vtsCancel = "Cancelling saving. Any edits you made have been restored in the VTS table.";
20854  var vtsRow = "Row";
20855  var vtsKind = "Kind";
20856  var vtsStart = "Start";
20857  var vtsEnd = "End";
20858  var vtsContent = "Content";
20859  var vtsActions = "Actions";
20860  var vtsNewRow = "A new row %1 has been inserted.";
20861  var vtsDeletedRow = "Row %1 has been deleted.";
20862  var vtsMovedRow = "Row %1 has been moved %2 and is now Row %3.";
20863  var zh_tw = {
20864  	playerHeading: playerHeading,
20865  	audioPlayer: audioPlayer,
20866  	videoPlayer: videoPlayer,
20867  	faster: faster,
20868  	slower: slower,
20869  	play: play,
20870  	pause: pause,
20871  	restart: restart,
20872  	prevTrack: prevTrack,
20873  	nextTrack: nextTrack,
20874  	rewind: rewind,
20875  	forward: forward,
20876  	captions: captions,
20877  	showCaptions: showCaptions,
20878  	hideCaptions: hideCaptions,
20879  	captionsOff: captionsOff,
20880  	showTranscript: showTranscript,
20881  	hideTranscript: hideTranscript,
20882  	turnOnDescriptions: turnOnDescriptions,
20883  	turnOffDescriptions: turnOffDescriptions,
20884  	chapters: chapters,
20885  	language: language,
20886  	sign: sign,
20887  	showSign: showSign,
20888  	hideSign: hideSign,
20889  	seekbarLabel: seekbarLabel,
20890  	mute: mute,
20891  	unmute: unmute,
20892  	volume: volume,
20893  	volumeUpDown: volumeUpDown,
20894  	preferences: preferences,
20895  	enterFullScreen: enterFullScreen,
20896  	exitFullScreen: exitFullScreen,
20897  	speed: speed,
20898  	spacebar: spacebar,
20899  	transcriptTitle: transcriptTitle,
20900  	lyricsTitle: lyricsTitle,
20901  	autoScroll: autoScroll,
20902  	statusPlaying: statusPlaying,
20903  	statusPaused: statusPaused,
20904  	statusStopped: statusStopped,
20905  	statusBuffering: statusBuffering,
20906  	statusEnd: statusEnd,
20907  	selectedTrack: selectedTrack,
20908  	alertDescribedVersion: alertDescribedVersion,
20909  	alertNonDescribedVersion: alertNonDescribedVersion,
20910  	prefMenuCaptions: prefMenuCaptions,
20911  	prefVoicedCaptions: prefVoicedCaptions,
20912  	prefMenuDescriptions: prefMenuDescriptions,
20913  	prefMenuKeyboard: prefMenuKeyboard,
20914  	prefMenuTranscript: prefMenuTranscript,
20915  	prefTitleCaptions: prefTitleCaptions,
20916  	prefTitleDescriptions: prefTitleDescriptions,
20917  	prefTitleKeyboard: prefTitleKeyboard,
20918  	prefTitleTranscript: prefTitleTranscript,
20919  	prefIntroDescription1: prefIntroDescription1,
20920  	prefDescription1: prefDescription1,
20921  	prefDescription2: prefDescription2,
20922  	prefDescription3: prefDescription3,
20923  	prefIntroDescriptionNone: prefIntroDescriptionNone,
20924  	prefIntroDescription3: prefIntroDescription3,
20925  	prefIntroDescription4: prefIntroDescription4,
20926  	prefIntroKeyboard1: prefIntroKeyboard1,
20927  	prefIntroKeyboard2: prefIntroKeyboard2,
20928  	prefIntroKeyboard3: prefIntroKeyboard3,
20929  	prefHeadingKeyboard1: prefHeadingKeyboard1,
20930  	prefHeadingKeyboard2: prefHeadingKeyboard2,
20931  	prefHeadingDescription: prefHeadingDescription,
20932  	prefHeadingTextDescription: prefHeadingTextDescription,
20933  	prefAltKey: prefAltKey,
20934  	prefCtrlKey: prefCtrlKey,
20935  	prefShiftKey: prefShiftKey,
20936  	prefNoKeyShortcuts: prefNoKeyShortcuts,
20937  	escapeKey: escapeKey,
20938  	escapeKeyFunction: escapeKeyFunction,
20939  	prefDescPause: prefDescPause,
20940  	prefDescVisible: prefDescVisible,
20941  	prefDescVoice: prefDescVoice,
20942  	prefDescRate: prefDescRate,
20943  	prefCaptionRate: prefCaptionRate,
20944  	prefDescPitch: prefDescPitch,
20945  	prefDescPitch1: prefDescPitch1,
20946  	prefDescPitch2: prefDescPitch2,
20947  	prefDescPitch3: prefDescPitch3,
20948  	prefDescPitch4: prefDescPitch4,
20949  	prefDescPitch5: prefDescPitch5,
20950  	sampleDescriptionText: sampleDescriptionText,
20951  	prefHighlight: prefHighlight,
20952  	prefTabbable: prefTabbable,
20953  	prefCaptionsFont: prefCaptionsFont,
20954  	prefCaptionsColor: prefCaptionsColor,
20955  	prefCaptionsBGColor: prefCaptionsBGColor,
20956  	prefCaptionsSize: prefCaptionsSize,
20957  	prefCaptionsOpacity: prefCaptionsOpacity,
20958  	prefCaptionsStyle: prefCaptionsStyle,
20959  	serif: serif,
20960  	sans: sans,
20961  	cursive: cursive,
20962  	fantasy: fantasy,
20963  	monospace: monospace,
20964  	white: white,
20965  	yellow: yellow,
20966  	green: green,
20967  	cyan: cyan,
20968  	blue: blue,
20969  	magenta: magenta,
20970  	red: red,
20971  	black: black,
20972  	transparent: transparent,
20973  	solid: solid,
20974  	captionsStylePopOn: captionsStylePopOn,
20975  	captionsStyleRollUp: captionsStyleRollUp,
20976  	prefCaptionsPosition: prefCaptionsPosition,
20977  	captionsPositionOverlay: captionsPositionOverlay,
20978  	captionsPositionBelow: captionsPositionBelow,
20979  	sampleCaptionText: sampleCaptionText,
20980  	prefSuccess: prefSuccess,
20981  	prefNoChange: prefNoChange,
20982  	save: save,
20983  	cancel: cancel,
20984  	dismissButton: dismissButton,
20985  	windowButtonLabel: windowButtonLabel,
20986  	windowMove: windowMove,
20987  	windowMoveLeft: windowMoveLeft,
20988  	windowMoveRight: windowMoveRight,
20989  	windowMoveUp: windowMoveUp,
20990  	windowMoveDown: windowMoveDown,
20991  	windowMoveStopped: windowMoveStopped,
20992  	transcriptControls: transcriptControls,
20993  	signControls: signControls,
20994  	windowMoveAlert: windowMoveAlert,
20995  	windowResize: windowResize,
20996  	windowResizeHeading: windowResizeHeading,
20997  	closeButtonLabel: closeButtonLabel,
20998  	width: width,
20999  	height: height,
21000  	resultsSummary1: resultsSummary1,
21001  	resultsSummary2: resultsSummary2,
21002  	resultsSummary3: resultsSummary3,
21003  	noResultsFound: noResultsFound,
21004  	searchButtonLabel: searchButtonLabel,
21005  	hour: hour,
21006  	minute: minute,
21007  	second: second,
21008  	hours: hours,
21009  	minutes: minutes,
21010  	seconds: seconds,
21011  	vtsHeading: vtsHeading,
21012  	vtsInstructions1: vtsInstructions1,
21013  	vtsInstructions2: vtsInstructions2,
21014  	vtsInstructions3: vtsInstructions3,
21015  	vtsInstructions4: vtsInstructions4,
21016  	vtsInstructions5: vtsInstructions5,
21017  	vtsSelectLanguage: vtsSelectLanguage,
21018  	vtsSave: vtsSave,
21019  	vtsReturn: vtsReturn,
21020  	vtsCancel: vtsCancel,
21021  	vtsRow: vtsRow,
21022  	vtsKind: vtsKind,
21023  	vtsStart: vtsStart,
21024  	vtsEnd: vtsEnd,
21025  	vtsContent: vtsContent,
21026  	vtsActions: vtsActions,
21027  	vtsNewRow: vtsNewRow,
21028  	vtsDeletedRow: vtsDeletedRow,
21029  	vtsMovedRow: vtsMovedRow
21030  };
21031
21032  const moduleFromTag = {
21033  	ca,
21034  	cs,
21035  	da,
21036  	de,
21037  	en,
21038  	es,
21039  	fr,
21040  	he,
21041  	id,
21042  	it,
21043  	ja,
21044  	ms,
21045  	nb,
21046  	nl,
21047  	pl,
21048  	pt,
21049  	'pt-BR': pt_br,
21050  	sk,
21051  	sv,
21052  	tr,
21053  	'zh-TW': zh_tw,
21054  };
21055
21056  function addTranslationFunctions(AblePlayer) {
21057  	AblePlayer.prototype.getSupportedLangs = function() {
21058  		// returns an array of languages for which AblePlayer has translation tables
21059  		var langs = {
21060  			'ca'    : 'Catalan',
21061  			'cs'    : 'Czech',
21062  			'da'    : 'Danish',
21063  			'de'    : 'German',
21064  			'en'    : 'English',
21065  			'es'    : 'Spanish',
21066  			'fr'    : 'French',
21067  			'he'    : 'Hebrew',
21068  			'id'    : 'Indonesian',
21069  			'it'    : 'Italian',
21070  			'ja'    : 'Japanese',
21071  			'ms'    : 'Malay',
21072  			'nb'    : 'Norwegian Bokmål',
21073  			'nl'    : 'Dutch',
21074  			'pl'    : 'Polish',
21075  			'pt'    : 'Portuguese',
21076  			'pt-BR' : 'Brazilian Portuguese',
21077  			'sk'    : 'Slovak',
21078  			'sv'    : 'Swedish',
21079  			'tr'    : 'Turkish',
21080  			'zh-TW' : 'Chinese (Taiwan)'
21081  		};
21082
21083  		return langs;
21084  	};
21085
21086  	/**
21087  	 * Fetch a translated string if it exists.
21088  	 *
21089  	 * @param {string} key JSON key to locate translated string.
21090  	 * @param {string} fallback Default language if no translation found.
21091  	 * @param {Array} args Ordered array of arguments to replace in string.
21092  	 * @returns
21093  	 */
21094  	AblePlayer.prototype.translate = function( key, fallback, args = Array() ) {
21095  		let translation = '';
21096  		if ( this.tt[ key ] ) {
21097  			translation = this.tt[ key ];
21098  		} else {
21099  			translation = fallback;
21100  		}
21101  		if ( args.length > 0 ) {
21102  			args.forEach( ( val, index ) => {
21103  				let ref = index + 1;
21104  				translation = translation.replace( '%' + ref, val );
21105  			});
21106  		}
21107
21108  		return translation;
21109  	};
21110
21111  	AblePlayer.prototype.getTranslationText = function() {
21112
21113  		// determine language, then get labels and prompts from corresponding translation var
21114  		var thisObj, supportedLangs, docLang, similarLangFound;
21115  		thisObj = this;
21116
21117  		supportedLangs = this.getSupportedLangs(); // returns an array
21118
21119  		if (this.lang) { // a data-lang attribute is included on the media element
21120  			this.lang;
21121  			if ( ! Object.hasOwn( supportedLangs,this.lang ) ) {
21122  				// the specified language code is not in the index
21123  				if ( this.lang.indexOf('-') == 2 ) {
21124  					// this is a localized lang attribute (e.g., fr-CA)
21125  					// try the parent language, given the first two characters
21126  					// if parent lang is supported. Use that, else null.
21127  					this.lang = ( Object.hasOwn(supportedLangs,this.lang.substring(0,2)) !== -1 ) ? this.lang.substring(0,2) : null;
21128  				} else {
21129  					// this is not a localized language.
21130  					// but maybe there's a similar localized language supported
21131  					// that has the same parent?
21132  					similarLangFound = false;
21133  					for ( const [key,value] of Object.entries(supportedLangs) ) {
21134  						if ( key.substring(0,2) == this.lang ) {
21135  							this.lang = key;
21136  							similarLangFound = true;
21137  						}
21138  					}
21139  					if ( !similarLangFound ) {
21140  						// language requested via data-lang is not supported
21141  						this.lang = null;
21142  					}
21143  				}
21144  			}
21145  		}
21146
21147  		if (!this.lang) {
21148  			// try the language of the web page, if specified
21149  			if ($('body').attr('lang')) {
21150  				docLang = $('body').attr('lang').toLowerCase();
21151  			} else if ($('html').attr('lang')) {
21152  				docLang = $('html').attr('lang').toLowerCase();
21153  			} else {
21154  				docLang = null;
21155  			}
21156  			if (docLang) {
21157  				if ( Object.hasOwn( supportedLangs,docLang ) ) {
21158  					// the document language is supported
21159  					this.lang = docLang;
21160  				} else {
21161  					// the document language is not supported
21162  					if (docLang.indexOf('-') == 2) {
21163  						// this is a localized lang attribute (e.g., fr-CA)
21164  						// try the parent language, given the first two characters
21165  						if ( Object.hasOwn(supportedLangs,docLang.substring(0,2)) ) {
21166  							// the parent language is supported. use that.
21167  							this.lang = docLang.substring(0,2);
21168  						}
21169  					}
21170  				}
21171  			}
21172  		}
21173
21174  		if (!this.lang) {
21175  			// No supported language has been specified by any means
21176  			// Fallback to English
21177  			this.lang = 'en';
21178  		}
21179
21180  		if (!this.searchLang) {
21181  			this.searchLang = this.lang;
21182  		}
21183  		const ttModule = moduleFromTag[this.lang];
21184  		if (!ttModule) {
21185  			console.log( "Error: Unable to load translation module for language:", this.lang);
21186  			thisObj.tt = {};
21187  			thisObj.translationFiles = false;
21188  		} else {
21189  			thisObj.tt = ttModule;
21190  			thisObj.translationFiles = true;
21191  		}
21192  	};
21193
21194  	AblePlayer.prototype.getSampleDescriptionText = function() {
21195  		if ( ! this.translationFiles ) {
21196  			this.sampleText = [];
21197  			let translation = { 'lang':'en', 'text': this.translate( 'sampleDescriptionText', 'Adjust settings to hear this sample text.' ) };
21198  			this.sampleText.push(translation);
21199  		} else {
21200  			// Create an array of sample description text in all languages
21201  			// This needs to be readily available for testing different voices
21202  			// in the Description Preferences dialog
21203  			var supportedLangs, thisText, translation;
21204
21205  			supportedLangs = this.getSupportedLangs();
21206
21207  			this.sampleText = [];
21208  			for ( const [tag,name] of Object.entries(supportedLangs) ) {
21209  				const ttModule = moduleFromTag[tag];
21210  				thisText = ttModule.sampleDescriptionText;
21211  				translation = {'lang':name, 'text': thisText};
21212  				this.sampleText.push(translation);
21213  			}
21214  		}
21215  	};
21216
21217  }
21218
21219  /* global Vimeo */
21220
21221  function addVimeoFunctions(AblePlayer) {
21222
21223  	AblePlayer.prototype.initVimeoPlayer = function () {
21224
21225  		var thisObj, deferred, promise, containerId, vimeoId, options;
21226  		thisObj = this;
21227
21228  		deferred = new this.defer();
21229  		promise = deferred.promise();
21230
21231  		containerId = this.mediaId + '_vimeo';
21232
21233  		// add container to which Vimeo player iframe will be appended
21234  		this.$mediaContainer.prepend($('<div>').attr('id', containerId));
21235
21236  		// if a described version is available && user prefers description
21237  		// init player using the described version
21238  		vimeoId = (this.vimeoDescId && this.prefDesc) ? this.vimeoDescId : this.vimeoId;
21239
21240  		this.activeVimeoId = vimeoId;
21241
21242  		// Notes re. Vimeo Embed Options:
21243  		// If a video is owned by a user with a paid Plus, PRO, or Business account,
21244  		// setting the "controls" option to "false" will hide the default controls, without hiding captions.
21245  		// This is a new option from Vimeo; previously used "background:true" to hide the controller,
21246  		// but that had unwanted side effects:
21247  		// - In addition to hiding the controls, it also hides captions
21248  		// - It automatically autoplays (initializing the player with autoplay:false does not override this)
21249  		// - It automatically loops (but this can be overridden by initializing the player with loop:false)
21250  		// - It automatically sets volume to 0 (not sure if this can be overridden, since no longer using the background option)
21251
21252  		if (this.playerWidth) {
21253  			if (this.vimeoUrlHasParams) {
21254  				// use url param, not id
21255  				options = {
21256  					url: vimeoId,
21257  					width: this.playerWidth,
21258  					controls: false
21259  				};
21260  			} else {
21261  				options = {
21262  					id: vimeoId,
21263  					width: this.playerWidth,
21264  					controls: false
21265  				};
21266  			}
21267  		} else {
21268  			// initialize without width & set width later
21269  			if (this.vimeoUrlHasParams) {
21270  				options = {
21271  					url: vimeoId,
21272  					controls: false
21273  				};
21274  			} else {
21275  				options = {
21276  					id: vimeoId,
21277  					controls: false
21278  				};
21279  			}
21280  		}
21281
21282  		this.vimeoPlayer = new Vimeo.Player(containerId, options);
21283
21284  		this.vimeoPlayer.ready().then(function() {
21285  			// add tabindex -1 on iframe so vimeo frame cannot be focused on
21286  			$('#'+containerId).children('iframe').attr({
21287  				'tabindex': '-1',
21288  				'aria-hidden': true
21289  			});
21290
21291  			// get video's intrinsic size and initiate player dimensions
21292  			thisObj.vimeoPlayer.getVideoWidth().then(function(width) {
21293  				if (width) {
21294  					// also get height
21295  					thisObj.vimeoPlayer.getVideoHeight().then(function(height) {
21296  						if (height) {
21297  							thisObj.resizePlayer(width,height);
21298  						}
21299  					});
21300  				}
21301  			}).catch(function(error) {
21302  				// an error occurred getting height or width
21303  				// TODO: Test this to see how gracefully it organically recovers
21304  			});
21305
21306  			if (!thisObj.hasPlaylist) {
21307  				// remove the media element, since Vimeo replaces that with its own element in an iframe
21308  				// this is handled differently for playlists. See buildplayer.js > cuePlaylistItem()
21309  				thisObj.$media.remove();
21310
21311  				// define variables that will impact player setup
21312
21313  				// vimeoSupportsPlaybackRateChange
21314  				// changing playbackRate is only supported if the video is hosted on a Pro or Business account
21315  				// unfortunately there is no direct way to query for that information.
21316  				// this.vimeoPlayer.getPlaybackRate() returns a value, regardless of account type
21317  				// This is a hack:
21318  				// Attempt to change the playbackRate. If it results in an error, assume changing playbackRate is not supported.
21319  				// Supported playbackRate values are 0.5 to 2.
21320  				thisObj.vimeoPlaybackRate = 1;
21321  				thisObj.vimeoPlayer.setPlaybackRate(thisObj.vimeoPlaybackRate).then(function(playbackRate) {
21322  				// playback rate was set
21323  					thisObj.vimeoSupportsPlaybackRateChange = true;
21324  				}).catch(function(error) {
21325  					thisObj.vimeoSupportsPlaybackRateChange = false;
21326  				});
21327  				deferred.resolve();
21328  			}
21329  		});
21330  		return promise;
21331  	};
21332
21333  	AblePlayer.prototype.getVimeoPaused = function () {
21334
21335  		var deferred, promise;
21336  		deferred = new this.defer();
21337  		promise = deferred.promise();
21338
21339  		this.vimeoPlayer.getPaused().then(function (paused) {
21340  			// paused is Boolean
21341  			deferred.resolve(paused);
21342  		});
21343
21344  		return promise;
21345  	};
21346
21347  	AblePlayer.prototype.getVimeoEnded = function () {
21348
21349  		var deferred, promise;
21350  		deferred = new this.defer();
21351  		promise = deferred.promise();
21352
21353  		this.vimeoPlayer.getEnded().then(function (ended) {
21354  			// ended is Boolean
21355  			deferred.resolve(ended);
21356  		});
21357
21358  		return promise;
21359  	};
21360
21361  	AblePlayer.prototype.getVimeoState = function () {
21362
21363  		var deferred, promise, promises, gettingPausedPromise, gettingEndedPromise;
21364
21365  		deferred = new this.defer();
21366  		promise = deferred.promise();
21367  		promises = [];
21368
21369  		gettingPausedPromise = this.vimeoPlayer.getPaused();
21370  		gettingEndedPromise = this.vimeoPlayer.getEnded();
21371
21372  		promises.push(gettingPausedPromise);
21373  		promises.push(gettingEndedPromise);
21374
21375  		gettingPausedPromise.then(function (paused) {
21376  			deferred.resolve(paused);
21377  		});
21378  		gettingEndedPromise.then(function (ended) {
21379  			deferred.resolve(ended);
21380  		});
21381  		$.when.apply($, promises).then(function () {
21382  			deferred.resolve();
21383  		});
21384  		return promise;
21385  	};
21386
21387  	AblePlayer.prototype.getVimeoCaptionTracks = function () {
21388
21389  		// get data via Vimeo Player API, and push data to this.captions
21390  		// Note: Vimeo doesn't expose the caption cues themselves
21391  		// so this.captions will only include metadata about caption tracks; not cues
21392  		var deferred = new this.defer();
21393  		var promise = deferred.promise();
21394
21395  		var thisObj, i, isDefaultTrack;
21396
21397  		thisObj = this;
21398
21399  		this.vimeoPlayer.getTextTracks().then(function(tracks) {
21400
21401  				// each Vimeo track includes the following:
21402  				// label (local name of the language)
21403  				// language (2-character code)
21404  				// kind (captions or subtitles, as declared by video owner)
21405  				// mode ('disabled' or 'showing')
21406
21407  				if (tracks.length) {
21408
21409  					// create a new button for each caption track
21410  					for (i=0; i<tracks.length; i++) {
21411
21412  						thisObj.hasCaptions = true;
21413  						if (thisObj.prefCaptions === 1) {
21414  								thisObj.captionsOn = true;
21415  						} else {
21416  							thisObj.captionsOn = false;
21417  						}
21418  						// assign the default track based on language of the player
21419  						if (tracks[i]['language'] === thisObj.lang) {
21420  							isDefaultTrack = true;
21421  						} else {
21422  								isDefaultTrack = false;
21423  						}
21424  						thisObj.tracks.push({
21425  							'kind': tracks[i]['kind'],
21426  							'language': tracks[i]['language'],
21427  							'label': tracks[i]['label'],
21428  							'def': isDefaultTrack
21429  						});
21430  					}
21431  					thisObj.captions = thisObj.tracks;
21432  					thisObj.hasCaptions = true;
21433
21434  					// setupPopups again with new captions array, replacing original
21435  					thisObj.setupPopups('captions');
21436  					deferred.resolve();
21437  			 	} else {
21438  					thisObj.hasCaptions = false;
21439  					thisObj.usingVimeoCaptions = false;
21440  					deferred.resolve();
21441  				}
21442  			});
21443
21444  		return promise;
21445  	};
21446
21447  	AblePlayer.prototype.getVimeoPosterUrl = function (vimeoId) {
21448  		const thisObj = this;
21449
21450  		// Vimeo Oembed only returns a 640px width image. Hope at some point there's an alternative.
21451  		var url = 'https://vimeo.com/api/oembed.json?url=https://vimeo.com/' + vimeoId, imageUrl = '';
21452  		console.log( url );
21453  		fetch( url ).then( response => {
21454
21455  			return response.json();
21456    		})
21457  		.then( json => {
21458  			imageUrl = json.thumbnail_url;
21459  		})
21460  		.catch( error => {
21461  			if (thisObj.debug) {
21462  				console.log( 'Vimeo API query: ' + error );
21463  			}
21464  		});
21465
21466  		return imageUrl;
21467  	};
21468
21469  	AblePlayer.prototype.getVimeoId = function (url) {
21470
21471  		// return a Vimeo ID, extracted from a full Vimeo URL
21472  		// Supported URL patterns are anything containing 'vimeo.com'
21473  		// and ending with a '/' followed by the ID.
21474  		// (Vimeo IDs do not have predicatable lengths)
21475
21476  		// Update: If URL contains parameters, return the full url
21477  		// This will need to be passed to the Vimeo Player API
21478  		// as a url parameter, not as an id parameter
21479  		this.vimeoUrlHasParams = false;
21480
21481  		let urlObject;
21482  		if (typeof url === 'number') {
21483  			// this is likely already a vimeo ID
21484  			return url;
21485  		} else {
21486  			urlObject = new URL(url);
21487  		}
21488  		if ( 'vimeo.com' === urlObject.hostname || 'player.vimeo.com' === urlObject.hostname ) {
21489  			// this is a full Vimeo URL
21490  			if ( '' !== urlObject.search ) {
21491  				// URL contains parameters
21492  				this.vimeoUrlHasParams = true;
21493  				return url;
21494  			} else {
21495  				if ( 'player.vimeo.com' === urlObject.hostname ) {
21496  					return urlObject.pathname.replace( '/video/', '' );
21497  				} else {
21498  					return urlObject.pathname.replace( '/', '' );
21499  				}
21500  			}
21501  		} else {
21502  			return url;
21503  		}
21504  	};
21505
21506  }
21507
21508  function addVolumeFunctions(AblePlayer) {
21509
21510  	AblePlayer.prototype.addVolumeSlider = function($div) {
21511
21512  		// Prior to v4.4.64, we were using a custom-build vertical volunme slider
21513  		// Changed to input type="range" because it's standard and gaining more widespread support
21514  		// including screen reader support
21515  		// TODO: Improve presentation of vertical slider. That requires some CSS finesse.
21516
21517  		var thisObj, volumeSliderId, volumeHelpId, volumePct, volumeLabel, volumeHeight;
21518
21519  		thisObj = this;
21520
21521  		// define a few variables
21522  		volumeSliderId = this.mediaId + '-volume-slider';
21523  		volumeHelpId = this.mediaId + '-volume-help';
21524
21525  		this.$volumeSlider = $('<div>',{
21526  			'id': volumeSliderId,
21527  			'class': 'able-volume-slider',
21528  			'aria-hidden': 'true'
21529  		}).hide();
21530  		this.$volumeSliderTooltip = $('<div>',{
21531  			'class': 'able-tooltip',
21532  			'role': 'tooltip'
21533  		}).hide();
21534  		this.$volumeRange = $('<input>',{
21535  			'type': 'range',
21536  			'min': '0',
21537  			'max': '10',
21538  			'step': '1',
21539  			'orient': 'vertical', // non-standard, but required for Firefox
21540  			'aria-label': this.translate( 'volumeUpDown', 'Volume up down' ),
21541  			'value': this.volume
21542  		});
21543  		volumePct = parseInt(thisObj.volume) / 10 * 100;
21544  		this.$volumeHelp = $('<div>',{
21545  			'id': volumeHelpId,
21546  			'class': 'able-volume-help',
21547  			'aria-live': 'polite'
21548  		}).text(volumePct + '%');
21549  		volumeLabel = this.$volumeButton.attr( 'aria-label' );
21550  		this.$volumeButton.attr( 'aria-label', volumeLabel + ' ' + volumePct + '%');
21551  		this.$volumeSlider.append(this.$volumeSliderTooltip,this.$volumeRange,this.$volumeHelp);
21552  		volumeHeight = this.$volumeButton.parents( '.able-control-row' )[0];
21553  		this.$volumeSlider.css( 'bottom', volumeHeight.offsetHeight );
21554
21555  		$div.append(this.$volumeSlider);
21556
21557  		// add event listeners
21558  		this.$volumeRange.on('change',function (e) {
21559  			thisObj.handleVolumeChange($(this).val());
21560  		});
21561
21562  		this.$volumeRange.on('input',function (e) {
21563  			thisObj.handleVolumeChange($(this).val());
21564  		});
21565
21566  		this.$volumeRange.on('keydown',function (e) {
21567
21568  			if (e.key === 'Escape' || e.key === 'Tab' || e.key === 'Enter') {
21569  				// close popup
21570  				if (thisObj.$volumeSlider.is(':visible')) {
21571  					thisObj.closingVolume = true; // stopgap
21572  					thisObj.hideVolumePopup();
21573  				} else {
21574  					if (!thisObj.closingVolume) {
21575  						thisObj.showVolumePopup();
21576  					}
21577  				}
21578  			} else {
21579  				return;
21580  			}
21581  		});
21582  	};
21583
21584  	AblePlayer.prototype.refreshVolumeHelp = function(volume) {
21585
21586  		// make adjustments based on current volume
21587  		var volumePct;
21588  		volumePct = (volume/10) * 100;
21589
21590  		// Update help text
21591  		if (this.$volumeHelp) {
21592  			this.$volumeHelp.text(volumePct + '%');
21593  		}
21594
21595  		// Update the default value of the volume slider input field
21596  		// This doesn't seem to be necessary; browsers remember the previous setting during a session
21597  		// but this is a fallback in case they don't
21598  		this.$volumeRange.attr('value',volume);
21599  	};
21600
21601  	AblePlayer.prototype.refreshVolumeButton = function(volume) {
21602
21603  		var volumeName, volumePct, volumeLabel;
21604
21605  		volumeName = this.getVolumeName(volume);
21606  		volumePct = (volume/10) * 100;
21607  		volumeLabel = this.translate( 'volume', 'Volume' ) + ' ' + volumePct + '%';
21608
21609  		this.getIcon( this.$volumeButton, 'volume-' + volumeName );
21610  		this.$volumeButton.attr( 'aria-label', volumeLabel );
21611  	};
21612
21613  	AblePlayer.prototype.handleVolumeButtonClick = function() {
21614
21615  		if (this.$volumeSlider.is(':visible')) {
21616  			this.hideVolumePopup();
21617  		} else {
21618  			this.showVolumePopup();
21619  		}
21620  	};
21621
21622  	AblePlayer.prototype.handleVolumeKeystroke = function(volume) {
21623  		// keyboard shortcuts for changing volume
21624  		if (this.isMuted() && volume > 0) {
21625  			this.setMute(false);
21626  		} else if (volume === 0) {
21627  			this.setMute(true);
21628  		} else {
21629  			this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js)
21630  			this.refreshVolumeHelp(volume);
21631  			this.refreshVolumeButton(volume);
21632  		}
21633  	};
21634
21635
21636  	AblePlayer.prototype.handleVolumeChange = function(volume) {
21637
21638  		// handle volume change using the volume input slider
21639
21640  		if (this.isMuted() && volume > 0) {
21641  			this.setMute(false);
21642  		} else if (volume === 0) {
21643  			this.setMute(true);
21644  		} else {
21645  			this.setVolume(volume); // this.volume will be updated after volumechange event fires (event.js)
21646  			this.refreshVolumeHelp(volume);
21647  			this.refreshVolumeButton(volume);
21648  		}
21649  	};
21650
21651  	AblePlayer.prototype.handleMute = function() {
21652
21653  		if (this.isMuted()) {
21654  			this.setMute(false);
21655  		} else {
21656  			this.setMute(true);
21657  		}
21658  	};
21659
21660  	AblePlayer.prototype.showVolumePopup = function() {
21661
21662  		this.closePopups();
21663  		this.$tooltipDiv.hide();
21664  		this.$volumeSlider.show().attr('aria-hidden','false');
21665  		this.$volumeButton.attr('aria-expanded','true');
21666  		this.$volumeButton.focus(); // for screen reader expanded state to be read
21667  		this.waitThenFocus(this.$volumeRange);
21668  	};
21669
21670  	AblePlayer.prototype.hideVolumePopup = function() {
21671
21672  		var thisObj = this;
21673
21674  		this.$volumeSlider.hide().attr('aria-hidden','true');
21675  		this.$volumeButton.attr('aria-expanded','false').focus();
21676  		// wait a second before resetting stopgap var
21677  		// otherwise the keypress used to close volume popup will trigger the volume button
21678  		setTimeout(function() {
21679  			thisObj.closingVolume = false;
21680  		}, 1000);
21681  	};
21682
21683  	AblePlayer.prototype.isMuted = function () {
21684
21685  		if (this.player === 'html5') {
21686  			return this.media.muted;
21687  		} else if (this.player === 'youtube') {
21688  			return this.youTubePlayer.isMuted();
21689  		}
21690  	};
21691
21692  	AblePlayer.prototype.setMute = function(mute) {
21693
21694  		// mute is either true (muting) or false (unmuting)
21695  		if (mute) {
21696  			// save current volume so it can be restored after unmute
21697  			this.lastVolume = this.volume;
21698  			this.volume = 0;
21699  		} else { // restore to previous volume
21700  			if (typeof this.lastVolume !== 'undefined') {
21701  				this.volume = this.lastVolume;
21702  			}
21703  		}
21704
21705  		if (this.player === 'html5') {
21706  			this.media.muted = mute;
21707  		} else if (this.player === 'youtube') {
21708  			if (mute) {
21709  				this.youTubePlayer.mute();
21710  			} else {
21711  				this.youTubePlayer.unMute();
21712  			}
21713  		}
21714  		this.setVolume(this.volume);
21715  		this.refreshVolumeHelp(this.volume);
21716  		this.refreshVolumeButton(this.volume);
21717  	};
21718
21719  	AblePlayer.prototype.setVolume = function (volume) {
21720
21721  		// volume is 1 to 10
21722  		// convert as needed depending on player
21723
21724  		var newVolume;
21725  		this.syncSignVideo( {'volume' : 0 } );
21726  		if (this.player === 'html5') {
21727  			// volume is 0 to 1
21728  			newVolume = volume / 10;
21729  			this.media.volume = newVolume;
21730  		} else if (this.player === 'youtube') {
21731  			// volume is 0 to 100
21732  			newVolume = volume * 10;
21733  			this.youTubePlayer.setVolume(newVolume);
21734  			this.volume = volume;
21735  		} else if (this.player === 'vimeo') {
21736  			// volume is 0 to 1
21737  			newVolume = volume / 10;
21738  			this.vimeoPlayer.setVolume(newVolume).then(function() {
21739  				// setVolume finished.
21740  				// successful completion also fires a 'volumechange' event (see event.js)
21741  			});
21742  		}
21743  		this.lastVolume = volume;
21744  	};
21745
21746  	AblePlayer.prototype.getVolume = function (volume) {
21747
21748  		// return volume using common audio control scale 1 to 10
21749  		if (this.player === 'html5') {
21750  			// uses 0 to 1 scale
21751  			return this.media.volume * 10;
21752  		} else if (this.player === 'youtube') {
21753  			// uses 0 to 100 scale
21754  			if (this.youTubePlayerReady) {
21755  				return this.youTubePlayer.getVolume() / 10;
21756  			}
21757  		}
21758  		if (this.player === 'vimeo') {
21759  			// uses 0 to 1 scale
21760  			// this.vimeoPlayer.getVolume() takes too long to resolve with a value
21761  			// Just use variable that's already been defined (should be the same value anyway)
21762  			return this.volume;
21763  		}
21764  	};
21765
21766  	AblePlayer.prototype.getVolumeName = function (volume) {
21767
21768  		// returns 'mute','soft','medium', or 'loud' depending on volume level
21769  		if (volume == 0) {
21770  			return 'mute';
21771  		} else if (volume == 10) {
21772  			return 'loud';
21773  		} else if (volume < 5) {
21774  			return 'soft';
21775  		} else {
21776  			return 'medium';
21777  		}
21778  	};
21779
21780  }
21781
21782  /* Video Transcript Sorter (VTS)
21783   * Used to synchronize time stamps from WebVTT resources
21784   * so they appear in the proper sequence within an auto-generated interactive transcript
21785  */
21786
21787
21788  function addVtsFunctions(AblePlayer) {
21789  	AblePlayer.prototype.injectVTS = function() {
21790
21791  		var thisObj, $heading, $instructions, $p1, $p2, $ul, $li1, $li2, $li3,
21792  		$fieldset, $legend, i, $radioDiv, radioId, $label, $radio, $saveButton, $savedTable;
21793
21794  		thisObj = this;
21795
21796  		if ( null !== document.getElementById( 'able-vts' ) ) {
21797  			// Are they qualifying tracks?
21798  			if (this.vtsTracks.length) {
21799  				// Build an array of unique languages
21800  				this.langs = [];
21801  				this.getAllLangs(this.vtsTracks);
21802
21803  				// Set the default VTS language
21804  				this.vtsLang = this.lang;
21805
21806  				// Inject a heading
21807  				let heading = this.translate( 'vtsHeading', 'Video Transcript Sorter' );
21808  				$heading = $('<h2>').text( heading ); // TODO: intelligently assign proper heading level
21809  				$('#able-vts').append($heading);
21810
21811  				// Inject an empty div for writing messages
21812  				this.$vtsAlert = $('<div>',{
21813  					'id': 'able-vts-alert',
21814  					'aria-live': 'polite',
21815  					'aria-atomic': 'true'
21816  				});
21817  				$('#able-vts').append(this.$vtsAlert);
21818
21819  				// Inject instructions (TODO: Localize)
21820  				$instructions = $('<div>',{
21821  					'id': 'able-vts-instructions'
21822  				});
21823  				$p1 = $('<p>').text( this.translate( 'vtsInstructions1', 'Use the Video Transcript Sorter to modify text tracks:' ) );
21824  				$ul = $('<ul>');
21825  				$li1 = $('<li>').text( this.translate( 'vtsInstructions2', 'Reorder chapters, descriptions, captions, and/or subtitles so they appear in the proper sequence in Able Player\'s auto-generated transcript.' ) );
21826  				$li2 = $('<li>').text( this.translate( 'vtsInstructions3', 'Modify content or start/end times (all are directly editable within the table).' ) );
21827  				$li3 = $('<li>').text( this.translate( 'vtsInstructions4', 'Add new content, such as chapters or descriptions.' ) );
21828  				$p2 = $('<p>').text( this.translate( 'vtsInstructions5', 'After editing, click the "Save Changes" button to generate new content for all relevant timed text files. The new text can be copied and pasted into new WebVTT files.' ) );
21829  				$ul.append($li1,$li2,$li3);
21830  				$instructions.append($p1,$ul,$p2);
21831  				$('#able-vts').append($instructions);
21832
21833  				// Inject a fieldset with radio buttons for each language
21834  				$fieldset = $('<fieldset>');
21835  				$legend = $('<legend>').text( this.translate( 'vtsSelectLanguage', 'Select a language' ) );
21836  				$fieldset.append($legend);
21837  				let $fieldWrapper = $( '<div class="vts-lang-selector"></div>' );
21838  				for (i in this.langs) {
21839  					radioId = 'vts-lang-radio-' + this.langs[i];
21840  					$radioDiv = $('<div>',{
21841  						// uncomment the following if label is native name
21842  						// 'lang': this.langs[i]
21843  					});
21844  					$radio = $('<input>', {
21845  						'type': 'radio',
21846  						'name': 'vts-lang',
21847  						'id': radioId,
21848  						'value': this.langs[i]
21849  					}).on('click',function() {
21850  						thisObj.vtsLang = $(this).val();
21851  						thisObj.showVtsAlert('Loading ' + thisObj.getLanguageName(thisObj.vtsLang) + ' tracks');
21852  						thisObj.injectVtsTable('update',thisObj.vtsLang);
21853  					});
21854  					if (this.langs[i] == this.lang) {
21855  						// this is the default language.
21856  						$radio.prop('checked',true);
21857  					}
21858  					$label = $('<label>', {
21859  						'for': radioId
21860  						// Two options for label:
21861  						// getLanguageName() - with second parameter "local" would return native name, otherwise returns English;
21862  						// TODO: if using this be sure to add lang attr to <div> (see above)
21863  					}).text(this.getLanguageName(this.langs[i]));
21864  					$radioDiv.append($radio,$label);
21865  					$fieldWrapper.append($radioDiv);
21866  				}
21867  				$fieldset.append( $fieldWrapper );
21868  				$('#able-vts').append($fieldset);
21869  				let vtsSave = this.translate( 'vtsSave', 'Generate new .vtt content' );
21870  				// Inject a button to generate new files.
21871  				$saveButton = $('<button>',{
21872  					'type': 'button',
21873  					'id': 'able-vts-save',
21874  					'value': 'save'
21875  				}).text( vtsSave );
21876  				$('#able-vts').append($saveButton);
21877
21878  				// Inject a table with one row for each cue in the default language
21879  				this.injectVtsTable('add',this.vtsLang);
21880
21881  				// TODO: Add drag/drop functionality for mousers
21882  				// Add event listeners for contenteditable cells
21883  				var kindOptions, beforeEditing, editedCell, editedContent;
21884  				kindOptions = ['captions','chapters','descriptions','subtitles'];
21885  				$('td[contenteditable="true"]').on('focus',function() {
21886  					beforeEditing = $(this).text();
21887  				}).on('blur',function() {
21888  					if (beforeEditing != $(this).text()) {
21889  						editedCell = $(this).index();
21890  						editedContent = $(this).text();
21891  						if (editedCell === 1) {
21892  							// do some simple spelling auto-correct
21893  							if ($.inArray(editedContent,kindOptions) === -1) {
21894  								// whatever user typed is not a valid kind
21895  								// assume they correctly typed the first character
21896  								if (editedContent.substring(0,1) === 's') {
21897  									$(this).text('subtitles');
21898  								} else if (editedContent.substring(0,1) === 'd') {
21899  									$(this).text('descriptions');
21900  								} else if (editedContent.substring(0,2) === 'ch') {
21901  									$(this).text('chapters');
21902  								} else {
21903  									// whatever else they types, assume 'captions'
21904  									$(this).text('captions');
21905  								}
21906  							}
21907  						} else if (editedCell === 2 || editedCell === 3) {
21908  							// start or end time
21909  							// ensure proper formatting (with 3 decimal places)
21910  							$(this).text(thisObj.formatTimestamp(editedContent));
21911  						}
21912  					}
21913  				}).on('keydown',function(e) {
21914  					// don't allow keystrokes to trigger Able Player (or other) functions
21915  					// while user is editing
21916  					e.stopPropagation();
21917  				});
21918
21919  				// handle click on the Save button
21920  				$('#able-vts-save').on('click',function(e) {
21921  					e.stopPropagation();
21922  					if ($(this).attr('value') == 'save') {
21923  						// replace table with WebVTT output in textarea fields (for copying/pasting)
21924  						$(this).attr('value','cancel').text( thisObj.translate( 'vtsReturn', 'Return to Editor' ) );
21925  						$savedTable = $('#able-vts table');
21926  						$('#able-vts-instructions').hide();
21927  						$('#able-vts > fieldset').hide();
21928  						$('#able-vts table').remove();
21929  						$('#able-vts-icon-credit').remove();
21930  						thisObj.parseVtsOutput($savedTable);
21931  					} else {
21932  						// cancel saving, and restore the table using edited content
21933  						$(this).attr('value','save').text( vtsSave );
21934  						$('#able-vts-output').remove();
21935  						$('#able-vts-instructions').show();
21936  						$('#able-vts > fieldset').show();
21937  						$('#able-vts').append($savedTable);
21938  						$('#able-vts').append(thisObj.getIconCredit());
21939  						thisObj.showVtsAlert( thisObj.translate( 'vtsCancel', 'Cancelling saving. Any edits you made have been restored in the VTS table.' ) );
21940  					}
21941  				});
21942  			}
21943  		}
21944  	};
21945
21946  	AblePlayer.prototype.setupVtsTracks = function(kind, lang, trackDesc, label, src, contents) {
21947
21948  		// TODO: Add support for trackDesc
21949  		// (to destinguish between tracks for the decribed vs non-described versions)
21950  		var srcFile, vtsCues;
21951
21952  		srcFile = this.getFilenameFromPath(src);
21953  		vtsCues = this.parseVtsTracks(contents);
21954
21955  		this.vtsTracks.push({
21956  			'kind': kind,
21957  			'language': lang,
21958  			'label': label,
21959  			'srcFile': srcFile,
21960  			'cues': vtsCues
21961  		});
21962  	};
21963
21964  	AblePlayer.prototype.getFilenameFromPath = function(path) {
21965
21966  		var lastSlash;
21967  		lastSlash = path.lastIndexOf('/');
21968  		// fix slashes.
21969  		return (lastSlash === -1) ? path : path.substring(lastSlash+1);
21970  	};
21971
21972  	AblePlayer.prototype.getFilenameFromTracks = function(kind,lang) {
21973
21974  		for (var i=0; i<this.vtsTracks.length; i++) {
21975  			if (this.vtsTracks[i].kind === kind && this.vtsTracks[i].language === lang) {
21976  				// this is a matching track
21977  				// srcFile has already been converted to filename from path before saving to vtsTracks
21978  				return this.vtsTracks[i].srcFile;
21979  			}
21980  		}
21981  		// no matching track found
21982  		return false;
21983  	};
21984
21985  	AblePlayer.prototype.parseVtsTracks = function(contents) {
21986
21987  		var rows, timeParts, cues, i, j, thisRow, nextRow, content, blankRow;
21988  		rows = contents.split("\n");
21989  		cues = [];
21990  		i = 0;
21991  		while (i < rows.length) {
21992  			thisRow = rows[i];
21993  			if (thisRow.indexOf(' --> ') !== -1) {
21994  				// this is probably a time row
21995  				timeParts = thisRow.trim().split(' ');
21996  				if (this.isValidTimestamp(timeParts[0]) && this.isValidTimestamp(timeParts[2])) {
21997  					// both timestamps are valid. This is definitely a time row
21998  					content = '';
21999  					j = i+1;
22000  					blankRow = false;
22001  					while (j < rows.length && !blankRow) {
22002  						nextRow = rows[j].trim();
22003  						if (nextRow.length > 0) {
22004  							if (content.length > 0) {
22005  								// add back the EOL between rows of content
22006  								content += "\n" + nextRow;
22007  							} else {
22008  								// this is the first row of content. No need for an EOL
22009  								content += nextRow;
22010  							}
22011  						} else {
22012  							blankRow = true;
22013  						}
22014  						j++;
22015  					}
22016  					cues.push({
22017  						'start': timeParts[0],
22018  						'end': timeParts[2],
22019  						'content': content
22020  					});
22021  					i = j; //skip ahead
22022  				}
22023  			} else {
22024  				i++;
22025  			}
22026  		}
22027  		return cues;
22028  	};
22029
22030  	AblePlayer.prototype.isValidTimestamp = function(timestamp) {
22031
22032  		// return true if timestamp contains only numbers or expected punctuation
22033  		return (/^[0-9:,.]*$/.test(timestamp)) ? true : false;
22034  	};
22035
22036  	AblePlayer.prototype.formatTimestamp = function(timestamp) {
22037
22038  		// timestamp is a string in the form "HH:MM:SS.xxx"
22039  		// Take some simple steps to ensure edited timestamp values still adhere to expected format
22040
22041  		var firstPart, lastPart;
22042
22043  		firstPart = timestamp.substring(0,timestamp.lastIndexOf('.')+1);
22044  		lastPart = timestamp.substring(timestamp.lastIndexOf('.')+1);
22045
22046  		// TODO: Be sure each component within firstPart has only exactly two digits
22047  		// Probably can't justify doing this automatically
22048  		// If users enters '5' for minutes, that could be either '05' or '50'
22049  		// This should trigger an error and prompt the user to correct the value before proceeding
22050
22051  		// Be sure lastPart has exactly three digits
22052  		if (lastPart.length > 3) {
22053  			// chop off any extra digits
22054  			lastPart = lastPart.substring(0,3);
22055  		} else if (lastPart.length < 3) {
22056  			// add trailing zeros
22057  			while (lastPart.length < 3) {
22058  				lastPart += '0';
22059  			}
22060  		}
22061  		return firstPart + lastPart;
22062  	};
22063
22064
22065  	AblePlayer.prototype.injectVtsTable = function(action,lang) {
22066
22067  		// action is either 'add' (for a new table) or 'update' (if user has selected a new lang)
22068
22069  		var $table, $thead, headers, i, $tr, $th, $td, rows, rowNum, rowId;
22070
22071  		if (action === 'update') {
22072  			// remove existing table
22073  			$('#able-vts table').remove();
22074  			$('#able-vts-icon-credit').remove();
22075  		}
22076
22077  		$table = $('<table>',{
22078  			'lang': lang
22079  		});
22080  		$thead = $( '<thead>' );
22081  		$tr = $( '<tr>' );
22082  		headers = [
22083  			this.translate( 'vtsRow', 'Row' ),
22084  			this.translate( 'vtsKind', 'Kind' ),
22085  			this.translate( 'vtsStart', 'Start' ),
22086  			this.translate( 'vtsEnd', 'End' ),
22087  			this.translate( 'vtsContent', 'Content' ),
22088  			this.translate( 'vtsActions', 'Actions' )
22089  		];
22090  		for (i=0; i < headers.length; i++) {
22091  			$th = $('<th>', {
22092  				'scope': 'col'
22093  			}).text(headers[i]);
22094  			if (headers[i] === 'Actions') {
22095  				$th.addClass('actions');
22096  			}
22097  			$tr.append($th);
22098  		}
22099  		$thead.append($tr);
22100  		$table.append($thead);
22101
22102  		// Get all rows (sorted by start time), and inject them into table
22103  		rows = this.getAllRows(lang);
22104  		for (i=0; i < rows.length; i++) {
22105  			rowNum = i + 1;
22106  			rowId = 'able-vts-row-' + rowNum;
22107  			$tr = $('<tr>',{
22108  				'id': rowId,
22109  				'class': 'kind-' + rows[i].kind
22110  			});
22111  			// Row #
22112  			$td = $('<td>').text(rowNum);
22113  			$tr.append($td);
22114
22115  			// Kind
22116  			$td = $('<td>',{
22117  				'contenteditable': 'true'
22118  			}).text(rows[i].kind);
22119  			$tr.append($td);
22120
22121  			// Start
22122  			$td = $('<td>',{
22123  				'contenteditable': 'true'
22124  			}).text(rows[i].start);
22125  			$tr.append($td);
22126
22127  			// End
22128  			$td = $('<td>',{
22129  				'contenteditable': 'true'
22130  			}).text(rows[i].end);
22131  			$tr.append($td);
22132
22133  			// Content
22134  			$td = $('<td>',{
22135  				'contenteditable': 'true'
22136  			}).text(rows[i].content); // TODO: Preserve tags
22137  			$tr.append($td);
22138
22139  					// Actions
22140  			$td = this.addVtsActionButtons(rowNum,rows.length);
22141  			$tr.append($td);
22142
22143  			$table.append($tr);
22144  		}
22145  		$('#able-vts').append($table);
22146
22147  		// Add credit for action button SVG icons
22148  		$('#able-vts').append(this.getIconCredit());
22149
22150  	};
22151
22152  	AblePlayer.prototype.addVtsActionButtons = function(rowNum,numRows) {
22153
22154  		// rowNum is the number of the current table row (starting with 1)
22155  		// numRows is the total number of rows (excluding the header row)
22156  		// TODO: Position buttons so they're vertically aligned, even if missing an Up or Down button
22157  		var thisObj, $td, buttons, i, button, $button, $svg, $g, pathString, pathString2, $path, $path2;
22158  		thisObj = this;
22159  		$td = $('<td>');
22160  		buttons = ['up','down','insert','delete'];
22161
22162  		for (i=0; i < buttons.length; i++) {
22163  			button = buttons[i];
22164  			if (button === 'up') {
22165  				if (rowNum > 1) {
22166  					$button = $('<button>',{
22167  						'id': 'able-vts-button-up-' + rowNum,
22168  						'title': 'Move up',
22169  						'aria-label': 'Move Row ' + rowNum + ' up'
22170  					}).on('click', function(el) {
22171  						thisObj.onClickVtsActionButton(el.currentTarget);
22172  					});
22173  					$svg = $('<svg>',{
22174  						'focusable': 'false',
22175  						'aria-hidden': 'true',
22176  						'x': '0px',
22177  						'y': '0px',
22178  						'width': '254.296px',
22179  						'height': '254.296px',
22180  						'viewBox': '0 0 254.296 254.296',
22181  						'style': 'enable-background:new 0 0 254.296 254.296'
22182  					});
22183  					pathString = 'M249.628,176.101L138.421,52.88c-6.198-6.929-16.241-6.929-22.407,0l-0.381,0.636L4.648,176.101'
22184  						+ 'c-6.198,6.897-6.198,18.052,0,24.981l0.191,0.159c2.892,3.305,6.865,5.371,11.346,5.371h221.937c4.577,0,8.613-2.161,11.41-5.594'
22185  						+ 'l0.064,0.064C255.857,194.153,255.857,182.998,249.628,176.101z';
22186  					$path = $('<path>',{
22187  						'd': pathString
22188  					});
22189  					$g = $('<g>').append($path);
22190  					$svg.append($g);
22191  					$button.append($svg);
22192  					// Refresh button in the DOM in order for browser to process & display the SVG
22193  					$button.html($button.html());
22194  					$td.append($button);
22195  				}
22196  			} else if (button === 'down') {
22197  				if (rowNum < numRows) {
22198  					$button = $('<button>',{
22199  						'id': 'able-vts-button-down-' + rowNum,
22200  						'title': 'Move down',
22201  						'aria-label': 'Move Row ' + rowNum + ' down'
22202  					}).on('click', function(el) {
22203  						thisObj.onClickVtsActionButton(el.currentTarget);
22204  					});
22205  					$svg = $('<svg>',{
22206  						'focusable': 'false',
22207  						'aria-hidden': 'true',
22208  						'x': '0px',
22209  						'y': '0px',
22210  						'width': '292.362px',
22211  						'height': '292.362px',
22212  						'viewBox': '0 0 292.362 292.362',
22213  						'style': 'enable-background:new 0 0 292.362 292.362'
22214  					});
22215  					pathString = 'M286.935,69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952,0-9.233,1.807-12.85,5.424'
22216  						+ 'C1.807,72.998,0,77.279,0,82.228c0,4.948,1.807,9.229,5.424,12.847l127.907,127.907c3.621,3.617,7.902,5.428,12.85,5.428'
22217  						+ 's9.233-1.811,12.847-5.428L286.935,95.074c3.613-3.617,5.427-7.898,5.427-12.847C292.362,77.279,290.548,72.998,286.935,69.377z';
22218  					$path = $('<path>',{
22219  						'd': pathString
22220  					});
22221  					$g = $('<g>').append($path);
22222  					$svg.append($g);
22223  					$button.append($svg);
22224  					// Refresh button in the DOM in order for browser to process & display the SVG
22225  					$button.html($button.html());
22226  					$td.append($button);
22227  				}
22228  			} else if (button === 'insert') {
22229  				// Add Insert button to all rows
22230  				$button = $('<button>',{
22231  					'id': 'able-vts-button-insert-' + rowNum,
22232  					'title': 'Insert row below',
22233  					'aria-label': 'Insert row before Row ' + rowNum
22234  				}).on('click', function(el) {
22235  					thisObj.onClickVtsActionButton(el.currentTarget);
22236  				});
22237  				$svg = $('<svg>',{
22238  					'focusable': 'false',
22239  					'aria-hidden': 'true',
22240  					'x': '0px',
22241  					'y': '0px',
22242  					'width': '401.994px',
22243  					'height': '401.994px',
22244  					'viewBox': '0 0 401.994 401.994',
22245  					'style': 'enable-background:new 0 0 401.994 401.994'
22246  				});
22247  				pathString = 'M394,154.175c-5.331-5.33-11.806-7.994-19.417-7.994H255.811V27.406c0-7.611-2.666-14.084-7.994-19.414'
22248  					+ 'C242.488,2.666,236.02,0,228.398,0h-54.812c-7.612,0-14.084,2.663-19.414,7.993c-5.33,5.33-7.994,11.803-7.994,19.414v118.775'
22249  					+ 'H27.407c-7.611,0-14.084,2.664-19.414,7.994S0,165.973,0,173.589v54.819c0,7.618,2.662,14.086,7.992,19.411'
22250  					+ 'c5.33,5.332,11.803,7.994,19.414,7.994h118.771V374.59c0,7.611,2.664,14.089,7.994,19.417c5.33,5.325,11.802,7.987,19.414,7.987'
22251  					+ 'h54.816c7.617,0,14.086-2.662,19.417-7.987c5.332-5.331,7.994-11.806,7.994-19.417V255.813h118.77'
22252  					+ 'c7.618,0,14.089-2.662,19.417-7.994c5.329-5.325,7.994-11.793,7.994-19.411v-54.819C401.991,165.973,399.332,159.502,394,154.175z';
22253  				$path = $('<path>',{
22254  					'd': pathString
22255  				});
22256  				$g = $('<g>').append($path);
22257  				$svg.append($g);
22258  				$button.append($svg);
22259  				// Refresh button in the DOM in order for browser to process & display the SVG
22260  				$button.html($button.html());
22261  				$td.append($button);
22262  			} else if (button === 'delete') {
22263  				// Add Delete button to all rows
22264  				$button = $('<button>',{
22265  					'id': 'able-vts-button-delete-' + rowNum,
22266  					'title': 'Delete row ',
22267  					'aria-label': 'Delete Row ' + rowNum
22268  				}).on('click', function(el) {
22269  					thisObj.onClickVtsActionButton(el.currentTarget);
22270  				});
22271  				$svg = $('<svg>',{
22272  					'focusable': 'false',
22273  					'aria-hidden': 'true',
22274  					'x': '0px',
22275  					'y': '0px',
22276  					'width': '508.52px',
22277  					'height': '508.52px',
22278  					'viewBox': '0 0 508.52 508.52',
22279  					'style': 'enable-background:new 0 0 508.52 508.52'
22280  				});
22281  				pathString = 'M397.281,31.782h-63.565C333.716,14.239,319.478,0,301.934,0h-95.347'
22282  					+ 'c-17.544,0-31.782,14.239-31.782,31.782h-63.565c-17.544,0-31.782,14.239-31.782,31.782h349.607'
22283  					+ 'C429.063,46.021,414.825,31.782,397.281,31.782z';
22284  				$path = $('<path>',{
22285  					'd': pathString
22286  				});
22287  				pathString2 = 'M79.456,476.737c0,17.544,14.239,31.782,31.782,31.782h286.042'
22288  					+ 'c17.544,0,31.782-14.239,31.782-31.782V95.347H79.456V476.737z M333.716,174.804c0-8.772,7.151-15.891,15.891-15.891'
22289  					+ 'c8.74,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.151,15.891-15.891,15.891c-8.74,0-15.891-7.151-15.891-15.891V174.804z'
22290  					+ 'M238.369,174.804c0-8.772,7.119-15.891,15.891-15.891c8.74,0,15.891,7.119,15.891,15.891v254.26'
22291  					+ 'c0,8.74-7.151,15.891-15.891,15.891c-8.772,0-15.891-7.151-15.891-15.891V174.804z M143.021,174.804'
22292  					+ 'c0-8.772,7.119-15.891,15.891-15.891c8.772,0,15.891,7.119,15.891,15.891v254.26c0,8.74-7.119,15.891-15.891,15.891'
22293  					+ 'c-8.772,0-15.891-7.151-15.891-15.891V174.804z';
22294  				$path2 = $('<path>',{
22295  					'd': pathString2
22296  				});
22297
22298  				$g = $('<g>').append($path,$path2);
22299  				$svg.append($g);
22300  				$button.append($svg);
22301  				// Refresh button in the DOM in order for browser to process & display the SVG
22302  				$button.html($button.html());
22303  				$td.append($button);
22304  			}
22305  		}
22306  		return $td;
22307  	};
22308
22309  	AblePlayer.prototype.updateVtsActionButtons = function($buttons,nextRowNum) {
22310
22311  		// TODO: Add some filters to this function to add or delete 'Up' and 'Down' buttons
22312  		// if row is moved to/from the first/last rows
22313  		var i, $thisButton, id, label, newId, newLabel;
22314  		for (i=0; i < $buttons.length; i++) {
22315  			$thisButton = $buttons.eq(i);
22316  			id = $thisButton.attr('id');
22317  			label = $thisButton.attr('aria-label');
22318  			// replace the integer (id) within each of the above strings
22319  			newId = id.replace(/[0-9]+/g, nextRowNum);
22320  			newLabel = label.replace(/[0-9]+/g, nextRowNum);
22321  			$thisButton.attr('id',newId);
22322  			$thisButton.attr('aria-label',newLabel);
22323  		}
22324  	};
22325
22326  	AblePlayer.prototype.getIconCredit = function() {
22327
22328  		var credit
22329  			= 'Action buttons made by <a target="_blank" rel="noreferrer" href="https://www.elegantthemes.com">Elegant Themes</a>'
22330  			+ ' from <a target="_blank" rel="noreferrer" href="https://www.flaticon.com">flaticon</a>'
22331  			+ ' are licensed by <a target="_blank" rel="noreferrer" href="https://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0">CC 3.0 BY</a>'
22332  		;
22333  		return '<div id="able-vts-icon-credit">' + credit + '</div>';
22334  	};
22335
22336  	AblePlayer.prototype.getAllLangs = function(tracks) {
22337
22338  		// update this.langs with any unique languages found in tracks
22339  		var i;
22340  		for (i in tracks) {
22341  			if (Object.hasOwn(tracks[i], 'language')) {
22342  				if ($.inArray(tracks[i].language,this.langs) === -1) {
22343  					// this language is not already in the langs array. Add it.
22344  					this.langs[this.langs.length] = tracks[i].language;
22345  				}
22346  			}
22347  		}
22348  	};
22349
22350  	AblePlayer.prototype.getAllRows = function(lang) {
22351
22352  		// returns an array of data to be displayed in VTS table
22353  		// includes all cues for tracks of any type with matching lang
22354  		// cues are sorted by start time
22355  		var i, track, c, cues;
22356  		cues = [];
22357  		for (i=0; i < this.vtsTracks.length; i++) {
22358  			track = this.vtsTracks[i];
22359  			if (track.language == lang) {
22360  				// this track matches the language. Add its cues to array
22361  				for (c in track.cues) {
22362  					cues.push({
22363  						'kind': track.kind,
22364  						'lang': lang,
22365  						'id': track.cues[c].id,
22366  						'start': track.cues[c].start,
22367  						'end': track.cues[c].end,
22368  						'content': track.cues[c].content
22369  					});
22370  				}
22371  			}
22372  		}
22373  		// Now sort cues by start time
22374  		cues.sort(function(a,b) {
22375  			return a.start > b.start ? 1 : -1;
22376  		});
22377  		return cues;
22378  	};
22379
22380
22381  	AblePlayer.prototype.onClickVtsActionButton = function(el) {
22382
22383  		// handle click on up, down, insert, or delete button
22384  		var idParts, action, rowNum;
22385  		idParts = $(el).attr('id').split('-');
22386  		action = idParts[3];
22387  		rowNum = idParts[4];
22388  		if (action == 'up') {
22389  			// move the row up
22390  			this.moveRow(rowNum,'up');
22391  		} else if (action == 'down') {
22392  			// move the row down
22393  			this.moveRow(rowNum,'down');
22394  		} else if (action == 'insert') {
22395  			// insert a row below
22396  			this.insertRow(rowNum);
22397  		} else if (action == 'delete') {
22398  			// delete the row
22399  			this.deleteRow(rowNum);
22400  		}
22401  	};
22402
22403  	AblePlayer.prototype.insertRow = function(rowNum) {
22404
22405  		// Insert empty row below rowNum
22406  		var $table, $rows, numRows, newRowNum, newRowId, $tr, $td, $select,
22407  		options, i, $option, newKind, newClass, $parentRow, nextRowNum, $buttons;
22408
22409  		$table = $('#able-vts table');
22410  		$rows = $table.find('tr');
22411
22412  		numRows = $rows.length - 1; // exclude header row
22413
22414  		newRowNum = parseInt(rowNum) + 1;
22415  		newRowId = 'able-vts-row-' + newRowNum;
22416
22417  		// Create an empty row
22418  		$tr = $('<tr>',{
22419  			'id': newRowId
22420  		});
22421
22422  		// Row #
22423  		$td = $('<td>').text(newRowNum);
22424  		$tr.append($td);
22425
22426  		// Kind (add a select field for chosing a kind)
22427  		newKind = null;
22428  		$select = $('<select>',{
22429  			'id': 'able-vts-kind-' + newRowNum,
22430  			'aria-label': 'What kind of track is this?',
22431  			'placeholder': 'Select a kind'
22432  		}).on('change',function() {
22433  			newKind = $(this).val();
22434  			newClass = 'kind-' + newKind;
22435  			$parentRow = $(this).closest('tr');
22436  			// replace the select field with the chosen value as text
22437  			$(this).parent().text(newKind);
22438  			// add a class to the parent row
22439  			$parentRow.addClass(newClass);
22440  		});
22441  		options = ['','captions','chapters','descriptions','subtitles'];
22442  		for (i=0; i<options.length; i++) {
22443  			$option = $('<option>',{
22444  				'value': options[i]
22445  			}).text(options[i]);
22446  			$select.append($option);
22447  		}
22448  		$td = $('<td>').append($select);
22449  		$tr.append($td);
22450
22451  		// Start
22452  		$td = $('<td>',{
22453  			'contenteditable': 'true'
22454  		}); // TODO; Intelligently assign a new start time (see getAdjustedTimes())
22455  		$tr.append($td);
22456
22457  		// End
22458  		$td = $('<td>',{
22459  			'contenteditable': 'true'
22460  		}); // TODO; Intelligently assign a new end time (see getAdjustedTimes())
22461  		$tr.append($td);
22462
22463  		// Content
22464  		$td = $('<td>',{
22465  			'contenteditable': 'true'
22466  		});
22467  		$tr.append($td);
22468
22469  		// Actions
22470  		$td = this.addVtsActionButtons(newRowNum,numRows);
22471  		$tr.append($td);
22472
22473  		// Now insert the new row
22474  		$table.find('tr').eq(rowNum).after($tr);
22475
22476  		// Update row.id, Row # cell, & action items for all rows after the inserted one
22477  		for (i=newRowNum; i <= numRows; i++) {
22478  			nextRowNum = i + 1;
22479  			$rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id
22480  			$rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td
22481  			$buttons = $rows.eq(i).find('button');
22482  			this.updateVtsActionButtons($buttons,nextRowNum);
22483  		}
22484
22485  		// Auto-adjust times
22486  		this.adjustTimes(newRowNum);
22487
22488  		// Announce the insertion
22489  		let newAlert = this.translate( 'vtsNewRow', 'A new row %1 has been inserted.', [ newRowNum ] );
22490  		this.showVtsAlert( newAlert );
22491
22492  		// Place focus in new select field
22493  		$select.trigger('focus');
22494
22495  	};
22496
22497  	AblePlayer.prototype.deleteRow = function(rowNum) {
22498
22499  		var $table, $rows, numRows, i, nextRowNum, $buttons;
22500
22501  		$table = $('#able-vts table');
22502  		$table[0].deleteRow(rowNum);
22503  		$rows = $table.find('tr'); // this does not include the deleted row
22504  		numRows = $rows.length - 1; // exclude header row
22505
22506  		// Update row.id, Row # cell, & action buttons for all rows after the deleted one
22507  		for (i=rowNum; i <= numRows; i++) {
22508  			nextRowNum = i;
22509  			$rows.eq(i).attr('id','able-vts-row-' + nextRowNum); // increment tr id
22510  			$rows.eq(i).find('td').eq(0).text(nextRowNum); // increment Row # as expressed in first td
22511  			$buttons = $rows.eq(i).find('button');
22512  			this.updateVtsActionButtons($buttons,nextRowNum);
22513  		}
22514
22515  		// Announce the deletion
22516  		let newAlert = this.translate( 'vtsDeletedRow', 'Row %1 has been deleted.', [ rowNum ] );
22517  		this.showVtsAlert( newAlert );
22518
22519  	};
22520
22521  	AblePlayer.prototype.moveRow = function(rowNum,direction) {
22522
22523  		// swap two rows
22524  		var $thisRow, otherRowNum, $otherRow, msg;
22525
22526  		$thisRow = $('#able-vts table').find('tr').eq(rowNum);
22527  		if (direction == 'up') {
22528  			otherRowNum = parseInt(rowNum) - 1;
22529  			$otherRow = $('#able-vts table').find('tr').eq(otherRowNum);
22530  			$otherRow.before($thisRow);
22531  		} else if (direction == 'down') {
22532  			otherRowNum = parseInt(rowNum) + 1;
22533  			$otherRow = $('#able-vts table').find('tr').eq(otherRowNum);
22534  			$otherRow.after($thisRow);
22535  		}
22536  		// Update row.id, Row # cell, & action buttons for the two swapped rows
22537  		$thisRow.attr('id','able-vts-row-' + otherRowNum);
22538  		$thisRow.find('td').eq(0).text(otherRowNum);
22539  		this.updateVtsActionButtons($thisRow.find('button'),otherRowNum);
22540  		$otherRow.attr('id','able-vts-row-' + rowNum);
22541  		$otherRow.find('td').eq(0).text(rowNum);
22542  		this.updateVtsActionButtons($otherRow.find('button'),rowNum);
22543
22544  		// auto-adjust times
22545  		this.adjustTimes(otherRowNum);
22546
22547  		// Announce the move
22548  		msg = this.translate( 'vtsMovedRow', 'Row %1 has been moved %2 and is now Row %3.', [ rowNum, direction, otherRowNum ] );
22549  		this.showVtsAlert(msg);
22550  	};
22551
22552  	AblePlayer.prototype.adjustTimes = function(rowNum) {
22553
22554  		// Adjusts start and end times of the current, previous, and next rows in VTS table
22555  		// after a move or insert
22556  		// NOTE: Fully automating this process would be extraordinarily complicated
22557  		// The goal here is simply to make subtle tweaks to ensure rows appear
22558  		// in the new order within the Able Player transcript
22559  		// Additional tweaking will likely be required by the user
22560
22561  		// HISTORY: Originally set minDuration to 2 seconds for captions and .500 for descriptions
22562  		// However, this can results in significant changes to existing caption timing,
22563  		// with not-so-positive results.
22564  		// As of 3.1.15, setting minDuration to .001 for all track kinds
22565  		// Users will have to make further adjustments manually if needed
22566
22567  		// TODO: Add WebVTT validation on save, since tweaking times is risky
22568
22569  		var	 minDuration, $rows, prevRowNum, nextRowNum, $row, $prevRow, $nextRow,
22570  				kind, prevKind, nextKind,
22571  				start, prevStart, nextStart,
22572  				end, prevEnd, nextEnd;
22573
22574  		// Define minimum duration (in seconds) for each kind of track
22575  		minDuration = [];
22576  		minDuration['captions'] = .001;
22577  		minDuration['descriptions'] = .001;
22578  		minDuration['chapters'] = .001;
22579
22580  		// refresh rows object
22581  		$rows = $('#able-vts table').find('tr');
22582
22583  		// Get kind, start, and end from current row
22584  		$row = $rows.eq(rowNum);
22585  		// row has a class that starts with "kind-"
22586  		// Extract kind from the class name
22587  		kind = ($row.is('[class^="kind-"]')) ? this.getKindFromClass($row.attr('class')) : 'captions';
22588
22589  		start = this.getSecondsFromColonTime($row.find('td').eq(2).text());
22590  		end = this.getSecondsFromColonTime($row.find('td').eq(3).text());
22591
22592  		// Get kind, start, and end from previous row
22593  		if (rowNum > 1) {
22594  			// this is not the first row. Include the previous row
22595  			prevRowNum = rowNum - 1;
22596  			$prevRow = $rows.eq(prevRowNum);
22597  			// row has a class that starts with "kind-"
22598  			// Extract kind from the class name
22599  			prevKind = ($prevRow.is('[class^="kind-"]')) ? this.getKindFromClass($prevRow.attr('class')) : null;
22600  			prevStart = this.getSecondsFromColonTime($prevRow.find('td').eq(2).text());
22601  			prevEnd = this.getSecondsFromColonTime($prevRow.find('td').eq(3).text());
22602  		} else {
22603  			// this is the first row
22604  			$prevRow = null;
22605  			prevKind = null;
22606  			prevStart = null;
22607  			prevEnd = null;
22608  		}
22609
22610  		// Get kind, start, and end from next row
22611  		if (rowNum < ($rows.length - 1)) {
22612  			// this is not the last row. Include the next row
22613  			nextRowNum = rowNum + 1;
22614  			$nextRow = $rows.eq(nextRowNum);
22615  			// row has a class that starts with "kind-"
22616  			// Extract kind from the class name
22617  			nextKind = ($nextRow.is('[class^="kind-"]')) ? this.getKindFromClass($nextRow.attr('class')) : null;
22618  			nextStart = this.getSecondsFromColonTime($nextRow.find('td').eq(2).text());
22619  			nextEnd = this.getSecondsFromColonTime($nextRow.find('td').eq(3).text());
22620  		} else {
22621  			// this is the last row
22622  			$nextRow = null;
22623  			nextKind = null;
22624  			nextStart = null;
22625  			nextEnd = null;
22626  		}
22627
22628  		if (isNaN(start)) {
22629  			if (prevKind == null) {
22630  				// The previous row was probably inserted, and user has not yet selected a kind
22631  				// automatically set it to captions
22632  				prevKind = 'captions';
22633  				$prevRow.attr('class','kind-captions');
22634  				$prevRow.find('td').eq(1).html('captions');
22635  			}
22636  			// Current row has no start time (i.e., it's an inserted row)
22637  			if (prevKind === 'captions') {
22638  				// start the new row immediately after the captions end
22639  				start = (parseFloat(prevEnd) + .001).toFixed(3);
22640  				// end the new row immediately before the next row starts
22641  				end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3);
22642  			} else if (prevKind === 'chapters') {
22643  				// start the new row immediately after the chapter start (not end)
22644  				start = (parseFloat(prevStart) + .001).toFixed(3);
22645  				// end the new row immediately before the next row starts
22646  				end = (nextStart) ? (parseFloat(nextStart) - .001).toFixed(3) : (parseFloat(start) + minDuration[kind]).toFixed(3);
22647  			} else if (prevKind === 'descriptions') {
22648  				// start the new row minDuration['descriptions'] after the description starts
22649  				// this will theoretically allow at least a small cushion for the description to be read
22650  				start = (parseFloat(prevStart) + minDuration['descriptions']).toFixed(3);
22651  				end = (parseFloat(start) + minDuration['descriptions']).toFixed(3);
22652  			}
22653  		} else {
22654  			// current row has a start time (i.e., an existing row has been moved))
22655  			if (prevStart) {
22656  				// this is not the first row.
22657  				if (prevStart < start) {
22658  					if (start < nextStart) ; else {
22659  						// nextStart needs to be incremented
22660  						nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3);
22661  						nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3);
22662  						// TODO: Ensure nextEnd does not exceed the following start (nextNextStart)
22663  						// Or... maybe this is getting too complicated and should be left up to the user
22664  					}
22665  				} else {
22666  					// start needs to be incremented
22667  					start = (parseFloat(prevStart) + minDuration[prevKind]).toFixed(3);
22668  					end = (parseFloat(start) + minDuration[kind]).toFixed(3);
22669  				}
22670  			} else {
22671  				// this is the first row
22672  				if (start < nextStart) ; else {
22673  					// nextStart needs to be incremented
22674  					nextStart = (parseFloat(start) + minDuration[kind]).toFixed(3);
22675  					nextEnd = (parseFloat(nextStart) + minDuration[nextKind]).toFixed(3);
22676  				}
22677  			}
22678  		}
22679
22680  		// check to be sure there is sufficient duration between new start & end times
22681  		if (end - start < minDuration[kind]) {
22682  			// duration is too short. Change end time
22683  			end = (parseFloat(start) + minDuration[kind]).toFixed(3);
22684  			if (nextStart) {
22685  				// this is not the last row
22686  				// increase start time of next row
22687  				nextStart = (parseFloat(end) + .001).toFixed(3);
22688  			}
22689  		}
22690
22691  		// Update all affected start/end times
22692  		$row.find('td').eq(2).text(this.formatSecondsAsColonTime(start,true));
22693  		$row.find('td').eq(3).text(this.formatSecondsAsColonTime(end,true));
22694  		if ($prevRow) {
22695  			$prevRow.find('td').eq(2).text(this.formatSecondsAsColonTime(prevStart,true));
22696  			$prevRow.find('td').eq(3).text(this.formatSecondsAsColonTime(prevEnd,true));
22697  		}
22698  		if ($nextRow) {
22699  			$nextRow.find('td').eq(2).text(this.formatSecondsAsColonTime(nextStart,true));
22700  			$nextRow.find('td').eq(3).text(this.formatSecondsAsColonTime(nextEnd,true));
22701  		}
22702  	};
22703
22704  	AblePlayer.prototype.getKindFromClass = function(myclass) {
22705
22706  		// This function is called when a class with prefix "kind-" is found in the class attribute
22707
22708  		var kindStart, kindEnd;
22709
22710  		kindStart = myclass.indexOf('kind-')+5;
22711  		kindEnd = myclass.indexOf(' ',kindStart);
22712  		if (kindEnd == -1) {
22713  			// no spaces found, "kind-" must be the only myclass
22714  			return myclass.substring(kindStart);
22715  		} else {
22716  			// kind-* is one of multiple classes
22717  			// the following will find it regardless of position of "kind-*" within the class string
22718  			return myclass.substring(kindStart,kindEnd);
22719  		}
22720  	};
22721
22722  	AblePlayer.prototype.showVtsAlert = function(message) {
22723
22724  		// this is distinct from greater Able Player showAlert()
22725  		// because it's positioning needs are unique
22726  		// For now, alertDiv is fixed at top left of screen
22727  		// but could ultimately be modified to appear near the point of action in the VTS table
22728  		const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
22729  		this.$vtsAlert.text(message).show();
22730  		delay(10000).then(() => {
22731  			this.$vtsAlert.text(message).hide();
22732  		});
22733  	};
22734
22735  	AblePlayer.prototype.parseVtsOutput = function($table) {
22736
22737  		// parse table into arrays, then into WebVTT content, for each kind
22738  		// Display the WebVTT content in textarea fields for users to copy and paste
22739  		var lang, i, kinds, kind, vtt, $rows, start, end, content, $output;
22740
22741  		lang = $table.attr('lang');
22742  		kinds = ['captions','chapters','descriptions','subtitles'];
22743  		vtt = {};
22744  		for (i=0; i < kinds.length; i++) {
22745  			kind = kinds[i];
22746  			vtt[kind] = 'WEBVTT' + "\n\n";
22747  		}
22748  		$rows = $table.find('tr');
22749  		if ($rows.length > 0) {
22750  			for (i=0; i < $rows.length; i++) {
22751  				kind = $rows.eq(i).find('td').eq(1).text();
22752  				if ($.inArray(kind,kinds) !== -1) {
22753  					start = $rows.eq(i).find('td').eq(2).text();
22754  					end = $rows.eq(i).find('td').eq(3).text();
22755  					content = $rows.eq(i).find('td').eq(4)[0].innerText;
22756  					if (start !== undefined && end !== undefined) {
22757  						vtt[kind] += start + ' --> ' + end + "\n";
22758  						if (content !== 'undefined') {
22759  							vtt[kind] += content;
22760  						}
22761  						vtt[kind] += "\n\n";
22762  					}
22763  				}
22764  			}
22765  		}
22766  		$output = $('<div>',{
22767  			'id': 'able-vts-output'
22768  		});
22769  		$('#able-vts').append($output);
22770  		for (i=0; i < kinds.length; i++) {
22771  			kind = kinds[i];
22772  			if (vtt[kind].length > 8) {
22773  				// some content has been added
22774  				this.showWebVttOutput(kind,vtt[kind],lang);
22775  			}
22776  		}
22777  	};
22778
22779  	AblePlayer.prototype.showWebVttOutput = function(kind,vttString,lang) {
22780
22781  		var $heading, filename, $p, pText, $textarea;
22782
22783  		$heading = $('<h3>').text( this.capitalizeFirstLetter( kind ) );
22784  		filename = this.getFilenameFromTracks(kind,lang);
22785  		pText = 'If you made changes, copy/paste the following content ';
22786  		if (filename) {
22787  			pText += 'to replace the original content of your ' + this.getLanguageName(lang) + ' ';
22788  			pText += '<em>' + kind + '</em> WebVTT file (<strong>' + filename + '</strong>).';
22789  		} else {
22790  			pText += 'into a new ' + this.getLanguageName(lang) + ' <em>' + kind + '</em> WebVTT file.';
22791  		}
22792  		$p = $('<p>',{
22793  			'class': 'able-vts-output-instructions'
22794  		}).html(pText);
22795  		$textarea = $('<textarea>').text(vttString);
22796  		$('#able-vts-output').append($heading,$p,$textarea);
22797  	};
22798
22799  }
22800
22801  function addWebvttFunctions(AblePlayer) {
22802  	// See section 4.1 of dev.w3.org/html5/webvtt for format details.
22803  	AblePlayer.prototype.parseWebVTT = function(data) {
22804
22805  		let srcFile = data.src;
22806  		let text    = data.text;
22807  		// Normalize line ends to \n.
22808  		text = text.replace(/(\r\n|\n|\r)/g,'\n');
22809
22810  		var parserState = {
22811  			src: srcFile,
22812  			text: text,
22813  			error: null,
22814  			metadata: {},
22815  			cues: [],
22816  			line: 1,
22817  			column: 1
22818  		};
22819
22820  		try {
22821  			act(parserState, parseFileBody);
22822  		}
22823  		catch (err) {
22824  			var errString = 'Invalid WebVTT file: ' + parserState.src + '\n';
22825  			errString += 'Line: ' + parserState.line + ', ';
22826  			errString += 'Column: ' + parserState.column + '\n';
22827  			errString += err;
22828  			if (console.warn) {
22829  				console.warn(errString);
22830  			} else if (console.log) {
22831  				console.log(errString);
22832  			}
22833  		}
22834  		return parserState;
22835  	};
22836
22837  	function actList(state, list) {
22838  		var results = [];
22839  		for (var ii = 0; ii < list.length; ii++) {
22840  			results.push(act(state, list[ii]));
22841  		}
22842  		return results;
22843  	}
22844
22845  	// Applies the action and checks for errors.
22846  	function act(state, action) {
22847  		var val = action(state);
22848  		if (state.error !== null) {
22849  			throw state.error;
22850  		}
22851  		return val;
22852  	}
22853
22854  	function updatePosition(state, cutText) {
22855  		for (var ii = 0; ii < cutText.length; ii++) {
22856  			if (cutText[ii] === '\n') {
22857  				state.column = 1;
22858  				state.line += 1;
22859  			} else {
22860  				state.column += 1;
22861  			}
22862  		}
22863  	}
22864
22865  	function cut(state, length) {
22866  		var returnText = state.text.substring(0, length);
22867  		updatePosition(state, returnText);
22868  		state.text = state.text.substring(length);
22869  		return returnText;
22870  	}
22871
22872  	function cutLine(state) {
22873  		var nextEOL = state.text.indexOf('\n');
22874  		var returnText;
22875  		if (nextEOL === -1) {
22876  			returnText = state.text;
22877  			updatePosition(state, returnText);
22878  			state.text = '';
22879  		} else {
22880  			returnText = state.text.substring(0, nextEOL);
22881  			updatePosition(state, returnText + '\n');
22882  			state.text = state.text.substring(nextEOL + 1);
22883  		}
22884  		return returnText;
22885  	}
22886
22887  	function peekLine(state) {
22888  		var nextEOL = state.text.indexOf('\n');
22889  		return (nextEOL === -1) ? state.text : state.text.substring(0, nextEOL);
22890  	}
22891
22892  	function parseFileBody(state) {
22893  		actList(state, [
22894  			eatOptionalBOM,
22895  			eatSignature]);
22896  		var c = state.text[0];
22897  		if (c === ' ' || c === '\t' || c === '\n') {
22898  			actList(state, [
22899  				eatUntilEOLInclusive,
22900  				parseMetadataHeaders,
22901  				eatAtLeast1EmptyLines,
22902  				parseCuesAndComments]);
22903  		} else {
22904  			state.error = "WEBVTT signature not followed by whitespace.";
22905  		}
22906  	}
22907
22908  	// Parses all metadata headers until a cue is discovered.
22909  	function parseMetadataHeaders(state) {
22910  		while (true) {
22911  			var nextLine = peekLine(state);
22912  			if (nextLine.indexOf('-->') !== -1) {
22913  				return;
22914  			} else if (nextLine.length === 0) {
22915  				return;
22916  			} else {
22917  				var keyValue = act(state, getMetadataKeyValue);
22918  				state.metadata[keyValue[0]] = keyValue[1];
22919  				act(state, eatUntilEOLInclusive);
22920  			}
22921  		}
22922  	}
22923
22924  	function nextSpaceOrNewline(s) {
22925  		var possible = [];
22926  		var spaceIndex = s.indexOf(' ');
22927  		if (spaceIndex >= 0) {
22928  			possible.push(spaceIndex);
22929  		}
22930  		var tabIndex = s.indexOf('\t');
22931  		if (tabIndex >= 0) {
22932  			possible.push(tabIndex);
22933  		}
22934  		var lineIndex = s.indexOf('\n');
22935  		if (lineIndex >= 0) {
22936  			possible.push(lineIndex);
22937  		}
22938
22939  		return Math.min.apply(null, possible);
22940  	}
22941
22942  	function getMetadataKeyValue(state) {
22943  		var next = state.text.indexOf('\n');
22944  		var pair = cut(state, next);
22945  		var colon = pair.indexOf(':');
22946  		if (colon === -1) {
22947  			state.error = 'Missing colon.';
22948  			return;
22949  		} else {
22950  			var pairName = pair.substring(0, colon);
22951  			var pairValue = pair.substring(colon + 1);
22952  			return [pairName, pairValue];
22953  		}
22954  	}
22955
22956  	function getSettingsKeyValue(state) {
22957  		var next = nextSpaceOrNewline(state.text);
22958  		var pair = cut(state, next);
22959  		var colon = pair.indexOf(':');
22960  		if (colon === -1) {
22961  			state.error = 'Missing colon.';
22962  			return;
22963  		} else {
22964  			var pairName = pair.substring(0, colon);
22965  			var pairValue = pair.substring(colon + 1);
22966  			return [pairName, pairValue];
22967  		}
22968  	}
22969
22970  	function parseCuesAndComments(state) {
22971  		while (true) {
22972  			var nextLine = peekLine(state);
22973  			// If NOTE is not on a line all its own, it must be followed by a space or tab.
22974  			if (nextLine.indexOf('NOTE') === 0 && ((nextLine.length === 4) || (nextLine[4] === ' ') || (nextLine[4] === '\t'))) {
22975  				actList(state, [eatComment, eatEmptyLines]);
22976  			} else if (nextLine.trim().length === 0 && state.text.length > 0) {
22977  				act(state, eatEmptyLines);
22978  			} else if (nextLine.trim().length > 0) {
22979  				act(state, parseCue);
22980  			} else {
22981  				// Everythings parsed!
22982  				return;
22983  			}
22984  		}
22985  	}
22986
22987  	function parseCue(state) {
22988
22989  		var nextLine = peekLine(state);
22990  		var cueId;
22991  		var errString;
22992
22993  		if(nextLine.indexOf('-->') === -1) {
22994  			cueId = cutLine(state);
22995  			nextLine = peekLine(state);
22996  			if(nextLine.indexOf('-->') === -1) {
22997  				errString = 'Invalid WebVTT file: ' + state.src + '\n';
22998  				errString += 'Line: ' + state.line + ', ';
22999  				errString += 'Column: ' + state.column + '\n';
23000  				errString += 'Expected cue timing for cueId \''+cueId+'\' but found: ' + nextLine + '\n';
23001  				if (console.warn) {
23002  					console.warn(errString);
23003  				} else if (console.log) {
23004  					console.log(errString);
23005  				}
23006  				return; // Return leaving line for parseCuesAndComments to handle
23007  			}
23008  		}
23009
23010  		var cueTimings = actList(state, [getTiming,
23011  																		 eatAtLeast1SpacesOrTabs,
23012  																		 eatArrow,
23013  																		 eatAtLeast1SpacesOrTabs,
23014  																		 getTiming]);
23015
23016  		var startTime = cueTimings[0];
23017  		var endTime = cueTimings[4];
23018  		if (startTime >= endTime) {
23019  			state.error = 'Start time is not sooner than end time.';
23020  			return;
23021  		}
23022
23023  		act(state, eatSpacesOrTabs);
23024  		var cueSettings = act(state, getCueSettings);
23025  		// Cut the newline.
23026  		cut(state, 1);
23027  		var components = act(state, getCuePayload);
23028
23029  		if (typeof cueId === 'undefined') {
23030  			cueId = state.cues.length + 1;
23031  		}
23032  		state.cues.push({
23033  			id: cueId,
23034  			start: startTime,
23035  			end: endTime,
23036  			settings: cueSettings,
23037  			components: components
23038  		});
23039  	}
23040
23041  	function getCueSettings(state) {
23042  		var cueSettings = {};
23043  		while (state.text.length > 0 && state.text[0] !== '\n') {
23044  			var keyValue = act(state, getSettingsKeyValue);
23045  			cueSettings[keyValue[0]] = keyValue[1];
23046  			act(state, eatSpacesOrTabs);
23047  		}
23048  		return cueSettings;
23049  	}
23050
23051  	function getCuePayload(state) {
23052  		// Parser based on instructions in draft.
23053  		var result = {type: 'internal', tagName: '', value: '', classes: [], annotation: '', parent: null, children: [], language: ''};
23054  		var current = result;
23055  		var languageStack = [];
23056  		while (state.text.length > 0) {
23057  			var nextLine = peekLine(state);
23058  			if (nextLine.indexOf('-->') !== -1 || /^\s+$/.test(nextLine)) {
23059  				break; // Handle empty cues
23060  			}
23061  			// Have to separately detect double-lines ending cue due to our non-standard parsing.
23062  			// TODO: Redo outer algorithm to conform to W3 spec?
23063  			if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') {
23064  				cut(state, 2);
23065  				break;
23066  			}
23067
23068  			var token = getCueToken(state);
23069  			// We'll use the tokens themselves as objects where possible.
23070  			if (token.type === 'string') {
23071  				current.children.push(token);
23072  			} else if (token.type === 'startTag') {
23073  				token.type = token.tagName;
23074  				// Define token.parent; added by Terrill to fix bug end 'endTag' loop
23075  				token.parent = current;
23076  				if ($.inArray(token.tagName, ['i', 'b', 'u', 'ruby']) !== -1) {
23077  					if (languageStack.length > 0) {
23078  						current.language = languageStack[languageStack.length - 1];
23079  					}
23080  					current.children.push(token);
23081  					current = token;
23082  				} else if (token.tagName === 'rt' && current.tagName === 'ruby') {
23083  					if (languageStack.length > 0) {
23084  						current.language = languageStack[languageStack.length - 1];
23085  					}
23086  					current.children.push(token);
23087  					current = token;
23088  				} else if (token.tagName === 'c') {
23089  					token.value = token.annotation;
23090  					if (languageStack.length > 0) {
23091  						current.language = languageStack[languageStack.length - 1];
23092  					}
23093  					current.children.push(token);
23094  					current = token;
23095  				} else if (token.tagName === 'v') {
23096  					token.value = token.annotation;
23097  					if (languageStack.length > 0) {
23098  						current.language = languageStack[languageStack.length - 1];
23099  					}
23100  					current.children.push(token);
23101  					current = token;
23102  				} else if (token.tagName === 'lang') {
23103  					languageStack.push(token.annotation);
23104  					if (languageStack.length > 0) {
23105  						current.language = languageStack[languageStack.length - 1];
23106  					}
23107  					current.children.push(token);
23108  					current = token;
23109  				}
23110  			} else if (token.type === 'endTag') {
23111  				if (token.tagName === current.type && $.inArray(token.tagName, ['c', 'i', 'b', 'u', 'ruby', 'rt', 'v']) !== -1) {
23112  					// NOTE from Terrill: This was resulting in an error because current.parent was undefined
23113  					// Fixed (I think) by assigning current token to token.parent in 'startTag' loop
23114  					current = current.parent;
23115  				} else if (token.tagName === 'lang' && current.type === 'lang') {
23116  					current = current.parent;
23117  					languageStack.pop();
23118  				} else if (token.tagName === 'ruby' && current.type === 'rt') {
23119  					current = current.parent.parent;
23120  				}
23121  			} else if (token.type === 'timestampTag') {
23122  				var tempState = {
23123  					text: token.value,
23124  					error: null,
23125  					metadata: {},
23126  					cues: [],
23127  					line: 1,
23128  					column: 1
23129  				};
23130  				try {
23131  					var timing = act(tempState, getTiming);
23132  					if (tempState.text.length === 0) {
23133  						token.value = timing;
23134  						current.push(token);
23135  					}
23136  				}
23137  				catch (e) {
23138  					// Errors are suppressed here, not sure why - VP 2026-03-02
23139  				}
23140  			}
23141  		}
23142  		return result;
23143  	}
23144
23145  	// Gets a single cue token; uses the method in the w3 specification.
23146  	function getCueToken(state) {
23147  		var tokenState = 'data';
23148  		var result = [];
23149  		var buffer = '';
23150  		var token = {type: '', tagName: '', value: '', classes: [], annotation: '', children: []};
23151
23152  		while (true) {
23153  			var c;
23154  			// Double newlines indicate end of token.
23155  			if (state.text.length >= 2 && state.text[0] === '\n' && state.text[1] === '\n') {
23156  				c = '\u0004';
23157  			} else if (state.text.length > 0) {
23158  				c = state.text[0];
23159  			} else {
23160  				// End of file.
23161  				c = '\u0004';
23162  			}
23163  			if (tokenState === 'data') {
23164  				if (c === '&') {
23165  					buffer = '&';
23166  					tokenState = 'escape';
23167  				} else if (c === '<') {
23168  					if (result.length === 0) {
23169  						tokenState = 'tag';
23170  					} else {
23171  						token.type = 'string';
23172  						token.value = result.join('');
23173  						return token;
23174  					}
23175  				} else if (c === '\u0004') {
23176  					return {type: 'string', value: result.join('')};
23177  				} else {
23178  					result.push(c);
23179  				}
23180  			} else if (tokenState === 'escape') {
23181  				if (c === '&') {
23182  					result.push(buffer);
23183  					buffer = '&';
23184  				} else if (c.match(/[0-9a-z]/)) {
23185  					buffer += c;
23186  				} else if (c === ';') {
23187  					if (buffer === '&amp') {
23188  						result.push('&');
23189  					} else if (buffer === '&lt') {
23190  						result.push('<');
23191  					} else if (buffer === '&gt') {
23192  						result.push('>');
23193  					} else if (buffer === '&lrm') {
23194  						result.push('\u200e');
23195  					} else if (buffer === '&rlm') {
23196  						result.push('\u200f');
23197  					} else if (buffer === '&nbsp') {
23198  						result.push('\u00a0');
23199  					} else {
23200  						result.push(buffer);
23201  						result.push(';');
23202  					}
23203  					tokenState = 'data';
23204  				} else if (c === '<' || c === '\u0004') {
23205  					result.push(buffer);
23206  					token.type = 'string';
23207  					token.value = result.join('');
23208  					return token;
23209  				} else if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') { // Handle unescaped & chars as strings
23210  					result.push(buffer);
23211  					token.type = 'string';
23212  					token.value = result.join('');
23213  					return token;
23214  				} else {
23215  					result.push(buffer);
23216  					tokenState = 'data';
23217  				}
23218  			} else if (tokenState === 'tag') {
23219  				if (c === '\t' || c === '\n' || c === '\u000c' || c === ' ') {
23220  					tokenState = 'startTagAnnotation';
23221  				} else if (c === '.') {
23222  					tokenState = 'startTagClass';
23223  				} else if (c === '/') {
23224  					tokenState = 'endTag';
23225  				} else if (c.match('[0-9]')) {
23226  					tokenState = 'timestampTag';
23227  					result.push(c);
23228  				} else if (c === '>') {
23229  					cut(state, 1);
23230  					break;
23231  				} else if (c === '\u0004') {
23232  					token.tagName = '';
23233  					token.type = 'startTag';
23234  					return token;
23235  				} else {
23236  					result.push(c);
23237  					tokenState = 'startTag';
23238  				}
23239  			} else if (tokenState === 'startTag') {
23240  				if (c === '\t' || c === '\u000c' || c === ' ') {
23241  					tokenState = 'startTagAnnotation';
23242  				} else if (c === '\n') {
23243  					buffer = c;
23244  					tokenState = 'startTagAnnotation';
23245  				} else if (c === '.') {
23246  					tokenState = 'startTagClass';
23247  				} else if (c === '>') {
23248  					cut(state, 1);
23249  					token.tagName = result.join('');
23250  					token.type = 'startTag';
23251  					return token;
23252  				} else if (c === '\u0004') {
23253  					token.tagName = result.join('');
23254  					token.type = 'startTag';
23255  					return token;
23256  				} else {
23257  					result.push(c);
23258  				}
23259  			} else if (tokenState === 'startTagClass') {
23260  				if (c === '\t' || c === '\u000c' || c === ' ') {
23261  					token.classes.push(buffer);
23262  					buffer = '';
23263  					tokenState = 'startTagAnnotation';
23264  				} else if (c === '\n') {
23265  					token.classes.push(buffer);
23266  					buffer = c;
23267  					tokenState = 'startTagAnnotation';
23268  				} else if (c === '.') {
23269  					token.classes.push(buffer);
23270  					buffer = "";
23271  				} else if (c === '>') {
23272  					cut(state, 1);
23273  					token.classes.push(buffer);
23274  					token.type = 'startTag';
23275  					token.tagName = result.join('');
23276  					return token;
23277  				} else if (c === '\u0004') {
23278  					token.classes.push(buffer);
23279  					token.type = 'startTag';
23280  					token.tagName = result.join('');
23281  					return token;
23282  				} else {
23283  					buffer += 'c';
23284  				}
23285  			} else if (tokenState === 'startTagAnnotation') {
23286  				if (c === '>') {
23287  					cut(state, 1);
23288  					buffer = buffer.trim().replace(/ +/, ' ');
23289  					token.type = 'startTag';
23290  					token.tagName = result.join('');
23291  					token.annotation = buffer;
23292  					return token;
23293  				} else if (c === '\u0004') {
23294  					buffer = buffer.trim().replace(/ +/, ' ');
23295  					token.type = 'startTag';
23296  					token.tagName = result.join('');
23297  					token.annotation = buffer;
23298  					return token;
23299  				} else {
23300  					buffer += c;
23301  				}
23302  			} else if (tokenState === 'endTag') {
23303  				if (c === '>') {
23304  					cut(state, 1);
23305  					token.type = 'endTag';
23306  					token.tagName = result.join('');
23307  					return token;
23308  				} else if (c === '\u0004') {
23309  					token.type = 'endTag';
23310  					token.tagName = result.join('');
23311  					return token;
23312  				} else {
23313  					result.push(c);
23314  				}
23315  			} else if (tokenState === 'timestampTag') {
23316  				if (c === '>') {
23317  					cut(state, 1);
23318  					token.type = 'timestampTag';
23319  					token.name = result.join('');
23320  					return token;
23321  				} else if (c === '\u0004') {
23322  					token.type = 'timestampTag';
23323  					token.name = result.join('');
23324  					return token;
23325  				} else {
23326  					result.push(c);
23327  				}
23328  			} else {
23329  				throw 'Unknown tokenState ' + tokenState;
23330  			}
23331
23332  			cut(state, 1);
23333  		}
23334  	}
23335
23336  	function eatComment(state) {
23337  		// Cut the NOTE line.
23338  		var noteLine = cutLine(state);
23339  		if (noteLine.indexOf('-->') !== -1) {
23340  			state.error = 'Invalid syntax: --> in NOTE line.';
23341  			return;
23342  		}
23343  		while (true) {
23344  			var nextLine = peekLine(state);
23345  			if ( nextLine.trim().length === 0) {
23346  				// End of comment.
23347  				return;
23348  			} else if (nextLine.indexOf('-->') !== -1) {
23349  				state.error = 'Invalid syntax: --> in comment.';
23350  				return;
23351  			} else {
23352  				cutLine(state);
23353  			}
23354  		}
23355  	}
23356
23357  	// Initial byte order mark.
23358  	function eatOptionalBOM(state) {
23359  		if (state.text[0] === '\ufeff') {
23360  			cut(state, 1);
23361  		}
23362
23363  	}
23364
23365  	// "WEBVTT" string.
23366  	function eatSignature(state) {
23367  		if (state.text.substring(0,6) === 'WEBVTT') {
23368  			cut(state, 6);
23369  		} else {
23370  			state.error = 'Invalid signature.';
23371  		}
23372  	}
23373
23374  	function eatArrow(state) {
23375  		if (state.text.length < 3 || state.text.substring(0,3) !== '-->') {
23376  			state.error = 'Missing -->';
23377  		} else {
23378  			cut(state, 3);
23379  		}
23380  	}
23381
23382  	function eatSpacesOrTabs(state) {
23383  		while (state.text[0] === '\t' || state.text[0] === ' ') {
23384  			cut(state, 1);
23385  		}
23386  	}
23387
23388  	function eatAtLeast1SpacesOrTabs(state) {
23389  		var numEaten = 0;
23390  		while (state.text[0] === '\t' || state.text[0] === ' ') {
23391  			cut(state, 1);
23392  			numEaten += 1;
23393  		}
23394  		if (numEaten === 0) {
23395  			state.error = 'Missing space.';
23396  		}
23397  	}
23398
23399  	function eatUntilEOLInclusive(state) {
23400  		var nextEOL = state.text.indexOf('\n');
23401  		if (nextEOL === -1) {
23402  			state.error = 'Missing EOL.';
23403  		} else {
23404  			cut(state, nextEOL + 1);
23405  		}
23406  	}
23407
23408  	function eatEmptyLines(state) {
23409  		while (state.text.length > 0) {
23410  			var nextLine = peekLine(state);
23411  			if ( nextLine.trim().length === 0) {
23412  				cutLine(state);
23413  			} else {
23414  				break;
23415  			}
23416  		}
23417  	}
23418
23419  	// Eats empty lines, but throws an error if there's not at least one.
23420  	function eatAtLeast1EmptyLines(state) {
23421  		var linesEaten = 0;
23422  		while (state.text.length > 0) {
23423  			var nextLine = peekLine(state);
23424  			if ( nextLine.trim().length === 0) {
23425  				cutLine(state);
23426  				linesEaten += 1;
23427  			} else {
23428  				break;
23429  			}
23430  		}
23431  		if (linesEaten === 0) {
23432  			state.error = 'Missing empty line.';
23433  		}
23434  	}
23435
23436  	function getTiming(state) {
23437  		var nextSpace = nextSpaceOrNewline(state.text);
23438  		if (nextSpace === -1) {
23439  			state.error('Missing timing.');
23440  			return;
23441  		}
23442  		var timestamp = cut(state, nextSpace);
23443
23444  		// The spec requires exactly 2 characters for minutes and seconds, and 2+ for hours,
23445  		// but some VTT generation creates 1 digit hour times (e.g. "1:02:24.000 --> 1:04:48.000") and it seems harmless to allow that here
23446  		var results = /((\d+):)?((\d\d):)(\d\d).(\d\d\d)|(\d+).(\d\d\d)/.exec(timestamp);
23447
23448  		if (!results) {
23449  			state.error = 'Unable to parse timestamp';
23450  			return;
23451  		}
23452  		var time = 0;
23453  		var hours = results[2];
23454  		var minutes = results[4];
23455
23456  		if (minutes) {
23457  			if (parseInt(minutes, 10) > 59) {
23458  				state.error = 'Invalid minute range';
23459  				return;
23460  			}
23461  			if (hours) {
23462  				time += 3600 * parseInt(hours, 10);
23463  			}
23464  			time += 60 * parseInt(minutes, 10);
23465  			var seconds = results[5];
23466  			if (parseInt(seconds, 10) > 59) {
23467  				state.error = 'Invalid second range';
23468  				return;
23469  			}
23470
23471  			time += parseInt(seconds, 10);
23472  			time += parseInt(results[6], 10) / 1000;
23473  		} else {
23474  			time += parseInt(results[7], 10);
23475  			time += parseInt(results[8], 10) / 1000;
23476  		}
23477
23478  		return time;
23479  	}
23480   }
23481
23482  /* global YT */
23483
23484  function addYoutubeFunctions(AblePlayer) {
23485
23486  	AblePlayer.prototype.initYouTubePlayer = function () {
23487
23488  		var thisObj, deferred, promise, youTubeId;
23489  		thisObj = this;
23490  		deferred = new this.defer();
23491  		promise = deferred.promise();
23492
23493  		this.youTubePlayerReady = false;
23494
23495  		// if a described version is available && user prefers desription
23496  		// init player using the described version
23497  		youTubeId = (this.youTubeDescId && this.prefDesc) ? this.youTubeDescId : this.youTubeId;
23498
23499  		this.activeYouTubeId = youTubeId;
23500  		if (AblePlayer.youTubeIframeAPIReady) {
23501  			// Script already loaded and ready.
23502  			thisObj.finalizeYoutubeInit().then(function() {
23503  				deferred.resolve();
23504  			});
23505  		} else {
23506  			// Has another player already started loading the script? If so, abort...
23507  			if (!AblePlayer.loadingYouTubeIframeAPI) {
23508  				thisObj.getScript('https://www.youtube.com/iframe_api', function () {
23509  					console.log( 'YouTube API loaded' );
23510  				});
23511  			}
23512
23513  			// Otherwise, keeping waiting for script load event...
23514  			$('body').on('youTubeIframeAPIReady', function () {
23515  				thisObj.finalizeYoutubeInit().then(function() {
23516  					deferred.resolve();
23517  				});
23518  			});
23519  		}
23520  		return promise;
23521  	};
23522
23523  	AblePlayer.prototype.finalizeYoutubeInit = function () {
23524
23525  		// This is called once we're sure the Youtube iFrame API is loaded -- see above
23526  		var deferred, promise, thisObj, containerId, ccLoadPolicy, autoplay;
23527
23528  		deferred = new this.defer();
23529  		promise = deferred.promise();
23530  		thisObj = this;
23531  		containerId = this.mediaId + '_youtube';
23532
23533  		this.$mediaContainer.prepend($('<div>').attr('id', containerId));
23534
23535  		// cc_load_policy:
23536  		// 0 - show captions depending on user's preference on YouTube
23537  		// 1 - show captions by default, even if the user has turned them off
23538  		// IMPORTANT: This *must* be set to 1 or some browsers
23539  		// fail to load any text tracks (observed in Chrome, not in Firefox)
23540  		ccLoadPolicy = 1;
23541  		autoplay = (this.okToPlay) ? 1 : 0;
23542
23543  		// Documentation https://developers.google.com/youtube/player_parameters
23544
23545  		if (typeof this.captionLang == 'undefined') {
23546  			// init using the default player lang
23547  			this.captionLang = this.lang;
23548  		}
23549  		this.youTubePlayer = new YT.Player(containerId, {
23550  			videoId: this.activeYouTubeId,
23551  			host: this.youTubeNoCookie ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com',
23552  			playerVars: {
23553  				autoplay: autoplay,
23554  				cc_lang_pref: this.captionLang, // set the caption language
23555  				cc_load_policy: ccLoadPolicy,
23556  				controls: 0, // no controls, using our own
23557  				disableKb: 1, // disable keyboard shortcuts, using our own
23558  				enablejsapi: 1,
23559  				hl: this.lang, // set the UI language to match Able Player
23560  				iv_load_policy: 3, // do not show video annotations
23561  				origin: window.location.origin,
23562  				playsinline: this.playsInline,
23563  				rel: 0, // when video ends, show only related videos from same channel (1 shows any)
23564  				start: this.startTime
23565  			},
23566  			events: {
23567  				onReady: function () {
23568  					thisObj.youTubePlayerReady = true;
23569  					if (!thisObj.playerWidth || !thisObj.playerHeight) {
23570  						thisObj.getYouTubeDimensions();
23571  					}
23572  					if (thisObj.playerWidth && thisObj.playerHeight) {
23573  						thisObj.youTubePlayer.setSize(thisObj.playerWidth,thisObj.playerHeight);
23574  					}
23575  					if (thisObj.swappingSrc) {
23576  						// swap is now complete
23577  						thisObj.swappingSrc = false;
23578  						thisObj.restoreFocus();
23579  						thisObj.cueingPlaylistItem = false;
23580  						if (thisObj.playing || thisObj.okToPlay) {
23581  							// resume playing
23582  							thisObj.playMedia();
23583  						}
23584  					}
23585  					if (thisObj.userClickedPlaylist) {
23586  						thisObj.userClickedPlaylist = false; // reset
23587  					}
23588  					if (thisObj.recreatingPlayer) {
23589  						thisObj.recreatingPlayer = false; // reset
23590  					}
23591  					deferred.resolve();
23592  				},
23593  				onError: function (e) {
23594  					deferred.reject();
23595  				},
23596  				onStateChange: function (e) {
23597  					thisObj.getPlayerState().then(function(playerState) {
23598  						// values of playerState: 'playing','paused','buffering','ended'
23599  						if (playerState === 'playing') {
23600  							if (thisObj.hasSignLanguage && thisObj.signVideo) {
23601  								thisObj.signVideo.play(true);
23602  							}
23603  							thisObj.playing = true;
23604  							thisObj.startedPlaying = true;
23605  							thisObj.paused = false;
23606  						} else if (playerState == 'ended') {
23607  							thisObj.onMediaComplete();
23608  						} else {
23609  							thisObj.playing = false;
23610  							thisObj.paused = true;
23611  						}
23612  						if (thisObj.stoppingYouTube && playerState === 'paused') {
23613  							if (thisObj.hasSignLanguage && thisObj.signVideo) {
23614  								thisObj.signVideo.pause(true);
23615  							}
23616  							if (typeof thisObj.$posterImg !== 'undefined') {
23617  								thisObj.$posterImg.show();
23618  							}
23619  							thisObj.stoppingYouTube = false;
23620  							thisObj.seeking = false;
23621  							thisObj.playing = false;
23622  							thisObj.paused = true;
23623  						}
23624  					});
23625  					// If caption tracks are hosted locally, but are also available on YouTube,
23626  					// we need to turn them off on YouTube or there will be redundant captions
23627  					// This is the most reliable event on which to unload the caption module
23628  					if (thisObj.player === 'youtube' && !thisObj.usingYouTubeCaptions) {
23629  						if (thisObj.youTubePlayer.getOptions('captions')) {
23630  							thisObj.youTubePlayer.unloadModule('captions');
23631  						}
23632  					}
23633  				},
23634  				onApiChange: function() {
23635  					// getDuration() can be fetched during API change event.
23636  					thisObj.duration = thisObj.youTubePlayer.getDuration();
23637  				},
23638  				onPlaybackQualityChange: function () {
23639  					// do something
23640  				},
23641  			}
23642  		});
23643  		if (!this.hasPlaylist) {
23644  			// remove the media element, since YouTube replaces that with its own element in an iframe
23645  			// this is handled differently for playlists. See buildplayer.js > cuePlaylistItem()
23646  			this.$media.remove();
23647  		}
23648  		return promise;
23649  	};
23650
23651  	AblePlayer.prototype.getYouTubeDimensions = function () {
23652
23653  		// The YouTube iframe API does not have a getSize() of equivalent method
23654  		// so, need to get dimensions from YouTube's iframe
23655  		var $iframe, width, height;
23656
23657  		$iframe = this.$ableWrapper.find('iframe');
23658  		if (typeof $iframe !== 'undefined') {
23659  			if ($iframe.prop('width')) {
23660  				width = $iframe.prop('width');
23661  				if ($iframe.prop('height')) {
23662  					height = $iframe.prop('height');
23663  					this.resizePlayer(width,height);
23664  				}
23665  			}
23666  		}
23667  	};
23668
23669  	/**
23670  	 * Get data from the YouTube iFrame API. Pushes data into `this.tracks` and `this.captions`.
23671  	 * Initiates play to trigger loading the captions module, then stops and collects data.
23672  	 *
23673  	 * @returns {Promise} promise
23674  	 */
23675  	AblePlayer.prototype.getYouTubeCaptionTracks = function () {
23676
23677  		var deferred = new this.defer();
23678  		var promise = deferred.promise();
23679  		var thisObj, ytTracks, i, trackLang, trackLabel, isDefaultTrack, apiTriggered = false;
23680
23681  		thisObj = this;
23682  		if (!this.youTubePlayer.getOption('captions','tracklist') ) {
23683  			// no tracks were found, probably because the captions module hasn't loaded
23684  			// play video briefly (required to load the captions module)
23685  			// and after the apiChange event is triggered, try again to retrieve tracks
23686  			this.youTubePlayer.addEventListener('onApiChange',function() {
23687  				apiTriggered = true;
23688  				// getDuration() also requires video to play briefly
23689  				// so, let's set that while we're here
23690  				thisObj.duration = thisObj.youTubePlayer.getDuration();
23691
23692  				if (thisObj.loadingYouTubeCaptions) {
23693  					// loadingYouTubeCaptions is a stopgap in case onApiChange is called more than once
23694  					ytTracks = thisObj.youTubePlayer.getOption('captions','tracklist');
23695  					if ( ! thisObj.okToPlay ) {
23696  						// Don't stopVideo() - that cancels loading, just pause.
23697  						// No need to seekTo(0) - the time passed isn't noticeable to the user
23698  						thisObj.youTubePlayer.pauseVideo();
23699  					}
23700  					if (ytTracks && ytTracks.length) {
23701  						// Step through ytTracks and add them to global tracks array
23702  						// Note: Unlike YouTube Data API, the IFrame Player API only returns
23703  						// tracks that are published, and does NOT include ASR captions
23704  						// So, no additional filtering is required
23705  						for (i=0; i < ytTracks.length; i++) {
23706  							trackLang = ytTracks[i].languageCode;
23707  							trackLabel = ytTracks[i].languageName; // displayName and languageName seem to always have the same value
23708  							isDefaultTrack = false;
23709  							if (typeof thisObj.captionLang !== 'undefined' && (trackLang === thisObj.captionLang) ) {
23710  								isDefaultTrack = true;
23711  							} else if (typeof thisObj.lang !== 'undefined') {
23712  								if (trackLang === thisObj.lang) {
23713  									isDefaultTrack = true;
23714  								}
23715  							}
23716  							thisObj.tracks.push({
23717  								'kind': 'captions',
23718  								'language': trackLang,
23719  								'label': trackLabel,
23720  								'def': isDefaultTrack
23721  							});
23722  							thisObj.captions.push({
23723  								'language': trackLang,
23724  								'label': trackLabel,
23725  								'def': isDefaultTrack,
23726  								'cues': null
23727  							});
23728  						}
23729  						thisObj.hasCaptions = true;
23730  						// setupPopups again with new captions array, replacing original
23731  						thisObj.setupPopups('captions');
23732  					} else {
23733  						// there are no YouTube captions
23734  						thisObj.usingYouTubeCaptions = false;
23735  						thisObj.hasCaptions = false;
23736  					}
23737  					thisObj.loadingYouTubeCaptions = false;
23738  					if (thisObj.okToPlay) {
23739  						thisObj.youTubePlayer.playVideo();
23740  					}
23741  				}
23742  				if (thisObj.captionLangPending) {
23743  					// user selected a new caption language prior to playback starting
23744  					// set it now
23745  					thisObj.youTubePlayer.setOption('captions', 'track', {'languageCode': thisObj.captionLangPending});
23746  					thisObj.captionLangPending = null;
23747  				}
23748  				if (typeof thisObj.prefCaptionsSize !== 'undefined') {
23749  					// set the default caption size
23750  					// this doesn't work until the captions module is loaded
23751  					thisObj.youTubePlayer.setOption('captions','fontSize',thisObj.translatePrefs('size',thisObj.prefCaptionsSize,'youtube'));
23752  				}
23753  				deferred.resolve();
23754  			});
23755  			// Trigger the above event listener by briefly playing the video
23756  			this.loadingYouTubeCaptions = true;
23757  			this.youTubePlayer.playVideo();
23758  			// If onApiChange has not been triggered, the captions module is not loading.
23759  			setTimeout(() => {
23760  				if ( ! apiTriggered ) {
23761  					setTimeout(() => {
23762  						// If a second passes without loading captions, assume there are none.
23763  						thisObj.youTubePlayer.pauseVideo();
23764  						deferred.resolve();
23765  					}, 500);
23766  				}
23767  			},500);
23768  		}
23769  		return promise;
23770  	};
23771
23772  	AblePlayer.prototype.getYouTubePosterUrl = function (youTubeId, width) {
23773
23774  		// return a URL for retrieving a YouTube poster image
23775  		// supported values of width: 120, 320, 480, 640, 1280, 1920.
23776  		var url = 'https://img.youtube.com/vi/' + youTubeId;
23777  		if (width == '120') {
23778  			// default (small) thumbnail, 120 x 90
23779  			return url + '/default.jpg';
23780  		} else if (width == '320') {
23781  			// medium quality thumbnail, 320 x 180
23782  			return url + '/mqdefault.jpg';
23783  		} else if (width == '480') {
23784  			// high quality thumbnail, 480 x 360
23785  			return url + '/hqdefault.jpg';
23786  		} else if (width == '640') {
23787  			// standard definition poster image, 640 x 480
23788  			return url + '/sddefault.jpg';
23789  		} else if (width == '1280') {
23790  			// standard definition poster image, 640 x 480
23791  			return url + '/hq720.jpg';
23792  		} else if ( width == '1920' ) {
23793  			// standard definition poster image, 640 x 480
23794  			return url + '/maxresdefault.jpg';
23795  		}
23796  		return false;
23797  	};
23798
23799  	AblePlayer.prototype.getYouTubeId = function (url) {
23800
23801  		// return a YouTube ID, extracted from a full YouTube URL
23802  		// Supported URL patterns:
23803  		// http|s://youtu.be/xxx
23804  		// http|s://www.youtube.com/watch?v=xxx
23805  		// http|s://www.youtube.com/embed/xxx
23806
23807  		// in all supported patterns, the id is the last 11 characters
23808  		var idStartPos, id;
23809
23810  		if (url.indexOf('youtu') !== -1) {
23811  			// this is a full Youtube URL
23812  			url = url.trim();
23813  			idStartPos = url.length - 11;
23814  			id = url.substring(idStartPos);
23815  			return id;
23816  		} else {
23817  			return url;
23818  		}
23819  };
23820
23821  }
23822
23823  // Order copied over from 2026-02-27 Gruntfile.js, in case it matters
23824  addInitializeFunctions(AblePlayer);
23825  addPreferenceFunctions(AblePlayer);
23826  addWebvttFunctions(AblePlayer);
23827  addBuildplayerFunctions(AblePlayer);
23828  addTrackFunctions(AblePlayer);
23829  addYoutubeFunctions(AblePlayer);
23830  addVolumeFunctions(AblePlayer);
23831  addMiscFunctions(AblePlayer);
23832  addDescriptionFunctions(AblePlayer);
23833  addBrowserFunctions(AblePlayer);
23834  addControlFunctions(AblePlayer);
23835  addCaptionFunctions(AblePlayer);
23836  addChaptersFunctions(AblePlayer);
23837  addMetadataFunctions(AblePlayer);
23838  addTranscriptFunctions(AblePlayer);
23839  addSearchFunctions(AblePlayer);
23840  addEventFunctions(AblePlayer);
23841  addDragdropFunctions(AblePlayer);
23842  addSignFunctions(AblePlayer);
23843  addLangsFunctions(AblePlayer);
23844  addTranslationFunctions(AblePlayer);
23845  addVtsFunctions(AblePlayer);
23846  addVimeoFunctions(AblePlayer);
23847
23848  AblePlayer.ablePlayerSetupWindow();
23849
23850  return AblePlayer;
23851
23852}));
23853//# sourceMappingURL=ableplayer.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.