PageSourceSearch

https://horasan.bel.tr/tema/genel/assets/bower_components/plyr-master/dist/plyr.js

js horasan.bel.tr collected 2026-09-25 09:16:53 UTC 303,287 bytes, 9,131 lines download raw bytes

1typeof navigator === "object" && (function (global, factory) {
2  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3  typeof define === 'function' && define.amd ? define('Plyr', factory) :
4  (global = global || self, global.Plyr = factory());
5}(this, function () { 'use strict';
6
7  function _classCallCheck(instance, Constructor) {
8    if (!(instance instanceof Constructor)) {
9      throw new TypeError("Cannot call a class as a function");
10    }
11  }
12
13  function _defineProperties(target, props) {
14    for (var i = 0; i < props.length; i++) {
15      var descriptor = props[i];
16      descriptor.enumerable = descriptor.enumerable || false;
17      descriptor.configurable = true;
18      if ("value" in descriptor) descriptor.writable = true;
19      Object.defineProperty(target, descriptor.key, descriptor);
20    }
21  }
22
23  function _createClass(Constructor, protoProps, staticProps) {
24    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
25    if (staticProps) _defineProperties(Constructor, staticProps);
26    return Constructor;
27  }
28
29  function _defineProperty(obj, key, value) {
30    if (key in obj) {
31      Object.defineProperty(obj, key, {
32        value: value,
33        enumerable: true,
34        configurable: true,
35        writable: true
36      });
37    } else {
38      obj[key] = value;
39    }
40
41    return obj;
42  }
43
44  function _slicedToArray(arr, i) {
45    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
46  }
47
48  function _toConsumableArray(arr) {
49    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
50  }
51
52  function _arrayWithoutHoles(arr) {
53    if (Array.isArray(arr)) {
54      for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
55
56      return arr2;
57    }
58  }
59
60  function _arrayWithHoles(arr) {
61    if (Array.isArray(arr)) return arr;
62  }
63
64  function _iterableToArray(iter) {
65    if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
66  }
67
68  function _iterableToArrayLimit(arr, i) {
69    var _arr = [];
70    var _n = true;
71    var _d = false;
72    var _e = undefined;
73
74    try {
75      for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
76        _arr.push(_s.value);
77
78        if (i && _arr.length === i) break;
79      }
80    } catch (err) {
81      _d = true;
82      _e = err;
83    } finally {
84      try {
85        if (!_n && _i["return"] != null) _i["return"]();
86      } finally {
87        if (_d) throw _e;
88      }
89    }
90
91    return _arr;
92  }
93
94  function _nonIterableSpread() {
95    throw new TypeError("Invalid attempt to spread non-iterable instance");
96  }
97
98  function _nonIterableRest() {
99    throw new TypeError("Invalid attempt to destructure non-iterable instance");
100  }
101
102  var defaults = {
103    addCSS: true,
104    // Add CSS to the element to improve usability (required here or in your CSS!)
105    thumbWidth: 15,
106    // The width of the thumb handle
107    watch: true // Watch for new elements that match a string target
108
109  };
110
111  // Element matches a selector
112  function matches(element, selector) {
113
114    function match() {
115      return Array.from(document.querySelectorAll(selector)).includes(this);
116    }
117
118    var matches = match;
119    return matches.call(element, selector);
120  }
121
122  // Trigger event
123  function trigger(element, type) {
124    if (!element || !type) {
125      return;
126    } // Create and dispatch the event
127
128
129    var event = new Event(type); // Dispatch the event
130
131    element.dispatchEvent(event);
132  }
133
134  // ==========================================================================
135  // Type checking utils
136  // ==========================================================================
137  var getConstructor = function getConstructor(input) {
138    return input !== null && typeof input !== 'undefined' ? input.constructor : null;
139  };
140
141  var instanceOf = function instanceOf(input, constructor) {
142    return Boolean(input && constructor && input instanceof constructor);
143  };
144
145  var isNullOrUndefined = function isNullOrUndefined(input) {
146    return input === null || typeof input === 'undefined';
147  };
148
149  var isObject = function isObject(input) {
150    return getConstructor(input) === Object;
151  };
152
153  var isNumber = function isNumber(input) {
154    return getConstructor(input) === Number && !Number.isNaN(input);
155  };
156
157  var isString = function isString(input) {
158    return getConstructor(input) === String;
159  };
160
161  var isBoolean = function isBoolean(input) {
162    return getConstructor(input) === Boolean;
163  };
164
165  var isFunction = function isFunction(input) {
166    return getConstructor(input) === Function;
167  };
168
169  var isArray = function isArray(input) {
170    return Array.isArray(input);
171  };
172
173  var isNodeList = function isNodeList(input) {
174    return instanceOf(input, NodeList);
175  };
176
177  var isElement = function isElement(input) {
178    return instanceOf(input, Element);
179  };
180
181  var isEvent = function isEvent(input) {
182    return instanceOf(input, Event);
183  };
184
185  var isEmpty = function isEmpty(input) {
186    return isNullOrUndefined(input) || (isString(input) || isArray(input) || isNodeList(input)) && !input.length || isObject(input) && !Object.keys(input).length;
187  };
188
189  var is = {
190    nullOrUndefined: isNullOrUndefined,
191    object: isObject,
192    number: isNumber,
193    string: isString,
194    boolean: isBoolean,
195    function: isFunction,
196    array: isArray,
197    nodeList: isNodeList,
198    element: isElement,
199    event: isEvent,
200    empty: isEmpty
201  };
202
203  // Get the number of decimal places
204  function getDecimalPlaces(value) {
205    var match = "".concat(value).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
206
207    if (!match) {
208      return 0;
209    }
210
211    return Math.max(0, // Number of digits right of decimal point.
212    (match[1] ? match[1].length : 0) - ( // Adjust for scientific notation.
213    match[2] ? +match[2] : 0));
214  } // Round to the nearest step
215
216  function round(number, step) {
217    if (step < 1) {
218      var places = getDecimalPlaces(step);
219      return parseFloat(number.toFixed(places));
220    }
221
222    return Math.round(number / step) * step;
223  }
224
225  var RangeTouch =
226  /*#__PURE__*/
227  function () {
228    /**
229     * Setup a new instance
230     * @param {String|Element} target
231     * @param {Object} options
232     */
233    function RangeTouch(target, options) {
234      _classCallCheck(this, RangeTouch);
235
236      if (is.element(target)) {
237        // An Element is passed, use it directly
238        this.element = target;
239      } else if (is.string(target)) {
240        // A CSS Selector is passed, fetch it from the DOM
241        this.element = document.querySelector(target);
242      }
243
244      if (!is.element(this.element) || !is.empty(this.element.rangeTouch)) {
245        return;
246      }
247
248      this.config = Object.assign({}, defaults, options);
249      this.init();
250    }
251
252    _createClass(RangeTouch, [{
253      key: "init",
254      value: function init() {
255        // Bail if not a touch enabled device
256        if (!RangeTouch.enabled) {
257          return;
258        } // Add useful CSS
259
260
261        if (this.config.addCSS) {
262          // TODO: Restore original values on destroy
263          this.element.style.userSelect = 'none';
264          this.element.style.webKitUserSelect = 'none';
265          this.element.style.touchAction = 'manipulation';
266        }
267
268        this.listeners(true);
269        this.element.rangeTouch = this;
270      }
271    }, {
272      key: "de
272stroy",
273      value: function destroy() {
274        // Bail if not a touch enabled device
275        if (!RangeTouch.enabled) {
276          return;
277        }
278
279        this.listeners(false);
280        this.element.rangeTouch = null;
281      }
282    }, {
283      key: "listeners",
284      value: function listeners(toggle) {
285        var _this = this;
286
287        var method = toggle ? 'addEventListener' : 'removeEventListener'; // Listen for events
288
289        ['touchstart', 'touchmove', 'touchend'].forEach(function (type) {
290          _this.element[method](type, function (event) {
291            return _this.set(event);
292          }, false);
293        });
294      }
295      /**
296       * Get the value based on touch position
297       * @param {Event} event
298       */
299
300    }, {
301      key: "get",
302      value: function get(event) {
303        if (!RangeTouch.enabled || !is.event(event)) {
304          return null;
305        }
306
307        var input = event.target;
308        var touch = event.changedTouches[0];
309        var min = parseFloat(input.getAttribute('min')) || 0;
310        var max = parseFloat(input.getAttribute('max')) || 100;
311        var step = parseFloat(input.getAttribute('step')) || 1;
312        var delta = max - min; // Calculate percentage
313
314        var percent;
315        var clientRect = input.getBoundingClientRect();
316        var thumbWidth = 100 / clientRect.width * (this.config.thumbWidth / 2) / 100; // Determine left percentage
317
318        percent = 100 / clientRect.width * (touch.clientX - clientRect.left); // Don't allow outside bounds
319
320        if (percent < 0) {
321          percent = 0;
322        } else if (percent > 100) {
323          percent = 100;
324        } // Factor in the thumb offset
325
326
327        if (percent < 50) {
328          percent -= (100 - percent * 2) * thumbWidth;
329        } else if (percent > 50) {
330          percent += (percent - 50) * 2 * thumbWidth;
331        } // Find the closest step to the mouse position
332
333
334        return min + round(delta * (percent / 100), step);
335      }
336      /**
337       * Update range value based on position
338       * @param {Event} event
339       */
340
341    }, {
342      key: "set",
343      value: function set(event) {
344        if (!RangeTouch.enabled || !is.event(event) || event.target.disabled) {
345          return;
346        } // Prevent text highlight on iOS
347
348
349        event.preventDefault(); // Set value
350
351        event.target.value = this.get(event); // Trigger event
352
353        trigger(event.target, event.type === 'touchend' ? 'change' : 'input');
354      }
355    }], [{
356      key: "setup",
357
358      /**
359       * Setup multiple instances
360       * @param {String|Element|NodeList|Array} target
361       * @param {Object} options
362       */
363      value: function setup(target) {
364        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
365        var targets = null;
366
367        if (is.empty(target) || is.string(target)) {
368          targets = Array.from(document.querySelectorAll(is.string(target) ? target : 'input[type="range"]'));
369        } else if (is.element(target)) {
370          targets = [target];
371        } else if (is.nodeList(target)) {
372          targets = Array.from(target);
373        } else if (is.array(target)) {
374          targets = target.filter(is.element);
375        }
376
377        if (is.empty(targets)) {
378          return null;
379        }
380
381        var config = Object.assign({}, defaults, options);
382
383        if (is.string(target) && config.watch) {
384          // Create an observer instance
385          var observer = new MutationObserver(function (mutations) {
386            Array.from(mutations).forEach(function (mutation) {
387              Array.from(mutation.addedNodes).forEach(function (node) {
388                if (!is.element(node) || !matches(node, target)) {
389                  return;
390                } // eslint-disable-next-line no-unused-vars
391
392
393                var range = new RangeTouch(node, config);
394              });
395            });
396          }); // Pass in the target node, as well as the observer options
397
398          observer.observe(document.body, {
399            childList: true,
400            subtree: true
401          });
402        }
403
404        return targets.map(function (t) {
405          return new RangeTouch(t, options);
406        });
407      }
408    }, {
409      key: "enabled",
410      get: function get() {
411        return 'ontouchstart' in document.documentElement;
412      }
413    }]);
414
415    return RangeTouch;
416  }();
417
418  // ==========================================================================
419  // Type checking utils
420  // ==========================================================================
421  var getConstructor$1 = function getConstructor(input) {
422    return input !== null && typeof input !== 'undefined' ? input.constructor : null;
423  };
424
425  var instanceOf$1 = function instanceOf(input, constructor) {
426    return Boolean(input && constructor && input instanceof constructor);
427  };
428
429  var isNullOrUndefined$1 = function isNullOrUndefined(input) {
430    return input === null || typeof input === 'undefined';
431  };
432
433  var isObject$1 = function isObject(input) {
434    return getConstructor$1(input) === Object;
435  };
436
437  var isNumber$1 = function isNumber(input) {
438    return getConstructor$1(input) === Number && !Number.isNaN(input);
439  };
440
441  var isString$1 = function isString(input) {
442    return getConstructor$1(input) === String;
443  };
444
445  var isBoolean$1 = function isBoolean(input) {
446    return getConstructor$1(input) === Boolean;
447  };
448
449  var isFunction$1 = function isFunction(input) {
450    return getConstructor$1(input) === Function;
451  };
452
453  var isArray$1 = function isArray(input) {
454    return Array.isArray(input);
455  };
456
457  var isWeakMap = function isWeakMap(input) {
458    return instanceOf$1(input, WeakMap);
459  };
460
461  var isNodeList$1 = function isNodeList(input) {
462    return instanceOf$1(input, NodeList);
463  };
464
465  var isElement$1 = function isElement(input) {
466    return instanceOf$1(input, Element);
467  };
468
469  var isTextNode = function isTextNode(input) {
470    return getConstructor$1(input) === Text;
471  };
472
473  var isEvent$1 = function isEvent(input) {
474    return instanceOf$1(input, Event);
475  };
476
477  var isKeyboardEvent = function isKeyboardEvent(input) {
478    return instanceOf$1(input, KeyboardEvent);
479  };
480
481  var isCue = function isCue(input) {
482    return instanceOf$1(input, window.TextTrackCue) || instanceOf$1(input, window.VTTCue);
483  };
484
485  var isTrack = function isTrack(input) {
486    return instanceOf$1(input, TextTrack) || !isNullOrUndefined$1(input) && isString$1(input.kind);
487  };
488
489  var isPromise = function isPromise(input) {
490    return instanceOf$1(input, Promise);
491  };
492
493  var isEmpty$1 = function isEmpty(input) {
494    return isNullOrUndefined$1(input) || (isString$1(input) || isArray$1(input) || isNodeList$1(input)) && !input.length || isObject$1(input) && !Object.keys(input).length;
495  };
496
497  var isUrl = function isUrl(input) {
498    // Accept a URL object
499    if (instanceOf$1(input, window.URL)) {
500      return true;
501    } // Must be string from here
502
503
504    if (!isString$1(input)) {
505      return false;
506    } // Add the protocol if required
507
508
509    var string = input;
510
511    if (!input.startsWith('http://') || !input.startsWith('https://')) {
512      string = "http://".concat(input);
513    }
514
515    try {
516      return !isEmpty$1(new URL(string).hostname);
517    } catch (e) {
518      return false;
519    }
520  };
521
522  var is$1 = {
523    nullOrUndefined: isNullOrUndefined$1,
524    object: isObject$1,
525    number: isNumber$1,
526    string: isString$1,
527    boolean: isBoolean$1,
528    function: isFunction$1,
529    array: isArray$1,
530    weakMap: isWeakMap,
531    nodeList: isNodeList$1,
532    element: isElement$1,
533    textNode: isTextNode,
534    event: isEvent$1,
535    keyboardEvent: isKeyboardEvent,
536    cue: isCue,
537    track: isTrack,
538    promise: isPromise,
539    url: isUrl,
540    empty: isEmpty$1
541  };
542
543  // ==========================================================================
544  var transitionEndEvent = function () {
545    var element = document.createElement('span');
546    var events = {
547      WebkitTransition: 'webkitTransitionEnd',
548      MozTransition: 'transitionend',
549      OTransition: 'oTransitionEnd otransitionend',
550      transition: 'transitionend'
551    };
552    var type = Object.keys(events).find(function (event) {
553      return element.style[event] !== undefined;
554    });
555    return is$1.string(type) ? events[type] : false;
556  }(); // Force repaint of element
557
558  function repaint(element, delay) {
559    setTimeout(function () {
560      try {
561        // eslint-disable-next-line no-param-reassign
562        element.hidden = true; // eslint-disable-next-line no-unused-expressions
563
564        element.offsetHeight; // eslint-disable-next-line no-param-reassign
565
566        element.hidden = false;
567      } catch (e) {// Do nothing
568      }
569    }, delay);
570  }
571
572  // ==========================================================================
573  // Browser sniffing
574  // Unfortunately, due to mixed support, UA sniffing is required
575  // ==========================================================================
576  var browser = {
577    isIE:
578    /* @cc_on!@ */
579    !!document.documentMode,
580    isEdge: window.navigator.userAgent.includes('Edge'),
581    isWebkit: 'WebkitAppearance' in document.documentElement.style && !/Edge/.test(navigator.userAgent),
582    isIPhone: /(iPhone|iPod)/gi.test(navigator.platform),
583    isIos: /(iPad|iPhone|iPod)/gi.test(navigator.platform)
584  };
585
586  // ==========================================================================
587  // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
588  // https://www.youtube.com/watch?v=NPM6172J22g
589
590  var supportsPassiveListeners = function () {
591    // Test via a getter in the options object to see if the passive property is accessed
592    var supported = false;
593
594    try {
595      var options = Object.defineProperty({}, 'passive', {
596        get: function get() {
597          supported = true;
598          return null;
599        }
600      });
601      window.addEventListener('test', null, options);
602      window.removeEventListener('test', null, options);
603    } catch (e) {// Do nothing
604    }
605
606    return supported;
607  }(); // Toggle event listener
608
609
610  function toggleListener(element, event, callback) {
611    var _this = this;
612
613    var toggle = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
614    var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
615    var capture = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
616
617    // Bail if no element, event, or callback
618    if (!element || !('addEventListener' in element) || is$1.empty(event) || !is$1.function(callback)) {
619      return;
620    } // Allow multiple events
621
622
623    var events = event.split(' '); // Build options
624    // Default to just the capture boolean for browsers with no passive listener support
625
626    var options = capture; // If passive events listeners are supported
627
628    if (supportsPassiveListeners) {
629      options = {
630        // Whether the listener can be passive (i.e. default never prevented)
631        passive: passive,
632        // Whether the listener is a capturing listener or not
633        capture: capture
634      };
635    } // If a single node is passed, bind the event listener
636
637
638    events.forEach(function (type) {
639      if (_this && _this.eventListeners && toggle) {
640        // Cache event listener
641        _this.eventListeners.push({
642          element: element,
643          type: type,
644          callback: callback,
645          options: options
646        });
647      }
648
649      element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
650    });
651  } // Bind event handler
652
653  function on(element) {
654    var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
655    var callback = arguments.length > 2 ? arguments[2] : undefined;
656    var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
657    var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
658    toggleListener.call(this, element, events, callback, true, passive, capture);
659  } // Unbind event handler
660
661  function off(element) {
662    var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
663    var callback = arguments.length > 2 ? arguments[2] : undefined;
664    var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
665    var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
666    toggleListener.call(this, element, events, callback, false, passive, capture);
667  } // Bind once-only event handler
668
669  function once(element) {
670    var _this2 = this;
671
672    var events = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
673    var callback = arguments.length > 2 ? arguments[2] : undefined;
674    var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
675    var capture = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
676
677    var onceCallback = function onceCallback() {
678      off(element, events, onceCallback, passive, capture);
679
680      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
681        args[_key] = arguments[_key];
682      }
683
684      callback.apply(_this2, args);
685    };
686
687    toggleListener.call(this, element, events, onceCallback, true, passive, capture);
688  } // Trigger event
689
690  function triggerEvent(element) {
691    var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
692    var bubbles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
693    var detail = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
694
695    // Bail if no element
696    if (!is$1.element(element) || is$1.empty(type)) {
697      return;
698    } // Create and dispatch the event
699
700
701    var event = new CustomEvent(type, {
702      bubbles: bubbles,
703      detail: Object.assign({}, detail, {
704        plyr: this
705      })
706    }); // Dispatch the event
707
708    element.dispatchEvent(event);
709  } // Unbind all cached event listeners
710
711  function unbindListeners() {
712    if (this && this.eventListeners) {
713      this.eventListeners.forEach(function (item) {
714        var element = item.element,
715            type = item.type,
716            callback = item.callback,
717            options = item.options;
718        element.removeEventListener(type, callback, options);
719      });
720      this.eventListeners = [];
721    }
722  } // Run method when / if player is ready
723
724  function ready() {
725    var _this3 = this;
726
727    return new Promise(function (resolve) {
728      return _this3.ready ? setTimeout(resolve, 0) : on.call(_this3, _this3.elements.container, 'ready', resolve);
729    }).then(function () {});
730  }
731
732  function cloneDeep(object) {
733    return JSON.parse(JSON.stringify(object));
734  } // Get a nested value in an object
735
736  function getDeep(object, path) {
737    return path.split('.').reduce(function (obj, key) {
738      return obj && obj[key];
739    }, object);
740  } // Deep extend destination object with N more objects
741
742  function extend() {
743    var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
744
745    for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
746      sources[_key - 1] = arguments[_key];
747    }
748
749    if (!sources.length) {
750      return target;
751    }
752
753    var source = sources.shift();
754
755    if (!is$1.object(source)) {
756      return target;
757    }
758
759    Object.keys(source).forEach(function (key) {
760      if (is$1.object(source[key])) {
761        if (!Object.keys(target).includes(key)) {
762          Object.assign(target, _defineProperty({}, key, {}));
763        }
764
765        extend(target[key], source[key]);
766      } else {
767        Object.assign(target, _defineProperty({}, key, source[key]));
768      }
769    });
770    return extend.apply(void 0, [target].concat(sources));
771  }
772
773  function wrap(elements, wrapper) {
774    // Convert `elements` to an array, if necessary.
775    var targets = elements.length ? elements : [elements]; // Loops backwards to prevent having to clone the wrapper on the
776    // first element (see `child` below).
777
778    Array.from(targets).reverse().forEach(function (element, index) {
779      var child = index > 0 ? wrapper.cloneNode(true) : wrapper; // Cache the current parent and sibling.
780
781      var parent = element.parentNode;
782      var sibling = element.nextSibling; // Wrap the element (is automatically removed from its current
783      // parent).
784
785      child.appendChild(element); // If the element had a sibling, insert the wrapper before
786      // the sibling to maintain the HTML structure; otherwise, just
787      // append it to the parent.
788
789      if (sibling) {
790        parent.insertBefore(child, sibling);
791      } else {
792        parent.appendChild(child);
793      }
794    });
795  } // Set attributes
796
797  function setAttributes(element, attributes) {
798    if (!is$1.element(element) || is$1.empty(attributes)) {
799      return;
800    } // Assume null and undefined attributes should be left out,
801    // Setting them would otherwise convert them to "null" and "undefined"
802
803
804    Object.entries(attributes).filter(function (_ref) {
805      var _ref2 = _slicedToArray(_ref, 2),
806          value = _ref2[1];
807
808      return !is$1.nullOrUndefined(value);
809    }
809).forEach(function (_ref3) {
810      var _ref4 = _slicedToArray(_ref3, 2),
811          key = _ref4[0],
812          value = _ref4[1];
813
814      return element.setAttribute(key, value);
815    });
816  } // Create a DocumentFragment
817
818  function createElement(type, attributes, text) {
819    // Create a new <element>
820    var element = document.createElement(type); // Set all passed attributes
821
822    if (is$1.object(attributes)) {
823      setAttributes(element, attributes);
824    } // Add text node
825
826
827    if (is$1.string(text)) {
828      element.innerText = text;
829    } // Return built element
830
831
832    return element;
833  } // Inaert an element after another
834
835  function insertAfter(element, target) {
836    if (!is$1.element(element) || !is$1.element(target)) {
837      return;
838    }
839
840    target.parentNode.insertBefore(element, target.nextSibling);
841  } // Insert a DocumentFragment
842
843  function insertElement(type, parent, attributes, text) {
844    if (!is$1.element(parent)) {
845      return;
846    }
847
848    parent.appendChild(createElement(type, attributes, text));
849  } // Remove element(s)
850
851  function removeElement(element) {
852    if (is$1.nodeList(element) || is$1.array(element)) {
853      Array.from(element).forEach(removeElement);
854      return;
855    }
856
857    if (!is$1.element(element) || !is$1.element(element.parentNode)) {
858      return;
859    }
860
861    element.parentNode.removeChild(element);
862  } // Remove all child elements
863
864  function emptyElement(element) {
865    if (!is$1.element(element)) {
866      return;
867    }
868
869    var length = element.childNodes.length;
870
871    while (length > 0) {
872      element.removeChild(element.lastChild);
873      length -= 1;
874    }
875  } // Replace element
876
877  function replaceElement(newChild, oldChild) {
878    if (!is$1.element(oldChild) || !is$1.element(oldChild.parentNode) || !is$1.element(newChild)) {
879      return null;
880    }
881
882    oldChild.parentNode.replaceChild(newChild, oldChild);
883    return newChild;
884  } // Get an attribute object from a string selector
885
886  function getAttributesFromSelector(sel, existingAttributes) {
887    // For example:
888    // '.test' to { class: 'test' }
889    // '#test' to { id: 'test' }
890    // '[data-test="test"]' to { 'data-test': 'test' }
891    if (!is$1.string(sel) || is$1.empty(sel)) {
892      return {};
893    }
894
895    var attributes = {};
896    var existing = extend({}, existingAttributes);
897    sel.split(',').forEach(function (s) {
898      // Remove whitespace
899      var selector = s.trim();
900      var className = selector.replace('.', '');
901      var stripped = selector.replace(/[[\]]/g, ''); // Get the parts and value
902
903      var parts = stripped.split('=');
904
905      var _parts = _slicedToArray(parts, 1),
906          key = _parts[0];
907
908      var value = parts.length > 1 ? parts[1].replace(/["']/g, '') : ''; // Get the first character
909
910      var start = selector.charAt(0);
911
912      switch (start) {
913        case '.':
914          // Add to existing classname
915          if (is$1.string(existing.class)) {
916            attributes.class = "".concat(existing.class, " ").concat(className);
917          } else {
918            attributes.class = className;
919          }
920
921          break;
922
923        case '#':
924          // ID selector
925          attributes.id = selector.replace('#', '');
926          break;
927
928        case '[':
929          // Attribute selector
930          attributes[key] = value;
931          break;
932
933        default:
934          break;
935      }
936    });
937    return extend(existing, attributes);
938  } // Toggle hidden
939
940  function toggleHidden(element, hidden) {
941    if (!is$1.element(element)) {
942      return;
943    }
944
945    var hide = hidden;
946
947    if (!is$1.boolean(hide)) {
948      hide = !element.hidden;
949    } // eslint-disable-next-line no-param-reassign
950
951
952    element.hidden = hide;
953  } // Mirror Element.classList.toggle, with IE compatibility for "force" argument
954
955  function toggleClass(element, className, force) {
956    if (is$1.nodeList(element)) {
957      return Array.from(element).map(function (e) {
958        return toggleClass(e, className, force);
959      });
960    }
961
962    if (is$1.element(element)) {
963      var method = 'toggle';
964
965      if (typeof force !== 'undefined') {
966        method = force ? 'add' : 'remove';
967      }
968
969      element.classList[method](className);
970      return element.classList.contains(className);
971    }
972
973    return false;
974  } // Has class name
975
976  function hasClass(element, className) {
977    return is$1.element(element) && element.classList.contains(className);
978  } // Element matches selector
979
980  function matches$1(element, selector) {
981
982    function match() {
983      return Array.from(document.querySelectorAll(selector)).includes(this);
984    }
985
986    var method = match;
987    return method.call(element, selector);
988  } // Find all elements
989
990  function getElements(selector) {
991    return this.elements.container.querySelectorAll(selector);
992  } // Find a single element
993
994  function getElement(selector) {
995    return this.elements.container.querySelector(selector);
996  } // Trap focus inside container
997
998  function trapFocus() {
999    var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1000    var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1001
1002    if (!is$1.element(element)) {
1003      return;
1004    }
1005
1006    var focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
1007    var first = focusable[0];
1008    var last = focusable[focusable.length - 1];
1009
1010    var trap = function trap(event) {
1011      // Bail if not tab key or not fullscreen
1012      if (event.key !== 'Tab' || event.keyCode !== 9) {
1013        return;
1014      } // Get the current focused element
1015
1016
1017      var focused = document.activeElement;
1018
1019      if (focused === last && !event.shiftKey) {
1020        // Move focus to first element that can be tabbed if Shift isn't used
1021        first.focus();
1022        event.preventDefault();
1023      } else if (focused === first && event.shiftKey) {
1024        // Move focus to last element that can be tabbed if Shift is used
1025        last.focus();
1026        event.preventDefault();
1027      }
1028    };
1029
1030    toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
1031  } // Set focus and tab focus class
1032
1033  function setFocus() {
1034    var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1035    var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1036
1037    if (!is$1.element(element)) {
1038      return;
1039    } // Set regular focus
1040
1041
1042    element.focus({
1043      preventScroll: true
1044    }); // If we want to mimic keyboard focus via tab
1045
1046    if (tabFocus) {
1047      toggleClass(element, this.config.classNames.tabFocus);
1048    }
1049  }
1050
1051  var defaultCodecs = {
1052    'audio/ogg': 'vorbis',
1053    'audio/wav': '1',
1054    'video/webm': 'vp8, vorbis',
1055    'video/mp4': 'avc1.42E01E, mp4a.40.2',
1056    'video/ogg': 'theora'
1057  }; // Check for feature support
1058
1059  var support = {
1060    // Basic support
1061    audio: 'canPlayType' in document.createElement('audio'),
1062    video: 'canPlayType' in document.createElement('video'),
1063    // Check for support
1064    // Basic functionality vs full UI
1065    check: function check(type, provider, playsinline) {
1066      var canPlayInline = browser.isIPhone && playsinline && support.playsinline;
1067      var api = support[type] || provider !== 'html5';
1068      var ui = api && support.rangeInput && (type !== 'video' || !browser.isIPhone || canPlayInline);
1069      return {
1070        api: api,
1071        ui: ui
1072      };
1073    },
1074    // Picture-in-picture support
1075    // Safari & Chrome only currently
1076    pip: function () {
1077      if (browser.isIPhone) {
1078        return false;
1079      } // Safari
1080      // https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls
1081
1082
1083      if (is$1.function(createElement('video').webkitSetPresentationMode)) {
1084        return true;
1085      } // Chrome
1086      // https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture
1087
1088
1089      if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {
1090        return true;
1091      }
1092
1093      return false;
1094    }(),
1095    // Airplay support
1096    // Safari only currently
1097    airplay: is$1.function(window.WebKitPlaybackTargetAvailabilityEvent),
1098    // Inline playback support
1099    // https://webkit.org/blog/6784/new-video-policies-for-ios/
1100    playsinline: 'playsInline' in document.createElement('video'),
1101    // Check for mime type support against a player instance
1102    // Credits: http://diveintohtml5.info/everything.html
1103    // Related: http://www.leanbackplayer.com/test/h5mt.html
1104    mime: function mime(input) {
1105      if (is$1.empty(input)) {
1106        return false;
1107      }
1108
1109      var _input$split = input.split('/'),
1110          _input$split2 = _slicedToArray(_input$split, 1),
1111          mediaType = _input$split2[0];
1112
1113      var type = input; // Verify we're using HTML5 and there's no media type mismatch
1114
1115      if (!this.isHTML5 || mediaType !== this.type) {
1116        return false;
1117      } // Add codec if required
1118
1119
1120      if (Object.keys(defaultCodecs).includes(type)) {
1121        type += "; codecs=\"".concat(defaultCodecs[input], "\"");
1122      }
1123
1124      try {
1125        return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));
1126      } catch (e) {
1127        return false;
1128      }
1129    },
1130    // Check for textTracks support
1131    textTracks: 'textTracks' in document.createElement('video'),
1132    // <input type="range"> Sliders
1133    rangeInput: function () {
1134      var range = document.createElement('input');
1135      range.type = 'range';
1136      return range.type === 'range';
1137    }(),
1138    // Touch
1139    // NOTE: Remember a device can be mouse + touch enabled so we check on first touch event
1140    touch: 'ontouchstart' in document.documentElement,
1141    // Detect transitions support
1142    transitions: transitionEndEvent !== false,
1143    // Reduced motion iOS & MacOS setting
1144    // https://webkit.org/blog/7551/responsive-design-for-motion/
1145    reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches
1146  };
1147
1148  function validateRatio(input) {
1149    if (!is$1.array(input) && (!is$1.string(input) || !input.includes(':'))) {
1150      return false;
1151    }
1152
1153    var ratio = is$1.array(input) ? input : input.split(':');
1154    return ratio.map(Number).every(is$1.number);
1155  }
1156  function reduceAspectRatio(ratio) {
1157    if (!is$1.array(ratio) || !ratio.every(is$1.number)) {
1158      return null;
1159    }
1160
1161    var _ratio = _slicedToArray(ratio, 2),
1162        width = _ratio[0],
1163        height = _ratio[1];
1164
1165    var getDivider = function getDivider(w, h) {
1166      return h === 0 ? w : getDivider(h, w % h);
1167    };
1168
1169    var divider = getDivider(width, height);
1170    return [width / divider, height / divider];
1171  }
1172  function getAspectRatio(input) {
1173    var parse = function parse(ratio) {
1174      return validateRatio(ratio) ? ratio.split(':').map(Number) : null;
1175    }; // Try provided ratio
1176
1177
1178    var ratio = parse(input); // Get from config
1179
1180    if (ratio === null) {
1181      ratio = parse(this.config.ratio);
1182    } // Get from embed
1183
1184
1185    if (ratio === null && !is$1.empty(this.embed) && is$1.array(this.embed.ratio)) {
1186      ratio = this.embed.ratio;
1187    } // Get from HTML5 video
1188
1189
1190    if (ratio === null && this.isHTML5) {
1191      var _this$media = this.media,
1192          videoWidth = _this$media.videoWidth,
1193          videoHeight = _this$media.videoHeight;
1194      ratio = reduceAspectRatio([videoWidth, videoHeight]);
1195    }
1196
1197    return ratio;
1198  } // Set aspect ratio for responsive container
1199
1200  function setAspectRatio(input) {
1201    if (!this.isVideo) {
1202      return {};
1203    }
1204
1205    var ratio = getAspectRatio.call(this, input);
1206
1207    var _ref = is$1.array(ratio) ? ratio : [0, 0],
1208        _ref2 = _slicedToArray(_ref, 2),
1209        w = _ref2[0],
1210        h = _ref2[1];
1211
1212    var padding = 100 / w * h;
1213    this.elements.wrapper.style.paddingBottom = "".concat(padding, "%"); // For Vimeo we have an extra <div> to hide the standard controls and UI
1214
1215    if (this.isVimeo && this.supported.ui) {
1216      var height = 240;
1217      var offset = (height - padding) / (height / 50);
1218      this.media.style.transform = "translateY(-".concat(offset, "%)");
1219    } else if (this.isHTML5) {
1220      this.elements.wrapper.classList.toggle(this.config.classNames.videoFixedRatio, ratio !== null);
1221    }
1222
1223    return {
1224      padding: padding,
1225      ratio: ratio
1226    };
1227  }
1228
1229  // ==========================================================================
1230  var html5 = {
1231    getSources: function getSources() {
1232      var _this = this;
1233
1234      if (!this.isHTML5) {
1235        return [];
1236      }
1237
1238      var sources = Array.from(this.media.querySelectorAll('source')); // Filter out unsupported sources (if type is specified)
1239
1240      return sources.filter(function (source) {
1241        var type = source.getAttribute('type');
1242
1243        if (is$1.empty(type)) {
1244          return true;
1245        }
1246
1247        return support.mime.call(_this, type);
1248      });
1249    },
1250    // Get quality levels
1251    getQualityOptions: function getQualityOptions() {
1252      // Get sizes from <source> elements
1253      return html5.getSources.call(this).map(function (source) {
1254        return Number(source.getAttribute('size'));
1255      }).filter(Boolean);
1256    },
1257    extend: function extend() {
1258      if (!this.isHTML5) {
1259        return;
1260      }
1261
1262      var player = this; // Set aspect ratio if fixed
1263
1264      if (!is$1.empty(this.config.ratio)) {
1265        setAspectRatio.call(player);
1266      } // Quality
1267
1268
1269      Object.defineProperty(player.media, 'quality', {
1270        get: function get() {
1271          // Get sources
1272          var sources = html5.getSources.call(player);
1273          var source = sources.find(function (s) {
1274            return s.getAttribute('src') === player.source;
1275          }); // Return size, if match is found
1276
1277          return source && Number(source.getAttribute('size'));
1278        },
1279        set: function set(input) {
1280          // Get sources
1281          var sources = html5.getSources.call(player); // Get first match for requested size
1282
1283          var source = sources.find(function (s) {
1284            return Number(s.getAttribute('size')) === input;
1285          }); // No matching source found
1286
1287          if (!source) {
1288            return;
1289          } // Get current state
1290
1291
1292          var _player$media = player.media,
1293              currentTime = _player$media.currentTime,
1294              paused = _player$media.paused,
1295              preload = _player$media.preload,
1296              readyState = _player$media.readyState; // Set new source
1297
1298          player.media.src = source.getAttribute('src'); // Prevent loading if preload="none" and the current source isn't loaded (#1044)
1299
1300          if (preload !== 'none' || readyState) {
1301            // Restore time
1302            player.once('loadedmetadata', function () {
1303              player.currentTime = currentTime; // Resume playing
1304
1305              if (!paused) {
1306                player.play();
1307              }
1308            }); // Load new source
1309
1310            player.media.load();
1311          } // Trigger change event
1312
1313
1314          triggerEvent.call(player, player.media, 'qualitychange', false, {
1315            quality: input
1316          });
1317        }
1318      });
1319    },
1320    // Cancel current network requests
1321    // See https://github.com/sampotts/plyr/issues/174
1322    cancelRequests: function cancelRequests() {
1323      if (!this.isHTML5) {
1324        return;
1325      } // Remove child sources
1326
1327
1328      removeElement(html5.getSources.call(this)); // Set blank video src attribute
1329      // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error
1330      // Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection
1331
1332      this.media.setAttribute('src', this.config.blankVideo); // Load the new empty source
1333      // This will cancel existing requests
1334      // See https://github.com/sampotts/plyr/issues/174
1335
1336      this.media.load(); // Debugging
1337
1338      this.debug.log('Cancelled network requests');
1339    }
1340  };
1341
1342  // ==========================================================================
1343
1344  function dedupe(array) {
1345    if (!is$1.array(array)) {
1346      return array;
1347    }
1348
1349    return array.filter(function (item, index) {
1350      return array.indexOf(item) === index;
1351    });
1352  } // Get the closest value in an array
1353
1354  function closest(array, value) {
1355    if (!is$1.array(array) || !array.length) {
1356      return null;
1357    }
1358
1359    return array.reduce(function (prev, curr) {
1360      return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev;
1361    });
1362  }
1363
1364  // ==========================================================================
1365
1366  function generateId(prefix) {
1367    return "".concat(prefix, "-").concat(Math.floor(Math.random() * 10000));
1368  } // Format string
1369
1370  function format(input) {
1371    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
1372      args[_key - 1] = arguments[_key];
1373    }
1374
1375    if (is$1.empty(input)) {
1376      return input;
1377    }
1378
1379    return input.toString().replace(/{(\d+)}/g, function (match, i) {
1380      return args[i].toString();
1381    });
1382  } // Get percentage
1383
1384  function getPercentage(current, max) {
1385    if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {
1386      return 0;
1387    }
1388
1389    return (current / max * 100).toFixed(2);
1390  } // Replace all occurances of a string in a string
1391
1392  function replaceAll() {
1393    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1394    var find = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
1395    var replace = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
1396    return input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1'), 'g'), replace.toString());
1397  } // Convert to title case
1398
1399  function toTitleCase() {
1400    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1401    return input.toString().replace(/\w\S*/g, function (text) {
1402      return text.charAt(0).toUpperCase() + text.substr(1).toLowerCase();
1403    });
1404  } // Convert string to pascalCase
1405
1406  function toPascalCase() {
1407    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1408    var string = input.toString(); // Convert kebab case
1409
1410    string = replaceAll(string, '-', ' '); // Convert snake case
1411
1412    string = replaceAll(string, '_', ' '); // Convert to title case
1413
1414    string = toTitleCase(string); // Convert to pascal case
1415
1416    return replaceAll(string, ' ', '');
1417  } // Convert string to pascalCase
1418
1419  function toCamelCase() {
1420    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1421    var string = input.toString(); // Convert to pascal case
1422
1423    string = toPascalCase(string); // Convert first character to lowercase
1424
1425    return string.charAt(0).toLowerCase() + string.slice(1);
1426  } // Remove HTML from a string
1427
1428  function stripHTML(source) {
1429    var fragment = document.createDocumentFragment();
1430    var element = document.createElement('div');
1431    fragment.appendChild(element);
1432    element.innerHTML = source;
1433    return fragment.firstChild.innerText;
1434  } // Like outerHTML, but also works for DocumentFragment
1435
1436  function getHTML(element) {
1437    var wrapper = document.createElement('div');
1438    wrapper.appendChild(element);
1439    return wrapper.innerHTML;
1440  }
1441
1442  var resources = {
1443    pip: 'PIP',
1444    airplay: 'AirPlay',
1445    html5: 'HTML5',
1446    vimeo: 'Vimeo',
1447    youtube: 'YouTube'
1448  };
1449  var i18n = {
1450    get: function get() {
1451      var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1452      var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1453
1454      if (is$1.empty(key) || is$1.empty(config)) {
1455        return '';
1456      }
1457
1458      var string = getDeep(config.i18n, key);
1459
1460      if (is$1.empty(string)) {
1461        if (Object.keys(resources).includes(key)) {
1462          return resources[key];
1463        }
1464
1465        return '';
1466      }
1467
1468      var replace = {
1469        '{seektime}': config.seekTime,
1470        '{title}': config.title
1471      };
1472      Object.entries(replace).forEach(function (_ref) {
1473        var _ref2 = _slicedToArray(_ref, 2),
1474            k = _ref2[0],
1475            v = _ref2[1];
1476
1477        string = replaceAll(string, k, v);
1478      });
1479      return string;
1480    }
1481  };
1482
1483  var Storage =
1484  /*#__PURE__*/
1485  function () {
1486    function Storage(player) {
1487      _classCallCheck(this, Storage);
1488
1489      this.enabled = player.config.storage.enabled;
1490      this.key = player.config.storage.key;
1491    } // Check for actual support (see if we can use it)
1492
1493
1494    _createClass(Storage, [{
1495      key: "get",
1496      value: function get(key) {
1497        if (!Storage.supported || !this.enabled) {
1498          return null;
1499        }
1500
1501        var store = window.localStorage.getItem(this.key);
1502
1503        if (is$1.empty(store)) {
1504          return null;
1505        }
1506
1507        var json = JSON.parse(store);
1508        return is$1.string(key) && key.length ? json[key] : json;
1509      }
1510    }, {
1511      key: "set",
1512      value: function set(object) {
1513        // Bail if we don't have localStorage support or it's disabled
1514        if (!Storage.supported || !this.enabled) {
1515          return;
1516        } // Can only store objectst
1517
1518
1519        if (!is$1.object(object)) {
1520          return;
1521        } // Get current storage
1522
1523
1524        var storage = this.get(); // Default to empty object
1525
1526        if (is$1.empty(storage)) {
1527          storage = {};
1528        } // Update the working copy of the values
1529
1530
1531        extend(storage, object); // Update storage
1532
1533        window.localStorage.setItem(this.key, JSON.stringify(storage));
1534      }
1535    }], [{
1536      key: "supported",
1537      get: function get() {
1538        try {
1539          if (!('localStorage' in window)) {
1540            return false;
1541          }
1542
1543          var test = '___test'; // Try to use it (it might be disabled, e.g. user is in private mode)
1544          // see: https://github.com/sampotts/plyr/issues/131
1545
1546          window.localStorage.setItem(test, test);
1547          window.localStorage.removeItem(test);
1548          return true;
1549        } catch (e) {
1550          return false;
1551        }
1552      }
1553    }]);
1554
1555    return Storage;
1556  }();
1557
1558  // ==========================================================================
1559  // Fetch wrapper
1560  // Using XHR to avoid issues with older browsers
1561  // ==========================================================================
1562  function fetch(url) {
1563    var responseType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'text';
1564    return new Promise(function (resolve, reject) {
1565      try {
1566        var request = new XMLHttpRequest(); // Check for CORS support
1567
1568        if (!('withCredentials' in request)) {
1569          return;
1570        }
1571
1572        request.addEventListener('load', function () {
1573          if (responseType === 'text') {
1574            try {
1575              resolve(JSON.parse(request.responseText));
1576            } catch (e) {
1577              resolve(request.responseText);
1578            }
1579          } else {
1580            resolve(request.response);
1581          }
1582        });
1583        request.addEventListener('error', function () {
1584          throw new Error(request.status);
1585        });
1586        request.open('GET', url, true); // Set the required response type
1587
1588        request.responseType = responseType;
1589        request.send();
1590      } catch (e) {
1591        reject(e);
1592      }
1593    });
1594  }
1595
1596  // ==========================================================================
1597
1598  function loadSprite(url, id) {
1599    if (!is$1.string(url)) {
1600      return;
1601    }
1602
1603    var prefix = 'cache';
1604    var hasId = is$1.string(id);
1605    var isCached = false;
1606
1607    var exists = function exists() {
1608      return document.getElementById(id) !== null;
1609    };
1610
1611    var update = function update(container, data) {
1612      // eslint-disable-next-line no-param-reassign
1613      container.innerHTML = data; // Check again incase of race condition
1614
1615      if (hasId && exists()) {
1616        return;
1617      } // Inject the SVG to the body
1618
1619
1620      document.body.insertAdjacentElement('afterbegin', container);
1621    }; // Only load once if ID set
1622
1623
1624    if (!hasId || !exists()) {
1625      var useStorage = Storage.supported; // Create container
1626
1627      var container = document.createElement('div');
1628      container.setAttribute('hidden', '');
1629
1630      if (hasId) {
1631        container.setAttribute('id', id);
1632      } // Check in cache
1633
1634
1635      if (useStorage) {
1636        var cached = window.localStorage.getItem("".concat(prefix, "-").concat(id));
1637        isCached = cached !== null;
1638
1639        if (isCached) {
1640          var data = JSON.parse(cached);
1641          update(container, data.content);
1642        }
1643      } // Get the sprite
1644
1645
1646      fetch(url).then(function (result) {
1647        if (is$1.empty(result)) {
1648          return;
1649        }
1650
1651        if (useStorage) {
1652          window.localStorage.setItem("".concat(prefix, "-").concat(id), JSON.stringify({
1653            content: result
1654          }));
1655        }
1656
1657        update(container, result);
1658      }).catch(function () {});
1659    }
1660  }
1661
1662  // ==========================================================================
1663
1664  var getHours = function getHours(value) {
1665    return Math.trunc(value / 60 / 60 % 60, 10);
1666  };
1667  var getMinutes = function getMinutes(value) {
1668    return Math.trunc(value / 60 % 60, 10);
1669  };
1670  var getSeconds = function getSeconds(value) {
1671    return Math.trunc(value % 60, 10);
1672  }; // Format time to UI friendly string
1673
1674  function formatTime() {
1675    var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
1676    var displayHours = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1677    var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1678
1679    // Bail if the value isn't a number
1680    if (!is$1.number(time)) {
1681      return formatTime(null, displayHours, inverted);
1682    } // Format time component to add leading zero
1683
1684
1685    var format = function format(value) {
1686      return "0".concat(value).slice(-2);
1687    }; // Breakdown to hours, mins, secs
1688
1689
1690    var hours = getHours(time);
1691    var mins = getMinutes(time);
1692    var secs = getSeconds(time); // Do we need to display hours?
1693
1694    if (displayHours || hours > 0) {
1695      hours = "".concat(hours, ":");
1696    } else {
1697      hours = '';
1698    } // Render
1699
1700
1701    return "".concat(inverted && time > 0 ? '-' : '').concat(hours).concat(format(mins), ":").concat(format(secs));
1702  }
1703
1704  var controls = {
1705    // Get icon URL
1706    getIconUrl: function getIconUrl() {
1707      var url = new URL(this.config.iconUrl, window.location);
1708      var cors = url.host !== window.location.host || browser.isIE && !window.svg4everybody;
1709      return {
1710        url: this.config.iconUrl,
1711        cors: cors
1712      };
1713    },
1714    // Find the UI controls
1715    findElements: function findElements() {
1716      try {
1717        this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper); // Buttons
1718
1719        this.elements.buttons = {
1720          play: getElements.call(this, this.config.selectors.buttons.play),
1721          pause: getElement.call(this, this.config.selectors.buttons.pause),
1722          restart: getElement.call(this, this.config.selectors.buttons.restart),
1723          rewind: getElement.call(this, this.config.selectors.buttons.rewind),
1724          fastForward: getElement.call(this, this.config.selectors.buttons.fastForward),
1725          mute: getElement.call(this, this.config.selectors.buttons.mute),
1726          pip: getElement.call(this, this.config.selectors.buttons.pip),
1727          airplay: getElement.call(this, this.config.selectors.buttons.airplay),
1728          settings: getElement.call(this, this.config.selectors.buttons.settings),
1729          captions: getElement.call(this, this.config.selectors.buttons.captions),
1730          fullscreen: getElement.call(this, this.config.selectors.buttons.fullscreen)
1731        }; // Progress
1732
1733        this.elements.progress = getElement.call(this, this.config.selectors.progress); // Inputs
1734
1735        this.elements.inputs = {
1736          seek: getElement.call(this, this.config.selectors.inputs.seek),
1737          volume: getElement.call(this, this.config.selectors.inputs.volume)
1738        }; // Display
1739
1740        this.elements.display = {
1741          buffer: getElement.call(this, this.config.selectors.display.buffer),
1742          currentTime: getElement.call(this, this.config.selectors.display.currentTime),
1743          duration: getElement.call(this, this.config.selectors.display.duration)
1744        }; // Seek tooltip
1745
1746        if (is$1.element(this.elements.progress)) {
1747          this.elements.display.seekTooltip = this.elements.progress.querySelector(".".concat(this.config.classNames.tooltip));
1748        }
1749
1750        return true;
1751      } catch (error) {
1752        // Log it
1753        this.debug.warn('It looks like there is a problem with your custom controls HTML', error); // Restore native video controls
1754
1755        this.toggleNativeControls(true);
1756        return false;
1757      }
1758    },
1759    // Create <svg> icon
1760    createIcon: function createIcon(type, attributes) {
1761      var namespace = 'http://www.w3.org/2000/svg';
1762      var iconUrl = controls.getIconUrl.call(this);
1763      var iconPath = "".concat(!iconUrl.cors ? iconUrl.url : '', "#").concat(this.config.iconPrefix); // Create <svg>
1764
1765      var icon = document.createElementNS(namespace, 'svg');
1766      setAttributes(icon, extend(attributes, {
1767        role: 'presentation',
1768        focusable: 'false'
1769      })); // Create the <use> to reference sprite
1770
1771      var use = document.createElementNS(namespace, 'use');
1772      var path = "".concat(iconPath, "-").concat(type); // Set `href` attributes
1773      // https://github.com/sampotts/plyr/issues/460
1774      // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
1775
1776      if ('href' in use) {
1777        use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);
1778      } // Always set the older attribute even though it's "deprecated" (it'll be around for ages)
1779
1780
1781      use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path); // Add <use> to <svg>
1782
1783      icon.appendChild(use);
1784      return icon;
1785    },
1786    // Create hidden text label
1787    createLabel: function createLabel(key) {
1788      var attr = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1789      var text = i18n.get(key, this.config);
1790      var attributes = Object.assign({}, attr, {
1791        class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')
1792      });
1793      return createElement('span', attributes, text);
1794    },
1795    // Create a badge
1796    createBadge: function createBadge(text) {
1797      if (is$1.empty(text)) {
1798        return null;
1799      }
1800
1801      var badge = createElement('span', {
1802        class: this.config.classNames.menu.value
1803      });
1804      badge.appendChild(createElement('span', {
1805        class: this.config.classNames.menu.badge
1806      }, text));
1807      return badge;
1808    },
1809    // Create a <button>
1810    createButton: function createButton(buttonType, attr) {
1811      var _this = this;
1812
1813      var attributes = extend({}, attr);
1814      var type = toCamelCase(buttonType);
1815      var props = {
1816        element: 'button',
1817        toggle: false,
1818        label: null,
1819        icon: null,
1820        labelPressed: null,
1821        iconPressed: null
1822      };
1823      ['element', 'icon', 'label'].forEach(function (key) {
1824        if (Object.keys(attributes).includes(key)) {
1825          props[key] = attributes[key];
1826          delete attributes[key];
1827        }
1828      }); // Default to 'button' type to prevent form submission
1829
1830      if (props.element === 'button' && !Object.keys(attributes).includes('type')) {
1831        attributes.type = 'button';
1832      } // Set class name
1833
1834
1835      if (Object.keys(attributes).includes('class')) {
1836        if (!attributes.class.split(' ').some(function (c) {
1837          return c === _this.config.classNames.control;
1838        })) {
1839          extend(attributes, {
1840            class: "".concat(attributes.class, " ").concat(this.config.classNames.control)
1841          });
1842        }
1843      } else {
1844        attributes.class = this.config.classNames.control;
1845      } // Large play button
1846
1847
1848      switch (buttonType) {
1849        case 'play':
1850          props.toggle = true;
1851          props.label = 'play';
1852          props.labelPressed = 'pause';
1853          props.icon = 'play';
1854          props.iconPressed = 'pause';
1855          break;
1856
1857        case 'mute':
1858          props.toggle = true;
1859          props.label = 'mute';
1860          props.labelPressed = 'unmute';
1861          props.icon = 'volume';
1862          props.iconPressed = 'muted';
1863          break;
1864
1865        case 'captions':
1866          props.toggle = true;
1867          props.label = 'enableCaptions';
1868          props.labelPressed = 'disableCaptions';
1869          props.icon = 'captions-off';
1870          props.iconPressed = 'captions-on';
1871          break;
1872
1873        case 'fullscreen':
1874          props.toggle = true;
1875          props.label = 'enterFullscreen';
1876          props.labelPressed = 'exitFullscreen';
1877          props.icon = 'enter-fullscreen';
1878          props.iconPressed = 'exit-fullscreen';
1879          break;
1880
1881        case 'play-large':
1882          attributes.class += " ".concat(this.config.classNames.control, "--overlaid");
1883          type = 'play';
1884          props.label = 'play';
1885          props.icon = 'play';
1886          break;
1887
1888        default:
1889          if (is$1.empty(props.label)) {
1890            props.label = type;
1891          }
1892
1893          if (is$1.empty(props.icon)) {
1894            props.icon = buttonType;
1895          }
1896
1897      }
1898
1899      var button = createElement(props.element); // Setup toggle icon and labels
1900
1901      if (props.toggle) {
1902        // Icon
1903        button.appendChild(controls.createIcon.call(this, props.iconPressed, {
1904          class: 'icon--pressed'
1905        }));
1906        button.appendChild(controls.createIcon.call(this, props.icon, {
1907          class: 'icon--not-pressed'
1908        })); // Label/Tooltip
1909
1910        button.appendChild(controls.createLabel.call(this, props.labelPressed, {
1911          class: 'label--pressed'
1912        }));
1913        button.appendChild(controls.createLabel.call(this, props.label, {
1914          class: 'label--not-pressed'
1915        }));
1916      } else {
1917        button.appendChild(controls.createIcon.call(this, props.icon));
1918        button.appendChild(controls.createLabel.call(this, props.label));
1919      } // Merge and set attributes
1920
1921
1922      extend(attributes, getAttributesFromSelector(this.config.selectors.buttons[type], attributes));
1923      setAttributes(button, attributes); // We have multiple play buttons
1924
1925      if (type === 'play') {
1926        if (!is$1.array(this.elements.buttons[type])) {
1927          this.elements.buttons[type] = [];
1928        }
1929
1930        this.elements.buttons[type].push(button);
1931      } else {
1932        this.elements.buttons[type] = button;
1933      }
1934
1935      return button;
1936    },
1937    // Create an <input type='range'>
1938    createRange: function createRange(type, attributes) {
1939      // Seek input
1940      var input = createElement('input', extend(getAttributesFromSelector(this.config.selectors.inputs[type]), {
1941        type: 'range',
1942        min: 0,
1943        max: 100,
1944        step: 0.01,
1945        value: 0,
1946        autocomplete: 'off',
1947        // A11y fixes for https://github.com/sampotts/plyr/issues/905
1948        role: 'slider',
1949        'aria-label': i18n.get(type, this.config),
1950        'aria-valuemin': 0,
1951        'aria-valuemax': 100,
1952        'aria-valuenow': 0
1953      }, attributes));
1954      this.elements.inputs[type] = input; // Set the fill for webkit now
1955
1956      controls.updateRangeFill.call(this, input); // Improve support on touch devices
1957
1958      RangeTouch.setup(input);
1959      return input;
1960    },
1961    // Create a <progress>
1962    createProgress: function createProgress(type, attributes) {
1963      var progress = createElement('progress', extend(getAttributesFromSelector(this.config.selectors.display[type]), {
1964        min: 0,
1965        max: 100,
1966        value: 0,
1967        role: 'progressbar',
1968        'aria-hidden': true
1969      }, attributes)); // Create the label inside
1970
1971      if (type !== 'volume') {
1972        progress.appendChild(createElement('span', null, '0'));
1973        var suffixKey = {
1974          played: 'played',
1975          buffer: 'buffered'
1976        }[type];
1977        var suffix = suffixKey ? i18n.get(suffixKey, this.config) : '';
1978        progress.innerText = "% ".concat(suffix.toLowerCase());
1979      }
1980
1981      this.elements.display[type] = progress;
1982      return progress;
1983    },
1984    // Create time display
1985    createTime: function createTime(type, attrs) {
1986      var attributes = getAttributesFromSelector(this.config.selectors.display[type], attrs);
1987      var container = createElement('div', extend(attributes, {
1988        class: "".concat(attributes.class ? attributes.class : '', " ").concat(this.config.classNames.display.time, " ").trim(),
1989        'aria-label': i18n.get(type, this.config)
1990      }), '00:00'); // Reference for updates
1991
1992      this.elements.display[type] = container;
1993      return container;
1994    },
1995    // Bind keyboard shortcuts for a menu item
1996    // We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focu
1996s
1997    // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
1998    bindMenuItemShortcuts: function bindMenuItemShortcuts(menuItem, type) {
1999      var _this2 = this;
2000
2001      // Navigate through menus via arrow keys and space
2002      on(menuItem, 'keydown keyup', function (event) {
2003        // We only care about space and ⬆️ ⬇️️ ➡️
2004        if (![32, 38, 39, 40].includes(event.which)) {
2005          return;
2006        } // Prevent play / seek
2007
2008
2009        event.preventDefault();
2010        event.stopPropagation(); // We're just here to prevent the keydown bubbling
2011
2012        if (event.type === 'keydown') {
2013          return;
2014        }
2015
2016        var isRadioButton = matches$1(menuItem, '[role="menuitemradio"]'); // Show the respective menu
2017
2018        if (!isRadioButton && [32, 39].includes(event.which)) {
2019          controls.showMenuPanel.call(_this2, type, true);
2020        } else {
2021          var target;
2022
2023          if (event.which !== 32) {
2024            if (event.which === 40 || isRadioButton && event.which === 39) {
2025              target = menuItem.nextElementSibling;
2026
2027              if (!is$1.element(target)) {
2028                target = menuItem.parentNode.firstElementChild;
2029              }
2030            } else {
2031              target = menuItem.previousElementSibling;
2032
2033              if (!is$1.element(target)) {
2034                target = menuItem.parentNode.lastElementChild;
2035              }
2036            }
2037
2038            setFocus.call(_this2, target, true);
2039          }
2040        }
2041      }, false); // Enter will fire a `click` event but we still need to manage focus
2042      // So we bind to keyup which fires after and set focus here
2043
2044      on(menuItem, 'keyup', function (event) {
2045        if (event.which !== 13) {
2046          return;
2047        }
2048
2049        controls.focusFirstMenuItem.call(_this2, null, true);
2050      });
2051    },
2052    // Create a settings menu item
2053    createMenuItem: function createMenuItem(_ref) {
2054      var _this3 = this;
2055
2056      var value = _ref.value,
2057          list = _ref.list,
2058          type = _ref.type,
2059          title = _ref.title,
2060          _ref$badge = _ref.badge,
2061          badge = _ref$badge === void 0 ? null : _ref$badge,
2062          _ref$checked = _ref.checked,
2063          checked = _ref$checked === void 0 ? false : _ref$checked;
2064      var attributes = getAttributesFromSelector(this.config.selectors.inputs[type]);
2065      var menuItem = createElement('button', extend(attributes, {
2066        type: 'button',
2067        role: 'menuitemradio',
2068        class: "".concat(this.config.classNames.control, " ").concat(attributes.class ? attributes.class : '').trim(),
2069        'aria-checked': checked,
2070        value: value
2071      }));
2072      var flex = createElement('span'); // We have to set as HTML incase of special characters
2073
2074      flex.innerHTML = title;
2075
2076      if (is$1.element(badge)) {
2077        flex.appendChild(badge);
2078      }
2079
2080      menuItem.appendChild(flex); // Replicate radio button behaviour
2081
2082      Object.defineProperty(menuItem, 'checked', {
2083        enumerable: true,
2084        get: function get() {
2085          return menuItem.getAttribute('aria-checked') === 'true';
2086        },
2087        set: function set(check) {
2088          // Ensure exclusivity
2089          if (check) {
2090            Array.from(menuItem.parentNode.children).filter(function (node) {
2091              return matches$1(node, '[role="menuitemradio"]');
2092            }
2092).forEach(function (node) {
2093              return node.setAttribute('aria-checked', 'false');
2094            });
2095          }
2096
2097          menuItem.setAttribute('aria-checked', check ? 'true' : 'false');
2098        }
2099      });
2100      this.listeners.bind(menuItem, 'click keyup', function (event) {
2101        if (is$1.keyboardEvent(event) && event.which !== 32) {
2102          return;
2103        }
2104
2105        event.preventDefault();
2106        event.stopPropagation();
2107        menuItem.checked = true;
2108
2109        switch (type) {
2110          case 'language':
2111            _this3.currentTrack = Number(value);
2112            break;
2113
2114          case 'quality':
2115            _this3.quality = value;
2116            break;
2117
2118          case 'speed':
2119            _this3.speed = parseFloat(value);
2120            break;
2121
2122          default:
2123            break;
2124        }
2125
2126        controls.showMenuPanel.call(_this3, 'home', is$1.keyboardEvent(event));
2127      }, type, false);
2128      controls.bindMenuItemShortcuts.call(this, menuItem, type);
2129      list.appendChild(menuItem);
2130    },
2131    // Format a time for display
2132    formatTime: function formatTime$1() {
2133      var time = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
2134      var inverted = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2135
2136      // Bail if the value isn't a number
2137      if (!is$1.number(time)) {
2138        return time;
2139      } // Always display hours if duration is over an hour
2140
2141
2142      var forceHours = getHours(this.duration) > 0;
2143      return formatTime(time, forceHours, inverted);
2144    },
2145    // Update the displayed time
2146    updateTimeDisplay: function updateTimeDisplay() {
2147      var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
2148      var time = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
2149      var inverted = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
2150
2151      // Bail if there's no element to display or the value isn't a number
2152      if (!is$1.element(target) || !is$1.number(time)) {
2153        return;
2154      } // eslint-disable-next-line no-param-reassign
2155
2156
2157      target.innerText = controls.formatTime(time, inverted);
2158    },
2159    // Update volume UI and storage
2160    updateVolume: function updateVolume() {
2161      if (!this.supported.ui) {
2162        return;
2163      } // Update range
2164
2165
2166      if (is$1.element(this.elements.inputs.volume)) {
2167        controls.setRange.call(this, this.elements.inputs.volume, this.muted ? 0 : this.volume);
2168      } // Update mute state
2169
2170
2171      if (is$1.element(this.elements.buttons.mute)) {
2172        this.elements.buttons.mute.pressed = this.muted || this.volume === 0;
2173      }
2174    },
2175    // Update seek value and lower fill
2176    setRange: function setRange(target) {
2177      var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
2178
2179      if (!is$1.element(target)) {
2180        return;
2181      } // eslint-disable-next-line
2182
2183
2184      target.value = value; // Webkit range fill
2185
2186      controls.updateRangeFill.call(this, target);
2187    },
2188    // Update <progress> elements
2189    updateProgress: function updateProgress(event) {
2190      var _this4 = this;
2191
2192      if (!this.supported.ui || !is$1.event(event)) {
2193        return;
2194      }
2195
2196      var value = 0;
2197
2198      var setProgress = function setProgress(target, input) {
2199        var val = is$1.number(input) ? input : 0;
2200        var progress = is$1.element(target) ? target : _this4.elements.display.buffer; // Update value and label
2201
2202        if (is$1.element(progress)) {
2203          progress.value = val; // Update text label inside
2204
2205          var label = progress.getElementsByTagName('span')[0];
2206
2207          if (is$1.element(label)) {
2208            label.childNodes[0].nodeValue = val;
2209          }
2210        }
2211      };
2212
2213      if (event) {
2214        switch (event.type) {
2215          // Video playing
2216          case 'timeupdate':
2217          case 'seeking':
2218          case 'seeked':
2219            value = getPercentage(this.currentTime, this.duration); // Set seek range value only if it's a 'natural' time event
2220
2221            if (event.type === 'timeupdate') {
2222              controls.setRange.call(this, this.elements.inputs.seek, value);
2223            }
2224
2225            break;
2226          // Check buffer status
2227
2228          case 'playing':
2229          case 'progress':
2230            setProgress(this.elements.display.buffer, this.buffered * 100);
2231            break;
2232
2233          default:
2234            break;
2235        }
2236      }
2237    },
2238    // Webkit polyfill for lower fill range
2239    updateRangeFill: function updateRangeFill(target) {
2240      // Get range from event if event passed
2241      var range = is$1.event(target) ? target.target : target; // Needs to be a valid <input type='range'>
2242
2243      if (!is$1.element(range) || range.getAttribute('type') !== 'range') {
2244        return;
2245      } // Set aria values for https://github.com/sampotts/plyr/issues/905
2246
2247
2248      if (matches$1(range, this.config.selectors.inputs.seek)) {
2249        range.setAttribute('aria-valuenow', this.currentTime);
2250        var currentTime = controls.formatTime(this.currentTime);
2251        var duration = controls.formatTime(this.duration);
2252        var format = i18n.get('seekLabel', this.config);
2253        range.setAttribute('aria-valuetext', format.replace('{currentTime}', currentTime).replace('{duration}', duration));
2254      } else if (matches$1(range, this.config.selectors.inputs.volume)) {
2255        var percent = range.value * 100;
2256        range.setAttribute('aria-valuenow', percent);
2257        range.setAttribute('aria-valuetext', "".concat(percent.toFixed(1), "%"));
2258      } else {
2259        range.setAttribute('aria-valuenow', range.value);
2260      } // WebKit only
2261
2262
2263      if (!browser.isWebkit) {
2264        return;
2265      } // Set CSS custom property
2266
2267
2268      range.style.setProperty('--value', "".concat(range.value / range.max * 100, "%"));
2269    },
2270    // Update hover tooltip for seeking
2271    updateSeekTooltip: function updateSeekTooltip(event) {
2272      var _this5 = this;
2273
2274      // Bail if setting not true
2275      if (!this.config.tooltips.seek || !is$1.element(this.elements.inputs.seek) || !is$1.element(this.elements.display.seekTooltip) || this.duration === 0) {
2276        return;
2277      }
2278
2279      var visible = "".concat(this.config.classNames.tooltip, "--visible");
2280
2281      var toggle = function toggle(show) {
2282        return toggleClass(_this5.elements.display.seekTooltip, visible, show);
2283      }; // Hide on touch
2284
2285
2286      if (this.touch) {
2287        toggle(false);
2288        return;
2289      } // Determine percentage, if already visible
2290
2291
2292      var percent = 0;
2293      var clientRect = this.elements.progress.getBoundingClientRect();
2294
2295      if (is$1.event(event)) {
2296        percent = 100 / clientRect.width * (event.pageX - clientRect.left);
2297      } else if (hasClass(this.elements.display.seekTooltip, visible)) {
2298        percent = parseFloat(this.elements.display.seekTooltip.style.left, 10);
2299      } else {
2300        return;
2301      } // Set bounds
2302
2303
2304      if (percent < 0) {
2305        percent = 0;
2306      } else if (percent > 100) {
2307        percent = 100;
2308      } // Display the time a click would seek to
2309
2310
2311      controls.updateTimeDisplay.call(this, this.elements.display.seekTooltip, this.duration / 100 * percent); // Set position
2312
2313      this.elements.display.seekTooltip.style.left = "".concat(percent, "%"); // Show/hide the tooltip
2314      // If the event is a moues in/out and percentage is inside bounds
2315
2316      if (is$1.event(event) && ['mouseenter', 'mouseleave'].includes(event.type)) {
2317        toggle(event.type === 'mouseenter');
2318      }
2319    },
2320    // Handle time change event
2321    timeUpdate: function timeUpdate(event) {
2322      // Only invert if only one time element is displayed and used for both duration and currentTime
2323      var invert = !is$1.element(this.elements.display.duration) && this.config.invertTime; // Duration
2324
2325      controls.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert); // Ignore updates while seeking
2326
2327      if (event && event.type === 'timeupdate' && this.media.seeking) {
2328        return;
2329      } // Playing progress
2330
2331
2332      controls.updateProgress.call(this, event);
2333    },
2334    // Show the duration on metadataloaded or durationchange events
2335    durationUpdate: function durationUpdate() {
2336      // Bail if no UI or durationchange event triggered after playing/seek when invertTime is false
2337      if (!this.supported.ui || !this.config.invertTime && this.currentTime) {
2338        return;
2339      } // If duration is the 2**32 (shaka), Infinity (HLS), DASH-IF (Number.MAX_SAFE_INTEGER || Number.MAX_VALUE) indicating live we hide the currentTime and progressbar.
2340      // https://github.com/video-dev/hls.js/blob/5820d29d3c4c8a46e8b75f1e3afa3e68c1a9a2db/src/controller/buffer-controller.js#L415
2341      // https://github.com/google/shaka-player/blob/4d889054631f4e1cf0fbd80ddd2b71887c02e232/lib/media/streaming_engine.js#L1062
2342      // https://github.com/Dash-Industry-Forum/dash.js/blob/69859f51b969645b234666800d4cb596d89c602d/src/dash/models/D
2342ashManifestModel.js#L338
2343
2344
2345      if (this.duration >= Math.pow(2, 32)) {
2346        toggleHidden(this.elements.display.currentTime, true);
2347        toggleHidden(this.elements.progress, true);
2348        return;
2349      } // Update ARIA values
2350
2351
2352      if (is$1.element(this.elements.inputs.seek)) {
2353        this.elements.inputs.seek.setAttribute('aria-valuemax', this.duration);
2354      } // If there's a spot to display duration
2355
2356
2357      var hasDuration = is$1.element(this.elements.display.duration); // If there's only one time display, display duration there
2358
2359      if (!hasDuration && this.config.displayDuration && this.paused) {
2360        controls.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);
2361      } // If there's a duration element, update content
2362
2363
2364      if (hasDuration) {
2365        controls.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);
2366      } // Update the tooltip (if visible)
2367
2368
2369      controls.updateSeekTooltip.call(this);
2370    },
2371    // Hide/show a tab
2372    toggleMenuButton: function toggleMenuButton(setting, toggle) {
2373      toggleHidden(this.elements.settings.buttons[setting], !toggle);
2374    },
2375    // Update the selected setting
2376    updateSetting: function updateSetting(setting, container, input) {
2377      var pane = this.elements.settings.panels[setting];
2378      var value = null;
2379      var list = container;
2380
2381      if (setting === 'captions') {
2382        value = this.currentTrack;
2383      } else {
2384        value = !is$1.empty(input) ? input : this[setting]; // Get default
2385
2386        if (is$1.empty(value)) {
2387          value = this.config[setting].default;
2388        } // Unsupported value
2389
2390
2391        if (!is$1.empty(this.options[setting]) && !this.options[setting].includes(value)) {
2392          this.debug.warn("Unsupported value of '".concat(value, "' for ").concat(setting));
2393          return;
2394        } // Disabled value
2395
2396
2397        if (!this.config[setting].options.includes(value)) {
2398          this.debug.warn("Disabled value of '".concat(value, "' for ").concat(setting));
2399          return;
2400        }
2401      } // Get the list if we need to
2402
2403
2404      if (!is$1.element(list)) {
2405        list = pane && pane.querySelector('[role="menu"]');
2406      } // If there's no list it means it's not been rendered...
2407
2408
2409      if (!is$1.element(list)) {
2410        return;
2411      } // Update the label
2412
2413
2414      var label = this.elements.settings.buttons[setting].querySelector(".".concat(this.config.classNames.menu.value));
2415      label.innerHTML = controls.getLabel.call(this, setting, value); // Find the radio option and check it
2416
2417      var target = list && list.querySelector("[value=\"".concat(value, "\"]"));
2418
2419      if (is$1.element(target)) {
2420        target.checked = true;
2421      }
2422    },
2423    // Translate a value into a nice label
2424    getLabel: function getLabel(setting, value) {
2425      switch (setting) {
2426        case 'speed':
2427          return value === 1 ? i18n.get('normal', this.config) : "".concat(value, "&times;");
2428
2429        case 'quality':
2430          if (is$1.number(value)) {
2431            var label = i18n.get("qualityLabel.".concat(value), this.config);
2432
2433            if (!label.length) {
2434              return "".concat(value, "p");
2435            }
2436
2437            return label;
2438          }
2439
2440          return toTitleCase(value);
2441
2442        case 'captions':
2443          return captions.getLabel.call(this);
2444
2445        default:
2446          return null;
2447      }
2448    },
2449    // Set the quality menu
2450    setQualityMenu: function setQualityMenu(options) {
2451      var _this6 = this;
2452
2453      // Menu required
2454      if (!is$1.element(this.elements.settings.panels.quality)) {
2455        return;
2456      }
2457
2458      var type = 'quality';
2459      var list = this.elements.settings.panels.quality.querySelector('[role="menu"]'); // Set options if passed and filter based on uniqueness and config
2460
2461      if (is$1.array(options)) {
2462        this.options.quality = dedupe(options).filter(function (quality) {
2463          return _this6.config.quality.options.includes(quality);
2464        });
2465      } // Toggle the pane and tab
2466
2467
2468      var toggle = !is$1.empty(this.options.quality) && this.options.quality.length > 1;
2469      controls.toggleMenuButton.call(this, type, toggle); // Empty the menu
2470
2471      emptyElement(list); // Check if we need to toggle the parent
2472
2473      controls.checkMenu.call(this); // If we're hiding, nothing more to do
2474
2475      if (!toggle) {
2476        return;
2477      } // Get the badge HTML for HD, 4K etc
2478
2479
2480      var getBadge = function getBadge(quality) {
2481        var label = i18n.get("qualityBadge.".concat(quality), _this6.config);
2482
2483        if (!label.length) {
2484          return null;
2485        }
2486
2487        return controls.createBadge.call(_this6, label);
2488      }; // Sort options by the config and then render options
2489
2490
2491      this.options.quality.sort(function (a, b) {
2492        var sorting = _this6.config.quality.options;
2493        return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;
2494      }
2494).forEach(function (quality) {
2495        controls.createMenuItem.call(_this6, {
2496          value: quality,
2497          list: list,
2498          type: type,
2499          title: controls.getLabel.call(_this6, 'quality', quality),
2500          badge: getBadge(quality)
2501        });
2502      });
2503      controls.updateSetting.call(this, type, list);
2504    },
2505    // Set the looping options
2506
2507    /* setLoopMenu() {
2508        // Menu required
2509        if (!is.element(this.elements.settings.panels.loop)) {
2510            return;
2511        }
2512         const options = ['start', 'end', 'all', 'reset'];
2513        const list = this.elements.settings.panels.loop.querySelector('[role="menu"]');
2514         // Show the pane and tab
2515        toggleHidden(this.elements.settings.buttons.loop, false);
2516        toggleHidden(this.elements.settings.panels.loop, false);
2517         // Toggle the pane and tab
2518        const toggle = !is.empty(this.loop.options);
2519        controls.toggleMenuButton.call(this, 'loop', toggle);
2520         // Empty the menu
2521        emptyElement(list);
2522         options.forEach(option => {
2523            const item = createElement('li');
2524             const button = createElement(
2525                'button',
2526                extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {
2527                    type: 'button',
2528                    class: this.config.classNames.control,
2529                    'data-plyr-loop-action': option,
2530                }),
2531                i18n.get(option, this.config)
2532            );
2533             if (['start', 'end'].includes(option)) {
2534                const badge = controls.createBadge.call(this, '00:00');
2535                button.appendChild(badge);
2536            }
2537             item.appendChild(button);
2538            list.appendChild(item);
2539        });
2540    }, */
2541    // Get current selected caption language
2542    // TODO: rework this to user the getter in the API?
2543    // Set a list of available captions languages
2544    setCaptionsMenu: function setCaptionsMenu() {
2545      var _this7 = this;
2546
2547      // Menu required
2548      if (!is$1.element(this.elements.settings.panels.captions)) {
2549        return;
2550      } // TODO: Captions or language? Currently it's mixed
2551
2552
2553      var type = 'captions';
2554      var list = this.elements.settings.panels.captions.querySelector('[role="menu"]');
2555      var tracks = captions.getTracks.call(this);
2556      var toggle = Boolean(tracks.length); // Toggle the pane and tab
2557
2558      controls.toggleMenuButton.call(this, type, toggle); // Empty the menu
2559
2560      emptyElement(list); // Check if we need to toggle the parent
2561
2562      controls.checkMenu.call(this); // If there's no captions, bail
2563
2564      if (!toggle) {
2565        return;
2566      } // Generate options data
2567
2568
2569      var options = tracks.map(function (track, value) {
2570        return {
2571          value: value,
2572          checked: _this7.captions.toggled && _this7.currentTrack === value,
2573          title: captions.getLabel.call(_this7, track),
2574          badge: track.language && controls.createBadge.call(_this7, track.language.toUpperCase()),
2575          list: list,
2576          type: 'language'
2577        };
2578      }); // Add the "Disabled" option to turn off captions
2579
2580      options.unshift({
2581        value: -1,
2582        checked: !this.captions.toggled,
2583        title: i18n.get('disabled', this.config),
2584        list: list,
2585        type: 'language'
2586      }); // Generate options
2587
2588      options.forEach(controls.createMenuItem.bind(this));
2589      controls.updateSetting.call(this, type, list);
2590    },
2591    // Set a list of available captions languages
2592    setSpeedMenu: function setSpeedMenu(options) {
2593      var _this8 = this;
2594
2595      // Menu required
2596      if (!is$1.element(this.elements.settings.panels.speed)) {
2597        return;
2598      }
2599
2600      var type = 'speed';
2601      var list = this.elements.settings.panels.speed.querySelector('[role="menu"]'); // Set the speed options
2602
2603      if (is$1.array(options)) {
2604        this.options.speed = options;
2605      } else if (this.isHTML5 || this.isVimeo) {
2606        this.options.speed = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
2607      } // Set options if passed and filter based on config
2608
2609
2610      this.options.speed = this.options.speed.filter(function (speed) {
2611        return _this8.config.speed.options.includes(speed);
2612      }); // Toggle the pane and tab
2613
2614      var toggle = !is$1.empty(this.options.speed) && this.options.speed.length > 1;
2615      controls.toggleMenuButton.call(this, type, toggle); // Empty the menu
2616
2617      emptyElement(list); // Check if we need to toggle the parent
2618
2619      controls.checkMenu.call(this); // If we're hiding, nothing more to do
2620
2621      if (!toggle) {
2622        return;
2623      } // Create items
2624
2625
2626      this.options.speed.forEach(function (speed) {
2627        controls.createMenuItem.call(_this8, {
2628          value: speed,
2629          list: list,
2630          type: type,
2631          title: controls.getLabel.call(_this8, 'speed', speed)
2632        });
2633      });
2634      controls.updateSetting.call(this, type, list);
2635    },
2636    // Check if we need to hide/show the settings menu
2637    checkMenu: function checkMenu() {
2638      var buttons = this.elements.settings.buttons;
2639      var visible = !is$1.empty(buttons) && Object.values(buttons).some(function (button) {
2640        return !button.hidden;
2641      });
2642      toggleHidden(this.elements.settings.menu, !visible);
2643    },
2644    // Focus the first menu item in a given (or visible) menu
2645    focusFirstMenuItem: function focusFirstMenuItem(pane) {
2646      var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2647
2648      if (this.elements.settings.popup.hidden) {
2649        return;
2650      }
2651
2652      var target = pane;
2653
2654      if (!is$1.element(target)) {
2655        target = Object.values(this.elements.settings.panels).find(function (p) {
2656          return !p.hidden;
2657        });
2658      }
2659
2660      var firstItem = target.querySelector('[role^="menuitem"]');
2661      setFocus.call(this, firstItem, tabFocus);
2662    },
2663    // Show/hide menu
2664    toggleMenu: function toggleMenu(input) {
2665      var popup = this.elements.settings.popup;
2666      var button = this.elements.buttons.settings; // Menu and button are required
2667
2668      if (!is$1.element(popup) || !is$1.element(button)) {
2669        return;
2670      } // True toggle by default
2671
2672
2673      var hidden = popup.hidden;
2674      var show = hidden;
2675
2676      if (is$1.boolean(input)) {
2677        show = input;
2678      } else if (is$1.keyboardEvent(input) && input.which === 27) {
2679        show = false;
2680      } else if (is$1.event(input)) {
2681        // If Plyr is in a shadowDOM, the event target is set to the component, instead of the
2682        // Element in the shadowDOM. The path, if available, is complete.
2683        var target = is$1.function(input.composedPath) ? input.composedPath()[0] : input.target;
2684        var isMenuItem = popup.contains(target); // If the click was inside the menu or if the click
2685        // wasn't the button or menu item and we're trying to
2686        // show the menu (a doc click shouldn't show the menu)
2687
2688        if (isMenuItem || !isMenuItem && input.target !== button && show) {
2689          return;
2690        }
2691      } // Set button attributes
2692
2693
2694      button.setAttribute('aria-expanded', show); // Show the actual popup
2695
2696      toggleHidden(popup, !show); // Add class hook
2697
2698      toggleClass(this.elements.container, this.config.classNames.menu.open, show); // Focus the first item if key interaction
2699
2700      if (show && is$1.keyboardEvent(input)) {
2701        controls.focusFirstMenuItem.call(this, null, true);
2702      } else if (!show && !hidden) {
2703        // If closing, re-focus the button
2704        setFocus.call(this, button, is$1.keyboardEvent(input));
2705      }
2706    },
2707    // Get the natural size of a menu panel
2708    getMenuSize: function getMenuSize(tab) {
2709      var clone = tab.cloneNode(true);
2710      clone.style.position = 'absolute';
2711      clone.style.opacity = 0;
2712      clone.removeAttribute('hidden'); // Append to parent so we get the "real" size
2713
2714      tab.parentNode.appendChild(clone); // Get the sizes before we remove
2715
2716      var width = clone.scrollWidth;
2717      var height = clone.scrollHeight; // Remove from the DOM
2718
2719      removeElement(clone);
2720      return {
2721        width: width,
2722        height: height
2723      };
2724    },
2725    // Show a panel in the menu
2726    showMenuPanel: function showMenuPanel() {
2727      var _this9 = this;
2728
2729      var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
2730      var tabFocus = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
2731      var target = this.elements.container.querySelector("#plyr-settings-".concat(this.id, "-").concat(type)); // Nothing to show, bail
2732
2733      if (!is$1.element(target)) {
2734        return;
2735      } // Hide all other panels
2736
2737
2738      var container = target.parentNode;
2739      var current = Array.from(container.children).find(function (node) {
2740        return !node.hidden;
2741      }); // If we can do fancy animations, we'll animate the height/width
2742
2743      if (support.transitions && !support.reducedMotion) {
2744        // Set the current width as a base
2745        container.style.width = "".concat(current.scrollWidth, "px");
2746        container.style.height = "".concat(current.scrollHeight, "px"); // Get potential sizes
2747
2748        var size = controls.getMenuSize.call(this, target); // Restore auto height/width
2749
2750        var restore = function restore(event) {
2751          // We're only bothered about height and width on the container
2752          if (event.target !== container || !['width', 'height'].includes(event.propertyName)) {
2753            return;
2754          } // Revert back to auto
2755
2756
2757          container.style.width = '';
2758          container.style.height = ''; // Only listen once
2759
2760          off.call(_this9, container, transitionEndEvent, restore);
2761        }; // Listen for the transition finishing and restore auto height/width
2762
2763
2764        on.call(this, container, transitionEndEvent, restore); // Set dimensions to target
2765
2766        container.style.width = "".concat(size.width, "px");
2767        container.style.height = "".concat(size.height, "px");
2768      } // Set attributes on current tab
2769
2770
2771      toggleHidden(current, true); // Set attributes on target
2772
2773      toggleHidden(target, false); // Focus the first item
2774
2775      controls.focusFirstMenuItem.call(this, target, tabFocus);
2776    },
2777    // Set the download URL
2778    setDownloadUrl: function setDownloadUrl() {
2779      var button = this.elements.buttons.download; // Bail if no button
2780
2781      if (!is$1.element(button)) {
2782        return;
2783      } // Set attribute
2784
2785
2786      button.setAttribute('href', this.download);
2787    },
2788    // Build the default HTML
2789    create: function create(data) {
2790      var _this10 = this;
2791
2792      var bindMenuItemShortcuts = controls.bindMenuItemShortcuts,
2793          createButton = controls.createButton,
2794          createProgress = controls.createProgress,
2795          createRange = controls.createRange,
2796          createTime = controls.createTime,
2797          setQualityMenu = controls.setQualityMenu,
2798          setSpeedMenu = controls.setSpeedMenu,
2799          showMenuPanel = controls.showMenuPanel;
2800      this.elements.controls = null; // Larger overlaid play button
2801
2802      if (this.config.controls.includes('play-large')) {
2803        this.elements.container.appendChild(createButton.call(this, 'play-large'));
2804      } // Create the container
2805
2806
2807      var container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));
2808      this.elements.controls = container; // Default item attributes
2809
2810      var defaultAttributes = {
2811        class: 'plyr__controls__item'
2812      }; // Loop through controls in order
2813
2814      dedupe(this.config.controls).forEach(function (control) {
2815        // Restart button
2816        if (control === 'restart') {
2817          container.appendChild(createButton.call(_this10, 'restart', defaultAttributes));
2818        } // Rewind button
2819
2820
2821        if (control === 'rewind') {
2822          container.appendChild(createButton.call(_this10, 'rewind', defaultAttributes));
2823        } // Play/Pause button
2824
2825
2826        if (control === 'play') {
2827          container.appendChild(createButton.call(_this10, 'play', defaultAttributes));
2828        } // Fast forward button
2829
2830
2831        if (control === 'fast-forward') {
2832          container.appendChild(createButton.call(_this10, 'fast-forward', defaultAttributes));
2833        } // Progress
2834
2835
2836        if (control === 'progress') {
2837          var progressContainer = createElement('div', {
2838            class: "".concat(defaultAttributes.class, " plyr__progress__container")
2839          });
2840          var progress = createElement('div', getAttributesFromSelector(_this10.config.selectors.progress)); // Seek range slider
2841
2842          progress.appendChild(createRange.call(_this10, 'seek', {
2843            id: "plyr-seek-".concat(data.id)
2844          })); // Buffer progress
2845
2846          progress.appendChild(createProgress.call(_this10, 'buffer')); // TODO: Add loop display indicator
2847          // Seek tooltip
2848
2849          if (_this10.config.tooltips.seek) {
2850            var tooltip = createElement('span', {
2851              class: _this10.config.classNames.tooltip
2852            }, '00:00');
2853            progress.appendChild(tooltip);
2854            _this10.elements.display.seekTooltip = tooltip;
2855          }
2856
2857          _this10.elements.progress = progress;
2858          progressContainer.appendChild(_this10.elements.progress);
2859          container.appendChild(progressContainer);
2860        } // Media current time display
2861
2862
2863        if (control === 'current-time') {
2864          container.appendChild(createTime.call(_this10, 'currentTime', defaultAttributes));
2865        } // Media duration display
2866
2867
2868        if (control === 'duration') {
2869          container.appendChild(createTime.call(_this10, 'duration', defaultAttributes));
2870        } // Volume controls
2871
2872
2873        if (control === 'mute' || control === 'volume') {
2874          var volume = _this10.elements.volume; // Create the volume container if needed
2875
2876          if (!is$1.element(volume) || !container.contains(volume)) {
2877            volume = createElement('div', extend({}, defaultAttributes, {
2878              class: "".concat(defaultAttributes.class, " plyr__volume").trim()
2879            }));
2880            _this10.elements.volume = volume;
2881            container.appendChild(volume);
2882          } // Toggle mute button
2883
2884
2885          if (control === 'mute') {
2886            volume.appendChild(createButton.call(_this10, 'mute'));
2887          } // Volume range control
2888
2889
2890          if (control === 'volume') {
2891            // Set the attributes
2892            var attributes = {
2893              max: 1,
2894              step: 0.05,
2895              value: _this10.config.volume
2896            }; // Create the volume range slider
2897
2898            volume.appendChild(createRange.call(_this10, 'volume', extend(attributes, {
2899              id: "plyr-volume-".concat(data.id)
2900            })));
2901          }
2902        } // Toggle captions button
2903
2904
2905        if (control === 'captions') {
2906          container.appendChild(createButton.call(_this10, 'captions', defaultAttributes));
2907        } // Settings button / menu
2908
2909
2910        if (control === 'settings' && !is$1.empty(_this10.config.settings)) {
2911          var wrapper = createElement('div', extend({}, defaultAttributes, {
2912            class: "".concat(defaultAttributes.class, " plyr__menu").trim(),
2913            hidden: ''
2914          }));
2915          wrapper.appendChild(createButton.call(_this10, 'settings', {
2916            'aria-haspopup': true,
2917            'aria-controls': "plyr-settings-".concat(data.id),
2918            'aria-expanded': false
2919          }));
2920          var popup = createElement('div', {
2921            class: 'plyr__menu__container',
2922            id: "plyr-settings-".concat(data.id),
2923            hidden: ''
2924          });
2925          var inner = createElement('div');
2926          var home = createElement('div', {
2927            id: "plyr-settings-".concat(data.id, "-home")
2928          }); // Create the menu
2929
2930          var menu = createElement('div', {
2931            role: 'menu'
2932          });
2933          home.appendChild(menu);
2934          inner.appendChild(home);
2935          _this10.elements.settings.panels.home = home; // Build the menu items
2936
2937          _this10.config.settings.forEach(function (type) {
2938            // TODO: bundle this with the createMenuItem helper and bindings
2939            var menuItem = createElement('button', extend(getAttributesFromSelector(_this10.config.selectors.buttons.settings), {
2940              type: 'button',
2941              class: "".concat(_this10.config.classNames.control, " ").concat(_this10.config.classNames.control, "--forward"),
2942              role: 'menuitem',
2943              'aria-haspopup': true,
2944              hidden: ''
2945            })); // Bind menu shortcuts for keyboard users
2946
2947            bindMenuItemShortcuts.call(_this10, menuItem, type); // Show menu on click
2948
2949            on(menuItem, 'click', function () {
2950              showMenuPanel.call(_this10, type, false);
2951            });
2952            var flex = createElement('span', null, i18n.get(type, _this10.config));
2953            var value = createElement('span', {
2954              class: _this10.config.classNames.menu.value
2955            }); // Speed contains HTML entities
2956
2957            value.innerHTML = data[type];
2958            flex.appendChild(value);
2959            menuItem.appendChild(flex);
2960            menu.appendChild(menuItem); // Build the panes
2961
2962            var pane = createElement('div', {
2963              id: "plyr-settings-".concat(data.id, "-").concat(type),
2964              hidden: ''
2965            }); // Back button
2966
2967            var backButton = createElement('button', {
2968              type: 'button',
2969              class: "".concat(_this10.config.classNames.control, " ").concat(_this10.config.classNames.control, "--back")
2970            }); // Visible label
2971
2972            backButton.appendChild(createElement('span', {
2973              'aria-hidden': true
2974            }, i18n.get(type, _this10.config))); // Screen reader label
2975
2976            backButton.appendChild(createElement('span', {
2977              class: _this10.config.classNames.hidden
2978            }, i18n.get('menuBack', _this10.config))); // Go back via keyboard
2979
2980            on(pane, 'keydown', function (event) {
2981              // We only care about <-
2982              if (event.which !== 37) {
2983                return;
2984              } // Prevent seek
2985
2986
2987              event.preventDefault();
2988              event.stopPropagation(); // Show the respective menu
2989
2990              showMenuPanel.call(_this10, 'home', true);
2991            }, false); // Go back via button click
2992
2993            on(backButton, 'click', function () {
2994              showMenuPanel.call(_this10, 'home', false);
2995            }); // Add to pane
2996
2997            pane.appendChild(backButton); // Menu
2998
2999            pane.appendChild(createElement('div', {
3000              role: 'menu'
3001            }));
3002            inner.appendChild(pane);
3003            _this10.elements.settings.buttons[type] = menuItem;
3004            _this10.elements.settings.panels[type] = pane;
3005          });
3006
3007          popup.appendChild(inner);
3008          wrapper.appendChild(popup);
3009          container.appendChild(wrapper);
3010          _this10.elements.settings.popup = popup;
3011          _this10.elements.settings.menu = wrapper;
3012        } // Picture in picture button
3013
3014
3015        if (control === 'pip' && support.pip) {
3016          container.appendChild(createButton.call(_this10, 'pip', defaultAttributes));
3017        } // Airplay button
3018
3019
3020        if (control === 'airplay' && support.airplay) {
3021          container.appendChild(createButton.call(_this10, 'airplay', defaultAttributes));
3022        } // Download button
3023
3024
3025        if (control === 'download') {
3026          var _attributes = extend({}, defaultAttributes, {
3027            element: 'a',
3028            href: _this10.download,
3029            target: '_blank'
3030          });
3031
3032          var download = _this10.config.urls.download;
3033
3034          if (!is$1.url(download) && _this10.isEmbed) {
3035            extend(_attributes, {
3036              icon: "logo-".concat(_this10.provider),
3037              label: _this10.provider
3038            });
3039          }
3040
3041          container.appendChild(createButton.call(_this10, 'download', _attributes));
3042        } // Toggle fullscreen button
3043
3044
3045        if (control === 'fullscreen') {
3046          container.appendChild(createButton.call(_this10, 'fullscreen', defaultAttributes));
3047        }
3048      }); // Set available quality levels
3049
3050      if (this.isHTML5) {
3051        setQualityMenu.call(this, html5.getQualityOptions.call(this));
3052      }
3053
3054      setSpeedMenu.call(this);
3055      return container;
3056    },
3057    // Insert controls
3058    inject: function inject() {
3059      var _this11 = this;
3060
3061      // Sprite
3062      if (this.config.loadSprite) {
3063        var icon = controls.getIconUrl.call(this); // Only load external sprite using AJAX
3064
3065        if (icon.cors) {
3066          loadSprite(icon.url, 'sprite-plyr');
3067        }
3068      } // Create a unique ID
3069
3070
3071      this.id = Math.floor(Math.random() * 10000); // Null by default
3072
3073      var container = null;
3074      this.elements.controls = null; // Set template properties
3075
3076      var props = {
3077        id: this.id,
3078        seektime: this.config.seekTime,
3079        title: this.config.title
3080      };
3081      var update = true; // If function, run it and use output
3082
3083      if (is$1.function(this.config.controls)) {
3084        this.config.controls = this.config.controls.call(this, props);
3085      } // Convert falsy controls to empty array (primarily for empty strings)
3086
3087
3088      if (!this.config.controls) {
3089        this.config.controls = [];
3090      }
3091
3092      if (is$1.element(this.config.controls) || is$1.string(this.config.controls)) {
3093        // HTMLElement or Non-empty string passed as the option
3094        container = this.config.controls;
3095      } else {
3096        // Create controls
3097        container = controls.create.call(this, {
3098          id: this.id,
3099          seektime: this.config.seekTime,
3100          speed: this.speed,
3101          quality: this.quality,
3102          captions: captions.getLabel.call(this) // TODO: Looping
3103          // loop: 'None',
3104
3105        });
3106        update = false;
3107      } // Replace props with their value
3108
3109
3110      var replace = function replace(input) {
3111        var result = input;
3112        Object.entries(props).forEach(function (_ref2) {
3113          var _ref3 = _slicedToArray(_ref2, 2),
3114              key = _ref3[0],
3115              value = _ref3[1];
3116
3117          result = replaceAll(result, "{".concat(key, "}"), value);
3118        });
3119        return result;
3120      }; // Update markup
3121
3122
3123      if (update) {
3124        if (is$1.string(this.config.controls)) {
3125          container = replace(container);
3126        } else if (is$1.element(container)) {
3127          container.innerHTML = replace(container.innerHTML);
3128        }
3129      } // Controls container
3130
3131
3132      var target; // Inject to custom location
3133
3134      if (is$1.string(this.config.selectors.controls.container)) {
3135        target = document.querySelector(this.config.selectors.controls.container);
3136      } // Inject into the container by default
3137
3138
3139      if (!is$1.element(target)) {
3140        target = this.elements.container;
3141      } // Inject controls HTML (needs to be before captions, hence "afterbegin")
3142
3143
3144      var insertMethod = is$1.element(container) ? 'insertAdjacentElement' : 'insertAdjacentHTML';
3145      target[insertMethod]('afterbegin', container); // Find the elements if need be
3146
3147      if (!is$1.element(this.elements.controls)) {
3148        controls.findElements.call(this);
3149      } // Add pressed property to buttons
3150
3151
3152      if (!is$1.empty(this.elements.buttons)) {
3153        var addProperty = function addProperty(button) {
3154          var className = _this11.config.classNames.controlPressed;
3155          Object.defineProperty(button, 'pressed', {
3156            enumerable: true,
3157            get: function get() {
3158              return hasClass(button, className);
3159            },
3160            set: function set() {
3161              var pressed = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
3162              toggleClass(button, className, pressed);
3163            }
3164          });
3165        }; // Toggle classname when pressed property is set
3166
3167
3168        Object.values(this.elements.buttons).filter(Boolean).forEach(function (button) {
3169          if (is$1.array(button) || is$1.nodeList(button)) {
3170            Array.from(button).filter(Boolean).forEach(addProperty);
3171          } else {
3172            addProperty(button);
3173          }
3174        });
3175      } // Edge sometimes doesn't finish the paint so force a repaint
3176
3177
3178      if (browser.isEdge) {
3179        repaint(target);
3180      } // Setup tooltips
3181
3182
3183      if (this.config.tooltips.controls) {
3184        var _this$config = this.config,
3185            classNames = _this$config.classNames,
3186            selectors = _this$config.selectors;
3187        var selector = "".concat(selectors.controls.wrapper, " ").concat(selectors.labels, " .").concat(classNames.hidden);
3188        var labels = getElements.call(this, selector);
3189        Array.from(labels).forEach(function (label) {
3190          toggleClass(label, _this11.config.classNames.hidden, false);
3191          toggleClass(label, _this11.config.classNames.tooltip, true);
3192        });
3193      }
3194    }
3195  };
3196
3197  /**
3198   * Parse a string to a URL object
3199   * @param {String} input - the URL to be parsed
3200   * @param {Boolean} safe - failsafe parsing
3201   */
3202
3203  function parseUrl(input) {
3204    var safe = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
3205    var url = input;
3206
3207    if (safe) {
3208      var parser = document.createElement('a');
3209      parser.href = url;
3210      url = parser.href;
3211    }
3212
3213    try {
3214      return new URL(url);
3215    } catch (e) {
3216      return null;
3217    }
3218  } // Convert object to URLSearchParams
3219
3220  function buildUrlParams(input) {
3221    var params = new URLSearchParams();
3222
3223    if (is$1.object(input)) {
3224      Object.entries(input).forEach(function (_ref) {
3225        var _ref2 = _slicedToArray(_ref, 2),
3226            key = _ref2[0],
3227            value = _ref2[1];
3228
3229        params.set(key, value);
3230      });
3231    }
3232
3233    return params;
3234  }
3235
3236  var captions = {
3237    // Setup captions
3238    setup: function setup() {
3239      // Requires UI support
3240      if (!this.supported.ui) {
3241        return;
3242      } // Only Vimeo and HTML5 video supported at this point
3243
3244
3245      if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {
3246        // Clear menu and hide
3247        if (is$1.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {
3248          controls.setCaptionsMenu.call(this);
3249        }
3250
3251        return;
3252      } // Inject the container
3253
3254
3255      if (!is$1.element(this.elements.captions)) {
3256        this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));
3257        insertAfter(this.elements.captions, this.elements.wrapper);
3258      } // Fix IE captions if CORS is used
3259      // Fetch captions and inject as blobs instead (data URIs not supported!)
3260
3261
3262      if (browser.isIE && window.URL) {
3263        var elements = this.media.querySelectorAll('track');
3264        Array.from(elements).forEach(function (track) {
3265          var src = track.getAttribute('src');
3266          var url = parseUrl(src);
3267
3268          if (url !== null && url.hostname !== window.location.href.hostname && ['http:', 'https:'].includes(url.protocol)) {
3269            fetch(src, 'blob').then(function (blob) {
3270              track.setAttribute('src', window.URL.createObjectURL(blob));
3271            }).catch(function () {
3272              removeElement(track);
3273            });
3274          }
3275        });
3276      } // Get and set initial data
3277      // The "preferred" options are not realized unless / until the wanted language has a match
3278      // * languages: Array of user's browser languages.
3279      // * language:  The language preferred by user settings or config
3280      // * active:    The state preferred by user settings or config
3281      // * toggled:   The real captions state
3282
3283
3284      var browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];
3285      var languages = dedupe(browserLanguages.map(function (language) {
3286        return language.split('-')[0];
3287      }));
3288      var language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase(); // Use first browser language when language is 'auto'
3289
3290      if (language === 'auto') {
3291        var _languages = _slicedToArray(languages, 1);
3292
3293        language = _languages[0];
3294      }
3295
3296      var active = this.storage.get('captions');
3297
3298      if (!is$1.boolean(active)) {
3299        active = this.config.captions.active;
3300      }
3301
3302      Object.assign(this.captions, {
3303        toggled: false,
3304        active: active,
3305        language: language,
3306        languages: languages
3307      }); // Watch changes to textTracks and update captions menu
3308
3309      if (this.isHTML5) {
3310        var trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';
3311        on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));
3312      } // Update available languages in list next tick (the event must not be triggered before the listeners)
3313
3314
3315      setTimeout(captions.update.bind(this), 0);
3316    },
3317    // Update available language options in settings based on tracks
3318    update: function update() {
3319      var _this = this;
3320
3321      var tracks = captions.getTracks.call(this, true); // Get the wanted language
3322
3323      var _this$captions = this.captions,
3324          active = _this$captions.active,
3325          language = _this$captions.language,
3326          meta = _this$captions.meta,
3327          currentTrackNode = _this$captions.currentTrackNode;
3328      var languageExists = Boolean(tracks.find(function (track) {
3329        return track.language === language;
3330      })); // Handle tracks (add event listener and "pseudo"-default)
3331
3332      if (this.isHTML5 && this.isVideo) {
3333        tracks.filter(function (track) {
3334          return !meta.get(track);
3335        }).forEach(function (track) {
3336          _this.debug.log('Track added', track); // Attempt to store if the original dom element was "default"
3337
3338
3339          meta.set(track, {
3340            default: track.mode === 'showing'
3341          }); // Turn off native caption rendering to avoid double captions
3342          // eslint-disable-next-line no-param-reassign
3343
3344          track.mode = 'hidden'; // Add event listener for cue changes
3345
3346          on.call(_this, track, 'cuechange', function () {
3347            return captions.updateCues.call(_this);
3348          });
3349        });
3350      } // Update language first time it matches, or if the previous matching track was removed
3351
3352
3353      if (languageExists && this.language !== language || !tracks.includes(currentTrackNode)) {
3354        captions.setLanguage.call(this, language);
3355        captions.toggle.call(this, active && languageExists);
3356      } // Enable or disable captions based on track length
3357
3358
3359      toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is$1.empty(tracks)); // Update available languages in list
3360
3361      if ((this.config.controls || []).includes('settings') && this.config.settings.includes('captions')) {
3362        controls.setCaptionsMenu.call(this);
3363      }
3364    },
3365    // Toggle captions display
3366    // Used internally for the toggleCaptions method, with the passive option forced to false
3367    toggle: function toggle(input) {
3368      var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
3369
3370      // If there's no full support
3371      if (!this.supported.ui) {
3372        return;
3373      }
3374
3375      var toggled = this.captions.toggled; // Current state
3376
3377      var activeClass = this.config.classNames.captions.active; // Get the next state
3378      // If the method is called without parameter, toggle based on current value
3379
3380      var active = is$1.nullOrUndefined(input) ? !toggled : input; // Update state and trigger event
3381
3382      if (active !== toggled) {
3383        // When passive, don't override user preferences
3384        if (!passive) {
3385          this.captions.active = active;
3386          this.storage.set({
3387            captions: active
3388          });
3389        } // Force language if the call isn't passive and there is no matching language to toggle to
3390
3391
3392        if (!this.language && active && !passive) {
3393          var tracks = captions.getTracks.call(this);
3394          var track = captions.findTrack.call(this, [this.captions.language].concat(_toConsumableArray(this.captions.languages)), true); // Override user preferences to avoid switching languages if a matching track is added
3395
3396          this.captions.language = track.language; // Set caption, but don't store in localStorage as user preference
3397
3398          captions.set.call(this, tracks.indexOf(track));
3399          return;
3400        } // Toggle button if it's enabled
3401
3402
3403        if (this.elements.buttons.captions) {
3404          this.elements.buttons.captions.pressed = active;
3405        } // Add class hook
3406
3407
3408        toggleClass(this.elements.container, activeClass, active);
3409        this.captions.toggled = active; // Update settings menu
3410
3411        controls.updateSetting.call(this, 'captions'); // Trigger event (not used internally)
3412
3413        triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');
3414      }
3415    },
3416    // Set captions by track index
3417    // Used internally for the currentTrack setter with the passive option forced to false
3418    set: function set(index) {
3419      var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
3420      var tracks = captions.getTracks.call(this); // Disable captions if setting to -1
3421
3422      if (index === -1) {
3423        captions.toggle.call(this, false, passive);
3424        return;
3425      }
3426
3427      if (!is$1.number(index)) {
3428        this.debug.warn('Invalid caption argument', index);
3429        return;
3430      }
3431
3432      if (!(index in tracks)) {
3433        this.debug.warn('Track not found', index);
3434        return;
3435      }
3436
3437      if (this.captions.currentTrack !== index) {
3438        this.captions.currentTrack = index;
3439        var track = tracks[index];
3440
3441        var _ref = track || {},
3442            language = _ref.language; // Store reference to node for invalidation on remove
3443
3444
3445        this.captions.currentTrackNode = track; // Update settings menu
3446
3447        controls.updateSetting.call(this, 'captions'); // When passive, don't override user preferences
3448
3449        if (!passive) {
3450          this.captions.language = language;
3451          this.storage.set({
3452            language: language
3453          });
3454        } // Handle Vimeo captions
3455
3456
3457        if (this.isVimeo) {
3458          this.embed.enableTextTrack(language);
3459        } // Trigger event
3460
3461
3462        triggerEvent.call(this, this.media, 'languagechange');
3463      } // Show captions
3464
3465
3466      captions.toggle.call(this, true, passive);
3467
3468      if (this.isHTML5 && this.isVideo) {
3469        // If we change the active track while a cue is already displayed we need to update it
3470        captions.updateCues.call(this);
3471      }
3472    },
3473    // Set captions by language
3474    // Used internally for the language setter with the passive option forced to fal
3474se
3475    setLanguage: function setLanguage(input) {
3476      var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
3477
3478      if (!is$1.string(input)) {
3479        this.debug.warn('Invalid language argument', input);
3480        return;
3481      } // Normalize
3482
3483
3484      var language = input.toLowerCase();
3485      this.captions.language = language; // Set currentTrack
3486
3487      var tracks = captions.getTracks.call(this);
3488      var track = captions.findTrack.call(this, [language]);
3489      captions.set.call(this, tracks.indexOf(track), passive);
3490    },
3491    // Get current valid caption tracks
3492    // If update is false it will also ignore tracks without metadata
3493    // This is used to "freeze" the language options when captions.update is false
3494    getTracks: function getTracks() {
3495      var _this2 = this;
3496
3497      var update = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
3498      // Handle media or textTracks missing or null
3499      var tracks = Array.from((this.media || {}).textTracks || []); // For HTML5, use cache instead of current tracks when it exists (if captions.update is false)
3500      // Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)
3501
3502      return tracks.filter(function (track) {
3503        return !_this2.isHTML5 || update || _this2.captions.meta.has(track);
3504      }).filter(function (track) {
3505        return ['captions', 'subtitles'].includes(track.kind);
3506      });
3507    },
3508    // Match tracks based on languages and get the first
3509    findTrack: function findTrack(languages) {
3510      var _this3 = this;
3511
3512      var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
3513      var tracks = captions.getTracks.call(this);
3514
3515      var sortIsDefault = function sortIsDefault(track) {
3516        return Number((_this3.captions.meta.get(track) || {}).default);
3517      };
3518
3519      var sorted = Array.from(tracks).sort(function (a, b) {
3520        return sortIsDefault(b) - sortIsDefault(a);
3521      });
3522      var track;
3523      languages.every(function (language) {
3524        track = sorted.find(function (t) {
3525          return t.language === language;
3526        });
3527        return !track; // Break iteration if there is a match
3528      }); // If no match is found but is required, get first
3529
3530      return track || (force ? sorted[0] : undefined);
3531    },
3532    // Get the current track
3533    getCurrentTrack: function getCurrentTrack() {
3534      return captions.getTracks.call(this)[this.currentTrack];
3535    },
3536    // Get UI label for track
3537    getLabel: function getLabel(track) {
3538      var currentTrack = track;
3539
3540      if (!is$1.track(currentTrack) && support.textTracks && this.captions.toggled) {
3541        currentTrack = captions.getCurrentTrack.call(this);
3542      }
3543
3544      if (is$1.track(currentTrack)) {
3545        if (!is$1.empty(currentTrack.label)) {
3546          return currentTrack.label;
3547        }
3548
3549        if (!is$1.empty(currentTrack.language)) {
3550          return track.language.toUpperCase();
3551        }
3552
3553        return i18n.get('enabled', this.config);
3554      }
3555
3556      return i18n.get('disabled', this.config);
3557    },
3558    // Update captions using current track's active cues
3559    // Also optional array argument in case there isn't any track (ex: vimeo)
3560    updateCues: function updateCues(input) {
3561      // Requires UI
3562      if (!this.supported.ui) {
3563        return;
3564      }
3565
3566      if (!is$1.element(this.elements.captions)) {
3567        this.debug.warn('No captions element to render to');
3568        return;
3569      } // Only accept array or empty input
3570
3571
3572      if (!is$1.nullOrUndefined(input) && !Array.isArray(input)) {
3573        this.debug.warn('updateCues: Invalid input', input);
3574        return;
3575      }
3576
3577      var cues = input; // Get cues from track
3578
3579      if (!cues) {
3580        var track = captions.getCurrentTrack.call(this);
3581        cues = Array.from((track || {}).activeCues || []).map(function (cue) {
3582          return cue.getCueAsHTML();
3583        }).map(getHTML);
3584      } // Set new caption text
3585
3586
3587      var content = cues.map(function (cueText) {
3588        return cueText.trim();
3589      }).join('\n');
3590      var changed = content !== this.elements.captions.innerHTML;
3591
3592      if (changed) {
3593        // Empty the container and create a new child element
3594        emptyElement(this.elements.captions);
3595        var caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));
3596        caption.innerHTML = content;
3597        this.elements.captions.appendChild(caption);
vendor: 5,419 bytes, lines 3597-3788
3597 // Trigger event
3598
3599        triggerEvent.call(this, this.media, 'cuechange');
3600      }
3601    }
3602  };
3603
3604  // ==========================================================================
3605  // Plyr default config
3606  // ==========================================================================
3607  var defaults$1 = {
3608    // Disable
3609    enabled: true,
3610    // Custom media title
3611    title: '',
3612    // Logging to console
3613    debug: false,
3614    // Auto play (if supported)
3615    autoplay: false,
3616    // Only allow one media playing at once (vimeo only)
3617    autopause: true,
3618    // Allow inline playback on iOS (this effects YouTube/Vimeo - HTML5 requires the attribute present)
3619    // TODO: Remove iosNative fullscreen option in favour of this (logic needs work)
3620    playsinline: true,
3621    // Default time to skip when rewind/fast forward
3622    seekTime: 10,
3623    // Default volume
3624    volume: 1,
3625    muted: false,
3626    // Pass a custom duration
3627    duration: null,
3628    // Display the media duration on load in the current time position
3629    // If you have opted to display both duration and currentTime, this is ignored
3630    displayDuration: true,
3631    // Invert the current time to be a countdown
3632    invertTime: true,
3633    // Clicking the currentTime inverts it's value to show time left rather than elapsed
3634    toggleInvert: true,
3635    // Force an aspect ratio
3636    // The format must be `'w:h'` (e.g. `'16:9'`)
3637    ratio: null,
3638    // Click video container to play/pause
3639    clickToPlay: true,
3640    // Auto hide the controls
3641    hideControls: true,
3642    // Reset to start when playback ended
3643    resetOnEnd: false,
3644    // Disable the standard context menu
3645    disableContextMenu: true,
3646    // Sprite (for icons)
3647    loadSprite: true,
3648    iconPrefix: 'plyr',
3649    iconUrl: 'https://cdn.plyr.io/3.5.6/plyr.svg',
3650    // Blank video (used to prevent errors on source change)
3651    blankVideo: 'https://cdn.plyr.io/static/blank.mp4',
3652    // Quality default
3653    quality: {
3654      default: 576,
3655      options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240]
3656    },
3657    // Set loops
3658    loop: {
3659      active: false // start: null,
3660      // end: null,
3661
3662    },
3663    // Speed default and options to display
3664    speed: {
3665      selected: 1,
3666      options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
3667    },
3668    // Keyboard shortcut settings
3669    keyboard: {
3670      focused: true,
3671      global: false
3672    },
3673    // Display tooltips
3674    tooltips: {
3675      controls: false,
3676      seek: true
3677    },
3678    // Captions settings
3679    captions: {
3680      active: false,
3681      language: 'auto',
3682      // Listen to new tracks added after Plyr is initialized.
3683      // This is needed for streaming captions, but may result in unselectable options
3684      update: false
3685    },
3686    // Fullscreen settings
3687    fullscreen: {
3688      enabled: true,
3689      // Allow fullscreen?
3690      fallback: true,
3691      // Fallback using full viewport/window
3692      iosNative: false // Use the native fullscreen in iOS (disables custom controls)
3693
3694    },
3695    // Local storage
3696    storage: {
3697      enabled: true,
3698      key: 'plyr'
3699    },
3700    // Default controls
3701    controls: ['play-large', // 'restart',
3702    // 'rewind',
3703    'play', // 'fast-forward',
3704    'progress', 'current-time', // 'duration',
3705    'mute', 'volume', 'captions', 'settings', 'pip', 'airplay', // 'download',
3706    'fullscreen'],
3707    settings: ['captions', 'quality', 'speed'],
3708    // Localisation
3709    i18n: {
3710      restart: 'Restart',
3711      rewind: 'Rewind {seektime}s',
3712      play: 'Play',
3713      pause: 'Pause',
3714      fastForward: 'Forward {seektime}s',
3715      seek: 'Seek',
3716      seekLabel: '{currentTime} of {duration}',
3717      played: 'Played',
3718      buffered: 'Buffered',
3719      currentTime: 'Current time',
3720      duration: 'Duration',
3721      volume: 'Volume',
3722      mute: 'Mute',
3723      unmute: 'Unmute',
3724      enableCaptions: 'Enable captions',
3725      disableCaptions: 'Disable captions',
3726      download: 'Download',
3727      enterFullscreen: 'Enter fullscreen',
3728      exitFullscreen: 'Exit fullscreen',
3729      frameTitle: 'Player for {title}',
3730      captions: 'Captions',
3731      settings: 'Settings',
3732      menuBack: 'Go back to previous menu',
3733      speed: 'Speed',
3734      normal: 'Normal',
3735      quality: 'Quality',
3736      loop: 'Loop',
3737      start: 'Start',
3738      end: 'End',
3739      all: 'All',
3740      reset: 'Reset',
3741      disabled: 'Disabled',
3742      enabled: 'Enabled',
3743      advertisement: 'Ad',
3744      qualityBadge: {
3745        2160: '4K',
3746        1440: 'HD',
3747        1080: 'HD',
3748        720: 'HD',
3749        576: 'SD',
3750        480: 'SD'
3751      }
3752    },
3753    // URLs
3754    urls: {
3755      download: null,
3756      vimeo: {
3757        sdk: 'https://player.vimeo.com/api/player.js',
3758        iframe: 'https://player.vimeo.com/video/{0}?{1}',
3759        api: 'https://vimeo.com/api/v2/video/{0}.json'
3760      },
3761      youtube: {
3762        sdk: 'https://www.youtube.com/iframe_api',
3763        api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}'
3764      },
3765      googleIMA: {
3766        sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'
3767      }
3768    },
3769    // Custom control listeners
3770    listeners: {
3771      seek: null,
3772      play: null,
3773      pause: null,
3774      restart: null,
3775      rewind: null,
3776      fastForward: null,
3777      mute: null,
3778      volume: null,
3779      captions: null,
3780      download: null,
3781      fullscreen: null,
3782      pip: null,
3783      airplay: null,
3784      speed: null,
3785      quality: null,
3786      loop: null,
3787      language: null
3788    },
3789    // Events to watch and bubble
3790    events: [// Events to watch on HTML5 media elements and bubble
3791    // https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events
3792    'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied', 'ratechange', 'cuechange', // Custom events
3793    'download', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready', // YouTube
3794    'statechange', // Quality
3795    'qualitychange', // Ads
3796    'adsloaded', 'adscontentpause', 'adscontentresume', 'adstarted', 'adsmidpoint', 'adscomplete', 'adsallcomplete', 'adsimpression', 'adsclick'],
3797    // Selectors
3798    // Change these to match your template if using custom HTML
3799    selectors: {
3800      editable: 'input, textarea, select, [contenteditable]',
3801      container: '.plyr',
3802      controls: {
3803        container: null,
3804        wrapper: '.plyr__controls'
3805      },
3806      labels: '[data-plyr]',
3807      buttons: {
3808        play: '[data-plyr="play"]',
3809        pause: '[data-plyr="pause"]',
3810        restart: '[data-plyr="restart"]',
3811        rewind: '[data-plyr="rewind"]',
3812        fastForward: '[data-plyr="fast-forward"]',
3813        mute: '[data-plyr="mute"]',
3814        captions: '[data-plyr="captions"]',
3815        download: '[data-plyr="download"]',
3816        fullscreen: '[data-plyr="fullscreen"]',
3817        pip: '[data-plyr="pip"]',
3818        airplay: '[data-plyr="airplay"]',
3819        settings: '[data-plyr="settings"]',
3820        loop: '[data-plyr="loop"]'
3821      },
3822      inputs: {
3823        seek: '[data-plyr="seek"]',
3824        volume: '[data-plyr="volume"]',
3825        speed: '[data-plyr="speed"]',
3826        language: '[data-plyr="language"]',
3827        quality: '[data-plyr="quality"]'
3828      },
3829      display: {
3830        currentTime: '.plyr__time--current',
3831        duration: '.plyr__time--duration',
3832        buffer: '.plyr__progress__buffer',
3833        loop: '.plyr__progress__loop',
3834        // Used later
3835        volume: '.plyr__volume--display'
3836      },
3837      progress: '.plyr__progress',
3838      captions: '.plyr__captions',
3839      caption: '.plyr__caption'
3840    },
3841    // Class hooks added to the player in different states
3842    classNames: {
3843      type: 'plyr--{0}',
3844      provider: 'plyr--{0}',
3845      video: 'plyr__video-wrapper',
3846      embed: 'plyr__video-embed',
3847      videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',
3848      embedContainer: 'plyr__video-embed__container',
3849      poster: 'plyr__poster',
3850      posterEnabled: 'plyr__poster-enabled',
3851      ads: 'plyr__ads',
3852      control: 'plyr__control',
3853      controlPressed: 'plyr__control--pressed',
3854      playing: 'plyr--playing',
3855      paused: 'plyr--paused',
3856      stopped: 'plyr--stopped',
3857      loading: 'plyr--loading',
3858      hover: 'plyr--hover',
3859      tooltip: 'plyr__tooltip',
3860      cues: 'plyr__cues',
3861      hidden: 'plyr__sr-only',
3862      hideControls: 'plyr--hide-controls',
3863      isIos: 'plyr--is-ios',
3864      isTouch: 'plyr--is-touch',
3865      uiSupported: 'plyr--full-ui',
3866      noTransition: 'plyr--no-transition',
3867      display: {
3868        time: 'plyr__time'
3869      },
3870      menu: {
3871        value: 'plyr__menu__value',
3872        badge: 'plyr__badge',
3873        open: 'plyr--menu-open'
3874      },
3875      captions: {
3876        enabled: 'plyr--captions-enabled',
3877        active: 'plyr--captions-active'
3878      },
3879      fullscreen: {
3880        enabled: 'plyr--fullscreen-enabled',
3881        fallback: 'plyr--fullscreen-fallback'
3882      },
3883      pip: {
3884        supported: 'plyr--pip-supported',
3885        active: 'plyr--pip-active'
3886      },
3887      airplay: {
3888        supported: 'plyr--airplay-supported',
3889        active: 'plyr--airplay-active'
3890      },
3891      tabFocus: 'plyr__tab-focus',
3892      previewThumbnails: {
3893        // Tooltip thumbs
3894        thumbContainer: 'plyr__preview-thumb',
3895        thumbContainerShown: 'plyr__preview-thumb--is-shown',
3896        imageContainer: 'plyr__preview-thumb__image-container',
3897        timeContainer: 'plyr__preview-thumb__time-container',
3898        // Scrubbing
3899        scrubbingContainer: 'plyr__preview-scrubbing',
3900        scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown'
3901      }
3902    },
3903    // Embed attributes
3904    attributes: {
3905      embed: {
3906        provider: 'data-plyr-provider',
3907        id: 'data-plyr-embed-id'
3908      }
3909    },
3910    // Advertisements plugin
3911    // Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio
3912    ads: {
3913      enabled: false,
3914      publisherId: '',
3915      tagUrl: ''
3916    },
3917    // Preview Thumbnails plugin
3918    previewThumbnails: {
3919      enabled: false,
3920      src: ''
3921    },
3922    // Vimeo plugin
3923    vimeo: {
3924      byline: false,
3925      portrait: false,
3926      title: false,
3927      speed: true,
3928      transparent: false
3929    },
3930    // YouTube plugin
3931    youtube: {
3932      noCookie: false,
3933      // Whether to use an alternative version of YouTube without cookies
3934      rel: 0,
3935      // No related vids
3936      showinfo: 0,
3937      // Hide info
3938      iv_load_policy: 3,
3939      // Hide annotations
3940      modestbranding: 1 // Hide logos as much as possible (they still show one in the corner when paused)
3941
3942    }
3943  };
3944
3945  // ==========================================================================
3946  // Plyr states
3947  // ==========================================================================
3948  var pip = {
3949    active: 'picture-in-picture',
3950    inactive: 'inline'
3951  };
3952
3953  // ==========================================================================
3954  // Plyr supported types and providers
3955  // ==========================================================================
3956  var providers = {
3957    html5: 'html5',
3958    youtube: 'youtube',
3959    vimeo: 'vimeo'
3960  };
3961  var types = {
3962    audio: 'audio',
3963    video: 'video'
3964  };
3965  /**
3966   * Get provider by URL
3967   * @param {String} url
3968   */
3969
3970  function getProviderByUrl(url) {
3971    // YouTube
3972    if (/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(url)) {
3973      return providers.youtube;
3974    } // Vimeo
3975
3976
3977    if (/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(url)) {
3978      return providers.vimeo;
3979    }
3980
3981    return null;
3982  }
3983
3984  // ==========================================================================
3985  // Console wrapper
3986  // ==========================================================================
3987  var noop = function noop() {};
3988
3989  var Console =
3990  /*#__PURE__*/
3991  function () {
3992    function Console() {
3993      var enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
3994
3995      _classCallCheck(this, Console);
3996
3997      this.enabled = window.console && enabled;
3998
3999      if (this.enabled) {
4000        this.log('Debugging enabled');
4001      }
4002    }
4003
4004    _createClass(Console, [{
4005      key: "log",
4006      get: function get() {
4007        // eslint-disable-next-line no-console
4008        return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;
4009      }
4010    }, {
4011      key: "warn",
4012      get: function get() {
4013        // eslint-disable-next-line no-console
4014        return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;
4015      }
4016    }, {
4017      key: "error",
4018      get: function get() {
4019        // eslint-disable-next-line no-console
4020        return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;
4021      }
4022    }]);
4023
4024    return Console;
4025  }();
4026
4027  function onChange() {
4028    if (!this.enabled) {
4029      return;
4030    } // Update toggle button
4031
4032
4033    var button = this.player.elements.buttons.fullscreen;
4034
4035    if (is$1.element(button)) {
4036      button.pressed = this.active;
4037    } // Trigger an event
4038
4039
4040    triggerEvent.call(this.player, this.target, this.active ? 'enterfullscreen' : 'exitfullscreen', true); // Trap focus in container
4041
4042    if (!browser.isIos) {
4043      trapFocus.call(this.player, this.target, this.active);
4044    }
4045  }
4046
4047  function toggleFallback() {
4048    var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
4049
4050    // Store or restore scroll position
4051    if (toggle) {
4052      this.scrollPosition = {
4053        x: window.scrollX || 0,
4054        y: window.scrollY || 0
4055      };
4056    } else {
4057      window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);
4058    } // Toggle scroll
4059
4060
4061    document.body.style.overflow = toggle ? 'hidden' : ''; // Toggle class hook
4062
4063    toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle); // Force full viewport on iPhone X+
4064
4065    if (browser.isIos) {
4066      var viewport = document.head.querySelector('meta[name="viewport"]');
4067      var property = 'viewport-fit=cover'; // Inject the viewport meta if required
4068
4069      if (!viewport) {
4070        viewport = document.createElement('meta');
4071        viewport.setAttribute('name', 'viewport');
4072      } // Check if the property already exists
4073
4074
4075      var hasProperty = is$1.string(viewport.content) && viewport.content.includes(property);
4076
4077      if (toggle) {
4078        this.cleanupViewport = !hasProperty;
4079
4080        if (!hasProperty) {
4081          viewport.content += ",".concat(property);
4082        }
4083      } else if (this.cleanupViewport) {
4084        viewport.content = viewport.content.split(',').filter(function (part) {
4085          return part.trim() !== property;
4086        }).join(',');
4087      }
4088    } // Toggle button and fire events
4089
4090
4091    onChange.call(this);
4092  }
4093
4094  var Fullscreen =
4095  /*#__PURE__*/
4096  function () {
4097    function Fullscreen(player) {
4098      var _this = this;
4099
4100      _classCallCheck(this, Fullscreen);
4101
4102      // Keep reference to parent
4103      this.player = player; // Get prefix
4104
4105      this.prefix = Fullscreen.prefix;
4106      this.property = Fullscreen.property; // Scroll position
4107
4108      this.scrollPosition = {
4109        x: 0,
4110        y: 0
4111      }; // Force the use of 'full window/browser' rather than fullscreen
4112
4113      this.forceFallback = player.config.fullscreen.fallback === 'force'; // Register event listeners
4114      // Handle event (incase user presses escape etc)
4115
4116      on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : "".concat(this.prefix, "fullscreenchange"), function () {
4117        // TODO: Filter for target??
4118        onChange.call(_this);
4119      }); // Fullscreen toggle on double click
4120
4121      on.call(this.player, this.player.elements.container, 'dblclick', function (event) {
4122        // Ignore double click in controls
4123        if (is$1.element(_this.player.elements.controls) && _this.player.elements.controls.contains(event.target)) {
4124          return;
4125        }
4126
4127        _this.toggle();
4128      }); // Update the UI
4129
4130      this.update();
4131    } // Determine if native supported
4132
4133
4134    _createClass(Fullscreen, [{
4135      key: "update",
4136      // Update UI
4137      value: function update() {
4138        if (this.enabled) {
4139          var mode;
4140
4141          if (this.forceFallback) {
4142            mode = 'Fallback (forced)';
4143          } else if (Fullscreen.native) {
4144            mode = 'Native';
4145          } else {
4146            mode = 'Fallback';
4147          }
4148
4149          this.player.debug.log("".concat(mode, " fullscreen enabled"));
4150        } else {
4151          this.player.debug.log('Fullscreen not supported and fallback disabled');
4152        } // Add styling hook to show button
4153
4154
4155        toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.enabled);
4156      } // Make an element fullscreen
4157
4158    }, {
4159      key: "enter",
4160      value: function enter() {
4161        if (!this.enabled) {
4162          return;
4163        } // iOS native fullscreen doesn't need the request step
4164
4165
4166        if (browser.isIos && this.player.config.fullscreen.iosNative) {
4167          this.target.webkitEnterFullscreen();
4168        } else if (!Fullscreen.native || this.forceFallback) {
4169          toggleFallback.call(this, true);
4170        } else if (!this.prefix) {
4171          this.target.requestFullscreen();
4172        } else if (!is$1.empty(this.prefix)) {
4173          this.target["".concat(this.prefix, "Request").concat(this.property)]();
4174        }
4175      } // Bail from fullscreen
4176
4177    }, {
4178      key: "exit",
4179      value: function exit() {
4180        if (!this.enabled) {
4181          return;
4182        } // iOS native fullscreen
4183
4184
4185        if (browser.isIos && this.player.config.fullscreen.iosNative) {
4186          this.target.webkitExitFullscreen();
4187          this.player.play();
4188        } else if (!Fullscreen.native || this.forceFallback) {
4189          toggleFallback.call(this, false);
4190        } else if (!this.prefix) {
4191          (document.cancelFullScreen || document.exitFullscreen).call(document);
4192        } else if (!is$1.empty(this.prefix)) {
4193          var action = this.prefix === 'moz' ? 'Cancel' : 'Exit';
4194          document["".concat(this.prefix).concat(action).concat(this.property)]();
4195        }
4196      } // Toggle state
4197
4198    }, {
4199      key: "toggle",
4200      value: function toggle() {
4201        if (!this.active) {
4202          this.enter();
4203        } else {
4204          this.exit();
4205        }
4206      }
4207    }, {
4208      key: "usingNative",
4209      // If we're actually using native
4210      get: function get() {
4211        return Fullscreen.native && !this.forceFallback;
4212      } // Get the prefix for handlers
4213
4214    }, {
4215      key: "enabled",
4216      // Determine if fullscreen is enabled
4217      get: function get() {
4218        return (Fullscreen.native || this.player.config.fullscreen.fallback) && this.player.config.fullscreen.enabled && this.player.supported.ui && this.player.isVideo;
4219      } // Get active state
4220
4221    }, {
4222      key: "active",
4223      get: function get() {
4224        if (!this.enabled) {
4225          return false;
4226        } // Fallback using classname
4227
4228
4229        if (!Fullscreen.native || this.forceFallback) {
4230          return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);
4231        }
4232
4233        var element = !this.prefix ? document.fullscreenElement : document["".concat(this.prefix).concat(this.property, "Element")];
4234        return element === this.target;
4235      } // Get target element
4236
4237    }, {
4238      key: "target",
4239      get: function get() {
4240        return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.container;
4241      }
4242    }], [{
4243      key: "native",
4244      get: function get() {
4245        return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);
4246      }
4247    }, {
4248      key: "prefix",
4249      get: function get() {
4250        // No prefix
4251        if (is$1.function(document.exitFullscreen)) {
4252          return '';
4253        } // Check for fullscreen support by vendor prefix
4254
4255
4256        var value = '';
4257        var prefixes = ['webkit', 'moz', 'ms'];
4258        prefixes.some(function (pre) {
4259          if (is$1.function(document["".concat(pre, "ExitFullscreen")]) || is$1.function(document["".concat(pre, "CancelFullScreen")])) {
4260            value = pre;
4261            return true;
4262          }
4263
4264          return false;
4265        });
4266        return value;
4267      }
4268    }, {
4269      key: "property",
4270      get: function get() {
4271        return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';
4272      }
4273    }]);
4274
4275    return Fullscreen;
4276  }();
4277
4278  // ==========================================================================
4279  // Load image avoiding xhr/fetch CORS issues
4280  // Server status can't be obtained this way unfortunately, so this uses "naturalWidth" to determine if the image has loaded
4281  // By default it checks if it is at least 1px, but you can add a second argument to change this
4282  // ==========================================================================
4283  function loadImage(src) {
4284    var minWidth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
4285    return new Promise(function (resolve, reject) {
4286      var image = new Image();
4287
4288      var handler = function handler() {
4289        delete image.onload;
4290        delete image.onerror;
4291        (image.naturalWidth >= minWidth ? resolve : reject)(image);
4292      };
4293
4294      Object.assign(image, {
4295        onload: handler,
4296        onerror: handler,
4297        src: src
4298      });
4299    });
4300  }
4301
4302  // ==========================================================================
4303  var ui = {
4304    addStyleHook: function addStyleHook() {
4305      toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);
4306      toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);
4307    },
4308    // Toggle native HTML5 media controls
4309    toggleNativeControls: function toggleNativeControls() {
4310      var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
4311
4312      if (toggle && this.isHTML5) {
4313        this.media.setAttribute('controls', '');
4314      } else {
4315        this.media.removeAttribute('controls');
4316      }
4317    },
4318    // Setup the UI
4319    build: function build() {
4320      var _this = this;
4321
4322      // Re-attach media element listeners
4323      // TODO: Use event bubbling?
4324      this.listeners.media(); // Don't setup interface if no support
4325
4326      if (!this.supported.ui) {
4327        this.debug.warn("Basic support only for ".concat(this.provider, " ").concat(this.type)); // Restore native controls
4328
4329        ui.toggleNativeControls.call(this, true); // Bail
4330
4331        return;
4332      } // Inject custom controls if not present
4333
4334
4335      if (!is$1.element(this.elements.controls)) {
4336        // Inject custom controls
4337        controls.inject.call(this); // Re-attach control listeners
4338
4339        this.listeners.controls();
4340      } // Remove native controls
4341
4342
4343      ui.toggleNativeControls.call(this); // Setup captions for HTML5
4344
4345      if (this.isHTML5) {
4346        captions.setup.call(this);
4347      } // Reset volume
4348
4349
4350      this.volume = null; // Reset mute state
4351
4352      this.muted = null; // Reset loop state
4353
4354      this.loop = null; // Reset quality setting
4355
4356      this.quality = null; // Reset speed
4357
4358      this.speed = null; // Reset volume display
4359
4360      controls.updateVolume.call(this); // Reset time display
4361
4362      controls.timeUpdate.call(this); // Update the UI
4363
4364      ui.checkPlaying.call(this); // Check for picture-in-picture support
4365
4366      toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo); // Check for airplay support
4367
4368      toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5); // Add iOS class
4369
4370      toggleClass(this.elements.container, this.config.classNames.isIos, browser.isIos); // Add touch class
4371
4372      toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch); // Ready for API calls
4373
4374      this.ready = true; // Ready event at end of execution stack
4375
4376      setTimeout(function () {
4377        triggerEvent.call(_this, _this.media, 'ready');
4378      }, 0); // Set the title
4379
4380      ui.setTitle.call(this); // Assure the poster image is set, if the property was added before the element was created
4381
4382      if (this.poster) {
4383        ui.setPoster.call(this, this.poster, false).catch(function () {});
4384      } // Manually set the duration if user has overridden it.
4385      // The event listeners for it doesn't get called if preload is disabled (#701)
4386
4387
4388      if (this.config.duration) {
4389        controls.durationUpdate.call(this);
4390      }
4391    },
4392    // Setup aria attribute for play and iframe title
4393    setTitle: function setTitle() {
4394      // Find the current text
4395      var label = i18n.get('play', this.config); // If there's a media title set, use that for the label
4396
4397      if (is$1.string(this.config.title) && !is$1.empty(this.config.title)) {
4398        label += ", ".concat(this.config.title);
4399      } // If there's a play button, set label
4400
4401
4402      Array.from(this.elements.buttons.play || []).forEach(function (button) {
4403        button.setAttribute('aria-label', label);
4404      }); // Set iframe title
4405      // https://github.com/sampotts/plyr/issues/124
4406
4407      if (this.isEmbed) {
4408        var iframe = getElement.call(this, 'iframe');
4409
4410        if (!is$1.element(iframe)) {
4411          return;
4412        } // Default to media type
4413
4414
4415        var title = !is$1.empty(this.config.title) ? this.config.title : 'video';
4416        var format = i18n.get('frameTitle', this.config);
4417        iframe.setAttribute('title', format.replace('{title}', title));
4418      }
4419    },
4420    // Toggle poster
4421    togglePoster: function togglePoster(enable) {
4422      toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);
4423    },
4424    // Set the poster image (async)
4425    // Used internally for the poster setter, with the passive option forced to false
4426    setPoster: function setPoster(poster) {
4427      var _this2 = this;
4428
4429      var passive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
4430
4431      // Don't override if call is passive
4432      if (passive && this.poster) {
4433        return Promise.reject(new Error('Poster already set'));
4434      } // Set property synchronously to respect the call order
4435
4436
4437      this.media.setAttribute('poster', poster); // Wait until ui is ready
4438
4439      return ready.call(this) // Load image
4440      .then(function () {
4441        return loadImage(poster);
4442      }).catch(function (err) {
4443        // Hide poster on error unless it's been set by another call
4444        if (poster === _this2.poster) {
4445          ui.togglePoster.call(_this2, false);
4446        } // Rethrow
4447
4448
4449        throw err;
4450      }).then(function () {
4451        // Prevent race conditions
4452        if (poster !== _this2.poster) {
4453          throw new Error('setPoster cancelled by later call to setPoster');
4454        }
4455      }).then(function () {
4456        Object.assign(_this2.elements.poster.style, {
4457          backgroundImage: "url('".concat(poster, "')"),
4458          // Reset backgroundSize as well (since it can be set to "cover" for padded thumbnails for youtube)
4459          backgroundSize: ''
4460        });
4461        ui.togglePoster.call(_this2, true);
4462        return poster;
4463      });
4464    },
4465    // Check playing state
4466    checkPlaying: function checkPlaying(event) {
4467      var _this3 = this;
4468
4469      // Class hooks
4470      toggleClass(this.elements.container, this.config.classNames.playing, this.playing);
4471      toggleClass(this.elements.container, this.config.classNames.paused, this.paused);
4472      toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped); // Set state
4473
4474      Array.from(this.elements.buttons.play || []).forEach(function (target) {
4475        Object.assign(target, {
4476          pressed: _this3.playing
4477        });
4478      }); // Only update controls on non timeupdate events
4479
4480      if (is$1.event(event) && event.type === 'timeupdate') {
4481        return;
4482      } // Toggle controls
4483
4484
4485      ui.toggleControls.call(this);
4486    },
4487    // Check if media is loading
4488    checkLoading: function checkLoading(event) {
4489      var _this4 = this;
4490
4491      this.loading = ['stalled', 'waiting'].includes(event.type); // Clear timer
4492
4493      clearTimeout(this.timers.loading); // Timer to prevent flicker when seeking
4494
4495      this.timers.loading = setTimeout(function () {
4496        // Update progress bar loading class state
4497        toggleClass(_this4.elements.container, _this4.config.classNames.loading, _this4.loading); // Update controls visibility
4498
4499        ui.toggleControls.call(_this4);
4500      }, this.loading ? 250 : 0);
4501    },
4502    // Toggle controls based on state and `force` argument
4503    toggleControls: function toggleControls(force) {
4504      var controlsElement = this.elements.controls;
4505
4506      if (controlsElement && this.config.hideControls) {
4507        // Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)
4508        var recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now(); // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide
4509
4510        this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));
4511      }
4512    }
4513  };
4514
4515  var Listeners =
4516  /*#__PURE__*/
4517  function () {
4518    function Listeners(player) {
4519      _classCallCheck(this, Listeners);
4520
4521      this.player = player;
4522      this.lastKey = null;
4523      this.focusTimer = null;
4524      this.lastKeyDown = null;
4525      this.handleKey = this.handleKey.bind(this);
4526      this.toggleMenu = this.toggleMenu.bind(this);
4527      this.setTabFocus = this.setTabFocus.bind(this);
4528      this.firstTouch = this.firstTouch.bind(this);
4529    } // Handle key presses
4530
4531
4532    _createClass(Listeners, [{
4533      key: "handleKey",
4534      value: function handleKey(event) {
4535        var player = this.player;
4536        var elements = player.elements;
4537        var code = event.keyCode ? event.keyCode : event.which;
4538        var pressed = event.type === 'keydown';
4539        var repeat = pressed && code === this.lastKey; // Bail if a modifier key is set
4540
4541        if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
4542          return;
4543        } // If the event is bubbled from the media element
4544        // Firefox doesn't get the keycode for whatever reason
4545
4546
4547        if (!is$1.number(code)) {
4548          return;
4549        } // Seek by the number keys
4550
4551
4552        var seekByKey = function seekByKey() {
4553          // Divide the max duration into 10th's and times by the number value
4554          player.currentTime = player.duration / 10 * (code - 48);
4555        }; // Handle the key on keydown
4556        // Reset on keyup
4557
4558
4559        if (pressed) {
4560          // Check focused element
4561          // and if the focused element is not editable (e.g. text input)
4562          // and any that accept key input http://webaim.org/techniques/keyboard/
4563          var focused = document.activeElement;
4564
4565          if (is$1.element(focused)) {
4566            var editable = player.config.selectors.editable;
4567            var seek = elements.inputs.seek;
4568
4569            if (focused !== seek && matches$1(focused, editable)) {
4570              return;
4571            }
4572
4573            if (event.which === 32 && matches$1(focused, 'button, [role^="menuitem"]')) {
4574              return;
4575            }
4576          } // Which keycodes should we prevent default
4577
4578
4579          var preventDefault = [32, 37, 38, 39, 40, 48, 49, 50, 51, 52, 53, 54, 56, 57, 67, 70, 73, 75, 76, 77, 79]; // If the code is found prevent default (e.g. prevent scrolling for arrows)
4580
4581          if (preventDefault.includes(code)) {
4582            event.preventDefault();
4583            event.stopPropagation();
4584          }
4585
4586          switch (code) {
4587            case 48:
4588            case 49:
4589            case 50:
4590            case 51:
4591            case 52:
4592            case 53:
4593            case 54:
4594            case 55:
4595            case 56:
4596            case 57:
4597              // 0-9
4598              if (!repeat) {
4599                seekByKey();
4600              }
4601
4602              break;
4603
4604            case 32:
4605            case 75:
4606              // Space and K key
4607              if (!repeat) {
4608                player.togglePlay();
4609              }
4610
4611              break;
4612
4613            case 38:
4614              // Arrow up
4615              player.increaseVolume(0.1);
4616              break;
4617
4618            case 40:
4619              // Arrow down
4620              player.decreaseVolume(0.1);
4621              break;
4622
4623            case 77:
4624              // M key
4625              if (!repeat) {
4626                player.muted = !player.muted;
4627              }
4628
4629              break;
4630
4631            case 39:
4632              // Arrow forward
4633              player.forward();
4634              break;
4635
4636            case 37:
4637              // Arrow back
4638              player.rewind();
4639              break;
4640
4641            case 70:
4642              // F key
4643              player.fullscreen.toggle();
4644              break;
4645
4646            case 67:
4647              // C key
4648              if (!repeat) {
4649                player.toggleCaptions();
4650              }
4651
4652              break;
4653
4654            case 76:
4655              // L key
4656              player.loop = !player.loop;
4657              break;
4658
4659            /* case 73:
4660                this.setLoop('start');
4661                break;
4662             case 76:
4663                this.setLoop();
4664                break;
4665             case 79:
4666                this.setLoop('end');
4667                break; */
4668
4669            default:
4670              break;
4671          } // Escape is handle natively when in full screen
4672          // So we only need to worry about non native
4673
4674
4675          if (code === 27 && !player.fullscreen.usingNative && player.fullscreen.active) {
4676            player.fullscreen.toggle();
4677          } // Store last code for next cycle
4678
4679
4680          this.lastKey = code;
4681        } else {
4682          this.lastKey = null;
4683        }
4684      } // Toggle menu
4685
4686    }, {
4687      key: "toggleMenu",
4688      value: function toggleMenu(event) {
4689        controls.toggleMenu.call(this.player, event);
4690      } // Device is touch enabled
4691
4692    }, {
4693      key: "firstTouch",
4694      value: function firstTouch() {
4695        var player = this.player;
4696        var elements = player.elements;
4697        player.touch = true; // Add touch class
4698
4699        toggleClass(elements.container, player.config.classNames.isTouch, true);
4700      }
4701    }, {
4702      key: "setTabFocus",
4703      value: function setTabFocus(event) {
4704        var player = this.player;
4705        var elements = player.elements;
4706        clearTimeout(this.focusTimer); // Ignore any key other than tab
4707
4708        if (event.type === 'keydown' && event.which !== 9) {
4709          return;
4710        } // Store reference to event timeStamp
4711
4712
4713        if (event.type === 'keydown') {
4714          this.lastKeyDown = event.timeStamp;
4715        } // Remove current classes
4716
4717
4718        var removeCurrent = function removeCurrent() {
4719          var className = player.config.classNames.tabFocus;
4720          var current = getElements.call(player, ".".concat(className));
4721          toggleClass(current, className, false);
4722        }; // Determine if a key was pressed to trigger this event
4723
4724
4725        var wasKeyDown = event.timeStamp - this.lastKeyDown <= 20; // Ignore focus events if a key was pressed prior
4726
4727        if (event.type === 'focus' && !wasKeyDown) {
4728          return;
4729        } // Remove all current
4730
4731
4732        removeCurrent(); // Delay the adding of classname until the focus has changed
4733        // This event fires before the focusin event
4734
4735        this.focusTimer = setTimeout(function () {
4736          var focused = document.activeElement; // Ignore if current focus element isn't inside the player
4737
4738          if (!elements.container.contains(focused)) {
4739            return;
4740          }
4741
4742          toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
4743        }, 10);
4744      } // Global window & document listeners
4745
4746    }, {
4747      key: "global",
4748      value: function global() {
4749        var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
4750        var player = this.player; // Keyboard shortcuts
4751
4752        if (player.config.keyboard.global) {
4753          toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false);
4754        } // Click anywhere closes menu
4755
4756
4757        toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle); // Detect touch by events
4758
4759        once.call(player, document.body, 'touchstart', this.firstTouch); // Tab focus detection
4760
4761        toggleListener.call(player, document.body, 'keydown focus blur', this.setTabFocus, toggle, false, true);
4762      } // Container listeners
4763
4764    }, {
4765      key: "container",
4766      value: function container() {
4767        var player = this.player;
4768        var config = player.config,
4769            elements = player.elements,
4770            timers = player.timers; // Keyboard shortcuts
4771
4772        if (!config.keyboard.global && config.keyboard.focused) {
4773          on.call(player, elements.container, 'keydown keyup', this.handleKey, false);
4774        } // Toggle controls on mouse events and entering fullscreen
4775
4776
4777        on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', function (event) {
4778          var controlsElement = elements.controls; // Remove button states for fullscreen
4779
4780          if (controlsElement && event.type === 'enterfullscreen') {
4781            controlsElement.pressed = false;
4782            controlsElement.hover = false;
4783          } // Show, then hide after a timeout unless another control event occurs
4784
4785
4786          var show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);
4787          var delay = 0;
4788
4789          if (show) {
4790            ui.toggleControls.call(player, true); // Use longer timeout for touch devices
4791
4792            delay = player.touch ? 3000 : 2000;
4793          } // Clear timer
4794
4795
4796          clearTimeout(timers.controls); // Set new timer to prevent flicker when seeking
4797
4798          timers.controls = setTimeout(function () {
4799            return ui.toggleControls.call(player, false);
4800          }, delay);
4801        }); // Set a gutter for Vimeo
4802
4803        var setGutter = function setGutter(ratio, padding, toggle) {
4804          if (!player.isVimeo) {
4805            return;
4806          }
4807
4808          var target = player.elements.wrapper.firstChild;
4809
4810          var _ratio = _slicedToArray(ratio, 2),
4811              y = _ratio[1];
4812
4813          var _getAspectRatio$call = getAspectRatio.call(player),
4814              _getAspectRatio$call2 = _slicedToArray(_getAspectRatio$call, 2),
4815              videoX = _getAspectRatio$call2[0],
4816              videoY = _getAspectRatio$call2[1];
4817
4818          target.style.maxWidth = toggle ? "".concat(y / videoY * videoX, "px") : null;
4819          target.style.margin = toggle ? '0 auto' : null;
4820        }; // Resize on fullscreen change
4821
4822
4823        var setPlayerSize = function setPlayerSize(measure) {
4824          // If we don't need to measure the viewport
4825          if (!measure) {
4826            return setAspectRatio.call(player);
4827          }
4828
4829          var rect = elements.container.getBoundingClientRect();
4830          var width = rect.width,
4831              height = rect.height;
4832          return setAspectRatio.call(player, "".concat(width, ":").concat(height));
4833        };
4834
4835        var resized = function resized() {
4836          clearTimeout(timers.resized);
4837          timers.resized = setTimeout(setPlayerSize, 50);
4838        };
4839
4840        on.call(player, elements.container, 'enterfullscreen exitfullscreen', function (event) {
4841          var _player$fullscreen = player.fullscreen,
4842              target = _player$fullscreen.target,
4843              usingNative = _player$fullscreen.usingNative; // Ignore events not from target
4844
4845          if (target !== elements.container) {
4846            return;
4847          } // If it's not an embed and no ratio specified
4848
4849
4850          if (!player.isEmbed && is$1.empty(player.config.ratio)) {
4851            return;
4852          }
4853
4854          var isEnter = event.type === 'enterfullscreen'; // Set the player size when entering fullscreen to viewport size
4855
4856          var _setPlayerSize = setPlayerSize(isEnter),
4857              padding = _setPlayerSize.padding,
4858              ratio = _setPlayerSize.ratio; // Set Vimeo gutter
4859
4860
4861          setGutter(ratio, padding, isEnter); // If not using native fullscreen, we need to check for resizes of viewport
4862
4863          if (!usingNative) {
4864            if (isEnter) {
4865              on.call(player, window, 'resize', resized);
4866            } else {
4867              off.call(player, window, 'resize', resized);
4868            }
4869          }
4870        });
4871      } // Listen for media events
4872
4873    }, {
4874      key: "media",
4875      value: function media() {
4876        var _this = this;
4877
4878        var player = this.player;
4879        var elements = player.elements; // Time change on media
4880
4881        on.call(player, player.media, 'timeupdate seeking seeked', function (event) {
4882          return controls.timeUpdate.call(player, event);
4883        }); // Display duration
4884
4885        on.call(player, player.media, 'durationchange loadeddata loadedmetadata', function (event) {
4886          return controls.durationUpdate.call(player, event);
4887        }); // Check for audio tracks on load
4888        // We can't use `loadedmetadata` as it doesn't seem to have audio tracks at that point
4889
4890        on.call(player, player.media, 'canplay loadeddata', function () {
4891          toggleHidden(elements.volume, !player.hasAudio);
4892          toggleHidden(elements.buttons.mute, !player.hasAudio);
4893        }); // Handle the media finishing
4894
4895        on.call(player, player.media, 'ended', function () {
4896          // Show poster on end
4897          if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {
4898            // Restart
4899            player.restart();
4900          }
4901        }); // Check for buffer progress
4902
4903        on.call(player, player.media, 'progress playing seeking seeked', function (event) {
4904          return controls.updateProgress.call(player, event);
4905        }); // Handle volume changes
4906
4907        on.call(player, player.media, 'volumechange', function (event) {
4908          return controls.updateVolume.call(player, event);
4909        }); // Handle play/pause
4910
4911        on.call(player, player.media, 'playing play pause ended emptied timeupdate', function (event) {
4912          return ui.checkPlaying.call(player, event);
4913        }); // Loading state
4914
4915        on.call(player, player.media, 'waiting canplay seeked playing', function (event) {
4916          return ui.checkLoading.call(player, event);
4917        }); // Click video
4918
4919        if (player.supported.ui && player.config.clickToPlay && !player.isAudio) {
4920          // Re-fetch the wrapper
4921          var wrapper = getElement.call(player, ".".concat(player.config.classNames.video)); // Bail if there's no wrapper (this should never happen)
4922
4923          if (!is$1.element(wrapper)) {
4924            return;
4925          } // On click play, pause or restart
4926
4927
4928          on.call(player, elements.container, 'click', function (event) {
4929            var targets = [elements.container, wrapper]; // Ignore if click if not container or in video wrapper
4930
4931            if (!targets.includes(event.target) && !wrapper.contains(event.target)) {
4932              return;
4933            } // Touch devices will just show controls (if hidden)
4934
4935
4936            if (player.touch && player.config.hideControls) {
4937              return;
4938            }
4939
4940            if (player.ended) {
4941              _this.proxy(event, player.restart, 'restart');
4942
4943              _this.proxy(event, player.play, 'play');
4944            } else {
4945              _this.proxy(event, player.togglePlay, 'play');
4946            }
4947          });
4948        } // Disable right click
4949
4950
4951        if (player.supported.ui && player.config.disableContextMenu) {
4952          on.call(player, elements.wrapper, 'contextmenu', function (event) {
4953            event.preventDefault();
4954          }, false);
4955        } // Volume change
4956
4957
4958        on.call(player, player.media, 'volumechange', function () {
4959          // Save to storage
4960          player.storage.set({
4961            volume: player.volume,
4962            muted: player.muted
4963          });
4964        }); // Speed change
4965
4966        on.call(player, player.media, 'ratechange', function () {
4967          // Update UI
4968          controls.updateSetting.call(player, 'speed'); // Save to storage
4969
4970
4971          player.storage.set({
4972            speed: player.speed
4973          });
4974        }); // Quality change
4975
4976        on.call(player, player.media, 'qualitychange', function (event) {
4977          // Update UI
4978          controls.updateSetting.call(player, 'quality', null, event.detail.quality);
4979        }); // Update download link when ready and if quality changes
4980
4981        on.call(player, player.media, 'ready qualitychange', function () {
4982          controls.setDownloadUrl.call(player);
4983        }); // Proxy events to container
4984        // Bubble up key events for Edge
4985
4986        var proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');
4987        on.call(player, player.media, proxyEvents, function (event) {
4988          var _event$detail = event.detail,
4989              detail = _event$detail === void 0 ? {}
4989 : _event$detail; // Get error details from media
4990
4991          if (event.type === 'error') {
4992            detail = player.media.error;
4993          }
4994
4995          triggerEvent.call(player, elements.container, event.type, true, detail);
4996        });
4997      } // Run default and custom handlers
4998
4999    }, {
5000      key: "proxy",
5001      value: function proxy(event, defaultHandler, customHandlerKey) {
5002        var player = this.player;
5003        var customHandler = player.config.listeners[customHandlerKey];
5004        var hasCustomHandler = is$1.function(customHandler);
5005        var returned = true; // Execute custom handler
5006
5007        if (hasCustomHandler) {
5008          returned = customHandler.call(player, event);
5009        } // Only call default handler if not prevented in custom handler
5010
5011
5012        if (returned && is$1.function(defaultHandler)) {
5013          defaultHandler.call(player, event);
5014        }
5015      } // Trigger custom and default handlers
5016
5017    }, {
5018      key: "bind",
5019      value: function bind(element, type, defaultHandler, customHandlerKey) {
5020        var _this2 = this;
5021
5022        var passive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
5023        var player = this.player;
5024        var customHandler = player.config.listeners[customHandlerKey];
5025        var hasCustomHandler = is$1.function(customHandler);
5026        on.call(player, element, type, function (event) {
5027          return _this2.proxy(event, defaultHandler, customHandlerKey);
5028        }, passive && !hasCustomHandler);
5029      } // Listen for control events
5030
5031    }, {
5032      key: "controls",
5033      value: function controls$1() {
5034        var _this3 = this;
5035
5036        var player = this.player;
5037        var elements = player.elements; // IE doesn't support input event, so we fallback to change
5038
5039        var inputEvent = browser.isIE ? 'change' : 'input'; // Play/pause toggle
5040
5041        if (elements.buttons.play) {
5042          Array.from(elements.buttons.play).forEach(function (button) {
5043            _this3.bind(button, 'click', player.togglePlay, 'play');
5044          });
5045        } // Pause
5046
5047
5048        this.bind(elements.buttons.restart, 'click', player.restart, 'restart'); // Rewind
5049
5050        this.bind(elements.buttons.rewind, 'click', player.rewind, 'rewind'); // Rewind
5051
5052        this.bind(elements.buttons.fastForward, 'click', player.forward, 'fastForward'); // Mute toggle
5053
5054        this.bind(elements.buttons.mute, 'click', function () {
5055          player.muted = !player.muted;
5056        }, 'mute'); // Captions toggle
5057
5058        this.bind(elements.buttons.captions, 'click', function () {
5059          return player.toggleCaptions();
5060        }); // Download
5061
5062        this.bind(elements.buttons.download, 'click', function () {
5063          triggerEvent.call(player, player.media, 'download');
5064        }, 'download'); // Fullscreen toggle
5065
5066        this.bind(elements.buttons.fullscreen, 'click', function () {
5067          player.fullscreen.toggle();
5068        }, 'fullscreen'); // Picture-in-Picture
5069
5070        this.bind(elements.buttons.pip, 'click', function () {
5071          player.pip = 'toggle';
5072        }, 'pip'); // Airplay
5073
5074        this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay'); // Settings menu - click toggle
5075
5076        this.bind(elements.buttons.settings, 'click', function (event) {
5077          // Prevent the document click listener closing the menu
5078          event.stopPropagation();
5079
5080          controls.toggleMenu.call(player, event);
5081        }); // Settings menu - keyboard toggle
5082        // We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus
5083        // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
5084
5085        this.bind(elements.buttons.settings, 'keyup', function (event) {
5086          var code = event.which; // We only care about space and return
5087
5088          if (![13, 32].includes(code)) {
5089            return;
5090          } // Because return triggers a click anyway, all we need to do is set focus
5091
5092
5093          if (code === 13) {
5094            controls.focusFirstMenuItem.call(player, null, true);
5095
5096            return;
5097          } // Prevent scroll
5098
5099
5100          event.preventDefault(); // Prevent playing video (Firefox)
5101
5102          event.stopPropagation(); // Toggle menu
5103
5104          controls.toggleMenu.call(player, event);
5105        }, null, false // Can't be passive as we're preventing default
5106        ); // Escape closes menu
5107
5108        this.bind(elements.settings.menu, 'keydown', function (event) {
5109          if (event.which === 27) {
5110            controls.toggleMenu.call(player, event);
5111          }
5112        }); // Set range input alternative "value", which matches the tooltip time (#954)
5113
5114        this.bind(elements.inputs.seek, 'mousedown mousemove', function (event) {
5115          var rect = elements.progress.getBoundingClientRect();
5116          var percent = 100 / rect.width * (event.pageX - rect.left);
5117          event.currentTarget.setAttribute('seek-value', percent);
5118        }); // Pause while seeking
5119
5120        this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', function (event) {
5121          var seek = event.currentTarget;
5122          var code = event.keyCode ? event.keyCode : event.which;
5123          var attribute = 'play-on-seeked';
5124
5125          if (is$1.keyboardEvent(event) && code !== 39 && code !== 37) {
5126            return;
5127          } // Record seek time so we can prevent hiding controls for a few seconds after seek
5128
5129
5130          player.lastSeekTime = Date.now(); // Was playing before?
5131
5132          var play = seek.hasAttribute(attribute); // Done seeking
5133
5134          var done = ['mouseup', 'touchend', 'keyup'].includes(event.type); // If we're done seeking and it was playing, resume playback
5135
5136          if (play && done) {
5137            seek.removeAttribute(attribute);
5138            player.play();
5139          } else if (!done && player.playing) {
5140            seek.setAttribute(attribute, '');
5141            player.pause();
5142          }
5143        }); // Fix range inputs on iOS
5144        // Super weird iOS bug where after you interact with an <input type="range">,
5145        // it takes over further interactions on the page. This is a hack
5146
5147        if (browser.isIos) {
5148          var inputs = getElements.call(player, 'input[type="range"]');
5149          Array.from(inputs).forEach(function (input) {
5150            return _this3.bind(input, inputEvent, function (event) {
5151              return repaint(event.target);
5152            });
5153          });
5154        } // Seek
5155
5156
5157        this.bind(elements.inputs.seek, inputEvent, function (event) {
5158          var seek = event.currentTarget; // If it exists, use seek-value instead of "value" for consistency with tooltip time (#954)
5159
5160          var seekTo = seek.getAttribute('seek-value');
5161
5162          if (is$1.empty(seekTo)) {
5163            seekTo = seek.value;
5164          }
5165
5166          seek.removeAttribute('seek-value');
5167          player.currentTime = seekTo / seek.max * player.duration;
5168        }, 'seek'); // Seek tooltip
5169
5170        this.bind(elements.progress, 'mouseenter mouseleave mousemove', function (event) {
5171          return controls.updateSeekTooltip.call(player, event);
5172        }); // Preview thumbnails plugin
5173        // TODO: Really need to work on some sort of plug-in wide event bus or pub-sub for this
5174
5175        this.bind(elements.progress, 'mousemove touchmove', function (event) {
5176          var previewThumbnails = player.previewThumbnails;
5177
5178          if (previewThumbnails && previewThumbnails.loaded) {
5179            previewThumbnails.startMove(event);
5180          }
5181        }); // Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering
5182
5183        this.bind(elements.progress, 'mouseleave click', function () {
5184          var previewThumbnails = player.previewThumbnails;
5185
5186          if (previewThumbnails && previewThumbnails.loaded) {
5187            previewThumbnails.endMove(false, true);
5188          }
5189        }); // Show scrubbing preview
5190
5191        this.bind(elements.progress, 'mousedown touchstart', function (event) {
5192          var previewThumbnails = player.previewThumbnails;
5193
5194          if (previewThumbnails && previewThumbnails.loaded) {
5195            previewThumbnails.startScrubbing(event);
5196          }
5197        });
5198        this.bind(elements.progress, 'mouseup touchend', function (event) {
5199          var previewThumbnails = player.previewThumbnails;
5200
5201          if (previewThumbnails && previewThumbnails.loaded) {
5202            previewThumbnails.endScrubbing(event);
5203          }
5204        }); // Polyfill for lower fill in <input type="range"> for webkit
5205
5206        if (browser.isWebkit) {
5207          Array.from(getElements.call(player, 'input[type="range"]')).forEach(function (element) {
5208            _this3.bind(element, 'input', function (event) {
5209              return controls.updateRangeFill.call(player, event.target);
5210            });
5211          });
5212        } // Current time invert
5213        // Only if one time element is used for both currentTime and duration
5214
5215
5216        if (player.config.toggleInvert && !is$1.element(elements.display.duration)) {
5217          this.bind(elements.display.currentTime, 'click', function () {
5218            // Do nothing if we're at the start
5219            if (player.currentTime === 0) {
5220              return;
5221            }
5222
5223            player.config.invertTime = !player.config.invertTime;
5224
5225            controls.timeUpdate.call(player);
5226          });
5227        } // Volume
5228
5229
5230        this.bind(elements.inputs.volume, inputEvent, function (event) {
5231          player.volume = event.target.value;
5232        }, 'volume'); // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)
5233
5234        this.bind(elements.controls, 'mouseenter mouseleave', function (event) {
5235          elements.controls.hover = !player.touch && event.type === 'mouseenter';
5236        }); // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)
5237
5238        this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', function (event) {
5239          elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);
5240        }); // Show controls when they receive focus (e.g., when using keyboard tab key)
5241
5242        this.bind(elements.controls, 'focusin', function () {
5243          var config = player.config,
5244              timers = player.timers; // Skip transition to prevent focus from scrolling the parent element
5245
5246          toggleClass(elements.controls, config.classNames.noTransition, true); // Toggle
5247
5248          ui.toggleControls.call(player, true); // Restore transition
5249
5250          setTimeout(function () {
5251            toggleClass(elements.controls, config.classNames.noTransition, false);
5252          }, 0); // Delay a little more for mouse users
5253
5254          var delay = _this3.touch ? 3000 : 4000; // Clear timer
5255
5256          clearTimeout(timers.controls); // Hide again after delay
5257
5258          timers.controls = setTimeout(function () {
5259            return ui.toggleControls.call(player, false);
5260          }, delay);
5261        }); // Mouse wheel for volume
5262
5263        this.bind(elements.inputs.volume, 'wheel', function (event) {
5264          // Detect "natural" scroll - suppored on OS X Safari only
5265          // Other browsers on OS X will be inverted until support improves
5266          var inverted = event.webkitDirectionInvertedFromDevice; // Get delta from event. Invert if `inverted` is true
5267
5268          var _map = [event.deltaX, -event.deltaY].map(function (value) {
5269            return inverted ? -value : value;
5270          }),
5271              _map2 = _slicedToArray(_map, 2),
5272              x = _map2[0],
5273              y = _map2[1]; // Using the biggest delta, normalize to 1 or -1 (or 0 if no delta)
5274
5275
5276          var direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y); // Change the volume by 2%
5277
5278          player.increaseVolume(direction / 50); // Don't break page scrolling at max and min
5279
5280          var volume = player.media.volume;
5281
5282          if (direction === 1 && volume < 1 || direction === -1 && volume > 0) {
5283            event.preventDefault();
5284          }
5285        }, 'volume', false);
5286      }
5287    }]);
5288
5289    return Listeners;
5290  }();
5291
5292  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
5293
5294  function createCommonjsModule(fn, module) {
5295  	return module = { exports: {} }, fn(module, module.exports), module.exports;
5296  }
5297
5298  var loadjs_umd = createCommonjsModule(function (module, exports) {
5299    (function (root, factory) {
5300      {
5301        module.exports = factory();
5302      }
5303    })(commonjsGlobal, function () {
5304      /**
5305       * Global dependencies.
5306       * @global {Object} document - DOM
5307       */
5308      var devnull = function devnull() {},
5309          bundleIdCache = {},
5310          bundleResultCache = {},
5311          bundleCallbackQueue = {};
5312      /**
5313       * Subscribe to bundle load event.
5314       * @param {string[]} bundleIds - Bundle ids
5315       * @param {Function} callbackFn - The callback function
5316       */
5317
5318
5319      function subscribe(bundleIds, callbackFn) {
5320        // listify
5321        bundleIds = bundleIds.push ? bundleIds : [bundleIds];
5322        var depsNotFound = [],
5323            i = bundleIds.length,
5324            numWaiting = i,
5325            fn,
5326            bundleId,
5327            r,
5328            q; // define callback function
5329
5330        fn = function fn(bundleId, pathsNotFound) {
5331          if (pathsNotFound.length) depsNotFound.push(bundleId);
5332          numWaiting--;
5333          if (!numWaiting) callbackFn(depsNotFound);
5334        }; // register callback
5335
5336
5337        while (i--) {
5338          bundleId = bundleIds[i]; // execute callback if in result cache
5339
5340          r = bundleResultCache[bundleId];
5341
5342          if (r) {
5343            fn(bundleId, r);
5344            continue;
5345          } // add to callback queue
5346
5347
5348          q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || [];
5349          q.push(fn);
5350        }
5351      }
5352      /**
5353       * Publish bundle load event.
5354       * @param {string} bundleId - Bundle id
5355       * @param {string[]} pathsNotFound - List of files not found
5356       */
5357
5358
5359      function publish(bundleId, pathsNotFound) {
5360        // exit if id isn't defined
5361        if (!bundleId) return;
5362        var q = bundleCallbackQueue[bundleId]; // cache result
5363
5364        bundleResultCache[bundleId] = pathsNotFound; // exit if queue is empty
5365
5366        if (!q) return; // empty callback queue
5367
5368        while (q.length) {
5369          q[0](bundleId, pathsNotFound);
5370          q.splice(0, 1);
5371        }
5372      }
5373      /**
5374       * Execute callbacks.
5375       * @param {Object or Function} args - The callback args
5376       * @param {string[]} depsNotFound - List of dependencies not found
5377       */
5378
5379
5380      function executeCallbacks(args, depsNotFound) {
5381        // accept function as argument
5382        if (args.call) args = {
5383          success: args
5384        }; // success and error callbacks
5385
5386        if (depsNotFound.length) (args.error || devnull)(depsNotFound);else (args.success || devnull)(args);
5387      }
5388      /**
5389       * Load individual file.
5390       * @param {string} path - The file path
5391       * @param {Function} callbackFn - The callback function
5392       */
5393
5394
5395      function loadFile(path, callbackFn, args, numTries) {
5396        var doc = document,
5397            async = args.async,
5398            maxTries = (args.numRetries || 0) + 1,
5399            beforeCallbackFn = args.before || devnull,
5400            pathStripped = path.replace(/^(css|img)!/, ''),
5401            isLegacyIECss,
5402            e;
5403        numTries = numTries || 0;
5404
5405        if (/(^css!|\.css$)/.test(path)) {
5406          // css
5407          e = doc.createElement('link');
5408          e.rel = 'stylesheet';
5409          e.href = pathStripped; // tag IE9+
5410
vendor: 4,329 bytes, lines 5411-5542
5411          isLegacyIECss = 'hideFocus' in e; // use preload in IE Edge (to detect load errors)
5412
5413          if (isLegacyIECss && e.relList) {
5414            isLegacyIECss = 0;
5415            e.rel = 'preload';
5416            e.as = 'style';
5417          }
5418        } else if (/(^img!|\.(png|gif|jpg|svg)$)/.test(path)) {
5419          // image
5420          e = doc.createElement('img');
5421          e.src = pathStripped;
5422        } else {
5423          // javascript
5424          e = doc.createElement('script');
5425          e.src = path;
5426          e.async = async === undefined ? true : async;
5427        }
5428
5429        e.onload = e.onerror = e.onbeforeload = function (ev) {
5430          var result = ev.type[0]; // treat empty stylesheets as failures to get around lack of onerror
5431          // support in IE9-11
5432
5433          if (isLegacyIECss) {
5434            try {
5435              if (!e.sheet.cssText.length) result = 'e';
5436            } catch (x) {
5437              // sheets objects created from load errors don't allow access to
5438              // `cssText` (unless error is Code:18 SecurityError)
5439              if (x.code != 18) result = 'e';
5440            }
5441          } // handle retries in case of load failure
5442
5443
5444          if (result == 'e') {
5445            // increment counter
5446            numTries += 1; // exit function and try again
5447
5448            if (numTries < maxTries) {
5449              return loadFile(path, callbackFn, args, numTries);
5450            }
5451          } else if (e.rel == 'preload' && e.as == 'style') {
5452            // activate preloaded stylesheets
5453            return e.rel = 'stylesheet'; // jshint ignore:line
5454          } // execute callback
5455
5456
5457          callbackFn(path, result, ev.defaultPrevented);
5458        }; // add to document (unless callback returns `false`)
5459
5460
5461        if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e);
5462      }
5463      /**
5464       * Load multiple files.
5465       * @param {string[]} paths - The file paths
5466       * @param {Function} callbackFn - The callback function
5467       */
5468
5469
5470      function loadFiles(paths, callbackFn, args) {
5471        // listify paths
5472        paths = paths.push ? paths : [paths];
5473        var numWaiting = paths.length,
5474            x = numWaiting,
5475            pathsNotFound = [],
5476            fn,
5477            i; // define callback function
5478
5479        fn = function fn(path, result, defaultPrevented) {
5480          // handle error
5481          if (result == 'e') pathsNotFound.push(path); // handle beforeload event. If defaultPrevented then that means the load
5482          // will be blocked (ex. Ghostery/ABP on Safari)
5483
5484          if (result == 'b') {
5485            if (defaultPrevented) pathsNotFound.push(path);else return;
5486          }
5487
5488          numWaiting--;
5489          if (!numWaiting) callbackFn(pathsNotFound);
5490        }; // load scripts
5491
5492
5493        for (i = 0; i < x; i++) {
5494          loadFile(paths[i], fn, args);
5495        }
5496      }
5497      /**
5498       * Initiate script load and register bundle.
5499       * @param {(string|string[])} paths - The file paths
5500       * @param {(string|Function|Object)} [arg1] - The (1) bundleId or (2) success
5501       *   callback or (3) object literal with success/error arguments, numRetries,
5502       *   etc.
5503       * @param {(Function|Object)} [arg2] - The (1) success callback or (2) object
5504       *   literal with success/error arguments, numRetries, etc.
5505       */
5506
5507
5508      function loadjs(paths, arg1, arg2) {
5509        var bundleId, args; // bundleId (if string)
5510
5511        if (arg1 && arg1.trim) bundleId = arg1; // args (default is {})
5512
5513        args = (bundleId ? arg2 : arg1) || {}; // throw error if bundle is already defined
5514
5515        if (bundleId) {
5516          if (bundleId in bundleIdCache) {
5517            throw "LoadJS";
5518          } else {
5519            bundleIdCache[bundleId] = true;
5520          }
5521        }
5522
5523        function loadFn(resolve, reject) {
5524          loadFiles(paths, function (pathsNotFound) {
5525            // execute callbacks
5526            executeCallbacks(args, pathsNotFound); // resolve Promise
5527
5528            if (resolve) {
5529              executeCallbacks({
5530                success: resolve,
5531                error: reject
5532              }, pathsNotFound);
5533            } // publish bundle load event
5534
5535
5536            publish(bundleId, pathsNotFound);
5537          }, args);
5538        }
5539
5540        if (args.returnPromise) return new Promise(loadFn);else loadFn();
5541      }
5542      /**
5543       * Execute callbacks when dependencies have been satisfied.
5544       * @param {(string|string[])} deps - List of bundle ids
5545       * @param {Object} args - success/error arguments
5546       */
5547
5548
5549      loadjs.ready = function ready(deps, args) {
5550        // subscribe to bundle load event
5551        subscribe(deps, function (depsNotFound) {
5552          // execute callbacks
5553          executeCallbacks(args, depsNotFound);
5554        });
5555        return loadjs;
5556      };
5557      /**
5558       * Manually satisfy bundle dependencies.
5559       * @param {string} bundleId - The bundle id
5560       */
5561
5562
5563      loadjs.done = function done(bundleId) {
5564        publish(bundleId, []);
5565      };
5566      /**
5567       * Reset loadjs dependencies statuses
5568       */
5569
5570
5571      loadjs.reset = function reset() {
5572        bundleIdCache = {};
5573        bundleResultCache = {};
5574        bundleCallbackQueue = {};
5575      };
5576      /**
5577       * Determine if bundle has already been defined
5578       * @param String} bundleId - The bundle id
5579       */
5580
5581
5582      loadjs.isDefined = function isDefined(bundleId) {
5583        return bundleId in bundleIdCache;
5584      }; // export
5585
5586
5587      return loadjs;
5588    });
5589  });
5590
5591  // ==========================================================================
5592  function loadScript(url) {
5593    return new Promise(function (resolve, reject) {
5594      loadjs_umd(url, {
5595        success: resolve,
5596        error: reject
5597      });
5598    });
5599  }
5600
5601  function parseId(url) {
5602    if (is$1.empty(url)) {
5603      return null;
5604    }
5605
5606    if (is$1.number(Number(url))) {
5607      return url;
5608    }
5609
5610    var regex = /^.*(vimeo.com\/|video\/)(\d+).*/;
5611    return url.match(regex) ? RegExp.$2 : url;
5612  } // Set playback state and trigger change (only on actual change)
5613
5614
5615  function assurePlaybackState(play) {
5616    if (play && !this.embed.hasPlayed) {
5617      this.embed.hasPlayed = true;
5618    }
5619
5620    if (this.media.paused === play) {
5621      this.media.paused = !play;
5622      triggerEvent.call(this, this.media, play ? 'play' : 'pause');
5623    }
5624  }
5625
5626  var vimeo = {
5627    setup: function setup() {
5628      var _this = this;
5629
5630      // Add embed class for responsive
5631      toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Set intial ratio
5632
5633      setAspectRatio.call(this); // Load the SDK if not already
5634
5635      if (!is$1.object(window.Vimeo)) {
5636        loadScript(this.config.urls.vimeo.sdk).then(function () {
5637          vimeo.ready.call(_this);
5638        }).catch(function (error) {
5639          _this.debug.warn('Vimeo SDK (player.js) failed to load', error);
5640        });
5641      } else {
5642        vimeo.ready.call(this);
5643      }
5644    },
5645    // API Ready
5646    ready: function ready() {
5647      var _this2 = this;
5648
5649      var player = this;
5650      var config = player.config.vimeo; // Get Vimeo params for the iframe
5651
5652      var params = buildUrlParams(extend({}, {
5653        loop: player.config.loop.active,
5654        autoplay: player.autoplay,
5655        muted: player.muted,
5656        gesture: 'media',
5657        playsinline: !this.config.fullscreen.iosNative
5658      }, config)); // Get the source URL or ID
5659
5660      var source = player.media.getAttribute('src'); // Get from <div> if needed
5661
5662      if (is$1.empty(source)) {
5663        source = player.media.getAttribute(player.config.attributes.embed.id);
5664      }
5665
5666      var id = parseId(source); // Build an iframe
5667
5668      var iframe = createElement('iframe');
5669      var src = format(player.config.urls.vimeo.iframe, id, params);
5670      iframe.setAttribute('src', src);
5671      iframe.setAttribute('allowfullscreen', '');
5672      iframe.setAttribute('allowtransparency', '');
5673      iframe.setAttribute('allow', 'autoplay'); // Get poster, if already set
5674
5675      var poster = player.poster; // Inject the package
5676
5677      var wrapper = createElement('div', {
5678        poster: poster,
5679        class: player.config.classNames.embedContainer
5680      });
5681      wrapper.appendChild(iframe);
5682      player.media = replaceElement(wrapper, player.media); // Get poster image
5683
5684      fetch(format(player.config.urls.vimeo.api, id), 'json').then(function (response) {
5685        if (is$1.empty(response)) {
5686          return;
5687        } // Get the URL for thumbnail
5688
5689
5690        var url = new URL(response[0].thumbnail_large); // Get original image
5691
5692        url.pathname = "".concat(url.pathname.split('_')[0], ".jpg"); // Set and show poster
5693
5694        ui.setPoster.call(player, url.href).catch(function () {});
5695      }); // Setup instance
5696      // https://github.com/vimeo/player.js
5697
5698      player.embed = new window.Vimeo.Player(iframe, {
5699        autopause: player.config.autopause,
5700        muted: player.muted
5701      });
5702      player.media.paused = true;
5703      player.media.currentTime = 0; // Disable native text track rendering
5704
5705      if (player.supported.ui) {
5706        player.embed.disableTextTrack();
5707      } // Create a faux HTML5 API using the Vimeo API
5708
5709
5710      player.media.play = function () {
5711        assurePlaybackState.call(player, true);
5712        return player.embed.play();
5713      };
5714
5715      player.media.pause = function () {
5716        assurePlaybackState.call(player, false);
5717        return player.embed.pause();
5718      };
5719
5720      player.media.stop = function () {
5721        player.pause();
5722        player.currentTime = 0;
5723      }; // Seeking
5724
5725
5726      var currentTime = player.media.currentTime;
5727      Object.defineProperty(player.media, 'currentTime', {
5728        get: function get() {
5729          return currentTime;
5730        },
5731        set: function set(time) {
5732          // Vimeo will automatically play on seek if the video hasn't been played before
5733          // Get current paused state and volume etc
5734          var embed = player.embed,
5735              media = player.media,
5736              paused = player.paused,
5737              volume = player.volume;
5738          var restorePause = paused && !embed.hasPlayed; // Set seeking state and trigger event
5739
5740          media.seeking = true;
5741          triggerEvent.call(player, media, 'seeking'); // If paused, mute until seek is complete
5742
5743          Promise.resolve(restorePause && embed.setVolume(0)) // Seek
5744          .then(function () {
5745            return embed.setCurrentTime(time);
5746          }) // Restore paused
5747          .then(function () {
5748            return restorePause && embed.pause();
5749          }) // Restore volume
5750          .then(function () {
5751            return restorePause && embed.setVolume(volume);
5752          }).catch(function () {// Do nothing
5753          });
5754        }
5755      }); // Playback speed
5756
5757      var speed = player.config.speed.selected;
5758      Object.defineProperty(player.media, 'playbackRate', {
5759        get: function get() {
5760          return speed;
5761        },
5762        set: function set(input) {
5763          player.embed.setPlaybackRate(input).then(function () {
5764            speed = input;
5765            triggerEvent.call(player, player.media, 'ratechange');
5766          }).catch(function (error) {
5767            // Hide menu item (and menu if empty)
5768            if (error.name === 'Error') {
5769              controls.setSpeedMenu.call(player, []);
5770            }
5771          });
5772        }
5773      }); // Volume
5774
5775      var volume = player.config.volume;
5776      Object.defineProperty(player.media, 'volume', {
5777        get: function get() {
5778          return volume;
5779        },
5780        set: function set(input) {
5781          player.embed.setVolume(input).then(function () {
5782            volume = input;
5783            triggerEvent.call(player, player.media, 'volumechange');
5784          });
5785        }
5786      }); // Muted
5787
5788      var muted = player.config.muted;
5789      Object.defineProperty(player.media, 'muted', {
5790        get: function get() {
5791          return muted;
5792        },
5793        set: function set(input) {
5794          var toggle = is$1.boolean(input) ? input : false;
5795          player.embed.setVolume(toggle ? 0 : player.config.volume).then(function () {
5796            muted = toggle;
5797            triggerEvent.call(player, player.media, 'volumechange');
5798          });
5799        }
5800      }); // Loop
5801
5802      var loop = player.config.loop;
5803      Object.defineProperty(player.media, 'loop', {
5804        get: function get() {
5805          return loop;
5806        },
5807        set: function set(input) {
5808          var toggle = is$1.boolean(input) ? input : player.config.loop.active;
5809          player.embed.setLoop(toggle).then(function () {
5810            loop = toggle;
5811          });
5812        }
5813      }); // Source
5814
5815      var currentSrc;
5816      player.embed.getVideoUrl().then(function (value) {
5817        currentSrc = value;
5818        controls.setDownloadUrl.call(player);
5819      }).catch(function (error) {
5820        _this2.debug.warn(error);
5821      });
5822      Object.defineProperty(player.media, 'currentSrc', {
5823        get: function get() {
5824          return currentSrc;
5825        }
5826      }); // Ended
5827
5828      Object.defineProperty(player.media, 'ended', {
5829        get: function get() {
5830          return player.currentTime === player.duration;
5831        }
5832      }); // Set aspect ratio based on video size
5833
5834      Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(function (dimensions) {
5835        var _dimensions = _slicedToArray(dimensions, 2),
5836            width = _dimensions[0],
5837            height = _dimensions[1];
5838
5839        player.embed.ratio = [width, height];
5840        setAspectRatio.call(_this2);
5841      }); // Set autopause
5842
5843      player.embed.setAutopause(player.config.autopause).then(function (state) {
5844        player.config.autopause = state;
5845      }); // Get title
5846
5847      player.embed.getVideoTitle().then(function (title) {
5848        player.config.title = title;
5849        ui.setTitle.call(_this2);
5850      }); // Get current time
5851
5852      player.embed.getCurrentTime().then(function (value) {
5853        currentTime = value;
5854        triggerEvent.call(player, player.media, 'timeupdate');
5855      }); // Get duration
5856
5857      player.embed.getDuration().then(function (value) {
5858        player.media.duration = value;
5859        triggerEvent.call(player, player.media, 'durationchange');
5860      }); // Get captions
5861
5862      player.embed.getTextTracks().then(function (tracks) {
5863        player.media.textTracks = tracks;
5864        captions.setup.call(player);
5865      });
5866      player.embed.on('cuechange', function (_ref) {
5867        var _ref$cues = _ref.cues,
5868            cues = _ref$cues === void 0 ? [] : _ref$cues;
5869        var strippedCues = cues.map(function (cue) {
5870          return stripHTML(cue.text);
5871        });
5872        captions.updateCues.call(player, strippedCues);
5873      });
5874      player.embed.on('loaded', function () {
5875        // Assure state and events are updated on autoplay
5876        player.embed.getPaused().then(function (paused) {
5877          assurePlaybackState.call(player, !paused);
5878
5879          if (!paused) {
5880            triggerEvent.call(player, player.media, 'playing');
5881          }
5882        });
5883
5884        if (is$1.element(player.embed.element) && player.supported.ui) {
5885          var frame = player.embed.element; // Fix keyboard focus issues
5886          // https://github.com/sampotts/plyr/issues/317
5887
5888          frame.setAttribute('tabindex', -1);
5889        }
5890      });
5891      player.embed.on('play', function () {
5892        assurePlaybackState.call(player, true);
5893        triggerEvent.call(player, player.media, 'playing');
5894      });
5895      player.embed.on('pause', function () {
5896        assurePlaybackState.call(player, false);
5897      });
5898      player.embed.on('timeupdate', function (data) {
5899        player.media.seeking = false;
5900        currentTime = data.seconds;
5901        triggerEvent.call(player, player.media, 'timeupdate');
5902      });
5903      player.embed.on('progress', function (data) {
5904        player.media.buffered = data.percent;
5905        triggerEvent.call(player, player.media, 'progress'); // Check all loaded
5906
5907        if (parseInt(data.percent, 10) === 1) {
5908          triggerEvent.call(player, player.media, 'canplaythrough');
5909        } // Get duration as if we do it before load, it gives an incorrect value
5910        // https://github.com/sampotts/plyr/issues/891
5911
5912
5913        player.embed.getDuration().then(function (value) {
5914          if (value !== player.media.duration) {
5915            player.media.duration = value;
5916            triggerEvent.call(player, player.media, 'durationchange');
5917          }
5918        });
5919      });
5920      player.embed.on('seeked', function () {
5921        player.media.seeking = false;
5922        triggerEvent.call(player, player.media, 'seeked');
5923      });
5924      player.embed.on('ended', function () {
5925        player.media.paused = true;
5926        triggerEvent.call(player, player.media, 'ended');
5927      });
5928      player.embed.on('error', function (detail) {
5929        player.media.error = detail;
5930        triggerEvent.call(player, player.media, 'error');
5931      }); // Rebuild UI
5932
5933      setTimeout(function () {
5934        return ui.build.call(player);
5935      }, 0);
5936    }
5937  };
5938
5939  // ==========================================================================
5940
5941  function parseId$1(url) {
5942    if (is$1.empty(url)) {
5943      return null;
5944    }
5945
5946    var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
5947    return url.match(regex) ? RegExp.$2 : url;
5948  } // Set playback state and trigger change (only on actual change)
5949
5950
5951  function assurePlaybackState$1(play) {
5952    if (play && !this.embed.hasPlayed) {
5953      this.embed.hasPlayed = true;
5954    }
5955
5956    if (this.media.paused === play) {
5957      this.media.paused = !play;
5958      triggerEvent.call(this, this.media, play ? 'play' : 'pause');
5959    }
5960  }
5961
5962  function getHost(config) {
5963    if (config.noCookie) {
5964      return 'https://www.youtube-nocookie.com';
5965    }
5966
5967    if (window.location.protocol === 'http:') {
5968      return 'http://www.youtube.com';
5969    } // Use YouTube's default
5970
5971
5972    return undefined;
5973  }
5974
5975  var youtube = {
5976    setup: function setup() {
5977      var _this = this;
5978
5979      // Add embed class for responsive
5980      toggleClass(this.elements.wrapper, this.config.classNames.embed, true); // Setup API
5981
5982      if (is$1.object(window.YT) && is$1.function(window.YT.Player)) {
5983        youtube.ready.call(this);
5984      } else {
5985        // Reference current global callback
5986        var callback = window.onYouTubeIframeAPIReady; // Set callback to process queue
5987
5988        window.onYouTubeIframeAPIReady = function () {
5989          // Call global callback if set
5990          if (is$1.function(callback)) {
5991            callback();
5992          }
5993
5994          youtube.ready.call(_this);
5995        }; // Load the SDK
5996
5997
5998        loadScript(this.config.urls.youtube.sdk).catch(function (error) {
5999          _this.debug.warn('YouTube API failed to load', error);
6000        });
6001      }
6002    },
6003    // Get the media title
6004    getTitle: function getTitle(videoId) {
6005      var _this2 = this;
6006
6007      var url = format(this.config.urls.youtube.api, videoId);
6008      fetch(url).then(function (data) {
6009        if (is$1.object(data)) {
6010          var title = data.title,
6011              height = data.height,
6012              width = data.width; // Set title
6013
6014          _this2.config.title = title;
6015          ui.setTitle.call(_this2); // Set aspect ratio
6016
6017          _this2.embed.ratio = [width, height];
6018        }
6019
6020        setAspectRatio.call(_this2);
6021      }).catch(function () {
6022        // Set aspect ratio
6023        setAspectRatio.call(_this2);
6024      });
6025    },
6026    // API ready
6027    ready: function ready() {
6028      var player = this; // Ignore already setup (race condition)
6029
6030      var currentId = player.media && player.media.getAttribute('id');
6031
6032      if (!is$1.empty(currentId) && currentId.startsWith('youtube-')) {
6033        return;
6034      } // Get the source URL or ID
6035
6036
6037      var source = player.media.getAttribute('src'); // Get from <div> if needed
6038
6039      if (is$1.empty(source)) {
6040        source = player.media.getAttribute(this.config.attributes.embed.id);
6041      } // Replace the <iframe> with a <div> due to YouTube API issues
6042
6043
6044      var videoId = parseId$1(source);
6045      var id = generateId(player.provider); // Get poster, if already set
6046
6047      var poster = player.poster; // Replace media element
6048
6049      var container = createElement('div', {
6050        id: id,
6051        poster: poster
6052      });
6053      player.media = replaceElement(container, player.media); // Id to poster wrapper
6054
6055      var posterSrc = function posterSrc(s) {
6056        return "https://i.ytimg.com/vi/".concat(videoId, "/").concat(s, "default.jpg");
6057      }; // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
6058
6059
6060      loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
6061      .catch(function () {
6062        return loadImage(posterSrc('sd'), 121);
6063      }) // 480p padded 4:3
6064      .catch(function () {
6065        return loadImage(posterSrc('hq'));
6066      }) // 360p padded 4:3. Always exists
6067      .then(function (image) {
6068        return ui.setPoster.call(player, image.src);
6069      }).then(function (src) {
6070        // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
6071        if (!src.includes('maxres')) {
6072          player.elements.poster.style.backgroundSize = 'cover';
6073        }
6074      }).catch(function () {});
6075      var config = player.config.youtube; // Setup instance
6076      // https://developers.google.com/youtube/iframe_api_reference
6077
6078      player.embed = new window.YT.Player(id, {
6079        videoId: videoId,
6080        host: getHost(config),
6081        playerVars: extend({}, {
6082          autoplay: player.config.autoplay ? 1 : 0,
6083          // Autoplay
6084          hl: player.config.hl,
6085          // iframe interface language
6086          controls: player.supported.ui ? 0 : 1,
6087          // Only show controls if not fully supported
6088          disablekb: 1,
6089          // Disable keyboard as we handle it
6090          playsinline: !player.config.fullscreen.iosNative ? 1 : 0,
6091          // Allow iOS inline playback
6092          // Captions are flaky on YouTube
6093          cc_load_policy: player.captions.active ? 1 : 0,
6094          cc_lang_pref: player.config.captions.language,
6095          // Tracking for stats
6096          widget_referrer: window ? window.location.href : null
6097        }, config),
6098        events: {
6099          onError: function onError(event) {
6100            // YouTube may fire onError twice, so only handle it once
6101            if (!player.media.error) {
6102              var code = event.data; // Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
6103
6104              var message = {
6105                2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
6106                5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
6107                100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
6108                101: 'The owner of the requested video does not allow it to be played in embedded players.',
6109                150: 'The owner of the requested video does not allow it to be played in embedded players.'
6110              }[code] || 'An unknown error occured';
6111              player.media.error = {
6112                code: code,
6113                message: message
6114              };
6115              triggerEvent.call(player, player.media, 'error');
6116            }
6117          },
6118          onPlaybackRateChange: function onPlaybackRateChange(event) {
6119            // Get the instance
6120            var instance = event.target; // Get current speed
6121
6122            player.media.playbackRate = instance.getPlaybackRate();
6123            triggerEvent.call(player, player.media, 'ratechange');
6124          },
6125          onReady: function onReady(event) {
6126            // Bail if onReady has already been called. See issue #1108
6127            if (is$1.function(player.media.play)) {
6128              return;
6129            } // Get the instance
6130
6131
6132            var instance = event.target; // Get the title
6133
6134            youtube.getTitle.call(player, videoId); // Create a faux HTML5 API using the YouTube API
6135
6136            player.media.play = function () {
6137              assurePlaybackState$1.call(player, true);
6138              instance.playVideo();
6139            };
6140
6141            player.media.pause = function () {
6142              assurePlaybackState$1.call(player, false);
6143              instance.pauseVideo();
6144            };
6145
6146            player.media.stop = function () {
6147              instance.stopVideo();
6148            };
6149
6150            player.media.duration = instance.getDuration();
6151            player.media.paused = true; // Seeking
6152
6153            player.media.currentTime = 0;
6154            Object.defineProperty(player.media, 'currentTime', {
6155              get: function get() {
6156                return Number(instance.getCurrentTime());
6157              },
6158              set: function set(time) {
6159                // If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
6160                if (player.paused && !player.embed.hasPlayed) {
6161                  player.embed.mute();
6162                } // Set seeking state and trigger event
6163
6164
6165                player.media.seeking = true;
6166                triggerEvent.call(player, player.media, 'seeking'); // Seek after events sent
6167
6168                instance.seekTo(time);
6169              }
6170            }); // Playback speed
6171
6172            Object.defineProperty(player.media, 'playbackRate', {
6173              get: function get() {
6174                return instance.getPlaybackRate();
6175              },
6176              set: function set(input) {
6177                instance.setPlaybackRate(input);
6178              }
6179            }); // Volume
6180
6181            var volume = player.config.volume;
6182            Object.defineProperty(player.media, 'volume', {
6183              get: function get() {
6184                return volume;
6185              },
6186              set: function set(input) {
6187                volume = input;
6188                instance.setVolume(volume * 100);
6189                triggerEvent.call(player, player.media, 'volumechange');
6190              }
6191            }); // Muted
6192
6193            var muted = player.config.muted;
6194            Object.defineProperty(player.media, 'muted', {
6195              get: function get() {
6196                return muted;
6197              },
6198              set: function set(input) {
6199                var toggle = is$1.boolean(input) ? input : muted;
6200                muted = toggle;
6201                instance[toggle ? 'mute' : 'unMute']();
6202                triggerEvent.call(player, player.media, 'volumechange');
6203              }
6204            }); // Source
6205
6206            Object.defineProperty(player.media, 'currentSrc', {
6207              get: function get() {
6208                return instance.getVideoUrl();
6209              }
6210            }); // Ended
6211
6212            Object.defineProperty(player.media, 'ended', {
6213              get: function get() {
6214                return player.currentTime === player.duration;
6215              }
6216            }); // Get available speeds
6217
6218            player.options.speed = instance.getAvailablePlaybackRates(); // Set the tabindex to avoid focus entering iframe
6219
6220            if (player.supported.ui) {
6221              player.media.setAttribute('tabindex', -1);
6222            }
6223
6224            triggerEvent.call(player, player.media, 'timeupdate');
6225            triggerEvent.call(player, player.media, 'durationchange'); // Reset timer
6226
6227            clearInterval(player.timers.buffering); // Setup buffering
6228
6229            player.timers.buffering = setInterval(function () {
6230              // Get loaded % from YouTube
6231              player.media.buffered = instance.getVideoLoadedFraction();
vendor: 6,022 bytes, lines 6231-6411
6231 // Trigger progress only when we actually buffer something
6232
6233              if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
6234                triggerEvent.call(player, player.media, 'progress');
6235              } // Set last buffer point
6236
6237
6238              player.media.lastBuffered = player.media.buffered; // Bail if we're at 100%
6239
6240              if (player.media.buffered === 1) {
6241                clearInterval(player.timers.buffering); // Trigger event
6242
6243                triggerEvent.call(player, player.media, 'canplaythrough');
6244              }
6245            }, 200); // Rebuild UI
6246
6247            setTimeout(function () {
6248              return ui.build.call(player);
6249            }, 50);
6250          },
6251          onStateChange: function onStateChange(event) {
6252            // Get the instance
6253            var instance = event.target; // Reset timer
6254
6255            clearInterval(player.timers.playing);
6256            var seeked = player.media.seeking && [1, 2].includes(event.data);
6257
6258            if (seeked) {
6259              // Unset seeking and fire seeked event
6260              player.media.seeking = false;
6261              triggerEvent.call(player, player.media, 'seeked');
6262            } // Handle events
6263            // -1   Unstarted
6264            // 0    Ended
6265            // 1    Playing
6266            // 2    Paused
6267            // 3    Buffering
6268            // 5    Video cued
6269
6270
6271            switch (event.data) {
6272              case -1:
6273                // Update scrubber
6274                triggerEvent.call(player, player.media, 'timeupdate'); // Get loaded % from YouTube
6275
6276                player.media.buffered = instance.getVideoLoadedFraction();
6277                triggerEvent.call(player, player.media, 'progress');
6278                break;
6279
6280              case 0:
6281                assurePlaybackState$1.call(player, false); // YouTube doesn't support loop for a single video, so mimick it.
6282
6283                if (player.media.loop) {
6284                  // YouTube needs a call to `stopVideo` before playing again
6285                  instance.stopVideo();
6286                  instance.playVideo();
6287                } else {
6288                  triggerEvent.call(player, player.media, 'ended');
6289                }
6290
6291                break;
6292
6293              case 1:
6294                // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
6295                if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
6296                  player.media.pause();
6297                } else {
6298                  assurePlaybackState$1.call(player, true);
6299                  triggerEvent.call(player, player.media, 'playing'); // Poll to get playback progress
6300
6301                  player.timers.playing = setInterval(function () {
6302                    triggerEvent.call(player, player.media, 'timeupdate');
6303                  }, 50); // Check duration again due to YouTube bug
6304                  // https://github.com/sampotts/plyr/issues/374
6305                  // https://code.google.com/p/gdata-issues/issues/detail?id=8690
6306
6307                  if (player.media.duration !== instance.getDuration()) {
6308                    player.media.duration = instance.getDuration();
6309                    triggerEvent.call(player, player.media, 'durationchange');
6310                  }
6311                }
6312
6313                break;
6314
6315              case 2:
6316                // Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
6317                if (!player.muted) {
6318                  player.embed.unMute();
6319                }
6320
6321                assurePlaybackState$1.call(player, false);
6322                break;
6323
6324              default:
6325                break;
6326            }
6327
6328            triggerEvent.call(player, player.elements.container, 'statechange', false, {
6329              code: event.data
6330            });
6331          }
6332        }
6333      });
6334    }
6335  };
6336
6337  // ==========================================================================
6338  var media = {
6339    // Setup media
6340    setup: function setup() {
6341      // If there's no media, bail
6342      if (!this.media) {
6343        this.debug.warn('No media element found!');
6344        return;
6345      } // Add type class
6346
6347
6348      toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', this.type), true); // Add provider class
6349
6350      toggleClass(this.elements.container, this.config.classNames.provider.replace('{0}', this.provider), true); // Add video class for embeds
6351      // This will require changes if audio embeds are added
6352
6353      if (this.isEmbed) {
6354        toggleClass(this.elements.container, this.config.classNames.type.replace('{0}', 'video'), true);
6355      } // Inject the player wrapper
6356
6357
6358      if (this.isVideo) {
6359        // Create the wrapper div
6360        this.elements.wrapper = createElement('div', {
6361          class: this.config.classNames.video
6362        }); // Wrap the video in a container
6363
6364        wrap(this.media, this.elements.wrapper); // Faux poster container
6365
6366        this.elements.poster = createElement('div', {
6367          class: this.config.classNames.poster
6368        });
6369        this.elements.wrapper.appendChild(this.elements.poster);
6370      }
6371
6372      if (this.isHTML5) {
6373        html5.extend.call(this);
6374      } else if (this.isYouTube) {
6375        youtube.setup.call(this);
6376      } else if (this.isVimeo) {
6377        vimeo.setup.call(this);
6378      }
6379    }
6380  };
6381
6382  var destroy = function destroy(instance) {
6383    // Destroy our adsManager
6384    if (instance.manager) {
6385      instance.manager.destroy();
6386    } // Destroy our adsManager
6387
6388
6389    if (instance.elements.displayContainer) {
6390      instance.elements.displayContainer.destroy();
6391    }
6392
6393    instance.elements.container.remove();
6394  };
6395
6396  var Ads =
6397  /*#__PURE__*/
6398  function () {
6399    /**
6400     * Ads constructor.
6401     * @param {Object} player
6402     * @return {Ads}
6403     */
6404    function Ads(player) {
6405      var _this = this;
6406
6407      _classCallCheck(this, Ads);
6408
6409      this.player = player;
6410      this.config = player.config.ads;
6411      this.playing = false;
6412      this.initialized = false;
6413      this.elements = {
6414        container: null,
6415        displayContainer: null
6416      };
6417      this.manager = null;
6418      this.loader = null;
6419      this.cuePoints = null;
6420      this.events = {};
6421      this.safetyTimer = null;
6422      this.countdownTimer = null; // Setup a promise to resolve when the IMA manager is ready
6423
6424      this.managerPromise = new Promise(function (resolve, reject) {
6425        // The ad is loaded and ready
6426        _this.on('loaded', resolve); // Ads failed
6427
6428
6429        _this.on('error', reject);
6430      });
6431      this.load();
6432    }
6433
6434    _createClass(Ads, [{
6435      key: "load",
6436
6437      /**
6438       * Load the IMA SDK
6439       */
6440      value: function load() {
6441        var _this2 = this;
6442
6443        if (!this.enabled) {
6444          return;
6445        } // Check if the Google IMA3 SDK is loaded or load it ourselves
6446
6447
6448        if (!is$1.object(window.google) || !is$1.object(window.google.ima)) {
6449          loadScript(this.player.config.urls.googleIMA.sdk).then(function () {
6450            _this2.ready();
6451          }).catch(function () {
6452            // Script failed to load or is blocked
6453            _this2.trigger('error', new Error('Google IMA SDK failed to load'));
6454          });
6455        } else {
6456          this.ready();
6457        }
6458      }
6459      /**
6460       * Get the ads instance ready
6461       */
6462
6463    }, {
6464      key: "ready",
6465      value: function ready() {
6466        var _this3 = this;
6467
6468        // Double check we're enabled
6469        if (!this.enabled) {
6470          destroy(this);
6471        } // Start ticking our safety timer. If the whole advertisement
6472        // thing doesn't resolve within our set time; we bail
6473
6474
6475        this.startSafetyTimer(12000, 'ready()'); // Clear the safety timer
6476
6477        this.managerPromise.then(function () {
6478          _this3.clearSafetyTimer('onAdsManagerLoaded()');
6479        }); // Set listeners on the Plyr instance
6480
6481        this.listeners(); // Setup the IMA SDK
6482
6483        this.setupIMA();
6484      } // Build the tag URL
6485
6486    }, {
6487      key: "setupIMA",
6488
6489      /**
6490       * In order for the SDK to display ads for our video, we need to tell it where to put them,
6491       * so here we define our ad container. This div is set up to render on top of the video player.
6492       * Using the code below, we tell the SDK to render ads within that div. We also provide a
6493       * handle to the content video player - the SDK will poll the current time of our player to
6494       * properly place mid-rolls. After we create the ad display container, we initialize it. On
6495       * mobile devices, this initialization is done as the result of a user action.
6496       */
6497      value: function setupIMA() {
6498        // Create the container for our advertisements
6499        this.elements.container = createElement('div', {
6500          class: this.player.config.classNames.ads
6501        });
6502        this.player.elements.container.appendChild(this.elements.container); // So we can run VPAID2
6503
6504        google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED); // Set language
6505
6506        google.ima.settings.setLocale(this.player.config.ads.language); // Set playback for iOS10+
6507
6508        google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline); // We assume the adContainer is the video container of the plyr element that will house the ads
6509
6510        this.elements.displayContainer = new google.ima.AdDisplayContainer(this.elements.container, this.player.media); // Request video ads to be pre-loaded
6511
6512        this.requestAds();
6513      }
6514      /**
6515       * Request advertisements
6516       */
6517
6518    }, {
6519      key: "requestAds",
6520      value: function requestAds() {
6521        var _this4 = this;
6522
6523        var container = this.player.elements.container;
6524
6525        try {
6526          // Create ads loader
6527          this.loader = new google.ima.AdsLoader(this.elements.displayContainer); // Listen and respond to ads loaded and error events
6528
6529          this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, function (event) {
6530            return _this4.onAdsManagerLoaded(event);
6531          }, false);
6532          this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
6533            return _this4.onAdError(error);
6534          }, false); // Request video ads
6535
6536          var request = new google.ima.AdsRequest();
6537          request.adTagUrl = this.tagUrl; // Specify the linear and nonlinear slot sizes. This helps the SDK
6538          // to select the correct creative if multiple are returned
6539
6540          request.linearAdSlotWidth = container.offsetWidth;
6541          request.linearAdSlotHeight = container.offsetHeight;
6542          request.nonLinearAdSlotWidth = container.offsetWidth;
6543          request.nonLinearAdSlotHeight = container.offsetHeight; // We only overlay ads as we only support video.
6544
6545          request.forceNonLinearFullSlot = false; // Mute based on current state
6546
6547          request.setAdWillPlayMuted(!this.player.muted);
6548          this.loader.requestAds(request);
6549        } catch (e) {
6550          this.onAdError(e);
6551        }
6552      }
6553      /**
6554       * Update the ad countdown
6555       * @param {Boolean} start
6556       */
6557
6558    }, {
6559      key: "pollCountdown",
6560      value: function pollCountdown() {
6561        var _this5 = this;
6562
6563        var start = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
6564
6565        if (!start) {
6566          clearInterval(this.countdownTimer);
6567          this.elements.container.removeAttribute('data-badge-text');
6568          return;
6569        }
6570
6571        var update = function update() {
6572          var time = formatTime(Math.max(_this5.manager.getRemainingTime(), 0));
6573          var label = "".concat(i18n.get('advertisement', _this5.player.config), " - ").concat(time);
6574
6575          _this5.elements.container.setAttribute('data-badge-text', label);
6576        };
6577
6578        this.countdownTimer = setInterval(update, 100);
6579      }
6580      /**
6581       * This method is called whenever the ads are ready inside the AdDisplayContainer
6582       * @param {Event} adsManagerLoadedEvent
6583       */
6584
6585    }, {
6586      key: "onAdsManagerLoaded",
6587      value: function onAdsManagerLoaded(event) {
6588        var _this6 = this;
6589
6590        // Load could occur after a source change (race condition)
6591        if (!this.enabled) {
6592          return;
6593        } // Get the ads manager
6594
6595
6596        var settings = new google.ima.AdsRenderingSettings(); // Tell the SDK to save and restore content video state on our behalf
6597
6598        settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
6599        settings.enablePreloading = true; // The SDK is polling currentTime on the contentPlayback. And needs a duration
6600        // so it can determine when to start the mid- and post-roll
6601
6602        this.manager = event.getAdsManager(this.player, settings); // Get the cue points for any mid-rolls by filtering out the pre- and post-roll
6603
6604        this.cuePoints = this.manager.getCuePoints(); // Add listeners to the required events
6605        // Advertisement error events
6606
6607        this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, function (error) {
6608          return _this6.onAdError(error);
6609        }); // Advertisement regular events
6610
6611        Object.keys(google.ima.AdEvent.Type).forEach(function (type) {
6612          _this6.manager.addEventListener(google.ima.AdEvent.Type[type], function (e) {
6613            return _this6.onAdEvent(e);
6614          });
6615        }); // Resolve our adsManager
6616
6617        this.trigger('loaded');
6618      }
6619    }, {
6620      key: "addCuePoints",
6621      value: function addCuePoints() {
6622        var _this7 = this;
6623
6624        // Add advertisement cue's within the time line if available
6625        if (!is$1.empty(this.cuePoints)) {
6626          this.cuePoints.forEach(function (cuePoint) {
6627            if (cuePoint !== 0 && cuePoint !== -1 && cuePoint < _this7.player.duration) {
6628              var seekElement = _this7.player.elements.progress;
6629
6630              if (is$1.element(seekElement)) {
6631                var cuePercentage = 100 / _this7.player.duration * cuePoint;
6632                var cue = createElement('span', {
6633                  class: _this7.player.config.classNames.cues
6634                });
6635                cue.style.left = "".concat(cuePercentage.toString(), "%");
6636                seekElement.appendChild(cue);
6637              }
6638            }
6639          });
6640        }
6641      }
6642      /**
6643       * This is where all the event handling takes place. Retrieve the ad from the event. Some
6644       * events (e.g. ALL_ADS_COMPLETED) don't have the ad object associated
6645       * https://developers.google.com/interactive-media-ads/docs/sdks/html5/v3/apis#ima.AdEvent.Type
6646       * @param {Event} event
6647       */
6648
6649    }, {
6650      key: "onAdEvent",
6651      value: function onAdEvent(event) {
6652        var _this8 = this;
6653
6654        var container = this.player.elements.container; // Retrieve the ad from the event. Some events (e.g. ALL_ADS_COMPLETED)
6655        // don't have ad object associated
6656
6657        var ad = event.getAd();
6658        var adData = event.getAdData(); // Proxy event
6659
6660        var dispatchEvent = function dispatchEvent(type) {
6661          triggerEvent.call(_this8.player, _this8.player.media, "ads".concat(type.replace(/_/g, '').toLowerCase()));
6662        }; // Bubble the event
6663
6664
6665        dispatchEvent(event.type);
6666
6667        switch (event.type) {
6668          case google.ima.AdEvent.Type.LOADED:
6669            // This is the first event sent for an ad - it is possible to determine whether the
6670            // ad is a video ad or an overlay
6671            this.trigger('loaded'); // Start countdown
6672
6673            this.pollCountdown(true);
6674
6675            if (!ad.isLinear()) {
6676              // Position AdDisplayContainer correctly for overlay
6677              ad.width = container.offsetWidth;
6678              ad.height = container.offsetHeight;
6679            } // console.info('Ad type: ' + event.getAd().getAdPodInfo().getPodIndex());
6680            // console.info('Ad time: ' + event.getAd().getAdPodInfo().getTimeOffset());
6681
6682
6683            break;
6684
6685          case google.ima.AdEvent.Type.STARTED:
6686            // Set volume to match player
6687            this.manager.setVolume(this.player.volume);
6688            break;
6689
6690          case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:
6691            // All ads for the current videos are done. We can now request new advertisements
6692            // in case the video is re-played
6693            // TODO: Example for what happens when a next video in a playlist would be loaded.
6694            // So here we load a new video when all ads are done.
6695            // Then we load new ads within a new adsManager. When the video
6696            // Is started - after - the ads are loaded, then we get ads.
6697            // You can also easily test cancelling and reloading by running
6698            // player.ads.cancel() and player.ads.play from the console I guess.
6699            // this.player.source = {
6700            //     type: 'video',
6701            //     title: 'View From A Blue Moon',
6702            //     sources: [{
6703            //         src:
6704            // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.mp4', type:
6705            // 'video/mp4', }], poster:
6706            // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.jpg', tracks:
6707            // [ { kind: 'captions', label: 'English', srclang: 'en', src:
6708            // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.en.vtt',
6709            // default: true, }, { kind: 'captions', label: 'French', srclang: 'fr', src:
6710            // 'https://cdn.plyr.io/static/demo/View_From_A_Blue_Moon_Trailer-HD.fr.vtt', }, ],
6711            // };
6712            // TODO: So there is still this thing where a video should only be allowed to start
6713            // playing when the IMA SDK is ready or has failed
6714            this.loadAds();
6715            break;
6716
6717          case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:
6718            // This event indicates the ad has started - the video player can adjust the UI,
6719            // for example display a pause button and remaining time. Fired when content should
6720            // be paused. This usually happens right before an ad is about to cover the content
6721            this.pauseContent();
6722            break;
6723
6724          case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:
6725            // This event indicates the ad has finished - the video player can perform
6726            // appropriate UI actions, such as removing the timer for remaining time detection.
6727            // Fired when content should be resumed. This usually happens when an ad finishes
6728            // or collapses
6729            this.pollCountdown();
6730            this.resumeContent();
6731            break;
6732
6733          case google.ima.AdEvent.Type.LOG:
6734            if (adData.adError) {
6735              this.player.debug.warn("Non-fatal ad error: ".concat(adData.adError.getMessage()));
6736            }
6737
6738            break;
6739
6740          default:
6741            break;
6742        }
6743      }
6744      /**
6745       * Any ad error handling comes through here
6746       * @param {Event} event
6747       */
6748
6749    }, {
6750      key: "onAdError",
6751      value: function onAdError(event) {
6752        this.cancel();
6753        this.player.debug.warn('Ads error', event);
6754      }
6755      /**
6756       * Setup hooks for Plyr and window events. This ensures
6757       * the mid- and post-roll launch at the correct time. And
6758       * resize the advertisement when the player resizes
6759       */
6760
6761    }, {
6762      key: "listeners",
6763      value: function listeners() {
6764        var _this9 = this;
6765
6766        var container = this.player.elements.container;
6767        var time;
6768        this.player.on('canplay', function () {
6769          _this9.addCuePoints();
6770        });
6771        this.player.on('ended', function () {
6772          _this9.loader.contentComplete();
6773        });
6774        this.player.on('timeupdate', function () {
6775          time = _this9.player.currentTime;
6776        });
6777        this.player.on('seeked', function () {
6778          var seekedTime = _this9.player.currentTime;
6779
6780          if (is$1.empty(_this9.cuePoints)) {
6781            return;
6782          }
6783
6784          _this9.cuePoints.forEach(function (cuePoint, index) {
6785            if (time < cuePoint && cuePoint < seekedTime) {
6786              _this9.manager.discardAdBreak();
6787
6788              _this9.cuePoints.splice(index, 1);
6789            }
6790          });
6791        }); // Listen to the resizing of the window. And resize ad accordingly
6792        // TODO: eventually implement ResizeObserver
6793
6794        window.addEventListener('resize', function () {
6795          if (_this9.manager) {
6796            _this9.manager.resize(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL);
6797          }
6798        });
6799      }
6800      /**
6801       * Initialize the adsManager and start playing advertisements
6802       */
6803
6804    }, {
6805      key: "play",
6806      value: function play() {
6807        var _this10 = this;
6808
6809        var container = this.player.elements.container;
6810
6811        if (!this.managerPromise) {
6812          this.resumeContent();
6813        } // Play the requested advertisement whenever the adsManager is ready
6814
6815
6816        this.managerPromise.then(function () {
6817          // Set volume to match player
6818          _this10.manager.setVolume(_this10.player.volume); // Initialize the container. Must be done via a user action on mobile devices
6819
6820
6821          _this10.elements.displayContainer.initialize();
6822
6823          try {
6824            if (!_this10.initialized) {
6825              // Initialize the ads manager. Ad rules playlist will start at this time
6826              _this10.manager.init(container.offsetWidth, container.offsetHeight, google.ima.ViewMode.NORMAL); // Call play to start showing the ad. Single video and overlay ads will
6827              // start at this time; the call will be ignored for ad rules
6828
6829
6830              _this10.manager.start();
6831            }
6832
6833            _this10.initialized = true;
6834          } catch (adError) {
6835            // An error may be thrown if there was a problem with the
6836            // VAST response
6837            _this10.onAdError(adError);
6838          }
6839        }).catch(function () {});
6840      }
6841      /**
6842       * Resume our video
6843       */
6844
6845    }, {
6846      key: "resumeContent",
6847      value: function resumeContent() {
6848        // Hide the advertisement container
6849        this.elements.container.style.zIndex = ''; // Ad is stopped
6850
6851        this.playing = false; // Play video
6852
6853        this.player.media.play();
6854      }
6855      /**
6856       * Pause our video
6857       */
6858
6859    }, {
6860      key: "pauseContent",
6861      value: function pauseContent() {
6862        // Show the advertisement container
6863        this.elements.container.style.zIndex = 3; // Ad is playing
6864
6865        this.playing = true; // Pause our video.
6866
6867        this.player.media.pause();
6868      }
6869      /**
6870       * Destroy the adsManager so we can grab new ads after this. If we don't then we're not
6871       * allowed to call new ads based on google policies, as they interpret this as an accidental
6872       * video requests. https://developers.google.com/interactive-
6873       * media-ads/docs/sdks/android/faq#8
6874       */
6875
6876    }, {
6877      key: "cancel",
6878      value: function cancel() {
6879        // Pause our video
6880        if (this.initialized) {
6881          this.resumeContent();
6882        } // Tell our instance that we're done for now
6883
6884
6885        this.trigger('error'); // Re-create our adsManager
6886
6887        this.loadAds();
6888      }
6889      /**
6890       * Re-create our adsManager
6891       */
6892
6893    }, {
6894      key: "loadAds",
6895      value: function loadAds() {
6896        var _this11 = this;
6897
6898        // Tell our adsManager to go bye bye
6899        this.managerPromise.then(function () {
6900          // Destroy our adsManager
6901          if (_this11.manager) {
6902            _this11.manager.destroy();
6903          } // Re-set our adsManager promises
6904
6905
6906          _this11.managerPromise = new Promise(function (resolve) {
6907            _this11.on('loaded', resolve);
6908
6909            _this11.player.debug.log(_this11.manager);
6910          }); // Now request some new advertisements
6911
6912          _this11.requestAds();
6913        }).catch(function () {});
6914      }
6915      /**
6916       * Handles callbacks after an ad event was invoked
6917       * @param {String} event - Event type
6918       */
6919
6920    }, {
6921      key: "trigger",
6922      value: function trigger(event) {
6923        var _this12 = this;
6924
6925        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
6926          args[_key - 1] = arguments[_key];
6927        }
6928
6929        var handlers = this.events[event];
6930
6931        if (is$1.array(handlers)) {
6932          handlers.forEach(function (handler) {
6933            if (is$1.function(handler)) {
6934              handler.apply(_this12, args);
6935            }
6936          });
6937        }
6938      }
6939      /**
6940       * Add event listeners
6941       * @param {String} event - Event type
6942       * @param {Function} callback - Callback for when event occurs
6943       * @return {Ads}
6944       */
6945
6946    }, {
6947      key: "on",
6948      value: function on(event, callback) {
6949        if (!is$1.array(this.events[event])) {
6950          this.events[event] = [];
6951        }
6952
6953        this.events[event].push(callback);
6954        return this;
6955      }
6956      /**
6957       * Setup a safety timer for when the ad network doesn't respond for whatever reason.
6958       * The advertisement has 12 seconds to get its things together. We stop this timer when the
6959       * advertisement is playing, or when a user action is required to start, then we clear the
6960       * timer on ad ready
6961       * @param {Number} time
6962       * @param {String} from
6963       */
6964
6965    }, {
6966      key: "startSafetyTimer",
6967      value: function startSafetyTimer(time, from) {
6968        var _this13 = this;
6969
6970        this.player.debug.log("Safety timer invoked from: ".concat(from));
6971        this.safetyTimer = setTimeout(function () {
6972          _this13.cancel();
6973
6974          _this13.clearSafetyTimer('startSafetyTimer()');
6975        }, time);
6976      }
6977      /**
6978       * Clear our safety timer(s)
6979       * @param {String} from
6980       */
6981
6982    }, {
6983      key: "clearSafetyTimer",
6984      value: function clearSafetyTimer(from) {
6985        if (!is$1.nullOrUndefined(this.safetyTimer)) {
6986          this.player.debug.log("Safety timer cleared from: ".concat(from));
6987          clearTimeout(this.safetyTimer);
6988          this.safetyTimer = null;
6989        }
6990      }
6991    }, {
6992      key: "enabled",
6993      get: function get() {
6994        var config = this.config;
6995        return this.player.isHTML5 && this.player.isVideo && config.enabled && (!is$1.empty(config.publisherId) || is$1.url(config.tagUrl));
6996      }
6997    }, {
6998      key: "tagUrl",
6999      get: function get() {
7000        var config = this.config;
7001
7002        if (is$1.url(config.tagUrl)) {
7003          return config.tagUrl;
7004        }
7005
7006        var params = {
7007          AV_PUBLISHERID: '58c25bb0073ef448b1087ad6',
7008          AV_CHANNELID: '5a0458dc28a06145e4519d21',
7009          AV_URL: window.location.hostname,
7010          cb: Date.now(),
7011          AV_WIDTH: 640,
7012          AV_HEIGHT: 480,
7013          AV_CDIM2: this.publisherId
7014        };
7015        var base = 'https://go.aniview.com/api/adserver6/vast/';
7016        return "".concat(base, "?").concat(buildUrlParams(params));
7017      }
7018    }]);
7019
7020    return Ads;
7021  }();
7022
7023  var parseVtt = function parseVtt(vttDataString) {
7024    var processedList = [];
7025    var frames = vttDataString.split(/\r\n\r\n|\n\n|\r\r/);
7026    frames.forEach(function (frame) {
7027      var result = {};
7028      var lines = frame.split(/\r\n|\n|\r/);
7029      lines.forEach(function (line) {
7030        if (!is$1.number(result.startTime)) {
7031          // The line with start and end times on it is the first line of interest
7032          var matchTimes = line.match(/([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})( ?--> ?)([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})/); // Note that this currently ignores caption formatting directives that are optionally on the end of this line - fine for non-captions VTT
7033
7034          if (matchTimes) {
7035            result.startTime = Number(matchTimes[1] || 0) * 60 * 60 + Number(matchTimes[2]) * 60 + Number(matchTimes[3]) + Number("0.".concat(matchTimes[4]));
7036            result.endTime = Number(matchTimes[6] || 0) * 60 * 60 + Number(matchTimes[7]) * 60 + Number(matchTimes[8]) + Number("0.".concat(matchTimes[9]));
7037          }
7038        } else if (!is$1.empty(line.trim()) && is$1.empty(result.text)) {
7039          // If we already have the startTime, then we're definitely up to the text line(s)
7040          var lineSplit = line.trim().split('#xywh=');
7041
7042          var _lineSplit = _slicedToArray(lineSplit, 1);
7043
7044          result.text = _lineSplit[0];
7045
7046          // If there's content in lineSplit[1], then we have sprites. If not, then it's just one frame per image
7047          if (lineSplit[1]) {
7048            var _lineSplit$1$split = lineSplit[1].split(',');
7049
7050            var _lineSplit$1$split2 = _slicedToArray(_lineSplit$1$split, 4);
7051
7052            result.x = _lineSplit$1$split2[0];
7053            result.y = _lineSplit$1$split2[1];
7054            result.w = _lineSplit$1$split2[2];
7055            result.h = _lineSplit$1$split2[3];
7056          }
7057        }
7058      });
7059
7060      if (result.text) {
7061        processedList.push(result);
7062      }
7063    });
7064    return processedList;
7065  };
7066  /**
7067   * Preview thumbnails for seek hover and scrubbing
7068   * Seeking: Hover over the seek bar (desktop only): shows a small preview container above the seek bar
7069   * Scrubbing: Click and drag the seek bar (desktop and mobile): shows the preview image over the entire video, as if the video is scrubbing at very high speed
7070   *
7071   * Notes:
7072   * - Thumbs are set via JS settings on Plyr init, not HTML5 'track' property. Using the track property w
7072ould be a bit gross, because it doesn't support custom 'kinds'. kind=metadata might be used for something else, and we want to allow multiple thumbnails tracks. Tracks must have a unique combination of 'kind' and 'label'. We would have to do something like kind=metadata,label=thumbnails1 / kind=metadata,label=thumbnails2. Square peg, round hole
7073   * - VTT info: the image URL is relative to the VTT, not the current document. But if the url starts with a slash, it will naturally be relative to the current domain. https://support.jwplayer.com/articles/how-to-add-preview-thumbnails
7074   * - This implementation uses multiple separate img elements. Other implementations use background-image on one element. This would be nice and simple, but Firefox and Safari have flickering issues with replacing backgrounds of larger images. It seems that YouTube perhaps only avoids this because they don't have the option for high-res previews (even the fullscreen ones, when mousedown/seeking). Images appear over the top of each other, and previous ones are discarded once the new ones have been rendered
7075   */
7076
7077
7078  var PreviewThumbnails =
7079  /*#__PURE__*/
7080  function () {
7081    /**
7082     * PreviewThumbnails constructor.
7083     * @param {Plyr} player
7084     * @return {PreviewThumbnails}
7085     */
7086    function PreviewThumbnails(player) {
7087      _classCallCheck(this, PreviewThumbnails);
7088
7089      this.player = player;
7090      this.thumbnails = [];
7091      this.loaded = false;
7092      this.lastMouseMoveTime = Date.now();
7093      this.mouseDown = false;
7094      this.loadedImages = [];
7095      this.elements = {
7096        thumb: {},
7097        scrubbing: {}
7098      };
7099      this.load();
7100    }
7101
7102    _createClass(PreviewThumbnails, [{
7103      key: "load",
7104      value: function load() {
7105        var _this = this;
7106
7107        // Togglethe regular seek tooltip
7108        if (this.player.elements.display.seekTooltip) {
7109          this.player.elements.display.seekTooltip.hidden = this.enabled;
7110        }
7111
7112        if (!this.enabled) {
7113          return;
7114        }
7115
7116        this.getThumbnails().then(function () {
7117          if (!_this.enabled) {
7118            return;
7119          } // Render DOM elements
7120
7121
7122          _this.render(); // Check to see if thumb container size was specified manually in CSS
7123
7124
7125          _this.determineContainerAutoSizing();
7126
7127          _this.loaded = true;
7128        });
7129      } // Download VTT files and parse them
7130
7131    }, {
7132      key: "getThumbnails",
7133      value: function getThumbnails() {
7134        var _this2 = this;
7135
7136        return new Promise(function (resolve) {
7137          var src = _this2.player.config.previewThumbnails.src;
7138
7139          if (is$1.empty(src)) {
7140            throw new Error('Missing previewThumbnails.src config attribute');
7141          } // If string, convert into single-element list
7142
7143
7144          var urls = is$1.string(src) ? [src] : src; // Loop through each src URL. Download and process the VTT file, storing the resulting data in this.thumbnails
7145
7146          var promises = urls.map(function (u) {
7147            return _this2.getThumbnail(u);
7148          });
7149          Promise.all(promises).then(function () {
7150            // Sort smallest to biggest (e.g., [120p, 480p, 1080p])
7151            _this2.thumbnails.sort(function (x, y) {
7152              return x.height - y.height;
7153            });
7154
7155            _this2.player.debug.log('Preview thumbnails', _this2.thumbnails);
7156
7157            resolve();
7158          });
7159        });
7160      } // Process individual VTT file
7161
7162    }, {
7163      key: "getThumbnail",
7164      value: function getThumbnail(url) {
7165        var _this3 = this;
7166
7167        return new Promise(function (resolve) {
7168          fetch(url).then(function (response) {
7169            var thumbnail = {
7170              frames: parseVtt(response),
7171              height: null,
7172              urlPrefix: ''
7173            }; // If the URLs don't start with '/', then we need to set their relative path to be the location of the VTT file
7174            // If the URLs do start with '/', then they obviously don't need a prefix, so it will remain blank
7175            // If the thumbnail URLs start with with none of '/', 'http://' or 'https://', then we need to set their relative path to be the location of the VTT file
7176
7177            if (!thumbnail.frames[0].text.startsWith('/') && !thumbnail.frames[0].text.startsWith('http://') && !thumbnail.frames[0].text.startsWith('https://')) {
7178              thumbnail.urlPrefix = url.substring(0, url.lastIndexOf('/') + 1);
7179            } // Download the first frame, so that we can determine/set the height of this thumbnailsDef
7180
7181
7182            var tempImage = new Image();
7183
7184            tempImage.onload = function () {
7185              thumbnail.height = tempImage.naturalHeight;
7186              thumbnail.width = tempImage.naturalWidth;
7187
7188              _this3.thumbnails.push(thumbnail);
7189
7190              resolve();
7191            };
7192
7193            tempImage.src = thumbnail.urlPrefix + thumbnail.frames[0].text;
7194          });
7195        });
7196      }
7197    }, {
7198      key: "startMove",
7199      value: function startMove(event) {
7200        if (!this.loaded) {
7201          return;
7202        }
7203
7204        if (!is$1.event(event) || !['touchmove', 'mousemove'].includes(event.type)) {
7205          return;
7206        } // Wait until media has a duration
7207
7208
7209        if (!this.player.media.duration) {
7210          return;
7211        }
7212
7213        if (event.type === 'touchmove') {
7214          // Calculate seek hover position as approx video seconds
7215          this.seekTime = this.player.media.duration * (this.player.elements.inputs.seek.value / 100);
7216        } else {
7217          // Calculate seek hover position as approx video seconds
7218          var clientRect = this.player.elements.progress.getBoundingClientRect();
7219          var percentage = 100 / clientRect.width * (event.pageX - clientRect.left);
7220          this.seekTime = this.player.media.duration * (percentage / 100);
7221
7222          if (this.seekTime < 0) {
7223            // The mousemove fires for 10+px out to the left
7224            this.seekTime = 0;
7225          }
7226
7227          if (this.seekTime > this.player.media.duration - 1) {
7228            // Took 1 second off the duration for safety, because different players can disagree on the real duration of a video
7229            this.seekTime = this.player.media.duration - 1;
7230          }
7231
7232          this.mousePosX = event.pageX; // Set time text inside image container
7233
7234          this.elements.thumb.time.innerText = formatTime(this.seekTime);
7235        } // Download and show image
7236
7237
7238        this.showImageAtCurrentTime();
7239      }
7240    }, {
7241      key: "endMove",
7242      value: function endMove() {
7243        this.toggleThumbContainer(false, true);
7244      }
7245    }, {
7246      key: "startScrubbing",
7247      value: function startScrubbing(event) {
7248        // Only act on left mouse button (0), or touch device (event.button is false)
7249        if (event.button === false || event.button === 0) {
7250          this.mouseDown = true; // Wait until media has a duration
7251
7252          if (this.player.media.duration) {
7253            this.toggleScrubbingContainer(true);
7254            this.toggleThumbContainer(false, true); // Download and show image
7255
7256            this.showImageAtCurrentTime();
7257          }
7258        }
7259      }
7260    }, {
7261      key: "endScrubbing",
7262      value: function endScrubbing() {
7263        var _this4 = this;
7264
7265        this.mouseDown = false; // Hide scrubbing preview. But wait until the video has successfully seeked before hiding the scrubbing preview
7266
7267        if (Math.ceil(this.lastTime) === Math.ceil(this.player.media.currentTime)) {
7268          // The video was already seeked/loaded at the chosen time - hide immediately
7269          this.toggleScrubbingContainer(false);
7270        } else {
7271          // The video hasn't seeked yet. Wait for that
7272          once.call(this.player, this.player.media, 'timeupdate', function () {
7273            // Re-check mousedown - we might have already started scrubbing again
7274            if (!_this4.mouseDown) {
7275              _this4.toggleScrubbingContainer(false);
7276            }
7277          });
7278        }
7279      }
7280      /**
7281       * Setup hooks for Plyr and window events
7282       */
7283
7284    }, {
7285      key: "listeners",
7286      value: function listeners() {
7287        var _this5 = this;
7288
7289        // Hide thumbnail preview - on mouse click, mouse leave (in listeners.js for now), and video play/seek. All four are required, e.g., for buffering
7290        this.player.on('play', function () {
7291          _this5.toggleThumbContainer(false, true);
7292        });
7293        this.player.on('seeked', function () {
7294          _this5.toggleThumbContainer(false);
7295        });
7296        this.player.on('timeupdate', function () {
7297          _this5.lastTime = _this5.player.media.currentTime;
7298        });
7299      }
7300      /**
7301       * Create HTML elements for image containers
7302       */
7303
7304    }, {
7305      key: "render",
7306      value: function render() {
7307        // Create HTML element: plyr__preview-thumbnail-container
7308        this.elements.thumb.container = createElement('div', {
7309          class: this.player.config.classNames.previewThumbnails.thumbContainer
7310        }); // Wrapper for the image for styling
7311
7312        this.elements.thumb.imageContainer = createElement('div', {
7313          class: this.player.config.classNames.previewThumbnails.imageContainer
7314        });
7315        this.elements.thumb.container.appendChild(this.elements.thumb.imageContainer); // Create HTML element, parent+span: time text (e.g., 01:32:00)
7316
7317        var timeContainer = createElement('div', {
7318          class: this.player.config.classNames.previewThumbnails.timeContainer
7319        });
7320        this.elements.thumb.time = createElement('span', {}, '00:00');
7321        timeContainer.appendChild(this.elements.thumb.time);
7322        this.elements.thumb.container.appendChild(timeContainer); // Inject the whole thumb
7323
7324        if (is$1.element(this.player.elements.progress)) {
7325          this.player.elements.progress.appendChild(this.elements.thumb.container);
7326        } // Create HTML element: plyr__preview-scrubbing-container
7327
7328
7329        this.elements.scrubbing.container = createElement('div', {
7330          class: this.player.config.classNames.previewThumbnails.scrubbingContainer
7331        });
7332        this.player.elements.wrapper.appendChild(this.elements.scrubbing.container);
7333      }
7334    }, {
7335      key: "showImageAtCurrentTime",
7336      value: function showImageAtCurrentTime() {
7337        var _this6 = this;
7338
7339        if (this.mouseDown) {
7340          this.setScrubbingContainerSize();
7341        } else {
7342          this.setThumbContainerSizeAndPos();
7343        } // Find the desired thumbnail index
7344        // TODO: Handle a video longer than the thumbs where thumbNum is null
7345
7346
7347        var thumbNum = this.thumbnails[0].frames.findIndex(function (frame) {
7348          return _this6.seekTime >= frame.startTime && _this6.seekTime <= frame.endTime;
7349        });
7350        var hasThumb = thumbNum >= 0;
7351        var qualityIndex = 0; // Show the thumb container if we're not scrubbing
7352
7353        if (!this.mouseDown) {
7354          this.toggleThumbContainer(hasThumb);
7355        } // No matching thumb found
7356
7357
7358        if (!hasThumb) {
7359          return;
7360        } // Check to see if we've already downloaded higher quality versions of this image
7361
7362
7363        this.thumbnails.forEach(function (thumbnail, index) {
7364          if (_this6.loadedImages.includes(thumbnail.frames[thumbNum].text)) {
7365            qualityIndex = index;
7366          }
7367        }); // Only proceed if either thumbnum or thumbfilename has changed
7368
7369        if (thumbNum !== this.showingThumb) {
7370          this.showingThumb = thumbNum;
7371          this.loadImage(qualityIndex);
7372        }
7373      } // Show the image that's currently specified in this.showingThumb
7374
7375    }, {
7376      key: "loadImage",
7377      value: function loadImage() {
7378        var _this7 = this;
7379
7380        var qualityIndex = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
7381        var thumbNum = this.showingThumb;
7382        var thumbnail = this.thumbnails[qualityIndex];
7383        var urlPrefix = thumbnail.urlPrefix;
7384        var frame = thumbnail.frames[thumbNum];
7385        var thumbFilename = thumbnail.frames[thumbNum].text;
7386        var thumbUrl = urlPrefix + thumbFilename;
7387
7388        if (!this.currentImageElement || this.currentImageElement.dataset.filename !== thumbFilename) {
7389          // If we're already loading a previous image, remove its onload handler - we don't want it to load after this one
7390          // Only do this if not using sprites. Without sprites we really want to show as many images as possible, as a best-effort
7391          if (this.loadingImage && this.usingSprites) {
7392            this.loadingImage.onload = null;
7393          } // We're building and adding a new image. In other implementations of similar functionality (YouTube), background image
7394          // is instead used. But this causes issues with larger images in Firefox and Safari - switching between background
7395          // images causes a flicker. Putting a new image over the top does not
7396
7397
7398          var previewImage = new Image();
7399          previewImage.src = thumbUrl;
7400          previewImage.dataset.index = thumbNum;
7401          previewImage.dataset.filename = thumbFilename;
7402          this.showingThumbFilename = thumbFilename;
7403          this.player.debug.log("Loading image: ".concat(thumbUrl)); // For some reason, passing the named function directly causes it to execute immediately. So I've wrapped it in an anonymous function...
7404
7405          previewImage.onload = function () {
7406            return _this7.showImage(previewImage, frame, qualityIndex, thumbNum, thumbFilename, true);
7407          };
7408
7409          this.loadingImage = previewImage;
7410          this.removeOldImages(previewImage);
7411        } else {
7412          // Update the existing image
7413          this.showImage(this.currentImageElement, frame, qualityIndex, thumbNum, thumbFilename, false);
7414          this.currentImageElement.dataset.index = thumbNum;
7415          this.removeOldImages(this.currentImageElement);
7416        }
7417      }
7418    }, {
7419      key: "showImage",
7420      value: function showImage(previewImage, frame, qualityIndex, thumbNum, thumbFilename) {
7421        var newImage = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
7422        this.player.debug.log("Showing thumb: ".concat(thumbFilename, ". num: ").concat(thumbNum, ". qual: ").concat(qualityIndex, ". newimg: ").concat(newImage));
7423        this.setImageSizeAndOffset(previewImage, frame);
7424
7425        if (newImage) {
7426          this.currentImageContainer.appendChild(previewImage);
7427          this.currentImageElement = previewImage;
7428
7429          if (!this.loadedImages.includes(thumbFilename)) {
7430            this.loadedImages.push(thumbFilename);
7431          }
7432        } // Preload images before and after the current one
7433        // Show higher quality of the same frame
7434        // Each step here has a short time delay, and only continues if still hovering/seeking the same spot. This is to protect slow connections from overloading
7435
7436
7437        this.preloadNearby(thumbNum, true).then(this.preloadNearby(thumbNum, false)).then(this.getHigherQuality(qualityIndex, previewImage, frame, thumbFilename));
7438      } // Remove all preview images that aren't the designated current image
7439
7440    }, {
7441      key: "removeOldImages",
7442      value: function removeOldImages(currentImage) {
7443        var _this8 = this;
7444
7445        // Get a list of all images, convert it from a DOM list to an array
7446        Array.from(this.currentImageContainer.children).forEach(function (image) {
7447          if (image.tagName.toLowerCase() !== 'img') {
7448            return;
7449          }
7450
7451          var removeDelay = _this8.usingSprites ? 500 : 1000;
7452
7453          if (image.dataset.index !== currentImage.dataset.index && !image.dataset.deleting) {
7454            // Wait 200ms, as the new image can take some time to show on certain browsers (even though it was downloaded before showing). This will prevent flicker, and show some generosity towards slower clients
7455            // First set attribute 'deleting' to prevent multi-handling of this on repeat firing of this function
7456            // eslint-disable-next-line no-param-reassign
7457            image.dataset.deleting = true; // This has to be set before the timeout - to prevent issues switching between hover and scrub
7458
7459            var currentImageContainer = _this8.currentImageContainer;
7460            setTimeout(function () {
7461              currentImageContainer.removeChild(image);
7462
7463              _this8.player.debug.log("Removing thumb: ".concat(image.dataset.filename));
7464            }, removeDelay);
7465          }
7466        });
7467      } // Preload images before and after the current one. Only if the user is still hovering/seeking the same frame
7468      // This will only preload the lowest quality
7469
7470    }, {
7471      key: "preloadNearby",
7472      value: function preloadNearby(thumbNum) {
7473        var _this9 = this;
7474
7475        var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
7476        return new Promise(function (resolve) {
7477          setTimeout(function () {
7478            var oldThumbFilename = _this9.thumbnails[0].frames[thumbNum].text;
7479
7480            if (_this9.showingThumbFilename === oldThumbFilename) {
7481              // Find the nearest thumbs with different filenames. Sometimes it'll be the next index, but in the case of sprites, it might be 100+ away
7482              var thumbnailsClone;
7483
7484              if (forward) {
7485                thumbnailsClone = _this9.thumbnails[0].frames.slice(thumbNum);
7486              } else {
7487                thumbnailsClone = _this9.thumbnails[0].frames.slice(0, thumbNum).reverse();
7488              }
7489
7490              var foundOne = false;
7491              thumbnailsClone.forEach(function (frame) {
7492                var newThumbFilename = frame.text;
7493
7494                if (newThumbFilename !== oldThumbFilename) {
7495                  // Found one with a different filename. Make sure it hasn't already been loaded on this page visit
7496                  if (!_this9.loadedImages.includes(newThumbFilename)) {
7497                    foundOne = true;
7498
7499                    _this9.player.debug.log("Preloading thumb filename: ".concat(newThumbFilename));
7500
7501                    var urlPrefix = _this9.thumbnails[0].urlPrefix;
7502                    var thumbURL = urlPrefix + newThumbFilename;
7503                    var previewImage = new Image();
7504                    previewImage.src = thumbURL;
7505
7506                    previewImage.onload = function () {
7507                      _this9.player.debug.log("Preloaded thumb filename: ".concat(newThumbFilename));
7508
7509                      if (!_this9.loadedImages.includes(newThumbFilename)) _this9.loadedImages.push(newThumbFilename); // We don't resolve until the thumb is loaded
7510
7511                      resolve();
7512                    };
7513                  }
7514                }
7515              }); // If there are none to preload then we want to resolve immediately
7516
7517              if (!foundOne) {
7518                resolve();
7519              }
7520            }
7521          }, 300);
7522        });
7523      } // If user has been hovering current image for half a second, look for a higher quality one
7524
7525    }, {
7526      key: "getHigherQuality",
7527      value: function getHigherQuality(currentQualityIndex, previewImage, frame, thumbFilename) {
7528        var _this10 = this;
7529
7530        if (currentQualityIndex < this.thumbnails.length - 1) {
7531          // Only use the higher quality version if it's going to look any better - if the current thumb is of a lower pixel density than the thumbnail container
7532          var previewImageHeight = previewImage.naturalHeight;
7533
7534          if (this.usingSprites) {
7535            previewImageHeight = frame.h;
7536          }
7537
7538          if (previewImageHeight < this.thumbContainerHeight) {
7539            // Recurse back to the loadImage function - show a higher quality one, but only if the viewer is on this frame for a while
7540            setTimeout(function () {
7541              // Make sure the mouse hasn't already moved on and started hovering at another image
7542              if (_this10.showingThumbFilename === thumbFilename) {
7543                _this10.player.debug.log("Showing higher quality thumb for: ".concat(thumbFilename));
7544
7545                _this10.loadImage(currentQualityIndex + 1);
7546              }
7547            }, 300);
7548          }
7549        }
7550      }
7551    }, {
7552      key: "toggleThumbContainer",
7553      value: function toggleThumbContainer() {
7554        var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
7555        var clearShowing = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
7556        var className = this.player.config.classNames.previewThumbnails.thumbContainerShown;
7557        this.elements.thumb.container.classList.toggle(className, toggle);
7558
7559        if (!toggle && clearShowing) {
7560          this.showingThumb = null;
7561          this.showingThumbFilename = null;
7562        }
7563      }
7564    }, {
7565      key: "toggleScrubbingContainer",
7566      value: function toggleScrubbingContainer() {
7567        var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
7568        var className = this.player.config.classNames.previewThumbnails.scrubbingContainerShown;
7569        this.elements.scrubbing.container.classList.toggle(className, toggle);
7570
7571        if (!toggle) {
7572          this.showingThumb = null;
7573          this.showingThumbFilename = null;
7574        }
7575      }
7576    }, {
7577      key: "determineContainerAutoSizing",
7578      value: function determineContainerAutoSizing() {
7579        if (this.elements.thumb.imageContainer.clientHeight > 20) {
7580          // This will prevent auto sizing in this.setThumbContainerSizeAndPos()
7581          this.sizeSpecifiedInCSS = true;
7582        }
7583      } // Set the size to be about a quarter of the size of video. Unless option dynamicSize === false, in which case it needs to be set in CSS
7584
7585    }, {
7586      key: "setThumbContainerSizeAndPos",
7587      value: function setThumbContainerSizeAndPos() {
7588        if (!this.sizeSpecifiedInCSS) {
7589          var thumbWidth = Math.floor(this.thumbContainerHeight * this.thumbAspectRatio);
7590          this.elements.thumb.imageContainer.style.height = "".concat(this.thumbContainerHeight, "px");
7591          this.elements.thumb.imageContainer.style.width = "".concat(thumbWidth, "px");
7592        }
7593
7594        this.setThumbContainerPos();
7595      }
7596    }, {
7597      key: "setThumbContainerPos",
7598      value: function setThumbContainerPos() {
7599        var seekbarRect = this.player.elements.progress.getBoundingClientRect();
7600        var plyrRect = this.player.elements.container.getBoundingClientRect();
7601        var container = this.elements.thumb.container; // Find the lowest and highest desired left-position, so we don't slide out the side of the video container
7602
7603        var minVal = plyrRect.left - seekbarRect.left + 10;
7604        var maxVal = plyrRect.right - seekbarRect.left - container.clientWidth - 10; // Set preview container position to: mousepos, minus seekbar.left, minus half of previewContainer.clientWidth
7605
7606        var previewPos = this.mousePosX - seekbarRect.left - container.clientWidth / 2;
7607
7608        if (previewPos < minVal) {
7609          previewPos = minVal;
7610        }
7611
7612        if (previewPos > maxVal) {
7613          previewPos = maxVal;
7614        }
7615
7616        container.style.left = "".concat(previewPos, "px");
7617      } // Can't use 100% width, in case the video is a different aspect ratio to the video container
7618
7619    }, {
7620      key: "setScrubbingContainerSize",
7621      value: function setScrubbingContainerSize() {
7622        this.elements.scrubbing.container.style.width = "".concat(this.player.media.clientWidth, "px"); // Can't use media.clientHeight - html5 video goes big and does black bars above and below
7623
7624        this.elements.scrubbing.container.style.height = "".concat(this.player.media.clientWidth / this.thumbAspectRatio, "px");
7625      } // Sprites need to be offset to the correct location
7626
7627    }, {
7628      key: "setImageSizeAndOffset",
7629      value: function setImageSizeAndOffset(previewImage, frame) {
7630        if (!this.usingSprites) {
7631          return;
7632        } // Find difference between height and preview container height
7633
7634
7635        var multiplier = this.thumbContainerHeight / frame.h; // eslint-disable-next-line no-param-reassign
7636
7637        previewImage.style.height = "".concat(Math.floor(previewImage.naturalHeight * multiplier), "px"); // eslint-disable-next-line no-param-reassign
7638
7639        previewImage.style.width = "".concat(Math.floor(previewImage.naturalWidth * multiplier), "px"); // eslint-disable-next-line no-param-reassign
7640
7641        previewImage.style.left = "-".concat(frame.x * multiplier, "px"); // eslint-disable-next-line no-param-reassign
7642
7643        previewImage.style.top = "-".concat(frame.y * multiplier, "px");
7644      }
7645    }, {
7646      key: "enabled",
7647      get: function get() {
7648        return this.player.isHTML5 && this.player.isVideo && this.player.config.previewThumbnails.enabled;
7649      }
7650    }, {
7651      key: "currentImageContainer",
7652      get: function get() {
7653        if (this.mouseDown) {
7654          return this.elements.scrubbing.container;
7655        }
7656
7657        return this.elements.thumb.imageContainer;
7658      }
7659    }, {
7660      key: "usingSprites",
7661      get: function get() {
7662        return Object.keys(this.thumbnails[0].frames[0]).includes('w');
7663      }
7664    }, {
7665      key: "thumbAspectRatio",
7666      get: function get() {
7667        if (this.usingSprites) {
7668          return this.thumbnails[0].frames[0].w / this.thumbnails[0].frames[0].h;
7669        }
7670
7671        return this.thumbnails[0].width / this.thumbnails[0].height;
7672      }
7673    }, {
7674      key: "thumbContainerHeight",
7675      get: function get() {
7676        if (this.mouseDown) {
7677          // Can't use media.clientHeight - HTML5 video goes big and does black bars above and below
7678          return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio);
7679        }
7680
7681        return Math.floor(this.player.media.clientWidth / this.thumbAspectRatio / 4);
7682      }
7683    }, {
7684      key: "currentImageElement",
7685      get: function get() {
7686        if (this.mouseDown) {
7687          return this.currentScrubbingImageElement;
7688        }
7689
7690        return this.currentThumbnailImageElement;
7691      },
7692      set: function set(element) {
7693        if (this.mouseDown) {
7694          this.currentScrubbingImageElement = element;
7695        } else {
7696          this.currentThumbnailImageElement = element;
7697        }
7698      }
7699    }]);
7700
7701    return PreviewThumbnails;
7702  }();
7703
7704  var source = {
7705    // Add elements to HTML5 media (source, tracks, etc)
7706    insertElements: function insertElements(type, attributes) {
7707      var _this = this;
7708
7709      if (is$1.string(attributes)) {
7710        insertElement(type, this.media, {
7711          src: attributes
7712        });
7713      } else if (is$1.array(attributes)) {
7714        attributes.forEach(function (attribute) {
7715          insertElement(type, _this.media, attribute);
7716        });
7717      }
7718    },
7719    // Update source
7720    // Sources are not checked for support so be careful
7721    change: function change(input) {
7722      var _this2 = this;
7723
7724      if (!getDeep(input, 'sources.length')) {
7725        this.debug.warn('Invalid source format');
7726        return;
7727      } // Cancel current network requests
7728
7729
7730      html5.cancelRequests.call(this); // Destroy instance and re-setup
7731
7732      this.destroy.call(this, function () {
7733        // Reset quality options
7734        _this2.options.quality = []; // Remove elements
7735
7736        removeElement(_this2.media);
7737        _this2.media = null; // Reset class name
7738
7739        if (is$1.element(_this2.elements.container)) {
7740          _this2.elements.container.removeAttribute('class');
7741        } // Set the type and provider
7742
7743
7744        var sources = input.sources,
7745            type = input.type;
7746
7747        var _sources = _slicedToArray(sources, 1),
7748            _sources$ = _sources[0],
7749            _sources$$provider = _sources$.provider,
7750            provider = _sources$$provider === void 0 ? providers.html5 : _sources$$provider,
7751            src = _sources$.src;
7752
7753        var tagName = provider === 'html5' ? type : 'div';
7754        var attributes = provider === 'html5' ? {} : {
7755          src: src
7756        };
7757        Object.assign(_this2, {
7758          provider: provider,
7759          type: type,
7760          // Check for support
7761          supported: support.check(type, provider, _this2.config.playsinline),
7762          // Create new element
7763          media: createElement(tagName, attributes)
7764        }); // Inject the new element
7765
7766        _this2.elements.container.appendChild(_this2.media); // Autoplay the new source?
7767
7768
7769        if (is$1.boolean(input.autoplay)) {
7770          _this2.config.autoplay = input.autoplay;
7771        } // Set attributes for audio and video
7772
7773
7774        if (_this2.isHTML5) {
7775          if (_this2.config.crossorigin) {
7776            _this2.media.setAttribute('crossorigin', '');
7777          }
7778
7779          if (_this2.config.autoplay) {
7780            _this2.media.setAttribute('autoplay', '');
7781          }
7782
7783          if (!is$1.empty(input.poster)) {
7784            _this2.poster = input.poster;
7785          }
7786
7787          if (_this2.config.loop.active) {
7788            _this2.media.setAttribute('loop', '');
7789          }
7790
7791          if (_this2.config.muted) {
7792            _this2.media.setAttribute('muted', '');
7793          }
7794
7795          if (_this2.config.playsinline) {
7796            _this2.media.setAttribute('playsinline', '');
7797          }
7798        } // Restore class hook
7799
7800
7801        ui.addStyleHook.call(_this2); // Set new sources for html5
7802
7803        if (_this2.isHTML5) {
7804          source.insertElements.call(_this2, 'source', sources);
7805        } // Set video title
7806
7807
7808        _this2.config.title = input.title; // Set up from scratch
7809
7810        media.setup.call(_this2); // HTML5 stuff
7811
7812        if (_this2.isHTML5) {
7813          // Setup captions
7814          if (Object.keys(input).includes('tracks')) {
7815            source.insertElements.call(_this2, 'track', input.tracks);
7816          }
7817        } // If HTML5 or embed but not fully supported, setupInterface and call ready now
7818
7819
7820        if (_this2.isHTML5 || _this2.isEmbed && !_this2.supported.ui) {
7821          // Setup interface
7822          ui.build.call(_this2);
7823        }
7823 // Load HTML5 sources
7824
7825
7826        if (_this2.isHTML5) {
7827          _this2.media.load();
7828        } // Reload thumbnails
7829
7830
7831        if (_this2.previewThumbnails) {
7832          _this2.previewThumbnails.load();
7833        } // Update the fullscreen support
7834
7835
7836        _this2.fullscreen.update();
7837      }, true);
7838    }
7839  };
7840
7841  /**
7842   * Returns a number whose value is limited to the given range.
7843   *
7844   * Example: limit the output of this computation to between 0 and 255
7845   * (x * 255).clamp(0, 255)
7846   *
7847   * @param {Number} input
7848   * @param {Number} min The lower boundary of the output range
7849   * @param {Number} max The upper boundary of the output range
7850   * @returns A number in the range [min, max]
7851   * @type Number
7852   */
7853  function clamp() {
7854    var input = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
7855    var min = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
7856    var max = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 255;
7857    return Math.min(Math.max(input, min), max);
7858  }
7859
7860  // TODO: Use a WeakMap for private globals
7861  // const globals = new WeakMap();
7862  // Plyr instance
7863
7864  var Plyr =
7865  /*#__PURE__*/
7866  function () {
7867    function Plyr(target, options) {
7868      var _this = this;
7869
7870      _classCallCheck(this, Plyr);
7871
7872      this.timers = {}; // State
7873
7874      this.ready = false;
7875      this.loading = false;
7876      this.failed = false; // Touch device
7877
7878      this.touch = support.touch; // Set the media element
7879
7880      this.media = target; // String selector passed
7881
7882      if (is$1.string(this.media)) {
7883        this.media = document.querySelectorAll(this.media);
7884      } // jQuery, NodeList or Array passed, use first element
7885
7886
7887      if (window.jQuery && this.media instanceof jQuery || is$1.nodeList(this.media) || is$1.array(this.media)) {
7888        // eslint-disable-next-line
7889        this.media = this.media[0];
7890      } // Set config
7891
7892
7893      this.config = extend({}, defaults$1, Plyr.defaults, options || {}, function () {
7894        try {
7895          return JSON.parse(_this.media.getAttribute('data-plyr-config'));
7896        } catch (e) {
7897          return {};
7898        }
7899      }()); // Elements cache
7900
7901      this.elements = {
7902        container: null,
7903        captions: null,
7904        buttons: {},
7905        display: {},
7906        progress: {},
7907        inputs: {},
7908        settings: {
7909          popup: null,
7910          menu: null,
7911          panels: {},
7912          buttons: {}
7913        }
7914      }; // Captions
7915
7916      this.captions = {
7917        active: null,
7918        currentTrack: -1,
7919        meta: new WeakMap()
7920      }; // Fullscreen
7921
7922      this.fullscreen = {
7923        active: false
7924      }; // Options
7925
7926      this.options = {
7927        speed: [],
7928        quality: []
7929      }; // Debugging
7930      // TODO: move to globals
7931
7932      this.debug = new Console(this.config.debug); // Log config options and support
7933
7934      this.debug.log('Config', this.config);
7935      this.debug.log('Support', support); // We need an element to setup
7936
7937      if (is$1.nullOrUndefined(this.media) || !is$1.element(this.media)) {
7938        this.debug.error('Setup failed: no suitable element passed');
7939        return;
7940      } // Bail if the element is initialized
7941
7942
7943      if (this.media.plyr) {
7944        this.debug.warn('Target already setup');
7945        return;
7946      } // Bail if not enabled
7947
7948
7949      if (!this.config.enabled) {
7950        this.debug.error('Setup failed: disabled by config');
7951        return;
7952      } // Bail if disabled or no basic support
7953      // You may want to disable certain UAs etc
7954
7955
7956      if (!support.check().api) {
7957        this.debug.error('Setup failed: no support');
7958        return;
7959      } // Cache original element state for .destroy()
7960
7961
7962      var clone = this.media.cloneNode(true);
7963      clone.autoplay = false;
7964      this.elements.original = clone; // Set media type based on tag or data attribute
7965      // Supported: video, audio, vimeo, youtube
7966
7967      var type = this.media.tagName.toLowerCase(); // Embed properties
7968
7969      var iframe = null;
7970      var url = null; // Different setup based on type
7971
7972      switch (type) {
7973        case 'div':
7974          // Find the frame
7975          iframe = this.media.querySelector('iframe'); // <iframe> type
7976
vendor: 4,265 bytes, lines 7977-8107
7977          if (is$1.element(iframe)) {
7978            // Detect provider
7979            url = parseUrl(iframe.getAttribute('src'));
7980            this.provider = getProviderByUrl(url.toString()); // Rework elements
7981
7982            this.elements.container = this.media;
7983            this.media = iframe; // Reset classname
7984
7985            this.elements.container.className = ''; // Get attributes from URL and set config
7986
7987            if (url.search.length) {
7988              var truthy = ['1', 'true'];
7989
7990              if (truthy.includes(url.searchParams.get('autoplay'))) {
7991                this.config.autoplay = true;
7992              }
7993
7994              if (truthy.includes(url.searchParams.get('loop'))) {
7995                this.config.loop.active = true;
7996              } // TODO: replace fullscreen.iosNative with this playsinline config option
7997              // YouTube requires the playsinline in the URL
7998
7999
8000              if (this.isYouTube) {
8001                this.config.playsinline = truthy.includes(url.searchParams.get('playsinline'));
8002                this.config.youtube.hl = url.searchParams.get('hl'); // TODO: Should this be setting language?
8003              } else {
8004                this.config.playsinline = true;
8005              }
8006            }
8007          } else {
8008            // <div> with attributes
8009            this.provider = this.media.getAttribute(this.config.attributes.embed.provider); // Remove attribute
8010
8011            this.media.removeAttribute(this.config.attributes.embed.provider);
8012          } // Unsupported or missing provider
8013
8014
8015          if (is$1.empty(this.provider) || !Object.keys(providers).includes(this.provider)) {
8016            this.debug.error('Setup failed: Invalid provider');
8017            return;
8018          } // Audio will come later for external providers
8019
8020
8021          this.type = types.video;
8022          break;
8023
8024        case 'video':
8025        case 'audio':
8026          this.type = type;
8027          this.provider = providers.html5; // Get config from attributes
8028
8029          if (this.media.hasAttribute('crossorigin')) {
8030            this.config.crossorigin = true;
8031          }
8032
8033          if (this.media.hasAttribute('autoplay')) {
8034            this.config.autoplay = true;
8035          }
8036
8037          if (this.media.hasAttribute('playsinline') || this.media.hasAttribute('webkit-playsinline')) {
8038            this.config.playsinline = true;
8039          }
8040
8041          if (this.media.hasAttribute('muted')) {
8042            this.config.muted = true;
8043          }
8044
8045          if (this.media.hasAttribute('loop')) {
8046            this.config.loop.active = true;
8047          }
8048
8049          break;
8050
8051        default:
8052          this.debug.error('Setup failed: unsupported type');
8053          return;
8054      } // Check for support again but with type
8055
8056
8057      this.supported = support.check(this.type, this.provider, this.config.playsinline); // If no support for even API, bail
8058
8059      if (!this.supported.api) {
8060        this.debug.error('Setup failed: no support');
8061        return;
8062      }
8063
8064      this.eventListeners = []; // Create listeners
8065
8066      this.listeners = new Listeners(this); // Setup local storage for user settings
8067
8068      this.storage = new Storage(this); // Store reference
8069
8070      this.media.plyr = this; // Wrap media
8071
8072      if (!is$1.element(this.elements.container)) {
8073        this.elements.container = createElement('div', {
8074          tabindex: 0
8075        });
8076        wrap(this.media, this.elements.container);
8077      } // Add style hook
8078
8079
8080      ui.addStyleHook.call(this); // Setup media
8081
8082      media.setup.call(this); // Listen for events if debugging
8083
8084      if (this.config.debug) {
8085        on.call(this, this.elements.container, this.config.events.join(' '), function (event) {
8086          _this.debug.log("event: ".concat(event.type));
8087        });
8088      } // Setup interface
8089      // If embed but not fully supported, build interface now to avoid flash of controls
8090
8091
8092      if (this.isHTML5 || this.isEmbed && !this.supported.ui) {
8093        ui.build.call(this);
8094      } // Container listeners
8095
8096
8097      this.listeners.container(); // Global listeners
8098
8099      this.listeners.global(); // Setup fullscreen
8100
8101      this.fullscreen = new Fullscreen(this); // Setup ads if provided
8102
8103      if (this.config.ads.enabled) {
8104        this.ads = new Ads(this);
8105      } // Autoplay if required
8106
8107
8108      if (this.isHTML5 && this.config.autoplay) {
8109        setTimeout(function () {
8110          return _this.play();
8111        }, 10);
8112      } // Seek time will be recorded (in listeners.js) so we can prevent hiding controls for a few seconds after seek
8113
8114
8115      this.lastSeekTime = 0; // Setup preview thumbnails if enabled
8116
8117      if (this.config.previewThumbnails.enabled) {
8118        this.previewThumbnails = new PreviewThumbnails(this);
8119      }
8120    } // ---------------------------------------
8121    // API
8122    // ---------------------------------------
8123
8124    /**
8125     * Types and provider helpers
8126     */
8127
8128
8129    _createClass(Plyr, [{
8130      key: "play",
8131
8132      /**
8133       * Play the media, or play the advertisement (if they are not blocked)
8134       */
8135      value: function play() {
8136        var _this2 = this;
8137
8138        if (!is$1.function(this.media.play)) {
8139          return null;
8140        } // Intecept play with ads
8141
8142
8143        if (this.ads && this.ads.enabled) {
8144          this.ads.managerPromise.then(function () {
8145            return _this2.ads.play();
8146          }).catch(function () {
8147            return _this2.media.play();
8148          });
8149        } // Return the promise (for HTML5)
8150
8151
8152        return this.media.play();
8153      }
8154      /**
8155       * Pause the media
8156       */
8157
8158    }, {
8159      key: "pause",
8160      value: function pause() {
8161        if (!this.playing || !is$1.function(this.media.pause)) {
8162          return;
8163        }
8164
8165        this.media.pause();
8166      }
8167      /**
8168       * Get playing state
8169       */
8170
8171    }, {
8172      key: "togglePlay",
8173
8174      /**
8175       * Toggle playback based on current status
8176       * @param {Boolean} input
8177       */
8178      value: function togglePlay(input) {
8179        // Toggle based on current state if nothing passed
8180        var toggle = is$1.boolean(input) ? input : !this.playing;
8181
8182        if (toggle) {
8183          this.play();
8184        } else {
8185          this.pause();
8186        }
8187      }
8188      /**
8189       * Stop playback
8190       */
8191
8192    }, {
8193      key: "stop",
8194      value: function stop() {
8195        if (this.isHTML5) {
8196          this.pause();
8197          this.restart();
8198        } else if (is$1.function(this.media.stop)) {
8199          this.media.stop();
8200        }
8201      }
8202      /**
8203       * Restart playback
8204       */
8205
8206    }, {
8207      key: "restart",
8208      value: function restart() {
8209        this.currentTime = 0;
8210      }
8211      /**
8212       * Rewind
8213       * @param {Number} seekTime - how far to rewind in seconds. Defaults to the config.seekTime
8214       */
8215
8216    }, {
8217      key: "rewind",
8218      value: function rewind(seekTime) {
8219        this.currentTime = this.currentTime - (is$1.number(seekTime) ? seekTime : this.config.seekTime);
8220      }
8221      /**
8222       * Fast forward
8223       * @param {Number} seekTime - how far to fast forward in seconds. Defaults to the config.seekTime
8224       */
8225
8226    }, {
8227      key: "forward",
8228      value: function forward(seekTime) {
8229        this.currentTime = this.currentTime + (is$1.number(seekTime) ? seekTime : this.config.seekTime);
8230      }
8231      /**
8232       * Seek to a time
8233       * @param {Number} input - where to seek to in seconds. Defaults to 0 (the start)
8234       */
8235
8236    }, {
8237      key: "increaseVolume",
8238
8239      /**
8240       * Increase volume
8241       * @param {Boolean} step - How much to decrease by (between 0 and 1)
8242       */
8243      value: function increaseVolume(step) {
8244        var volume = this.media.muted ? 0 : this.volume;
8245        this.volume = volume + (is$1.number(step) ? step : 0);
8246      }
8247      /**
8248       * Decrease volume
8249       * @param {Boolean} step - How much to decrease by (between 0 and 1)
8250       */
8251
8252    }, {
8253      key: "decreaseVolume",
8254      value: function decreaseVolume(step) {
8255        this.increaseVolume(-step);
8256      }
8257      /**
8258       * Set muted state
8259       * @param {Boolean} mute
8260       */
8261
8262    }, {
8263      key: "toggleCaptions",
8264
8265      /**
8266       * Toggle captions
8267       * @param {Boolean} input - Whether to enable captions
8268       */
8269      value: function toggleCaptions(input) {
8270        captions.toggle.call(this, input, false);
8271      }
8272      /**
8273       * Set the caption track by index
8274       * @param {Number} - Caption index
8275       */
8276
8277    }, {
8278      key: "airplay",
8279
8280      /**
8281       * Trigger the airplay dialog
8282       * TODO: update player with state, support, enabled
8283       */
8284      value: function airplay() {
8285        // Show dialog if supported
8286        if (support.airplay) {
8287          this.media.webkitShowPlaybackTargetPicker();
8288        }
8289      }
8290      /**
8291       * Toggle the player controls
8292       * @param {Boolean} [toggle] - Whether to show the controls
8293       */
8294
8295    }, {
8296      key: "toggleControls",
8297      value: function toggleControls(toggle) {
8298        // Don't toggle if missing UI support or if it's audio
8299        if (this.supported.ui && !this.isAudio) {
8300          // Get state before change
8301          var isHidden = hasClass(this.elements.container, this.config.classNames.hideControls); // Negate the argument if not undefined since adding the class to hides the controls
8302
8303          var force = typeof toggle === 'undefined' ? undefined : !toggle; // Apply and get updated state
8304
8305          var hiding = toggleClass(this.elements.container, this.config.classNames.hideControls, force); // Close menu
8306
8307          if (hiding && this.config.controls.includes('settings') && !is$1.empty(this.config.settings)) {
8308            controls.toggleMenu.call(this, false);
8309          } // Trigger event on change
8310
8311
8312          if (hiding !== isHidden) {
8313            var eventName = hiding ? 'controlshidden' : 'controlsshown';
8314            triggerEvent.call(this, this.media, eventName);
8315          }
8316
8317          return !hiding;
8318        }
8319
8320        return false;
8321      }
8322      /**
8323       * Add event listeners
8324       * @param {String} event - Event type
8325       * @param {Function} callback - Callback for when event occurs
8326       */
8327
8328    }, {
8329      key: "on",
8330      value: function on$1(event, callback) {
8331        on.call(this, this.elements.container, event, callback);
8332      }
8333      /**
8334       * Add event listeners once
8335       * @param {String} event - Event type
8336       * @param {Function} callback - Callback for when event occurs
8337       */
8338
8339    }, {
8340      key: "once",
8341      value: function once$1(event, callback) {
8342        once.call(this, this.elements.container, event, callback);
8343      }
8344      /**
8345       * Remove event listeners
8346       * @param {String} event - Event type
8347       * @param {Function} callback - Callback for when event occurs
8348       */
8349
8350    }, {
8351      key: "off",
8352      value: function off$1(event, callback) {
8353        off(this.elements.container, event, callback);
8354      }
8355      /**
8356       * Destroy an instance
8357       * Event listeners are removed when elements are removed
8358       * http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory
8359       * @param {Function} callback - Callback for when destroy is complete
8360       * @param {Boolean} soft - Whether it's a soft destroy (for source changes etc)
8361       */
8362
8363    }, {
8364      key: "destroy",
8365      value: function destroy(callback) {
8366        var _this3 = this;
8367
8368        var soft = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
8369
8370        if (!this.ready) {
8371          return;
8372        }
8373
8374        var done = function done() {
8375          // Reset overflow (incase destroyed while in fullscreen)
8376          document.body.style.overflow = ''; // GC for embed
8377
8378          _this3.embed = null; // If it's a soft destroy, make minimal changes
8379
8380          if (soft) {
8381            if (Object.keys(_this3.elements).length) {
8382              // Remove elements
8383              removeElement(_this3.elements.buttons.play);
8384              removeElement(_this3.elements.captions);
8385              removeElement(_this3.elements.controls);
8386              removeElement(_this3.elements.wrapper); // Clear for GC
8387
8388              _this3.elements.buttons.play = null;
8389              _this3.elements.captions = null;
8390              _this3.elements.controls = null;
8391              _this3.elements.wrapper = null;
8392            } // Callback
8393
8394
8395            if (is$1.function(callback)) {
8396              callback();
8397            }
8398          } else {
8399            // Unbind listeners
8400            unbindListeners.call(_this3); // Replace the container with the original element provided
8401
8402            replaceElement(_this3.elements.original, _this3.elements.container); // Event
8403
8404            triggerEvent.call(_this3, _this3.elements.original, 'destroyed', true); // Callback
8405
8406            if (is$1.function(callback)) {
8407              callback.call(_this3.elements.original);
8408            } // Reset state
8409
8410
8411            _this3.ready = false; // Clear for garbage collection
8412
8413            setTimeout(function () {
8414              _this3.elements = null;
8415              _this3.media = null;
8416            }, 200);
8417          }
8418        }; // Stop playback
8419
8420
8421        this.stop(); // Clear timeouts
8422
8423        clearTimeout(this.timers.loading);
8424        clearTimeout(this.timers.controls);
8425        clearTimeout(this.timers.resized); // Provider specific stuff
8426
8427        if (this.isHTML5) {
8428          // Restore native video controls
8429          ui.toggleNativeControls.call(this, true); // Clean up
8430
8431          done();
8432        } else if (this.isYouTube) {
8433          // Clear timers
8434          clearInterval(this.timers.buffering);
8435          clearInterval(this.timers.playing); // Destroy YouTube API
8436
8437          if (this.embed !== null && is$1.function(this.embed.destroy)) {
8438            this.embed.destroy();
8439          } // Clean up
8440
8441
8442          done();
8443        } else if (this.isVimeo) {
8444          // Destroy Vimeo API
8445          // then clean up (wait, to prevent postmessage errors)
8446          if (this.embed !== null) {
8447            this.embed.unload().then(done);
8448          } // Vimeo does not always return
8449
8450
8451          setTimeout(done, 200);
8452        }
8453      }
8454      /**
8455       * Check for support for a mime type (HTML5 only)
8456       * @param {String} type - Mime type
8457       */
8458
8459    }, {
8460      key: "supports",
8461      value: function supports(type) {
8462        return support.mime.call(this, type);
8463      }
8464      /**
8465       * Check for support
8466       * @param {String} type - Player type (audio/video)
8467       * @param {String} provider - Provider (html5/youtube/vimeo)
8468       * @param {Boolean} inline - Where player has `playsinline` sttribute
8469       */
8470
8471    }, {
8472      key: "isHTML5",
8473      get: function get() {
8474        return this.provider === providers.html5;
8475      }
8476    }, {
8477      key: "isEmbed",
8478      get: function get() {
8479        return this.isYouTube || this.isVimeo;
8480      }
8481    }, {
8482      key: "isYouTube",
8483      get: function get() {
8484        return this.provider === providers.youtube;
8485      }
8486    }, {
8487      key: "isVimeo",
8488      get: function get() {
8489        return this.provider === providers.vimeo;
8490      }
8491    }, {
8492      key: "isVideo",
8493      get: function get() {
8494        return this.type === types.video;
8495      }
8496    }, {
8497      key: "isAudio",
8498      get: function get() {
8499        return this.type === types.audio;
8500      }
8501    }, {
8502      key: "playing",
8503      get: function get() {
8504        return Boolean(this.ready && !this.paused && !this.ended);
8505      }
8506      /**
8507       * Get paused state
8508       */
8509
8510    }, {
8511      key: "paused",
8512      get: function get() {
8513        return Boolean(this.media.paused);
8514      }
8515      /**
8516       * Get stopped state
8517       */
8518
8519    }, {
8520      key: "stopped",
8521      get: function get() {
8522        return Boolean(this.paused && this.currentTime === 0);
8523      }
8524      /**
8525       * Get ended state
8526       */
8527
8528    }, {
8529      key: "ended",
8530      get: function get() {
8531        return Boolean(this.media.ended);
8532      }
8533    }, {
8534      key: "currentTime",
8535      set: function set(input) {
8536        // Bail if media duration isn't available yet
8537        if (!this.duration) {
8538          return;
8539        } // Validate input
8540
8541
8542        var inputIsValid = is$1.number(input) && input > 0; // Set
8543
8544        this.media.currentTime = inputIsValid ? Math.min(input, this.duration) : 0; // Logging
8545
8546        this.debug.log("Seeking to ".concat(this.currentTime, " seconds"));
8547      }
8548      /**
8549       * Get current time
8550       */
8551      ,
8552      get: function get() {
8553        return Number(this.media.currentTime);
8554      }
8555      /**
8556       * Get buffered
8557       */
8558
8559    }, {
8560      key: "buffered",
8561      get: function get() {
8562        var buffered = this.media.buffered; // YouTube / Vimeo return a float between 0-1
8563
8564        if (is$1.number(buffered)) {
8565          return buffered;
8566        } // HTML5
8567        // TODO: Handle buffered chunks of the media
8568        // (i.e. seek to another section buffers only that section)
8569
8570
8571        if (buffered && buffered.length && this.duration > 0) {
8572          return buffered.end(0) / this.duration;
8573        }
8574
8575        return 0;
8576      }
8577      /**
8578       * Get seeking status
8579       */
8580
8581    }, {
8582      key: "seeking",
8583      get: function get() {
8584        return Boolean(this.media.seeking);
8585      }
8586      /**
8587       * Get the duration of the current media
8588       */
8589
8590    }, {
8591      key: "duration",
8592      get: function get() {
8593        // Faux duration set via config
8594        var fauxDuration = parseFloat(this.config.duration); // Media duration can be NaN or Infinity before the media has loaded
8595
8596        var realDuration = (this.media || {}).duration;
8597        var duration = !is$1.number(realDuration) || realDuration === Infinity ? 0 : realDuration; // If config duration is funky, use regular duration
8598
8599        return fauxDuration || duration;
8600      }
8601      /**
8602       * Set the player volume
8603       * @param {Number} value - must be between 0 and 1. Defaults to the value from local storage and config.volume if not set in storage
8604       */
8605
8606    }, {
8607      key: "volume",
8608      set: function set(value) {
8609        var volume = value;
8610        var max = 1;
8611        var min = 0;
8612
8613        if (is$1.string(volume)) {
8614          volume = Number(volume);
8615        } // Load volume from storage if no value specified
8616
8617
8618        if (!is$1.number(volume)) {
8619          volume = this.storage.get('volume');
8620        } // Use config if all else fails
8621
8622
8623        if (!is$1.number(volume)) {
8624          volume = this.config.volume;
8625        } // Maximum is volumeMax
8626
8627
8628        if (volume > max) {
8629          volume = max;
8630        } // Minimum is volumeMin
8631
8632
8633        if (volume < min) {
8634          volume = min;
8635        } // Update config
8636
8637
8638        this.config.volume = volume; // Set the player volume
8639
8640        this.media.volume = volume; // If muted, and we're increasing volume manually, reset muted state
8641
8642        if (!is$1.empty(value) && this.muted && volume > 0) {
8643          this.muted = false;
8644        }
8645      }
8646      /**
8647       * Get the current player volume
8648       */
8649      ,
8650      get: function get() {
8651        return Number(this.media.volume);
8652      }
8653    }, {
8654      key: "muted",
8655      set: function set(mute) {
8656        var toggle = mute; // Load muted state from storage
8657
8658        if (!is$1.boolean(toggle)) {
8659          toggle = this.storage.get('muted');
8660        } // Use config if all else fails
8661
8662
8663        if (!is$1.boolean(toggle)) {
8664          toggle = this.config.muted;
8665        } // Update config
8666
8667
8668        this.config.muted = toggle; // Set mute on the player
8669
8670        this.media.muted = toggle;
8671      }
8672      /**
8673       * Get current muted state
8674       */
8675      ,
8676      get: function get() {
8677        return Boolean(this.media.muted);
8678      }
8679      /**
8680       * Check if the media has audio
8681       */
8682
8683    }, {
8684      key: "hasAudio",
8685      get: function get() {
8686        // Assume yes for all non HTML5 (as we can't tell...)
8687        if (!this.isHTML5) {
8688          return true;
8689        }
8690
8691        if (this.isAudio) {
8692          return true;
8693        } // Get audio tracks
8694
8695
8696        return Boolean(this.media.mozHasAudio) || Boolean(this.media.webkitAudioDecodedByteCount) || Boolean(this.media.audioTracks && this.media.audioTracks.length);
8697      }
8698      /**
8699       * Set playback speed
8700       * @param {Number} speed - the speed of playback (0.5-2.0)
8701       */
8702
8703    }, {
8704      key: "speed",
8705      set: function set(input) {
8706        var _this4 = this;
8707
8708        var speed = null;
8709
8710        if (is$1.number(input)) {
8711          speed = input;
8712        }
8713
8714        if (!is$1.number(speed)) {
8715          speed = this.storage.get('speed');
8716        }
8717
8718        if (!is$1.number(speed)) {
8719          speed = this.config.speed.selected;
8720        } // Clamp to min/max
8721
8722
8723        var min = this.minimumSpeed,
8724            max = this.maximumSpeed;
8725        speed = clamp(speed, min, max); // Update config
8726
8727        this.config.speed.selected = speed; // Set media speed
8728
8729        setTimeout(function () {
8730          _this4.media.playbackRate = speed;
8731        }, 0);
8732      }
8733      /**
8734       * Get current playback speed
8735       */
8736      ,
8737      get: function get() {
8738        return Number(this.media.playbackRate);
8739      }
8740      /**
8741       * Get the minimum allowed speed
8742       */
8743
8744    }, {
8745      key: "minimumSpeed",
8746      get: function get() {
8747        if (this.isYouTube) {
8748          // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
8749          return Math.min.apply(Math, _toConsumableArray(this.options.speed));
8750        }
8751
8752        if (this.isVimeo) {
8753          // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
8754          return 0.5;
8755        } // https://stackoverflow.com/a/32320020/1191319
8756
8757
8758        return 0.0625;
8759      }
8760      /**
8761       * Get the maximum allowed speed
8762       */
8763
8764    }, {
8765      key: "maximumSpeed",
8766      get: function get() {
8767        if (this.isYouTube) {
8768          // https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate
8769          return Math.max.apply(Math, _toConsumableArray(this.options.speed));
8770        }
8771
8772        if (this.isVimeo) {
8773          // https://github.com/vimeo/player.js/#setplaybackrateplaybackrate-number-promisenumber-rangeerrorerror
8774          return 2;
8775        } // https://stackoverflow.com/a/32320020/1191319
8776
8777
8778        return 16;
8779      }
8780      /**
8781       * Set playback quality
8782       * Currently HTML5 & YouTube only
8783       * @param {Number} input - Quality level
8784       */
8785
8786    }, {
8787      key: "quality",
8788      set: function set(input) {
8789        var config = this.config.quality;
8790        var options = this.options.quality;
8791
8792        if (!options.length) {
8793          return;
8794        }
8795
8796        var quality = [!is$1.empty(input) && Number(input), this.storage.get('quality'), config.selected, config.default].find(is$1.number);
8797        var updateStorage = true;
8798
8799        if (!options.includes(quality)) {
8800          var value = closest(options, quality);
8801          this.debug.warn("Unsupported quality option: ".concat(quality, ", using ").concat(value, " instead"));
8802          quality = value; // Don't update storage if quality is not supported
8803
8804          updateStorage = false;
8805        } // Update config
8806
8807
8808        config.selected = quality; // Set quality
8809
8810        this.media.quality = quality; // Save to storage
8811
8812        if (updateStorage) {
8813          this.storage.set({
8814            quality: quality
8815          });
8816        }
8817      }
8818      /**
8819       * Get current quality level
8820       */
8821      ,
8822      get: function get() {
8823        return this.media.quality;
8824      }
8825      /**
8826       * Toggle loop
8827       * TODO: Finish fancy new logic. Set the indicator on load as user may pass loop as config
8828       * @param {Boolean} input - Whether to loop or not
8829       */
8830
8831    }, {
8832      key: "loop",
8833      set: function set(input) {
8834        var toggle = is$1.boolean(input) ? input : this.config.loop.active;
8835        this.config.loop.active = toggle;
8836        this.media.loop = toggle; // Set default to be a true toggle
8837
8838        /* const type = ['start', 'end', 'all', 'none', 'toggle'].includes(input) ? input : 'toggle';
8839         switch (type) {
8840            case 'start':
8841                if (this.config.loop.end && this.config.loop.end <= this.currentTime) {
8842                    this.config.loop.end = null;
8843                }
8844                this.config.loop.start = this.currentTime;
8845                // this.config.loop.indicator.start = this.elements.display.played.value;
8846                break;
8847             case 'end':
8848                if (this.config.loop.start >= this.currentTime) {
8849                    return this;
8850                }
8851                this.config.loop.end = this.currentTime;
8852                // this.config.loop.indicator.end = this.elements.display.played.value;
8853                break;
8854             case 'all':
8855                this.config.loop.start = 0;
8856                this.config.loop.end = this.duration - 2;
8857                this.config.loop.indicator.start = 0;
8858                this.config.loop.indicator.end = 100;
8859                break;
8860             case 'toggle':
8861                if (this.config.loop.active) {
8862                    this.config.loop.start = 0;
8863                    this.config.loop.end = null;
8864                } else {
8865                    this.config.loop.start = 0;
8866                    this.config.loop.end = this.duration - 2;
8867                }
8868                break;
8869             default:
8870                this.config.loop.start = 0;
8871                this.config.loop.end = null;
8872                break;
8873        } */
8874      }
8875      /**
8876       * Get current loop state
8877       */
8878      ,
8879      get: function get() {
8880        return Boolean(this.media.loop);
8881      }
8882      /**
8883       * Set new media source
8884       * @param {Object} input - The new source object (see docs)
8885       */
8886
8887    }, {
8888      key: "source",
8889      set: function set(input) {
8890        source.change.call(this, input);
8891      }
8892      /**
8893       * Get current source
8894       */
8895      ,
8896      get: function get() {
8897        return this.media.currentSrc;
8898      }
8899      /**
8900       * Get a download URL (either source or custom)
8901       */
8902
8903    }, {
8904      key: "download",
8905      get: function get() {
8906        var download = this.config.urls.download;
8907        return is$1.url(download) ? download : this.source;
8908      }
8909      /**
8910       * Set the download URL
8911       */
8912      ,
8913      set: function set(input) {
8914        if (!is$1.url(input)) {
8915          return;
8916        }
8917
8918        this.config.urls.download = input;
8919        controls.setDownloadUrl.call(this);
8920      }
8921      /**
8922       * Set the poster image for a video
8923       * @param {String} input - the URL for the new poster image
8924       */
8925
8926    }, {
8927      key: "poster",
8928      set: function set(input) {
8929        if (!this.isVideo) {
8930          this.debug.warn('Poster can only be set for video');
8931          return;
8932        }
8933
8934        ui.setPoster.call(this, input, false).catch(function () {});
8935      }
8936      /**
8937       * Get the current poster image
8938       */
8939      ,
8940      get: function get() {
8941        if (!this.isVideo) {
8942          return null;
8943        }
8944
8945        return this.media.getAttribute('poster');
8946      }
8947      /**
8948       * Get the current aspect ratio in use
8949       */
8950
8951    }, {
8952      key: "ratio",
8953      get: function get() {
8954        if (!this.isVideo) {
8955          return null;
8956        }
8957
8958        var ratio = reduceAspectRatio(getAspectRatio.call(this));
8959        return is$1.array(ratio) ? ratio.join(':') : ratio;
8960      }
8961      /**
8962       * Set video aspect ratio
8963       */
8964      ,
8965      set: function set(input) {
8966        if (!this.isVideo) {
8967          this.debug.warn('Aspect ratio can only be set for video');
8968          return;
8969        }
8970
8971        if (!is$1.string(input) || !validateRatio(input)) {
8972          this.debug.error("Invalid aspect ratio specified (".concat(input, ")"));
8973          return;
8974        }
8975
8976        this.config.ratio = input;
8977        setAspectRatio.call(this);
8978      }
8979      /**
8980       * Set the autoplay state
8981       * @param {Boolean}
8981 input - Whether to autoplay or not
8982       */
8983
8984    }, {
8985      key: "autoplay",
8986      set: function set(input) {
8987        var toggle = is$1.boolean(input) ? input : this.config.autoplay;
8988        this.config.autoplay = toggle;
8989      }
8990      /**
8991       * Get the current autoplay state
8992       */
8993      ,
8994      get: function get() {
8995        return Boolean(this.config.autoplay);
8996      }
8997    }, {
8998      key: "currentTrack",
8999      set: function set(input) {
9000        captions.set.call(this, input, false);
9001      }
9002      /**
9003       * Get the current caption track index (-1 if disabled)
9004       */
9005      ,
9006      get: function get() {
9007        var _this$captions = this.captions,
9008            toggled = _this$captions.toggled,
9009            currentTrack = _this$captions.currentTrack;
9010        return toggled ? currentTrack : -1;
9011      }
9012      /**
9013       * Set the wanted language for captions
9014       * Since tracks can be added later it won't update the actual caption track until there is a matching track
9015       * @param {String} - Two character ISO language code (e.g. EN, FR, PT, etc)
9016       */
9017
9018    }, {
9019      key: "language",
9020      set: function set(input) {
9021        captions.setLanguage.call(this, input, false);
9022      }
9023      /**
9024       * Get the current track's language
9025       */
9026      ,
9027      get: function get() {
9028        return (captions.getCurrentTrack.call(this) || {}).language;
9029      }
9030      /**
9031       * Toggle picture-in-picture playback on WebKit/MacOS
9032       * TODO: update player with state, support, enabled
9033       * TODO: detect outside changes
9034       */
9035
9036    }, {
9037      key: "pip",
9038      set: function set(input) {
9039        // Bail if no support
9040        if (!support.pip) {
9041          return;
9042        } // Toggle based on current state if not passed
9043
9044
9045        var toggle = is$1.boolean(input) ? input : !this.pip; // Toggle based on current state
9046        // Safari
9047
9048        if (is$1.function(this.media.webkitSetPresentationMode)) {
9049          this.media.webkitSetPresentationMode(toggle ? pip.active : pip.inactive);
9050        } // Chrome
9051
9052
9053        if (is$1.function(this.media.requestPictureInPicture)) {
9054          if (!this.pip && toggle) {
9055            this.media.requestPictureInPicture();
9056          } else if (this.pip && !toggle) {
9057            document.exitPictureInPicture();
9058          }
9059        }
9060      }
9061      /**
9062       * Get the current picture-in-picture state
9063       */
9064      ,
9065      get: function get() {
9066        if (!support.pip) {
9067          return null;
9068        } // Safari
9069
9070
9071        if (!is$1.empty(this.media.webkitPresentationMode)) {
9072          return this.media.webkitPresentationMode === pip.active;
9073        } // Chrome
9074
9075
9076        return this.media === document.pictureInPictureElement;
9077      }
9078    }], [{
9079      key: "supported",
9080      value: function supported(type, provider, inline) {
9081        return support.check(type, provider, inline);
9082      }
9083      /**
9084       * Load an SVG sprite into the page
9085       * @param {String} url - URL for the SVG sprite
9086       * @param {String} [id] - Unique ID
9087       */
9088
9089    }, {
9090      key: "loadSprite",
9091      value: function loadSprite$1(url, id) {
9092        return loadSprite(url, id);
9093      }
9094      /**
9095       * Setup multiple instances
9096       * @param {*} selector
9097       * @param {Object} options
9098       */
9099
9100    }, {
9101      key: "setup",
9102      value: function setup(selector) {
9103        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9104        var targets = null;
9105
9106        if (is$1.string(selector)) {
9107          targets = Array.from(document.querySelectorAll(selector));
9108        } else if (is$1.nodeList(selector)) {
9109          targets = Array.from(selector);
9110        } else if (is$1.array(selector)) {
9111          targets = selector.filter(is$1.element);
9112        }
9113
9114        if (is$1.empty(targets)) {
9115          return null;
9116        }
9117
9118        return targets.map(function (t) {
9119          return new Plyr(t, options);
9120        });
9121      }
9122    }]);
9123
9124    return Plyr;
9125  }();
9126
9127  Plyr.defaults = cloneDeep(defaults$1);
9128
9129  return Plyr;
9130
9131}));

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.