From a41d98e82c6f6d49252824cbb276f1a2cfcc1861 Mon Sep 17 00:00:00 2001 From: Stephen Cameron Date: Thu, 1 Jun 2017 18:44:52 +0200 Subject: [PATCH] Update video.js to version 6.2.0. WIP 897 @0:15 --- js/libs/videojs/video.js | 41391 ++++++++++++++---------------- style/videojs/video-js-core.css | 314 +- 2 files changed, 19508 insertions(+), 22197 deletions(-) diff --git a/js/libs/videojs/video.js b/js/libs/videojs/video.js index d6e8b104..55386da1 100755 --- a/js/libs/videojs/video.js +++ b/js/libs/videojs/video.js @@ -1,6 +1,6 @@ /** * @license - * Video.js 6.1.0 + * Video.js 6.2.0 * Copyright Brightcove, Inc. * Available under Apache License Version 2.0 * @@ -10,14592 +10,12976 @@ * */ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojs = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1 && arguments[1] !== undefined ? arguments[1] : {}; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + return version; +}(); - tag = 'button'; +var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE; +var IS_ANY_SAFARI = IS_SAFARI || IS_IOS; + +var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch); + +var BACKGROUND_SIZE_SUPPORTED = isReal() && 'backgroundSize' in window_1.document.createElement('video').style; + +var browser = (Object.freeze || Object)({ + IS_IPAD: IS_IPAD, + IS_IPHONE: IS_IPHONE, + IS_IPOD: IS_IPOD, + IS_IOS: IS_IOS, + IOS_VERSION: IOS_VERSION, + IS_ANDROID: IS_ANDROID, + ANDROID_VERSION: ANDROID_VERSION, + IS_OLD_ANDROID: IS_OLD_ANDROID, + IS_NATIVE_ANDROID: IS_NATIVE_ANDROID, + IS_FIREFOX: IS_FIREFOX, + IS_EDGE: IS_EDGE, + IS_CHROME: IS_CHROME, + CHROME_VERSION: CHROME_VERSION, + IS_IE8: IS_IE8, + IE_VERSION: IE_VERSION, + IS_SAFARI: IS_SAFARI, + IS_ANY_SAFARI: IS_ANY_SAFARI, + TOUCH_ENABLED: TOUCH_ENABLED, + BACKGROUND_SIZE_SUPPORTED: BACKGROUND_SIZE_SUPPORTED +}); - props = (0, _obj.assign)({ - innerHTML: '', - className: this.buildCSSClass() - }, props); +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; - // Add attributes for button element - attributes = (0, _obj.assign)({ - // Necessary since the default button type is "submit" - 'type': 'button', - // let the screen reader user know that the text of the button may change - 'aria-live': 'polite' - }, attributes); - var el = _component2['default'].prototype.createEl.call(this, tag, props, attributes); - this.createControlTextEl(el); - return el; - }; - /** - * Add a child `Component` inside of this `Button`. - * - * @param {string|Component} child - * The name or instance of a child to add. - * - * @param {Object} [options={}] - * The key/value store of options that will get passed to children of - * the child. - * - * @return {Component} - * The `Component` that gets added as a child. When using a string the - * `Component` will get created by this process. - * - * @deprecated since version 5 - */ - Button.prototype.addChild = function addChild(child) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var className = this.constructor.name; - _log2['default'].warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.'); +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; - // Avoid the error message generated by ClickableComponent's addChild method - return _component2['default'].prototype.addChild.call(this, child, options); - }; - /** - * Enable the `Button` element so that it can be activated or clicked. Use this with - * {@link Button#disable}. - */ - Button.prototype.enable = function enable() { - _ClickableComponent.prototype.enable.call(this); - this.el_.removeAttribute('disabled'); - }; - /** - * Enable the `Button` element so that it cannot be activated or clicked. Use this with - * {@link Button#enable}. - */ - Button.prototype.disable = function disable() { - _ClickableComponent.prototype.disable.call(this); - this.el_.setAttribute('disabled', 'disabled'); - }; - /** - * This gets called when a `Button` has focus and `keydown` is triggered via a key - * press. - * - * @param {EventTarget~Event} event - * The event that caused this function to get called. - * - * @listens keydown - */ - Button.prototype.handleKeyPress = function handleKeyPress(event) { - // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button. - if (event.which === 32 || event.which === 13) { - return; +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; - // Pass keypress handling up for unsupported keys - _ClickableComponent.prototype.handleKeyPress.call(this, event); - }; - return Button; -}(_clickableComponent2['default']); -_component2['default'].registerComponent('Button', Button); -exports['default'] = Button; -},{"3":3,"5":5,"91":91,"93":93}],3:[function(_dereq_,module,exports){ -'use strict'; -exports.__esModule = true; -var _component = _dereq_(5); -var _component2 = _interopRequireDefault(_component); -var _dom = _dereq_(85); -var Dom = _interopRequireWildcard(_dom); -var _events = _dereq_(86); -var Events = _interopRequireWildcard(_events); +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } -var _fn = _dereq_(88); + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; -var Fn = _interopRequireWildcard(_fn); -var _log = _dereq_(91); -var _log2 = _interopRequireDefault(_log); -var _document = _dereq_(99); -var _document2 = _interopRequireDefault(_document); -var _obj = _dereq_(93); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file button.js - */ +var taggedTemplateLiteralLoose = function (strings, raw) { + strings.raw = raw; + return strings; +}; +/** + * @file obj.js + * @module obj + */ /** - * Clickable Component which is clickable or keyboard actionable, - * but is not a native HTML button. + * @callback obj:EachCallback * - * @extends Component + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over */ -var ClickableComponent = function (_Component) { - _inherits(ClickableComponent, _Component); - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function ClickableComponent(player, options) { - _classCallCheck(this, ClickableComponent); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); +/** + * @callback obj:ReduceCallback + * + * @param {Mixed} accum + * The value that is accumulating over the reduce loop. + * + * @param {Mixed} value + * The current key for the object that is being iterated over. + * + * @param {string} key + * The current key-value for object that is being iterated over + * + * @return {Mixed} + * The new accumulated value. + */ +var toString = Object.prototype.toString; - _this.emitTapEvents(); +/** + * Get the keys of an Object + * + * @param {Object} + * The Object to get the keys from + * + * @return {string[]} + * An array of the keys from the object. Returns an empty array if the + * object passed in was invalid or had no keys. + * + * @private + */ +var keys = function keys(object) { + return isObject(object) ? Object.keys(object) : []; +}; - _this.enable(); - return _this; - } +/** + * Array-like iteration for objects. + * + * @param {Object} object + * The object to iterate over + * + * @param {obj:EachCallback} fn + * The callback function which is called for each key in the object. + */ +function each(object, fn) { + keys(object).forEach(function (key) { + return fn(object[key], key); + }); +} - /** - * Create the `Component`s DOM element. - * - * @param {string} [tag=div] - * The element's node type. - * - * @param {Object} [props={}] - * An object of properties that should be set on the element. - * - * @param {Object} [attributes={}] - * An object of attributes that should be set on the element. - * - * @return {Element} - * The element that gets created. - */ +/** + * Array-like reduce for objects. + * + * @param {Object} object + * The Object that you want to reduce. + * + * @param {Function} fn + * A callback function which is called for each key in the object. It + * receives the accumulated value and the per-iteration value and key + * as arguments. + * + * @param {Mixed} [initial = 0] + * Starting value + * + * @return {Mixed} + * The final accumulated value. + */ +function reduce(object, fn) { + var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; + return keys(object).reduce(function (accum, key) { + return fn(accum, object[key], key); + }, initial); +} - ClickableComponent.prototype.createEl = function createEl() { - var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; - var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; +/** + * Object.assign-style object shallow merge/extend. + * + * @param {Object} target + * @param {Object} ...sources + * @return {Object} + */ +function assign(target) { + for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + sources[_key - 1] = arguments[_key]; + } - props = (0, _obj.assign)({ - innerHTML: '', - className: this.buildCSSClass(), - tabIndex: 0 - }, props); + if (Object.assign) { + return Object.assign.apply(Object, [target].concat(sources)); + } - if (tag === 'button') { - _log2['default'].error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.'); + sources.forEach(function (source) { + if (!source) { + return; } - // Add ARIA attributes for clickable element which is not a native HTML button - attributes = (0, _obj.assign)({ - 'role': 'button', + each(source, function (value, key) { + target[key] = value; + }); + }); - // let the screen reader user know that the text of the element may change - 'aria-live': 'polite' - }, attributes); + return target; +} - this.tabIndex_ = props.tabIndex; +/** + * Returns whether a value is an object of any kind - including DOM nodes, + * arrays, regular expressions, etc. Not functions, though. + * + * This avoids the gotcha where using `typeof` on a `null` value + * results in `'object'`. + * + * @param {Object} value + * @return {Boolean} + */ +function isObject(value) { + return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object'; +} - var el = _Component.prototype.createEl.call(this, tag, props, attributes); +/** + * Returns whether an object appears to be a "plain" object - that is, a + * direct instance of `Object`. + * + * @param {Object} value + * @return {Boolean} + */ +function isPlain(value) { + return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object; +} - this.createControlTextEl(el); +/** + * @file log.js + * @module log + */ +var log = void 0; - return el; - }; +// This is the private tracking variable for logging level. +var level = 'all'; - /** - * Create a control text element on this `Component` - * - * @param {Element} [el] - * Parent element for the control text. - * - * @return {Element} - * The control text element that gets created. - */ +// This is the private tracking variable for the logging history. +var history = []; +/** + * Log messages to the console and history based on the type of message + * + * @private + * @param {string} type + * The name of the console method to use. + * + * @param {Array} args + * The arguments to be passed to the matching console method. + * + * @param {boolean} [stringify] + * By default, only old IEs should get console argument stringification, + * but this is exposed as a parameter to facilitate testing. + */ +var logByType = function logByType(type, args) { + var stringify = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : !!IE_VERSION && IE_VERSION < 11; - ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) { - this.controlTextEl_ = Dom.createEl('span', { - className: 'vjs-control-text' - }); + var lvl = log.levels[level]; + var lvlRegExp = new RegExp('^(' + lvl + ')$'); - if (el) { - el.appendChild(this.controlTextEl_); - } + if (type !== 'log') { - this.controlText(this.controlText_, el); + // Add the type to the front of the message when it's not "log". + args.unshift(type.toUpperCase() + ':'); + } - return this.controlTextEl_; - }; + // Add a clone of the args at this point to history. + if (history) { + history.push([].concat(args)); + } - /** - * Get or set the localize text to use for the controls on the `Component`. - * - * @param {string} [text] - * Control text for element. - * - * @param {Element} [el=this.el()] - * Element to set the title on. - * - * @return {string} - * - The control text when getting - */ + // Add console prefix after adding to history. + args.unshift('VIDEOJS:'); + // If there's no console then don't try to output messages, but they will + // still be stored in history. + // + // Was setting these once outside of this function, but containing them + // in the function makes it easier to test cases where console doesn't exist + // when the module is executed. + var fn = window_1.console && window_1.console[type]; - ClickableComponent.prototype.controlText = function controlText(text) { - var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el(); + // Bail out if there's no console or if this type is not allowed by the + // current logging level. + if (!fn || !lvl || !lvlRegExp.test(type)) { + return; + } - if (!text) { - return this.controlText_ || 'Need Text'; - } + // IEs previous to 11 log objects uselessly as "[object Object]"; so, JSONify + // objects and arrays for those less-capable browsers. + if (stringify) { + args = args.map(function (a) { + if (isObject(a) || Array.isArray(a)) { + try { + return JSON.stringify(a); + } catch (x) { + return String(a); + } + } - var localizedText = this.localize(text); + // Cast to string before joining, so we get null and undefined explicitly + // included in output (as we would in a modern console). + return String(a); + }).join(' '); + } - this.controlText_ = text; - this.controlTextEl_.innerHTML = localizedText; - if (!this.nonIconControl) { - // Set title attribute if only an icon is shown - el.setAttribute('title', localizedText); - } - }; + // Old IE versions do not allow .apply() for console methods (they are + // reported as objects rather than functions). + if (!fn.apply) { + fn(args); + } else { + fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args); + } +}; - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ +/** + * Logs plain debug messages. Similar to `console.log`. + * + * @class + * @param {Mixed[]} args + * One or more messages or objects that should be logged. + */ +log = function log() { + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + logByType('log', args); +}; - ClickableComponent.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this); - }; +/** + * Enumeration of available logging levels, where the keys are the level names + * and the values are `|`-separated strings containing logging methods allowed + * in that logging level. These strings are used to create a regular expression + * matching the function name being called. + * + * Levels provided by video.js are: + * + * - `off`: Matches no calls. Any value that can be cast to `false` will have + * this effect. The most restrictive. + * - `all` (default): Matches only Video.js-provided functions (`log`, + * `log.warn`, and `log.error`). + * - `warn`: Matches `log.warn` and `log.error` calls. + * - `error`: Matches only `log.error` calls. + * + * @type {Object} + */ +log.levels = { + all: 'log|warn|error', + error: 'error', + off: '', + warn: 'warn|error', + DEFAULT: level +}; - /** - * Enable this `Component`s element. - */ +/** + * Get or set the current logging level. If a string matching a key from + * {@link log.levels} is provided, acts as a setter. Regardless of argument, + * returns the current logging level. + * + * @param {string} [lvl] + * Pass to set a new logging level. + * + * @return {string} + * The current logging level. + */ +log.level = function (lvl) { + if (typeof lvl === 'string') { + if (!log.levels.hasOwnProperty(lvl)) { + throw new Error('"' + lvl + '" in not a valid log level'); + } + level = lvl; + } + return level; +}; +/** + * Returns an array containing everything that has been logged to the history. + * + * This array is a shallow clone of the internal history record. However, its + * contents are _not_ cloned; so, mutating objects inside this array will + * mutate them in history. + * + * @return {Array} + */ +log.history = function () { + return history ? [].concat(history) : []; +}; - ClickableComponent.prototype.enable = function enable() { - if (!this.enabled_) { - this.enabled_ = true; - this.removeClass('vjs-disabled'); - this.el_.setAttribute('aria-disabled', 'false'); - if (typeof this.tabIndex_ !== 'undefined') { - this.el_.setAttribute('tabIndex', this.tabIndex_); - } - this.on(['tap', 'click'], this.handleClick); - this.on('focus', this.handleFocus); - this.on('blur', this.handleBlur); - } - }; +/** + * Clears the internal history tracking, but does not prevent further history + * tracking. + */ +log.history.clear = function () { + if (history) { + history.length = 0; + } +}; - /** - * Disable this `Component`s element. - */ +/** + * Disable history tracking if it is currently enabled. + */ +log.history.disable = function () { + if (history !== null) { + history.length = 0; + history = null; + } +}; +/** + * Enable history tracking if it is currently disabled. + */ +log.history.enable = function () { + if (history === null) { + history = []; + } +}; - ClickableComponent.prototype.disable = function disable() { - this.enabled_ = false; - this.addClass('vjs-disabled'); - this.el_.setAttribute('aria-disabled', 'true'); - if (typeof this.tabIndex_ !== 'undefined') { - this.el_.removeAttribute('tabIndex'); - } - this.off(['tap', 'click'], this.handleClick); - this.off('focus', this.handleFocus); - this.off('blur', this.handleBlur); - }; +/** + * Logs error messages. Similar to `console.error`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as an error + */ +log.error = function () { + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } - /** - * This gets called when a `ClickableComponent` gets: - * - Clicked (via the `click` event, listening starts in the constructor) - * - Tapped (via the `tap` event, listening starts in the constructor) - * - The following things happen in order: - * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the - * `ClickableComponent`. - * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using - * {@link ClickableComponent#handleKeyPress}. - * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses - * the space or enter key. - * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown` - * event as a parameter. - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - * @abstract - */ + return logByType('error', args); +}; +/** + * Logs warning messages. Similar to `console.warn`. + * + * @param {Mixed[]} args + * One or more messages or objects that should be logged as a warning. + */ +log.warn = function () { + for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + args[_key3] = arguments[_key3]; + } - ClickableComponent.prototype.handleClick = function handleClick(event) {}; + return logByType('warn', args); +}; - /** - * This gets called when a `ClickableComponent` gains focus via a `focus` event. - * Turns on listening for `keydown` events. When they happen it - * calls `this.handleKeyPress`. - * - * @param {EventTarget~Event} event - * The `focus` event that caused this function to be called. - * - * @listens focus - */ +var log$1 = log; +function clean (s) { + return s.replace(/\n\r?\s*/g, '') +} - ClickableComponent.prototype.handleFocus = function handleFocus(event) { - Events.on(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); - }; - /** - * Called when this ClickableComponent has focus and a key gets pressed down. By - * default it will call `this.handleClick` when the key is space or enter. - * - * @param {EventTarget~Event} event - * The `keydown` event that caused this function to be called. - * - * @listens keydown - */ +var tsml = function tsml (sa) { + var s = '' + , i = 0; + for (; i < arguments.length; i++) + s += clean(sa[i]) + (arguments[i + 1] || ''); - ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) { + return s +}; - // Support Space (32) or Enter (13) key operation to fire a click event - if (event.which === 32 || event.which === 13) { - event.preventDefault(); - this.trigger('click'); - } else if (_Component.prototype.handleKeyPress) { +/** + * @file computed-style.js + * @module computed-style + */ +/** + * A safe getComputedStyle with an IE8 fallback. + * + * This is needed because in Firefox, if the player is loaded in an iframe with + * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to + * make sure that the player doesn't break in these cases. + * + * @param {Element} el + * The element you want the computed style of + * + * @param {string} prop + * The property name you want + * + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + * + * @static + * @const + */ +function computedStyle(el, prop) { + if (!el || !prop) { + return ''; + } - // Pass keypress handling up for unsupported keys - _Component.prototype.handleKeyPress.call(this, event); - } - }; + if (typeof window_1.getComputedStyle === 'function') { + var cs = window_1.getComputedStyle(el); - /** - * Called when a `ClickableComponent` loses focus. Turns off the listener for - * `keydown` events. Which Stops `this.handleKeyPress` from getting called. - * - * @param {EventTarget~Event} event - * The `blur` event that caused this function to be called. - * - * @listens blur - */ - - - ClickableComponent.prototype.handleBlur = function handleBlur(event) { - Events.off(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); - }; - - return ClickableComponent; -}(_component2['default']); - -_component2['default'].registerComponent('ClickableComponent', ClickableComponent); -exports['default'] = ClickableComponent; - -},{"5":5,"85":85,"86":86,"88":88,"91":91,"93":93,"99":99}],4:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _button = _dereq_(2); - -var _button2 = _interopRequireDefault(_button); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + return cs ? cs[prop] : ''; + } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file close-button.js - */ + return el.currentStyle[prop] || ''; +} +var _templateObject = taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']); /** - * The `CloseButton` is a `{@link Button}` that fires a `close` event when - * it gets clicked. + * @file dom.js + * @module dom + */ +/** + * Detect if a value is a string with any non-whitespace characters. + * + * @param {string} str + * The string to check + * + * @return {boolean} + * - True if the string is non-blank + * - False otherwise * - * @extends Button */ -var CloseButton = function (_Button) { - _inherits(CloseButton, _Button); - - /** - * Creates an instance of the this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function CloseButton(player, options) { - _classCallCheck(this, CloseButton); - - var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); +function isNonBlankString(str) { + return typeof str === 'string' && /\S/.test(str); +} - _this.controlText(options && options.controlText || _this.localize('Close')); - return _this; +/** + * Throws an error if the passed string has whitespace. This is used by + * class methods to be relatively consistent with the classList API. + * + * @param {string} str + * The string to check for whitespace. + * + * @throws {Error} + * Throws an error if there is whitespace in the string. + * + */ +function throwIfWhitespace(str) { + if (/\s/.test(str)) { + throw new Error('class has illegal whitespace characters'); } +} - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - CloseButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this); - }; - - /** - * This gets called when a `CloseButton` gets clicked. See - * {@link ClickableComponent#handleClick} for more information on when this will be - * triggered - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - * @fires CloseButton#close - */ - - - CloseButton.prototype.handleClick = function handleClick(event) { - - /** - * Triggered when the a `CloseButton` is clicked. - * - * @event CloseButton#close - * @type {EventTarget~Event} - * - * @property {boolean} [bubbles=false] - * set to false so that the close event does not - * bubble up to parents if there is no listener - */ - this.trigger({ type: 'close', bubbles: false }); - }; - - return CloseButton; -}(_button2['default']); - -_component2['default'].registerComponent('CloseButton', CloseButton); -exports['default'] = CloseButton; - -},{"2":2,"5":5}],5:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _window = _dereq_(100); - -var _window2 = _interopRequireDefault(_window); - -var _evented = _dereq_(53); - -var _evented2 = _interopRequireDefault(_evented); - -var _stateful = _dereq_(54); - -var _stateful2 = _interopRequireDefault(_stateful); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _domData = _dereq_(84); +/** + * Produce a regular expression for matching a className within an elements className. + * + * @param {string} className + * The className to generate the RegExp for. + * + * @return {RegExp} + * The RegExp that will check for a specific `className` in an elements + * className. + */ +function classRegExp(className) { + return new RegExp('(^|\\s)' + className + '($|\\s)'); +} -var DomData = _interopRequireWildcard(_domData); +/** + * Whether the current DOM interface appears to be real. + * + * @return {Boolean} + */ +function isReal() { + return ( -var _fn = _dereq_(88); + // Both document and window will never be undefined thanks to `global`. + document_1 === window_1.document && -var Fn = _interopRequireWildcard(_fn); + // In IE < 9, DOM methods return "object" as their type, so all we can + // confidently check is that it exists. + typeof document_1.createElement !== 'undefined' + ); +} -var _guid = _dereq_(90); +/** + * Determines, via duck typing, whether or not a value is a DOM element. + * + * @param {Mixed} value + * The thing to check + * + * @return {boolean} + * - True if it is a DOM element + * - False otherwise + */ +function isEl(value) { + return isObject(value) && value.nodeType === 1; +} -var Guid = _interopRequireWildcard(_guid); +/** + * Creates functions to query the DOM using a given method. + * + * @param {string} method + * The method to create the query with. + * + * @return {Function} + * The query method + */ +function createQuerier(method) { + return function (selector, context) { + if (!isNonBlankString(selector)) { + return document_1[method](null); + } + if (isNonBlankString(context)) { + context = document_1.querySelector(context); + } -var _log = _dereq_(91); + var ctx = isEl(context) ? context : document_1; -var _log2 = _interopRequireDefault(_log); + return ctx[method] && ctx[method](selector); + }; +} -var _toTitleCase = _dereq_(96); +/** + * Creates an element and applies properties. + * + * @param {string} [tagName='div'] + * Name of tag to be created. + * + * @param {Object} [properties={}] + * Element properties to be applied. + * + * @param {Object} [attributes={}] + * Element attributes to be applied. + * + * @param {String|Element|TextNode|Array|Function} [content] + * Contents for the element (see: {@link dom:normalizeContent}) + * + * @return {Element} + * The element that was created. + */ +function createEl() { + var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div'; + var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var content = arguments[3]; -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + var el = document_1.createElement(tagName); -var _mergeOptions = _dereq_(92); + Object.getOwnPropertyNames(properties).forEach(function (propName) { + var val = properties[propName]; -var _mergeOptions2 = _interopRequireDefault(_mergeOptions); + // See #2176 + // We originally were accepting both properties and attributes in the + // same object, but that doesn't work so well. + if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') { + log$1.warn(tsml(_templateObject, propName, val)); + el.setAttribute(propName, val); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + // Handle textContent since it's not supported everywhere and we have a + // method for it. + } else if (propName === 'textContent') { + textContent(el, val); + } else { + el[propName] = val; + } + }); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + el.setAttribute(attrName, attributes[attrName]); + }); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** - * Player Component - Base class for all UI objects - * - * @file component.js - */ + if (content) { + appendContent(el, content); + } + return el; +} /** - * Base class for all UI Components. - * Components are UI objects which represent both a javascript object and an element - * in the DOM. They can be children of other components, and can have - * children themselves. + * Injects text into an element, replacing any existing contents entirely. * - * Components can also use methods from {@link EventTarget} + * @param {Element} el + * The element to add text content into + * + * @param {string} text + * The text content to add. + * + * @return {Element} + * The element with added text content. */ -var Component = function () { +function textContent(el, text) { + if (typeof el.textContent === 'undefined') { + el.innerText = text; + } else { + el.textContent = text; + } + return el; +} - /** - * A callback that is called when a component is ready. Does not have any - * paramters and any callback value will be ignored. - * - * @callback Component~ReadyCallback - * @this Component - */ +/** + * Insert an element as the first child node of another + * + * @param {Element} child + * Element to insert + * + * @param {Element} parent + * Element to insert child into + */ +function prependTo(child, parent) { + if (parent.firstChild) { + parent.insertBefore(child, parent.firstChild); + } else { + parent.appendChild(child); + } +} - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - * - * @param {Object[]} [options.children] - * An array of children objects to intialize this component with. Children objects have - * a name property that will be used if more than one component of the same type needs to be - * added. - * - * @param {Component~ReadyCallback} [ready] - * Function that gets called when the `Component` is ready. - */ - function Component(player, options, ready) { - _classCallCheck(this, Component); +/** + * Check if an element has a CSS class + * + * @param {Element} element + * Element to check + * + * @param {string} classToCheck + * Class name to check for + * + * @return {boolean} + * - True if the element had the class + * - False otherwise. + * + * @throws {Error} + * Throws an error if `classToCheck` has white space. + */ +function hasClass(element, classToCheck) { + throwIfWhitespace(classToCheck); + if (element.classList) { + return element.classList.contains(classToCheck); + } + return classRegExp(classToCheck).test(element.className); +} - // The component might be the player itself and we can't pass `this` to super - if (!player && this.play) { - this.player_ = player = this; // eslint-disable-line - } else { - this.player_ = player; - } +/** + * Add a CSS class name to an element + * + * @param {Element} element + * Element to add class name to. + * + * @param {string} classToAdd + * Class name to add. + * + * @return {Element} + * The dom element with the added class name. + */ +function addClass(element, classToAdd) { + if (element.classList) { + element.classList.add(classToAdd); - // Make a copy of prototype.options_ to protect against overriding defaults - this.options_ = (0, _mergeOptions2['default'])({}, this.options_); + // Don't need to `throwIfWhitespace` here because `hasElClass` will do it + // in the case of classList not being supported. + } else if (!hasClass(element, classToAdd)) { + element.className = (element.className + ' ' + classToAdd).trim(); + } - // Updated options with supplied options - options = this.options_ = (0, _mergeOptions2['default'])(this.options_, options); + return element; +} - // Get ID from options or options element if one is supplied - this.id_ = options.id || options.el && options.el.id; +/** + * Remove a CSS class name from an element + * + * @param {Element} element + * Element to remove a class name from. + * + * @param {string} classToRemove + * Class name to remove + * + * @return {Element} + * The dom element with class name removed. + */ +function removeClass(element, classToRemove) { + if (element.classList) { + element.classList.remove(classToRemove); + } else { + throwIfWhitespace(classToRemove); + element.className = element.className.split(/\s+/).filter(function (c) { + return c !== classToRemove; + }).join(' '); + } - // If there was no ID from the options, generate one - if (!this.id_) { - // Don't require the player ID function in the case of mock players - var id = player && player.id && player.id() || 'no_player'; + return element; +} - this.id_ = id + '_component_' + Guid.newGUID(); - } +/** + * The callback definition for toggleElClass. + * + * @callback Dom~PredicateCallback + * @param {Element} element + * The DOM element of the Component. + * + * @param {string} classToToggle + * The `className` that wants to be toggled + * + * @return {boolean|undefined} + * - If true the `classToToggle` will get added to `element`. + * - If false the `classToToggle` will get removed from `element`. + * - If undefined this callback will be ignored + */ - this.name_ = options.name || null; +/** + * Adds or removes a CSS class name on an element depending on an optional + * condition or the presence/absence of the class name. + * + * @param {Element} element + * The element to toggle a class name on. + * + * @param {string} classToToggle + * The class that should be toggled + * + * @param {boolean|PredicateCallback} [predicate] + * See the return value for {@link Dom~PredicateCallback} + * + * @return {Element} + * The element with a class that has been toggled. + */ +function toggleClass(element, classToToggle, predicate) { - // Create element if one wasn't provided in options - if (options.el) { - this.el_ = options.el; - } else if (options.createEl !== false) { - this.el_ = this.createEl(); - } + // This CANNOT use `classList` internally because IE does not support the + // second parameter to the `classList.toggle()` method! Which is fine because + // `classList` will be used by the add/remove functions. + var has = hasClass(element, classToToggle); - // Make this an evented object and use `el_`, if available, as its event bus - (0, _evented2['default'])(this, { eventBusKey: this.el_ ? 'el_' : null }); - (0, _stateful2['default'])(this, this.constructor.defaultState); + if (typeof predicate === 'function') { + predicate = predicate(element, classToToggle); + } - this.children_ = []; - this.childIndex_ = {}; - this.childNameIndex_ = {}; + if (typeof predicate !== 'boolean') { + predicate = !has; + } - // Add any child components in options - if (options.initChildren !== false) { - this.initChildren(); - } + // If the necessary class operation matches the current state of the + // element, no action is required. + if (predicate === has) { + return; + } - this.ready(ready); - // Don't want to trigger ready here or it will before init is actually - // finished for all children that run this constructor + if (predicate) { + addClass(element, classToToggle); + } else { + removeClass(element, classToToggle); + } - if (options.reportTouchActivity !== false) { - this.enableTouchActivity(); + return element; +} + +/** + * Apply attributes to an HTML element. + * + * @param {Element} el + * Element to add attributes to. + * + * @param {Object} [attributes] + * Attributes to be applied. + */ +function setAttributes(el, attributes) { + Object.getOwnPropertyNames(attributes).forEach(function (attrName) { + var attrValue = attributes[attrName]; + + if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) { + el.removeAttribute(attrName); + } else { + el.setAttribute(attrName, attrValue === true ? '' : attrValue); } - } + }); +} - /** - * Dispose of the `Component` and all child components. - * - * @fires Component#dispose - */ +/** + * Get an element's attribute values, as defined on the HTML tag + * Attributes are not the same as properties. They're defined on the tag + * or with setAttribute (which shouldn't be used with HTML) + * This will return true or false for boolean attributes. + * + * @param {Element} tag + * Element from which to get tag attributes. + * + * @return {Object} + * All attributes of the element. + */ +function getAttributes(tag) { + var obj = {}; + // known boolean attributes + // we can check for matching boolean properties, but older browsers + // won't know about HTML5 boolean attributes that we still read from + var knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ','; - Component.prototype.dispose = function dispose() { + if (tag && tag.attributes && tag.attributes.length > 0) { + var attrs = tag.attributes; - /** - * Triggered when a `Component` is disposed. - * - * @event Component#dispose - * @type {EventTarget~Event} - * - * @property {boolean} [bubbles=false] - * set to false so that the close event does not - * bubble up - */ - this.trigger({ type: 'dispose', bubbles: false }); + for (var i = attrs.length - 1; i >= 0; i--) { + var attrName = attrs[i].name; + var attrVal = attrs[i].value; - // Dispose all children. - if (this.children_) { - for (var i = this.children_.length - 1; i >= 0; i--) { - if (this.children_[i].dispose) { - this.children_[i].dispose(); - } + // check for known booleans + // the matching element property will return a value for typeof + if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) { + // the value of an included boolean attribute is typically an empty + // string ('') which would equal false if we just check for a false value. + // we also don't want support bad code like autoplay='false' + attrVal = attrVal !== null ? true : false; } - } - // Delete child references - this.children_ = null; - this.childIndex_ = null; - this.childNameIndex_ = null; + obj[attrName] = attrVal; + } + } - if (this.el_) { - // Remove element from DOM - if (this.el_.parentNode) { - this.el_.parentNode.removeChild(this.el_); - } + return obj; +} - DomData.removeData(this.el_); - this.el_ = null; - } - }; +/** + * Get the value of an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to get the value of + * + * @return {string} + * value of the attribute + */ +function getAttribute(el, attribute) { + return el.getAttribute(attribute); +} - /** - * Return the {@link Player} that the `Component` has attached to. - * - * @return {Player} - * The player that this `Component` has attached to. - */ +/** + * Set the value of an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to set + * + * @param {string} value + * Value to set the attribute to + */ +function setAttribute(el, attribute, value) { + el.setAttribute(attribute, value); +} +/** + * Remove an element's attribute + * + * @param {Element} el + * A DOM element + * + * @param {string} attribute + * Attribute to remove + */ +function removeAttribute(el, attribute) { + el.removeAttribute(attribute); +} - Component.prototype.player = function player() { - return this.player_; +/** + * Attempt to block the ability to select text while dragging controls + */ +function blockTextSelection() { + document_1.body.focus(); + document_1.onselectstart = function () { + return false; }; +} - /** - * Deep merge of options objects with new options. - * > Note: When both `obj` and `options` contain properties whose values are objects. - * The two properties get merged using {@link module:mergeOptions} - * - * @param {Object} obj - * The object that contains new options. - * - * @return {Object} - * A new object of `this.options_` and `obj` merged together. - * - * @deprecated since version 5 - */ +/** + * Turn off text selection blocking + */ +function unblockTextSelection() { + document_1.onselectstart = function () { + return true; + }; +} +/** + * Identical to the native `getBoundingClientRect` function, but ensures that + * the method is supported at all (it is in all browsers we claim to support) + * and that the element is in the DOM before continuing. + * + * This wrapper function also shims properties which are not provided by some + * older browsers (namely, IE8). + * + * Additionally, some browsers do not support adding properties to a + * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard + * properties (except `x` and `y` which are not widely supported). This helps + * avoid implementations where keys are non-enumerable. + * + * @param {Element} el + * Element whose `ClientRect` we want to calculate. + * + * @return {Object|undefined} + * Always returns a plain + */ +function getBoundingClientRect(el) { + if (el && el.getBoundingClientRect && el.parentNode) { + var rect = el.getBoundingClientRect(); + var result = {}; - Component.prototype.options = function options(obj) { - _log2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); + ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) { + if (rect[k] !== undefined) { + result[k] = rect[k]; + } + }); - if (!obj) { - return this.options_; + if (!result.height) { + result.height = parseFloat(computedStyle(el, 'height')); } - this.options_ = (0, _mergeOptions2['default'])(this.options_, obj); - return this.options_; - }; + if (!result.width) { + result.width = parseFloat(computedStyle(el, 'width')); + } - /** - * Get the `Component`s DOM element - * - * @return {Element} - * The DOM element for this `Component`. - */ + return result; + } +} +/** + * The postion of a DOM element on the page. + * + * @typedef {Object} module:dom~Position + * + * @property {number} left + * Pixels to the left + * + * @property {number} top + * Pixels on top + */ - Component.prototype.el = function el() { - return this.el_; - }; +/** + * Offset Left. + * getBoundingClientRect technique from + * John Resig + * + * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/ + * + * @param {Element} el + * Element from which to get offset + * + * @return {module:dom~Position} + * The position of the element that was passed in. + */ +function findPosition(el) { + var box = void 0; - /** - * Create the `Component`s DOM element. - * - * @param {string} [tagName] - * Element's DOM node type. e.g. 'div' - * - * @param {Object} [properties] - * An object of properties that should be set. - * - * @param {Object} [attributes] - * An object of attributes that should be set. - * - * @return {Element} - * The element that gets created. - */ + if (el.getBoundingClientRect && el.parentNode) { + box = el.getBoundingClientRect(); + } + if (!box) { + return { + left: 0, + top: 0 + }; + } - Component.prototype.createEl = function createEl(tagName, properties, attributes) { - return Dom.createEl(tagName, properties, attributes); - }; + var docEl = document_1.documentElement; + var body = document_1.body; - /** - * Localize a string given the string in english. - * - * If tokens are provided, it'll try and run a simple token replacement on the provided string. - * The tokens it loooks for look like `{1}` with the index being 1-indexed into the tokens array. - * - * If a `defaultValue` is provided, it'll use that over `string`, - * if a value isn't found in provided language files. - * This is useful if you want to have a descriptive key for token replacement - * but have a succinct localized string and not require `en.json` to be included. - * - * Currently, it is used for the progress bar timing. - * ```js - * { - * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" - * } - * ``` - * It is then used like so: - * ```js - * this.localize('progress bar timing: currentTime={1} duration{2}', - * [this.player_.currentTime(), this.player_.duration()], - * '{1} of {2}'); - * ``` - * - * Which outputs something like: `01:23 of 24:56`. - * - * - * @param {string} string - * The string to localize and the key to lookup in the language files. - * @param {string[]} [tokens] - * If the current item has token replacements, provide the tokens here. - * @param {string} [defaultValue] - * Defaults to `string`. Can be a default value to use for token replacement - * if the lookup key is needed to be separate. - * - * @return {string} - * The localized string or if no localization exists the english string. - */ + var clientLeft = docEl.clientLeft || body.clientLeft || 0; + var scrollLeft = window_1.pageXOffset || body.scrollLeft; + var left = box.left + scrollLeft - clientLeft; + var clientTop = docEl.clientTop || body.clientTop || 0; + var scrollTop = window_1.pageYOffset || body.scrollTop; + var top = box.top + scrollTop - clientTop; - Component.prototype.localize = function localize(string, tokens) { - var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; + // Android sometimes returns slightly off decimal values, so need to round + return { + left: Math.round(left), + top: Math.round(top) + }; +} - var code = this.player_.language && this.player_.language(); - var languages = this.player_.languages && this.player_.languages(); - var language = languages && languages[code]; - var primaryCode = code && code.split('-')[0]; - var primaryLang = languages && languages[primaryCode]; +/** + * x and y coordinates for a dom element or mouse pointer + * + * @typedef {Object} Dom~Coordinates + * + * @property {number} x + * x coordinate in pixels + * + * @property {number} y + * y coordinate in pixels + */ - var localizedString = defaultValue; +/** + * Get pointer position in element + * Returns an object with x and y coordinates. + * The base on the coordinates are the bottom left of the element. + * + * @param {Element} el + * Element on which to get the pointer position on + * + * @param {EventTarget~Event} event + * Event object + * + * @return {Dom~Coordinates} + * A Coordinates object corresponding to the mouse position. + * + */ +function getPointerPosition(el, event) { + var position = {}; + var box = findPosition(el); + var boxW = el.offsetWidth; + var boxH = el.offsetHeight; - if (language && language[string]) { - localizedString = language[string]; - } else if (primaryLang && primaryLang[string]) { - localizedString = primaryLang[string]; - } + var boxY = box.top; + var boxX = box.left; + var pageY = event.pageY; + var pageX = event.pageX; - if (tokens) { - localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { - var value = tokens[index - 1]; - var ret = value; + if (event.changedTouches) { + pageX = event.changedTouches[0].pageX; + pageY = event.changedTouches[0].pageY; + } - if (typeof value === 'undefined') { - ret = match; - } + position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH)); + position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW)); - return ret; - }); - } + return position; +} - return localizedString; - }; +/** + * Determines, via duck typing, whether or not a value is a text node. + * + * @param {Mixed} value + * Check if this value is a text node. + * + * @return {boolean} + * - True if it is a text node + * - False otherwise + */ +function isTextNode(value) { + return isObject(value) && value.nodeType === 3; +} - /** - * Return the `Component`s DOM element. This is where children get inserted. - * This will usually be the the same as the element returned in {@link Component#el}. - * - * @return {Element} - * The content element for this `Component`. - */ +/** + * Empties the contents of an element. + * + * @param {Element} el + * The element to empty children from + * + * @return {Element} + * The element with no children + */ +function emptyEl(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } + return el; +} +/** + * Normalizes content for eventual insertion into the DOM. + * + * This allows a wide range of content definition methods, but protects + * from falling into the trap of simply writing to `innerHTML`, which is + * an XSS concern. + * + * The content for an element can be passed in multiple types and + * combinations, whose behavior is as follows: + * + * @param {String|Element|TextNode|Array|Function} content + * - String: Normalized into a text node. + * - Element/TextNode: Passed through. + * - Array: A one-dimensional array of strings, elements, nodes, or functions + * (which return single strings, elements, or nodes). + * - Function: If the sole argument, is expected to produce a string, element, + * node, or array as defined above. + * + * @return {Array} + * All of the content that was passed in normalized. + */ +function normalizeContent(content) { - Component.prototype.contentEl = function contentEl() { - return this.contentEl_ || this.el_; - }; + // First, invoke content if it is a function. If it produces an array, + // that needs to happen before normalization. + if (typeof content === 'function') { + content = content(); + } - /** - * Get this `Component`s ID - * - * @return {string} - * The id of this `Component` - */ + // Next up, normalize to an array, so one or many items can be normalized, + // filtered, and returned. + return (Array.isArray(content) ? content : [content]).map(function (value) { + // First, invoke value if it is a function to produce a new value, + // which will be subsequently normalized to a Node of some kind. + if (typeof value === 'function') { + value = value(); + } - Component.prototype.id = function id() { - return this.id_; - }; + if (isEl(value) || isTextNode(value)) { + return value; + } - /** - * Get the `Component`s name. The name gets used to reference the `Component` - * and is set during registration. - * - * @return {string} - * The name of this `Component`. - */ + if (typeof value === 'string' && /\S/.test(value)) { + return document_1.createTextNode(value); + } + }).filter(function (value) { + return value; + }); +} +/** + * Normalizes and appends content to an element. + * + * @param {Element} el + * Element to append normalized content to. + * + * + * @param {String|Element|TextNode|Array|Function} content + * See the `content` argument of {@link dom:normalizeContent} + * + * @return {Element} + * The element with appended normalized content. + */ +function appendContent(el, content) { + normalizeContent(content).forEach(function (node) { + return el.appendChild(node); + }); + return el; +} - Component.prototype.name = function name() { - return this.name_; - }; +/** + * Normalizes and inserts content into an element; this is identical to + * `appendContent()`, except it empties the element first. + * + * @param {Element} el + * Element to insert normalized content into. + * + * @param {String|Element|TextNode|Array|Function} content + * See the `content` argument of {@link dom:normalizeContent} + * + * @return {Element} + * The element with inserted normalized content. + * + */ +function insertContent(el, content) { + return appendContent(emptyEl(el), content); +} - /** - * Get an array of all child components - * - * @return {Array} - * The children - */ +/** + * Finds a single DOM element matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {Element|null} + * The element that was found or null. + */ +var $ = createQuerier('querySelector'); +/** + * Finds a all DOM elements matching `selector` within the optional + * `context` of another DOM element (defaulting to `document`). + * + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|String} [context=document] + * A DOM element within which to query. Can also be a selector + * string in which case the first matching element will be used + * as context. If missing (or no element matches selector), falls + * back to `document`. + * + * @return {NodeList} + * A element list of elements that were found. Will be empty if none were found. + * + */ +var $$ = createQuerier('querySelectorAll'); + + + +var Dom = (Object.freeze || Object)({ + isReal: isReal, + isEl: isEl, + createEl: createEl, + textContent: textContent, + prependTo: prependTo, + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + toggleClass: toggleClass, + setAttributes: setAttributes, + getAttributes: getAttributes, + getAttribute: getAttribute, + setAttribute: setAttribute, + removeAttribute: removeAttribute, + blockTextSelection: blockTextSelection, + unblockTextSelection: unblockTextSelection, + getBoundingClientRect: getBoundingClientRect, + findPosition: findPosition, + getPointerPosition: getPointerPosition, + isTextNode: isTextNode, + emptyEl: emptyEl, + normalizeContent: normalizeContent, + appendContent: appendContent, + insertContent: insertContent, + $: $, + $$: $$ +}); - Component.prototype.children = function children() { - return this.children_; - }; - - /** - * Returns the child `Component` with the given `id`. - * - * @param {string} id - * The id of the child `Component` to get. - * - * @return {Component|undefined} - * The child `Component` with the given `id` or undefined. - */ +/** + * @file guid.js + * @module guid + */ +/** + * Unique ID for an element or function + * @type {Number} + */ +var _guid = 1; - Component.prototype.getChildById = function getChildById(id) { - return this.childIndex_[id]; - }; +/** + * Get a unique auto-incrementing ID by number that has not been returned before. + * + * @return {number} + * A new unique ID. + */ +function newGUID() { + return _guid++; +} - /** - * Returns the child `Component` with the given `name`. - * - * @param {string} name - * The name of the child `Component` to get. - * - * @return {Component|undefined} - * The child `Component` with the given `name` or undefined. - */ +/** + * @file dom-data.js + * @module dom-data + */ +/** + * Element Data Store. + * + * Allows for binding data to an element without putting it directly on the + * element. Ex. Event listeners are stored here. + * (also from jsninja.com, slightly modified and updated for closure compiler) + * + * @type {Object} + * @private + */ +var elData = {}; +/* + * Unique attribute name to store an element's guid in + * + * @type {String} + * @constant + * @private + */ +var elIdAttr = 'vdata' + new Date().getTime(); - Component.prototype.getChild = function getChild(name) { - if (!name) { - return; - } +/** + * Returns the cache object where data for an element is stored + * + * @param {Element} el + * Element to store data for. + * + * @return {Object} + * The cache object for that el that was passed in. + */ +function getData(el) { + var id = el[elIdAttr]; - name = (0, _toTitleCase2['default'])(name); + if (!id) { + id = el[elIdAttr] = newGUID(); + } - return this.childNameIndex_[name]; - }; + if (!elData[id]) { + elData[id] = {}; + } - /** - * Add a child `Component` inside the current `Component`. - * - * - * @param {string|Component} child - * The name or instance of a child to add. - * - * @param {Object} [options={}] - * The key/value store of options that will get passed to children of - * the child. - * - * @param {number} [index=this.children_.length] - * The index to attempt to add a child into. - * - * @return {Component} - * The `Component` that gets added as a child. When using a string the - * `Component` will get created by this process. - */ + return elData[id]; +} +/** + * Returns whether or not an element has cached data + * + * @param {Element} el + * Check if this element has cached data. + * + * @return {boolean} + * - True if the DOM element has cached data. + * - False otherwise. + */ +function hasData(el) { + var id = el[elIdAttr]; - Component.prototype.addChild = function addChild(child) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; + if (!id) { + return false; + } - var component = void 0; - var componentName = void 0; + return !!Object.getOwnPropertyNames(elData[id]).length; +} - // If child is a string, create component with options - if (typeof child === 'string') { - componentName = (0, _toTitleCase2['default'])(child); +/** + * Delete data for the element from the cache and the guid attr from getElementById + * + * @param {Element} el + * Remove cached data for this element. + */ +function removeData(el) { + var id = el[elIdAttr]; - var componentClassName = options.componentClass || componentName; + if (!id) { + return; + } - // Set name through options - options.name = componentName; + // Remove all stored data + delete elData[id]; - // Create a new object & element for this controls set - // If there's no .player_, this is a player - var ComponentClass = Component.getComponent(componentClassName); + // Remove the elIdAttr property from the DOM node + try { + delete el[elIdAttr]; + } catch (e) { + if (el.removeAttribute) { + el.removeAttribute(elIdAttr); + } else { + // IE doesn't appear to support removeAttribute on the document element + el[elIdAttr] = null; + } + } +} - if (!ComponentClass) { - throw new Error('Component ' + componentClassName + ' does not exist'); - } +/** + * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/) + * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible) + * This should work very similarly to jQuery's events, however it's based off the book version which isn't as + * robust as jquery's, so there's probably some differences. + * + * @module events + */ - // data stored directly on the videojs object may be - // misidentified as a component to retain - // backwards-compatibility with 4.x. check to make sure the - // component class can be instantiated. - if (typeof ComponentClass !== 'function') { - return null; - } +/** + * Clean up the listener cache and dispatchers + * + * @param {Element|Object} elem + * Element to clean up + * + * @param {string} type + * Type of event to clean up + */ +function _cleanUpEvents(elem, type) { + var data = getData(elem); - component = new ComponentClass(this.player_ || this, options); + // Remove the events of a particular type if there are none left + if (data.handlers[type].length === 0) { + delete data.handlers[type]; + // data.handlers[type] = null; + // Setting to null was causing an error with data.handlers - // child is a component instance - } else { - component = child; + // Remove the meta-handler from the element + if (elem.removeEventListener) { + elem.removeEventListener(type, data.dispatcher, false); + } else if (elem.detachEvent) { + elem.detachEvent('on' + type, data.dispatcher); } + } - this.children_.splice(index, 0, component); + // Remove the events object if there are no types left + if (Object.getOwnPropertyNames(data.handlers).length <= 0) { + delete data.handlers; + delete data.dispatcher; + delete data.disabled; + } - if (typeof component.id === 'function') { - this.childIndex_[component.id()] = component; - } + // Finally remove the element data if there is no data left + if (Object.getOwnPropertyNames(data).length === 0) { + removeData(elem); + } +} - // If a name wasn't used to create the component, check if we can use the - // name function of the component - componentName = componentName || component.name && (0, _toTitleCase2['default'])(component.name()); +/** + * Loops through an array of event types and calls the requested method for each type. + * + * @param {Function} fn + * The event method we want to use. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} callback + * Event listener. + */ +function _handleMultipleEvents(fn, elem, types, callback) { + types.forEach(function (type) { + // Call the event method for each one of the types + fn(elem, type, callback); + }); +} - if (componentName) { - this.childNameIndex_[componentName] = component; - } +/** + * Fix a native event to have standard property values + * + * @param {Object} event + * Event object to fix. + * + * @return {Object} + * Fixed event object. + */ +function fixEvent(event) { - // Add the UI object's element to the container div (box) - // Having an element is not required - if (typeof component.el === 'function' && component.el()) { - var childNodes = this.contentEl().children; - var refNode = childNodes[index] || null; + function returnTrue() { + return true; + } - this.contentEl().insertBefore(component.el(), refNode); - } + function returnFalse() { + return false; + } - // Return so it can stored on parent object if desired. - return component; - }; - - /** - * Remove a child `Component` from this `Component`s list of children. Also removes - * the child `Component`s element from this `Component`s element. - * - * @param {Component} component - * The child `Component` to remove. - */ + // Test if fixing up is needed + // Used to check if !event.stopPropagation instead of isPropagationStopped + // But native events return true for stopPropagation, but don't have + // other expected methods like isPropagationStopped. Seems to be a problem + // with the Javascript Ninja code. So we're just overriding all events now. + if (!event || !event.isPropagationStopped) { + var old = event || window_1.event; + event = {}; + // Clone the old object so that we can modify the values event = {}; + // IE8 Doesn't like when you mess with native event properties + // Firefox returns false for event.hasOwnProperty('type') and other props + // which makes copying more difficult. + // TODO: Probably best to create a whitelist of event props + for (var key in old) { + // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y + // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation + // and webkitMovementX/Y + if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') { + // Chrome 32+ warns if you try to copy deprecated returnValue, but + // we still want to if preventDefault isn't supported (IE8). + if (!(key === 'returnValue' && old.preventDefault)) { + event[key] = old[key]; + } + } + } - Component.prototype.removeChild = function removeChild(component) { - if (typeof component === 'string') { - component = this.getChild(component); + // The event occurred on this element + if (!event.target) { + event.target = event.srcElement || document_1; } - if (!component || !this.children_) { - return; + // Handle which other element the event is related to + if (!event.relatedTarget) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; } - var childFound = false; + // Stop the default browser action + event.preventDefault = function () { + if (old.preventDefault) { + old.preventDefault(); + } + event.returnValue = false; + old.returnValue = false; + event.defaultPrevented = true; + }; - for (var i = this.children_.length - 1; i >= 0; i--) { - if (this.children_[i] === component) { - childFound = true; - this.children_.splice(i, 1); - break; + event.defaultPrevented = false; + + // Stop the event from bubbling + event.stopPropagation = function () { + if (old.stopPropagation) { + old.stopPropagation(); } - } + event.cancelBubble = true; + old.cancelBubble = true; + event.isPropagationStopped = returnTrue; + }; - if (!childFound) { - return; - } + event.isPropagationStopped = returnFalse; - this.childIndex_[component.id()] = null; - this.childNameIndex_[component.name()] = null; + // Stop the event from bubbling and executing other handlers + event.stopImmediatePropagation = function () { + if (old.stopImmediatePropagation) { + old.stopImmediatePropagation(); + } + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; - var compEl = component.el(); + event.isImmediatePropagationStopped = returnFalse; - if (compEl && compEl.parentNode === this.contentEl()) { - this.contentEl().removeChild(component.el()); - } - }; + // Handle mouse position + if (event.clientX !== null && event.clientX !== undefined) { + var doc = document_1.documentElement; + var body = document_1.body; - /** - * Add and initialize default child `Component`s based upon options. - */ + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + // Handle key presses + event.which = event.charCode || event.keyCode; - Component.prototype.initChildren = function initChildren() { - var _this = this; + // Fix button for mouse clicks: + // 0 == left; 1 == middle; 2 == right + if (event.button !== null && event.button !== undefined) { - var children = this.options_.children; + // The following is disabled because it does not pass videojs-standard + // and... yikes. + /* eslint-disable */ + event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0; + /* eslint-enable */ + } + } - if (children) { - // `this` is `parent` - var parentOptions = this.options_; + // Returns fixed-up instance + return event; +} - var handleAdd = function handleAdd(child) { - var name = child.name; - var opts = child.opts; +/** + * Add an event listener to element + * It stores the handler function in a separate cache object + * and adds a generic handler to the element's event, + * along with a unique id (guid) to the element. + * + * @param {Element|Object} elem + * Element or object to bind listeners to + * + * @param {string|string[]} type + * Type of event to bind to. + * + * @param {EventTarget~EventListener} fn + * Event listener. + */ +function on(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(on, elem, type, fn); + } - // Allow options for children to be set at the parent options - // e.g. videojs(id, { controlBar: false }); - // instead of videojs(id, { children: { controlBar: false }); - if (parentOptions[name] !== undefined) { - opts = parentOptions[name]; - } + var data = getData(elem); - // Allow for disabling default components - // e.g. options['children']['posterImage'] = false - if (opts === false) { - return; - } + // We need a place to store all our handler data + if (!data.handlers) { + data.handlers = {}; + } - // Allow options to be passed as a simple boolean if no configuration - // is necessary. - if (opts === true) { - opts = {}; - } + if (!data.handlers[type]) { + data.handlers[type] = []; + } - // We also want to pass the original player options - // to each component as well so they don't need to - // reach back into the player for options later. - opts.playerOptions = _this.options_.playerOptions; + if (!fn.guid) { + fn.guid = newGUID(); + } - // Create and add the child component. - // Add a direct reference to the child by name on the parent instance. - // If two of the same component are used, different names should be supplied - // for each - var newChild = _this.addChild(name, opts); + data.handlers[type].push(fn); - if (newChild) { - _this[name] = newChild; - } - }; + if (!data.dispatcher) { + data.disabled = false; - // Allow for an array of children details to passed in the options - var workingChildren = void 0; - var Tech = Component.getComponent('Tech'); + data.dispatcher = function (event, hash) { - if (Array.isArray(children)) { - workingChildren = children; - } else { - workingChildren = Object.keys(children); + if (data.disabled) { + return; } - workingChildren - // children that are in this.options_ but also in workingChildren would - // give us extra children we do not want. So, we want to filter them out. - .concat(Object.keys(this.options_).filter(function (child) { - return !workingChildren.some(function (wchild) { - if (typeof wchild === 'string') { - return child === wchild; - } - return child === wchild.name; - }); - })).map(function (child) { - var name = void 0; - var opts = void 0; - - if (typeof child === 'string') { - name = child; - opts = children[name] || _this.options_[name] || {}; - } else { - name = child.name; - opts = child; - } + event = fixEvent(event); - return { name: name, opts: opts }; - }).filter(function (child) { - // we have to make sure that child.name isn't in the techOrder since - // techs are registerd as Components but can't aren't compatible - // See https://github.com/videojs/video.js/issues/2772 - var c = Component.getComponent(child.opts.componentClass || (0, _toTitleCase2['default'])(child.name)); + var handlers = data.handlers[event.type]; - return c && !Tech.isTech(c); - }).forEach(handleAdd); - } - }; + if (handlers) { + // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. + var handlersCopy = handlers.slice(0); - /** - * Builds the default DOM class name. Should be overriden by sub-components. - * - * @return {string} - * The DOM class name for this object. - * - * @abstract - */ + for (var m = 0, n = handlersCopy.length; m < n; m++) { + if (event.isImmediatePropagationStopped()) { + break; + } else { + try { + handlersCopy[m].call(elem, event, hash); + } catch (e) { + log$1.error(e); + } + } + } + } + }; + } + if (data.handlers[type].length === 1) { + if (elem.addEventListener) { + elem.addEventListener(type, data.dispatcher, false); + } else if (elem.attachEvent) { + elem.attachEvent('on' + type, data.dispatcher); + } + } +} - Component.prototype.buildCSSClass = function buildCSSClass() { - // Child classes can include a function that does: - // return 'CLASS NAME' + this._super(); - return ''; - }; +/** + * Removes event listeners from an element + * + * @param {Element|Object} elem + * Object to remove listeners from. + * + * @param {string|string[]} [type] + * Type of listener to remove. Don't include to remove all events from element. + * + * @param {EventTarget~EventListener} [fn] + * Specific listener to remove. Don't include to remove listeners for an event + * type. + */ +function off(elem, type, fn) { + // Don't want to add a cache object through getElData if not needed + if (!hasData(elem)) { + return; + } - /** - * Bind a listener to the component's ready state. - * Different from event listeners in that if the ready event has already happened - * it will trigger the function immediately. - * - * @return {Component} - * Returns itself; method can be chained. - */ + var data = getData(elem); + // If no events exist, nothing to unbind + if (!data.handlers) { + return; + } - Component.prototype.ready = function ready(fn) { - var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + if (Array.isArray(type)) { + return _handleMultipleEvents(off, elem, type, fn); + } - if (fn) { - if (this.isReady_) { - if (sync) { - fn.call(this); - } else { - // Call the function asynchronously by default for consistency - this.setTimeout(fn, 1); - } - } else { - this.readyQueue_ = this.readyQueue_ || []; - this.readyQueue_.push(fn); - } - } + // Utility function + var removeType = function removeType(t) { + data.handlers[t] = []; + _cleanUpEvents(elem, t); }; - /** - * Trigger all the ready listeners for this `Component`. - * - * @fires Component#ready - */ - + // Are we removing all bound events? + if (!type) { + for (var t in data.handlers) { + removeType(t); + } + return; + } - Component.prototype.triggerReady = function triggerReady() { - this.isReady_ = true; + var handlers = data.handlers[type]; - // Ensure ready is triggerd asynchronously - this.setTimeout(function () { - var readyQueue = this.readyQueue_; + // If no handlers exist, nothing to unbind + if (!handlers) { + return; + } - // Reset Ready Queue - this.readyQueue_ = []; + // If no listener was provided, remove all listeners for type + if (!fn) { + removeType(type); + return; + } - if (readyQueue && readyQueue.length > 0) { - readyQueue.forEach(function (fn) { - fn.call(this); - }, this); + // We're only removing a single handler + if (fn.guid) { + for (var n = 0; n < handlers.length; n++) { + if (handlers[n].guid === fn.guid) { + handlers.splice(n--, 1); } + } + } - // Allow for using event listeners also - /** - * Triggered when a `Component` is ready. - * - * @event Component#ready - * @type {EventTarget~Event} - */ - this.trigger('ready'); - }, 1); - }; - - /** - * Find a single DOM element matching a `selector`. This can be within the `Component`s - * `contentEl()` or another custom context. - * - * @param {string} selector - * A valid CSS selector, which will be passed to `querySelector`. - * - * @param {Element|string} [context=this.contentEl()] - * A DOM element within which to query. Can also be a selector string in - * which case the first matching element will get used as context. If - * missing `this.contentEl()` gets used. If `this.contentEl()` returns - * nothing it falls back to `document`. - * - * @return {Element|null} - * the dom element that was found, or null - * - * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) - */ + _cleanUpEvents(elem, type); +} +/** + * Trigger an event for an element + * + * @param {Element|Object} elem + * Element to trigger an event on + * + * @param {EventTarget~Event|string} event + * A string (the type) or an event object with a type attribute + * + * @param {Object} [hash] + * data hash to pass along with the event + * + * @return {boolean|undefined} + * - Returns the opposite of `defaultPrevented` if default was prevented + * - Otherwise returns undefined + */ +function trigger(elem, event, hash) { + // Fetches element data and a reference to the parent (for bubbling). + // Don't want to add a data object to cache for every parent, + // so checking hasElData first. + var elemData = hasData(elem) ? getData(elem) : {}; + var parent = elem.parentNode || elem.ownerDocument; + // type = event.type || event, + // handler; - Component.prototype.$ = function $(selector, context) { - return Dom.$(selector, context || this.contentEl()); - }; + // If an event name was passed as a string, creates an event out of it + if (typeof event === 'string') { + event = { type: event, target: elem }; + } + // Normalizes the event properties. + event = fixEvent(event); - /** - * Finds all DOM element matching a `selector`. This can be within the `Component`s - * `contentEl()` or another custom context. - * - * @param {string} selector - * A valid CSS selector, which will be passed to `querySelectorAll`. - * - * @param {Element|string} [context=this.contentEl()] - * A DOM element within which to query. Can also be a selector string in - * which case the first matching element will get used as context. If - * missing `this.contentEl()` gets used. If `this.contentEl()` returns - * nothing it falls back to `document`. - * - * @return {NodeList} - * a list of dom elements that were found - * - * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) - */ + // If the passed element has a dispatcher, executes the established handlers. + if (elemData.dispatcher) { + elemData.dispatcher.call(elem, event, hash); + } + // Unless explicitly stopped or the event does not bubble (e.g. media events) + // recursively calls this function to bubble the event up the DOM. + if (parent && !event.isPropagationStopped() && event.bubbles === true) { + trigger.call(null, parent, event, hash); - Component.prototype.$$ = function $$(selector, context) { - return Dom.$$(selector, context || this.contentEl()); - }; + // If at the top of the DOM, triggers the default action unless disabled. + } else if (!parent && !event.defaultPrevented) { + var targetData = getData(event.target); - /** - * Check if a component's element has a CSS class name. - * - * @param {string} classToCheck - * CSS class name to check. - * - * @return {boolean} - * - True if the `Component` has the class. - * - False if the `Component` does not have the class` - */ + // Checks if the target has a default action for this event. + if (event.target[event.type]) { + // Temporarily disables event dispatching on the target as we have already executed the handler. + targetData.disabled = true; + // Executes the default action. + if (typeof event.target[event.type] === 'function') { + event.target[event.type](); + } + // Re-enables event dispatching. + targetData.disabled = false; + } + } + // Inform the triggerer if the default was prevented by returning false + return !event.defaultPrevented; +} - Component.prototype.hasClass = function hasClass(classToCheck) { - return Dom.hasClass(this.el_, classToCheck); +/** + * Trigger a listener only once for an event + * + * @param {Element|Object} elem + * Element or object to bind to. + * + * @param {string|string[]} type + * Name/type of event + * + * @param {Event~EventListener} fn + * Event Listener function + */ +function one(elem, type, fn) { + if (Array.isArray(type)) { + return _handleMultipleEvents(one, elem, type, fn); + } + var func = function func() { + off(elem, type, func); + fn.apply(this, arguments); }; - /** - * Add a CSS class name to the `Component`s element. - * - * @param {string} classToAdd - * CSS class name to add - */ - - - Component.prototype.addClass = function addClass(classToAdd) { - Dom.addClass(this.el_, classToAdd); - }; + // copy the guid to the new function so it can removed using the original function's ID + func.guid = fn.guid = fn.guid || newGUID(); + on(elem, type, func); +} - /** - * Remove a CSS class name from the `Component`s element. - * - * @param {string} classToRemove - * CSS class name to remove - */ +var Events = (Object.freeze || Object)({ + fixEvent: fixEvent, + on: on, + off: off, + trigger: trigger, + one: one +}); +/** + * @file setup.js - Functions for setting up a player without + * user interaction based on the data-setup `attribute` of the video tag. + * + * @module setup + */ +var _windowLoaded = false; +var videojs$2 = void 0; - Component.prototype.removeClass = function removeClass(classToRemove) { - Dom.removeClass(this.el_, classToRemove); - }; - - /** - * Add or remove a CSS class name from the component's element. - * - `classToToggle` gets added when {@link Component#hasClass} would return false. - * - `classToToggle` gets removed when {@link Component#hasClass} would return true. - * - * @param {string} classToToggle - * The class to add or remove based on (@link Component#hasClass} - * - * @param {boolean|Dom~predicate} [predicate] - * An {@link Dom~predicate} function or a boolean - */ +/** + * Set up any tags that have a data-setup `attribute` when the player is started. + */ +var autoSetup = function autoSetup() { + // Protect against breakage in non-browser environments. + if (!isReal()) { + return; + } - Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) { - Dom.toggleClass(this.el_, classToToggle, predicate); - }; + // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack* + // var vids = Array.prototype.slice.call(document.getElementsByTagName('video')); + // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio')); + // var mediaEls = vids.concat(audios); - /** - * Show the `Component`s element if it is hidden by removing the - * 'vjs-hidden' class name from it. - */ + // Because IE8 doesn't support calling slice on a node list, we need to loop + // through each list of elements to build up a new, combined list of elements. + var vids = document_1.getElementsByTagName('video'); + var audios = document_1.getElementsByTagName('audio'); + var mediaEls = []; + if (vids && vids.length > 0) { + for (var i = 0, e = vids.length; i < e; i++) { + mediaEls.push(vids[i]); + } + } - Component.prototype.show = function show() { - this.removeClass('vjs-hidden'); - }; + if (audios && audios.length > 0) { + for (var _i = 0, _e = audios.length; _i < _e; _i++) { + mediaEls.push(audios[_i]); + } + } - /** - * Hide the `Component`s element if it is currently showing by adding the - * 'vjs-hidden` class name to it. - */ + // Check if any media elements exist + if (mediaEls && mediaEls.length > 0) { + for (var _i2 = 0, _e2 = mediaEls.length; _i2 < _e2; _i2++) { + var mediaEl = mediaEls[_i2]; - Component.prototype.hide = function hide() { - this.addClass('vjs-hidden'); - }; + // Check if element exists, has getAttribute func. + // IE seems to consider typeof el.getAttribute == 'object' instead of + // 'function' like expected, at least when loading the player immediately. + if (mediaEl && mediaEl.getAttribute) { - /** - * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' - * class name to it. Used during fadeIn/fadeOut. - * - * @private - */ + // Make sure this player hasn't already been set up. + if (mediaEl.player === undefined) { + var options = mediaEl.getAttribute('data-setup'); + // Check if data-setup attr exists. + // We only auto-setup if they've added the data-setup attr. + if (options !== null) { + // Create new video.js instance. + videojs$2(mediaEl); + } + } - Component.prototype.lockShowing = function lockShowing() { - this.addClass('vjs-lock-showing'); - }; + // If getAttribute isn't defined, we need to wait for the DOM. + } else { + autoSetupTimeout(1); + break; + } + } - /** - * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' - * class name from it. Used during fadeIn/fadeOut. - * - * @private - */ + // No videos were found, so keep looping unless page is finished loading. + } else if (!_windowLoaded) { + autoSetupTimeout(1); + } +}; +/** + * Wait until the page is loaded before running autoSetup. This will be called in + * autoSetup if `hasLoaded` returns false. + * + * @param {number} wait + * How long to wait in ms + * + * @param {module:videojs} [vjs] + * The videojs library function + */ +function autoSetupTimeout(wait, vjs) { + if (vjs) { + videojs$2 = vjs; + } - Component.prototype.unlockShowing = function unlockShowing() { - this.removeClass('vjs-lock-showing'); - }; + window_1.setTimeout(autoSetup, wait); +} +if (isReal() && document_1.readyState === 'complete') { + _windowLoaded = true; +} else { /** - * Get the value of an attribute on the `Component`s element. - * - * @param {string} attribute - * Name of the attribute to get the value from. - * - * @return {string|null} - * - The value of the attribute that was asked for. - * - Can be an empty string on some browsers if the attribute does not exist - * or has no value - * - Most browsers will return null if the attibute does not exist or has - * no value. + * Listen for the load event on window, and set _windowLoaded to true. * - * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} + * @listens load */ + one(window_1, 'load', function () { + _windowLoaded = true; + }); +} +/** + * @file stylesheet.js + * @module stylesheet + */ +/** + * Create a DOM syle element given a className for it. + * + * @param {string} className + * The className to add to the created style element. + * + * @return {Element} + * The element that was created. + */ +var createStyleElement = function createStyleElement(className) { + var style = document_1.createElement('style'); - Component.prototype.getAttribute = function getAttribute(attribute) { - return Dom.getAttribute(this.el_, attribute); - }; + style.className = className; - /** - * Set the value of an attribute on the `Component`'s element - * - * @param {string} attribute - * Name of the attribute to set. - * - * @param {string} value - * Value to set the attribute to. - * - * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} - */ + return style; +}; +/** + * Add text to a DOM element. + * + * @param {Element} el + * The Element to add text content to. + * + * @param {string} content + * The text to add to the element. + */ +var setTextContent = function setTextContent(el, content) { + if (el.styleSheet) { + el.styleSheet.cssText = content; + } else { + el.textContent = content; + } +}; - Component.prototype.setAttribute = function setAttribute(attribute, value) { - Dom.setAttribute(this.el_, attribute, value); - }; +/** + * @file fn.js + * @module fn + */ +/** + * Bind (a.k.a proxy or Context). A simple method for changing the context of a function + * It also stores a unique id on the function so it can be easily removed from events. + * + * @param {Mixed} context + * The object to bind as scope. + * + * @param {Function} fn + * The function to be bound to a scope. + * + * @param {number} [uid] + * An optional unique ID for the function to be set + * + * @return {Function} + * The new function that will be bound into the context given + */ +var bind = function bind(context, fn, uid) { + // Make sure the function has a unique ID + if (!fn.guid) { + fn.guid = newGUID(); + } - /** - * Remove an attribute from the `Component`s element. - * - * @param {string} attribute - * Name of the attribute to remove. - * - * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} - */ + // Create the new function that changes the context + var bound = function bound() { + return fn.apply(context, arguments); + }; + // Allow for the ability to individualize this function + // Needed in the case where multiple objects might share the same prototype + // IF both items add an event listener with the same function, then you try to remove just one + // it will remove both because they both have the same guid. + // when using this, you need to use the bind method when you remove the listener as well. + // currently used in text tracks + bound.guid = uid ? uid + '_' + fn.guid : fn.guid; - Component.prototype.removeAttribute = function removeAttribute(attribute) { - Dom.removeAttribute(this.el_, attribute); - }; + return bound; +}; - /** - * Get or set the width of the component based upon the CSS styles. - * See {@link Component#dimension} for more detailed information. - * - * @param {number|string} [num] - * The width that you want to set postfixed with '%', 'px' or nothing. - * - * @param {boolean} [skipListeners] - * Skip the componentresize event trigger - * - * @return {number|string} - * The width when getting, zero if there is no width. Can be a string - * postpixed with '%' or 'px'. - */ +/** + * Wraps the given function, `fn`, with a new function that only invokes `fn` + * at most once per every `wait` milliseconds. + * + * @param {Function} fn + * The function to be throttled. + * + * @param {Number} wait + * The number of milliseconds by which to throttle. + * + * @return {Function} + */ +var throttle = function throttle(fn, wait) { + var last = Date.now(); + var throttled = function throttled() { + var now = Date.now(); - Component.prototype.width = function width(num, skipListeners) { - return this.dimension('width', num, skipListeners); + if (now - last >= wait) { + fn.apply(undefined, arguments); + last = now; + } }; - /** - * Get or set the height of the component based upon the CSS styles. - * See {@link Component#dimension} for more detailed information. - * - * @param {number|string} [num] - * The height that you want to set postfixed with '%', 'px' or nothing. - * - * @param {boolean} [skipListeners] - * Skip the componentresize event trigger - * - * @return {number|string} - * The width when getting, zero if there is no width. Can be a string - * postpixed with '%' or 'px'. - */ - - - Component.prototype.height = function height(num, skipListeners) { - return this.dimension('height', num, skipListeners); - }; + return throttled; +}; - /** - * Set both the width and height of the `Component` element at the same time. - * - * @param {number|string} width - * Width to set the `Component`s element to. - * - * @param {number|string} height - * Height to set the `Component`s element to. - */ +/** + * @file src/js/event-target.js + */ +/** + * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It + * adds shorthand functions that wrap around lengthy functions. For example: + * the `on` function is a wrapper around `addEventListener`. + * + * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} + * @class EventTarget + */ +var EventTarget = function EventTarget() {}; +/** + * A Custom DOM event. + * + * @typedef {Object} EventTarget~Event + * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} + */ - Component.prototype.dimensions = function dimensions(width, height) { - // Skip componentresize listeners on width for optimization - this.width(width, true); - this.height(height); - }; +/** + * All event listeners should follow the following format. + * + * @callback EventTarget~EventListener + * @this {EventTarget} + * + * @param {EventTarget~Event} event + * the event that triggered this function + * + * @param {Object} [hash] + * hash of data sent during the event + */ - /** - * Get or set width or height of the `Component` element. This is the shared code - * for the {@link Component#width} and {@link Component#height}. - * - * Things to know: - * - If the width or height in an number this will return the number postfixed with 'px'. - * - If the width/height is a percent this will return the percent postfixed with '%' - * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function - * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. - * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} - * for more information - * - If you want the computed style of the component, use {@link Component#currentWidth} - * and {@link {Component#currentHeight} - * - * @fires Component#componentresize - * - * @param {string} widthOrHeight - 8 'width' or 'height' - * - * @param {number|string} [num] - 8 New dimension - * - * @param {boolean} [skipListeners] - * Skip componentresize event trigger - * - * @return {number} - * The dimension when getting or 0 if unset - */ +/** + * An object containing event names as keys and booleans as values. + * + * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} + * will have extra functionality. See that function for more information. + * + * @property EventTarget.prototype.allowedEvents_ + * @private + */ +EventTarget.prototype.allowedEvents_ = {}; +/** + * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a + * function that will get called when an event with a certain name gets triggered. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to call with `EventTarget`s + */ +EventTarget.prototype.on = function (type, fn) { + // Remove the addEventListener alias before calling Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; - Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { - if (num !== undefined) { - // Set to zero if null or literally NaN (NaN !== NaN) - if (num === null || num !== num) { - num = 0; - } + this.addEventListener = function () {}; + on(this, type, fn); + this.addEventListener = ael; +}; - // Check if using css width/height (% or px) and adjust - if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { - this.el_.style[widthOrHeight] = num; - } else if (num === 'auto') { - this.el_.style[widthOrHeight] = ''; - } else { - this.el_.style[widthOrHeight] = num + 'px'; - } +/** + * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#on} + */ +EventTarget.prototype.addEventListener = EventTarget.prototype.on; - // skipListeners allows us to avoid triggering the resize event when setting both width and height - if (!skipListeners) { - /** - * Triggered when a component is resized. - * - * @event Component#componentresize - * @type {EventTarget~Event} - */ - this.trigger('componentresize'); - } +/** + * Removes an `event listener` for a specific event from an instance of `EventTarget`. + * This makes it so that the `event listener` will no longer get called when the + * named event happens. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to remove. + */ +EventTarget.prototype.off = function (type, fn) { + off(this, type, fn); +}; - return; - } +/** + * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#off} + */ +EventTarget.prototype.removeEventListener = EventTarget.prototype.off; - // Not setting a value, so getting it - // Make sure element exists - if (!this.el_) { - return 0; - } +/** + * This function will add an `event listener` that gets triggered only once. After the + * first trigger it will get removed. This is like adding an `event listener` + * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. + * + * @param {string|string[]} type + * An event name or an array of event names. + * + * @param {EventTarget~EventListener} fn + * The function to be called once for each event name. + */ +EventTarget.prototype.one = function (type, fn) { + // Remove the addEventListener alialing Events.on + // so we don't get into an infinite type loop + var ael = this.addEventListener; - // Get dimension value from style - var val = this.el_.style[widthOrHeight]; - var pxIndex = val.indexOf('px'); + this.addEventListener = function () {}; + one(this, type, fn); + this.addEventListener = ael; +}; - if (pxIndex !== -1) { - // Return the pixel value with no 'px' - return parseInt(val.slice(0, pxIndex), 10); - } +/** + * This function causes an event to happen. This will then cause any `event listeners` + * that are waiting for that event, to get called. If there are no `event listeners` + * for an event then nothing will happen. + * + * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. + * Trigger will also call the `on` + `uppercaseEventName` function. + * + * Example: + * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call + * `onClick` if it exists. + * + * @param {string|EventTarget~Event|Object} event + * The name of the event, an `Event`, or an object with a key of type set to + * an event name. + */ +EventTarget.prototype.trigger = function (event) { + var type = event.type || event; - // No px so using % or no style was set, so falling back to offsetWidth/height - // If component has display:none, offset will return 0 - // TODO: handle display:none and no dimension style using px - return parseInt(this.el_['offset' + (0, _toTitleCase2['default'])(widthOrHeight)], 10); - }; + if (typeof event === 'string') { + event = { type: type }; + } + event = fixEvent(event); - /** - * Get the width or the height of the `Component` elements computed style. Uses - * `window.getComputedStyle`. - * - * @param {string} widthOrHeight - * A string containing 'width' or 'height'. Whichever one you want to get. - * - * @return {number} - * The dimension that gets asked for or 0 if nothing was set - * for that dimension. - */ + if (this.allowedEvents_[type] && this['on' + type]) { + this['on' + type](event); + } + trigger(this, event); +}; - Component.prototype.currentDimension = function currentDimension(widthOrHeight) { - var computedWidthOrHeight = 0; +/** + * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic + * the standard DOM API. + * + * @function + * @see {@link EventTarget#trigger} + */ +EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; - if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { - throw new Error('currentDimension only accepts width or height value'); - } +/** + * @file mixins/evented.js + * @module evented + */ +/** + * Returns whether or not an object has had the evented mixin applied. + * + * @param {Object} object + * An object to test. + * + * @return {boolean} + * Whether or not the object appears to be evented. + */ +var isEvented = function isEvented(object) { + return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { + return typeof object[k] === 'function'; + }); +}; - if (typeof _window2['default'].getComputedStyle === 'function') { - var computedStyle = _window2['default'].getComputedStyle(this.el_); +/** + * Whether a value is a valid event type - non-empty string or array. + * + * @private + * @param {string|Array} type + * The type value to test. + * + * @return {boolean} + * Whether or not the type is a valid event type. + */ +var isValidEventType = function isValidEventType(type) { + return ( + // The regex here verifies that the `type` contains at least one non- + // whitespace character. + typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length + ); +}; - computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; - } +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the target does not appear to be a valid event target. + * + * @param {Object} target + * The object to test. + */ +var validateTarget = function validateTarget(target) { + if (!target.nodeName && !isEvented(target)) { + throw new Error('Invalid target; must be a DOM node or evented object.'); + } +}; - // remove 'px' from variable and parse as integer - computedWidthOrHeight = parseFloat(computedWidthOrHeight); +/** + * Validates a value to determine if it is a valid event target. Throws if not. + * + * @private + * @throws {Error} + * If the type does not appear to be a valid event type. + * + * @param {string|Array} type + * The type to test. + */ +var validateEventType = function validateEventType(type) { + if (!isValidEventType(type)) { + throw new Error('Invalid event type; must be a non-empty string or array.'); + } +}; - // if the computed value is still 0, it's possible that the browser is lying - // and we want to check the offset values. - // This code also runs on IE8 and wherever getComputedStyle doesn't exist. - if (computedWidthOrHeight === 0) { - var rule = 'offset' + (0, _toTitleCase2['default'])(widthOrHeight); +/** + * Validates a value to determine if it is a valid listener. Throws if not. + * + * @private + * @throws {Error} + * If the listener is not a function. + * + * @param {Function} listener + * The listener to test. + */ +var validateListener = function validateListener(listener) { + if (typeof listener !== 'function') { + throw new Error('Invalid listener; must be a function.'); + } +}; - computedWidthOrHeight = this.el_[rule]; - } +/** + * Takes an array of arguments given to `on()` or `one()`, validates them, and + * normalizes them into an object. + * + * @private + * @param {Object} self + * The evented object on which `on()` or `one()` was called. This + * object will be bound as the `this` value for the listener. + * + * @param {Array} args + * An array of arguments passed to `on()` or `one()`. + * + * @return {Object} + * An object containing useful values for `on()` or `one()` calls. + */ +var normalizeListenArgs = function normalizeListenArgs(self, args) { - return computedWidthOrHeight; - }; + // If the number of arguments is less than 3, the target is always the + // evented object itself. + var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; + var target = void 0; + var type = void 0; + var listener = void 0; - /** - * An object that contains width and height values of the `Component`s - * computed style. Uses `window.getComputedStyle`. - * - * @typedef {Object} Component~DimensionObject - * - * @property {number} width - * The width of the `Component`s computed style. - * - * @property {number} height - * The height of the `Component`s computed style. - */ + if (isTargetingSelf) { + target = self.eventBusEl_; - /** - * Get an object that contains width and height values of the `Component`s - * computed style. - * - * @return {Component~DimensionObject} - * The dimensions of the components element - */ + // Deal with cases where we got 3 arguments, but we are still listening to + // the evented object itself. + if (args.length >= 3) { + args.shift(); + } + type = args[0]; + listener = args[1]; + } else { + target = args[0]; + type = args[1]; + listener = args[2]; + } - Component.prototype.currentDimensions = function currentDimensions() { - return { - width: this.currentDimension('width'), - height: this.currentDimension('height') - }; - }; + validateTarget(target); + validateEventType(type); + validateListener(listener); - /** - * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. - * - * @return {number} width - * The width of the `Component`s computed style. - */ + listener = bind(self, listener); + return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; +}; - Component.prototype.currentWidth = function currentWidth() { - return this.currentDimension('width'); - }; +/** + * Adds the listener to the event type(s) on the target, normalizing for + * the type of target. + * + * @private + * @param {Element|Object} target + * A DOM node or evented object. + * + * @param {string} method + * The event binding method to use ("on" or "one"). + * + * @param {string|Array} type + * One or more event type(s). + * + * @param {Function} listener + * A listener function. + */ +var listen = function listen(target, method, type, listener) { + validateTarget(target); + + if (target.nodeName) { + Events[method](target, type, listener); + } else { + target[method](type, listener); + } +}; + +/** + * Contains methods that provide event capabilites to an object which is passed + * to {@link module:evented|evented}. + * + * @mixin EventedMixin + */ +var EventedMixin = { /** - * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. + * Add a listener to an event (or events) on this object or another evented + * object. * - * @return {number} height - * The height of the `Component`s computed style. + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. */ + on: function on$$1() { + var _this = this; + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } - Component.prototype.currentHeight = function currentHeight() { - return this.currentDimension('height'); - }; + var _normalizeListenArgs = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs.isTargetingSelf, + target = _normalizeListenArgs.target, + type = _normalizeListenArgs.type, + listener = _normalizeListenArgs.listener; - /** - * Set the focus to this component - */ + listen(target, 'on', type, listener); + // If this object is listening to another evented object. + if (!isTargetingSelf) { - Component.prototype.focus = function focus() { - this.el_.focus(); - }; + // If this object is disposed, remove the listener. + var removeListenerOnDispose = function removeListenerOnDispose() { + return _this.off(target, type, listener); + }; - /** - * Remove the focus from this component - */ + // Use the same function ID as the listener so we can remove it later it + // using the ID of the original listener. + removeListenerOnDispose.guid = listener.guid; + // Add a listener to the target's dispose event as well. This ensures + // that if the target is disposed BEFORE this object, we remove the + // removal listener that was just added. Otherwise, we create a memory leak. + var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { + return _this.off('dispose', removeListenerOnDispose); + }; - Component.prototype.blur = function blur() { - this.el_.blur(); - }; + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + removeRemoverOnTargetDispose.guid = listener.guid; - /** - * Emit a 'tap' events when touch event support gets detected. This gets used to - * support toggling the controls through a tap on the video. They get enabled - * because every sub-component would have extra overhead otherwise. - * - * @private - * @fires Component#tap - * @listens Component#touchstart - * @listens Component#touchmove - * @listens Component#touchleave - * @listens Component#touchcancel - * @listens Component#touchend - */ + listen(this, 'on', 'dispose', removeListenerOnDispose); + listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); + } + }, - Component.prototype.emitTapEvents = function emitTapEvents() { - // Track the start time so we can determine how long the touch lasted - var touchStart = 0; - var firstTouch = null; + /** + * Add a listener to an event (or events) on this object or another evented + * object. The listener will only be called once and then removed. + * + * @param {string|Array|Element|Object} targetOrType + * If this is a string or array, it represents the event type(s) + * that will trigger the listener. + * + * Another evented object can be passed here instead, which will + * cause the listener to listen for events on _that_ object. + * + * In either case, the listener's `this` value will be bound to + * this object. + * + * @param {string|Array|Function} typeOrListener + * If the first argument was a string or array, this should be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function. + */ + one: function one$$1() { + var _this2 = this; - // Maximum movement allowed during a touch event to still be considered a tap - // Other popular libs use anywhere from 2 (hammer.js) to 15, - // so 10 seems like a nice, round number. - var tapMovementThreshold = 10; + for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } - // The maximum length a touch can be while still being considered a tap - var touchTimeThreshold = 200; + var _normalizeListenArgs2 = normalizeListenArgs(this, args), + isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, + target = _normalizeListenArgs2.target, + type = _normalizeListenArgs2.type, + listener = _normalizeListenArgs2.listener; - var couldBeTap = void 0; + // Targeting this evented object. - this.on('touchstart', function (event) { - // If more than one finger, don't consider treating this as a click - if (event.touches.length === 1) { - // Copy pageX/pageY from the object - firstTouch = { - pageX: event.touches[0].pageX, - pageY: event.touches[0].pageY - }; - // Record start time so we can detect a tap vs. "touch and hold" - touchStart = new Date().getTime(); - // Reset couldBeTap tracking - couldBeTap = true; - } - }); - this.on('touchmove', function (event) { - // If more than one finger, don't consider treating this as a click - if (event.touches.length > 1) { - couldBeTap = false; - } else if (firstTouch) { - // Some devices will throw touchmoves for all but the slightest of taps. - // So, if we moved only a small distance, this could still be a tap - var xdiff = event.touches[0].pageX - firstTouch.pageX; - var ydiff = event.touches[0].pageY - firstTouch.pageY; - var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); + if (isTargetingSelf) { + listen(target, 'one', type, listener); - if (touchDistance > tapMovementThreshold) { - couldBeTap = false; + // Targeting another evented object. + } else { + var wrapper = function wrapper() { + for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { + largs[_key3] = arguments[_key3]; } - } - }); - - var noTap = function noTap() { - couldBeTap = false; - }; - // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s - this.on('touchleave', noTap); - this.on('touchcancel', noTap); + _this2.off(target, type, wrapper); + listener.apply(null, largs); + }; - // When the touch ends, measure how long it took and trigger the appropriate - // event - this.on('touchend', function (event) { - firstTouch = null; - // Proceed only if the touchmove/leave/cancel event didn't happen - if (couldBeTap === true) { - // Measure how long the touch lasted - var touchTime = new Date().getTime() - touchStart; + // Use the same function ID as the listener so we can remove it later + // it using the ID of the original listener. + wrapper.guid = listener.guid; + listen(target, 'one', type, wrapper); + } + }, - // Make sure the touch was less than the threshold to be considered a tap - if (touchTime < touchTimeThreshold) { - // Don't let browser turn this into a click - event.preventDefault(); - /** - * Triggered when a `Component` is tapped. - * - * @event Component#tap - * @type {EventTarget~Event} - */ - this.trigger('tap'); - // It may be good to copy the touchend event object and change the - // type to tap, if the other event properties aren't exact after - // Events.fixEvent runs (e.g. event.target) - } - } - }); - }; /** - * This function reports user activity whenever touch events happen. This can get - * turned off by any sub-components that wants touch events to act another way. + * Removes listener(s) from event(s) on an evented object. * - * Report user touch activity when touch events occur. User activity gets used to - * determine when controls should show/hide. It is simple when it comes to mouse - * events, because any mouse event should show the controls. So we capture mouse - * events that bubble up to the player and report activity when that happens. - * With touch events it isn't as easy as `touchstart` and `touchend` toggle player - * controls. So touch events can't help us at the player level either. + * @param {string|Array|Element|Object} [targetOrType] + * If this is a string or array, it represents the event type(s). * - * User activity gets checked asynchronously. So what could happen is a tap event - * on the video turns the controls off. Then the `touchend` event bubbles up to - * the player. Which, if it reported user activity, would turn the controls right - * back on. We also don't want to completely block touch events from bubbling up. - * Furthermore a `touchmove` event and anything other than a tap, should not turn - * controls back on. + * Another evented object can be passed here instead, in which case + * ALL 3 arguments are _required_. * - * @listens Component#touchstart - * @listens Component#touchmove - * @listens Component#touchend - * @listens Component#touchcancel + * @param {string|Array|Function} [typeOrListener] + * If the first argument was a string or array, this may be the + * listener function. Otherwise, this is a string or array of event + * type(s). + * + * @param {Function} [listener] + * If the first argument was another evented object, this will be + * the listener function; otherwise, _all_ listeners bound to the + * event type(s) will be removed. */ + off: function off$$1(targetOrType, typeOrListener, listener) { + // Targeting this evented object. + if (!targetOrType || isValidEventType(targetOrType)) { + off(this.eventBusEl_, targetOrType, typeOrListener); - Component.prototype.enableTouchActivity = function enableTouchActivity() { - // Don't continue if the root player doesn't support reporting user activity - if (!this.player() || !this.player().reportUserActivity) { - return; - } - - // listener for reporting that the user is active - var report = Fn.bind(this.player(), this.player().reportUserActivity); + // Targeting another evented object. + } else { + var target = targetOrType; + var type = typeOrListener; - var touchHolding = void 0; + // Fail fast and in a meaningful way! + validateTarget(target); + validateEventType(type); + validateListener(listener); - this.on('touchstart', function () { - report(); - // For as long as the they are touching the device or have their mouse down, - // we consider them active even if they're not moving their finger or mouse. - // So we want to continue to update that they are active - this.clearInterval(touchHolding); - // report at the same interval as activityCheck - touchHolding = this.setInterval(report, 250); - }); + // Ensure there's at least a guid, even if the function hasn't been used + listener = bind(this, listener); - var touchEnd = function touchEnd(event) { - report(); - // stop the interval that maintains activity if the touch is holding - this.clearInterval(touchHolding); - }; + // Remove the dispose listener on this evented object, which was given + // the same guid as the event listener in on(). + this.off('dispose', listener); - this.on('touchmove', report); - this.on('touchend', touchEnd); - this.on('touchcancel', touchEnd); - }; + if (target.nodeName) { + off(target, type, listener); + off(target, 'dispose', listener); + } else if (isEvented(target)) { + target.off(type, listener); + target.off('dispose', listener); + } + } + }, - /** - * A callback that has no parameters and is bound into `Component`s context. - * - * @callback Component~GenericCallback - * @this Component - */ /** - * Creates a function that runs after an `x` millisecond timeout. This function is a - * wrapper around `window.setTimeout`. There are a few reasons to use this one - * instead though: - * 1. It gets cleared via {@link Component#clearTimeout} when - * {@link Component#dispose} gets called. - * 2. The function callback will gets turned into a {@link Component~GenericCallback} - * - * > Note: You can use `window.clearTimeout` on the id returned by this function. This - * will cause its dispose listener not to get cleaned up! Please use - * {@link Component#clearTimeout} or {@link Component#dispose}. + * Fire an event on this evented object, causing its listeners to be called. * - * @param {Component~GenericCallback} fn - * The function that will be run after `timeout`. + * @param {string|Object} event + * An event type or an object with a type property. * - * @param {number} timeout - * Timeout in milliseconds to delay before executing the specified function. + * @param {Object} [hash] + * An additional object to pass along to listeners. * - * @return {number} - * Returns a timeout ID that gets used to identify the timeout. It can also - * get used in {@link Component#clearTimeout} to clear the timeout that - * was set. - * - * @listens Component#dispose - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} + * @returns {boolean} + * Whether or not the default behavior was prevented. */ + trigger: function trigger$$1(event, hash) { + return trigger(this.eventBusEl_, event, hash); + } +}; +/** + * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. + * + * @param {Object} target + * The object to which to add event methods. + * + * @param {Object} [options={}] + * Options for customizing the mixin behavior. + * + * @param {String} [options.eventBusKey] + * By default, adds a `eventBusEl_` DOM element to the target object, + * which is used as an event bus. If the target object already has a + * DOM element that should be used, pass its key here. + * + * @return {Object} + * The target object. + */ +function evented(target) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var eventBusKey = options.eventBusKey; - Component.prototype.setTimeout = function setTimeout(fn, timeout) { - fn = Fn.bind(this, fn); + // Set or create the eventBusEl_. - var timeoutId = _window2['default'].setTimeout(fn, timeout); - var disposeFn = function disposeFn() { - this.clearTimeout(timeoutId); - }; + if (eventBusKey) { + if (!target[eventBusKey].nodeName) { + throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); + } + target.eventBusEl_ = target[eventBusKey]; + } else { + target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' }); + } - disposeFn.guid = 'vjs-timeout-' + timeoutId; + assign(target, EventedMixin); - this.on('dispose', disposeFn); + // When any evented object is disposed, it removes all its listeners. + target.on('dispose', function () { + return target.off(); + }); - return timeoutId; - }; + return target; +} + +/** + * @file mixins/stateful.js + * @module stateful + */ +/** + * Contains methods that provide statefulness to an object which is passed + * to {@link module:stateful}. + * + * @mixin StatefulMixin + */ +var StatefulMixin = { /** - * Clears a timeout that gets created via `window.setTimeout` or - * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} - * use this function instead of `window.clearTimout`. If you don't your dispose - * listener will not get cleaned up until {@link Component#dispose}! - * - * @param {number} timeoutId - * The id of the timeout to clear. The return value of - * {@link Component#setTimeout} or `window.setTimeout`. - * - * @return {number} - * Returns the timeout id that was cleared. + * A hash containing arbitrary keys and values representing the state of + * the object. * - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} + * @type {Object} */ - - - Component.prototype.clearTimeout = function clearTimeout(timeoutId) { - _window2['default'].clearTimeout(timeoutId); - - var disposeFn = function disposeFn() {}; - - disposeFn.guid = 'vjs-timeout-' + timeoutId; - - this.off('dispose', disposeFn); - - return timeoutId; - }; + state: {}, /** - * Creates a function that gets run every `x` milliseconds. This function is a wrapper - * around `window.setInterval`. There are a few reasons to use this one instead though. - * 1. It gets cleared via {@link Component#clearInterval} when - * {@link Component#dispose} gets called. - * 2. The function callback will be a {@link Component~GenericCallback} - * - * @param {Component~GenericCallback} fn - * The function to run every `x` seconds. - * - * @param {number} interval - * Execute the specified function every `x` milliseconds. + * Set the state of an object by mutating its + * {@link module:stateful~StatefulMixin.state|state} object in place. * - * @return {number} - * Returns an id that can be used to identify the interval. It can also be be used in - * {@link Component#clearInterval} to clear the interval. + * @fires module:stateful~StatefulMixin#statechanged + * @param {Object|Function} stateUpdates + * A new set of properties to shallow-merge into the plugin state. + * Can be a plain object or a function returning a plain object. * - * @listens Component#dispose - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} + * @returns {Object|undefined} + * An object containing changes that occurred. If no changes + * occurred, returns `undefined`. */ + setState: function setState(stateUpdates) { + var _this = this; + // Support providing the `stateUpdates` state as a function. + if (typeof stateUpdates === 'function') { + stateUpdates = stateUpdates(); + } - Component.prototype.setInterval = function setInterval(fn, interval) { - fn = Fn.bind(this, fn); - - var intervalId = _window2['default'].setInterval(fn, interval); + var changes = void 0; - var disposeFn = function disposeFn() { - this.clearInterval(intervalId); - }; + each(stateUpdates, function (value, key) { - disposeFn.guid = 'vjs-interval-' + intervalId; + // Record the change if the value is different from what's in the + // current state. + if (_this.state[key] !== value) { + changes = changes || {}; + changes[key] = { + from: _this.state[key], + to: value + }; + } - this.on('dispose', disposeFn); + _this.state[key] = value; + }); - return intervalId; - }; + // Only trigger "statechange" if there were changes AND we have a trigger + // function. This allows us to not require that the target object be an + // evented object. + if (changes && isEvented(this)) { - /** - * Clears an interval that gets created via `window.setInterval` or - * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} - * use this function instead of `window.clearInterval`. If you don't your dispose - * listener will not get cleaned up until {@link Component#dispose}! - * - * @param {number} intervalId - * The id of the interval to clear. The return value of - * {@link Component#setInterval} or `window.setInterval`. - * - * @return {number} - * Returns the interval id that was cleared. - * - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} - */ + /** + * An event triggered on an object that is both + * {@link module:stateful|stateful} and {@link module:evented|evented} + * indicating that its state has changed. + * + * @event module:stateful~StatefulMixin#statechanged + * @type {Object} + * @property {Object} changes + * A hash containing the properties that were changed and + * the values they were changed `from` and `to`. + */ + this.trigger({ + changes: changes, + type: 'statechanged' + }); + } + return changes; + } +}; - Component.prototype.clearInterval = function clearInterval(intervalId) { - _window2['default'].clearInterval(intervalId); +/** + * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target + * object. + * + * If the target object is {@link module:evented|evented} and has a + * `handleStateChanged` method, that method will be automatically bound to the + * `statechanged` event on itself. + * + * @param {Object} target + * The object to be made stateful. + * + * @param {Object} [defaultState] + * A default set of properties to populate the newly-stateful object's + * `state` property. + * + * @returns {Object} + * Returns the `target`. + */ +function stateful(target, defaultState) { + assign(target, StatefulMixin); - var disposeFn = function disposeFn() {}; + // This happens after the mixing-in because we need to replace the `state` + // added in that step. + target.state = assign({}, target.state, defaultState); - disposeFn.guid = 'vjs-interval-' + intervalId; + // Auto-bind the `handleStateChanged` method of the target object if it exists. + if (typeof target.handleStateChanged === 'function' && isEvented(target)) { + target.on('statechanged', target.handleStateChanged); + } - this.off('dispose', disposeFn); + return target; +} - return intervalId; - }; +/** + * @file to-title-case.js + * @module to-title-case + */ - /** - * Queues up a callback to be passed to requestAnimationFrame (rAF), but - * with a few extra bonuses: - * - * - Supports browsers that do not support rAF by falling back to - * {@link Component#setTimeout}. - * - * - The callback is turned into a {@link Component~GenericCallback} (i.e. - * bound to the component). - * - * - Automatic cancellation of the rAF callback is handled if the component - * is disposed before it is called. - * - * @param {Component~GenericCallback} fn - * A function that will be bound to this component and executed just - * before the browser's next repaint. - * - * @return {number} - * Returns an rAF ID that gets used to identify the timeout. It can - * also be used in {@link Component#cancelAnimationFrame} to cancel - * the animation frame callback. - * - * @listens Component#dispose - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} - */ +/** + * Uppercase the first letter of a string. + * + * @param {string} string + * String to be uppercased + * + * @return {string} + * The string with an uppercased first letter + */ +function toTitleCase(string) { + if (typeof string !== 'string') { + return string; + } + return string.charAt(0).toUpperCase() + string.slice(1); +} - Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { - var _this2 = this; - - if (this.supportsRaf_) { - fn = Fn.bind(this, fn); +/** + * Compares the TitleCase versions of the two strings for equality. + * + * @param {string} str1 + * The first string to compare + * + * @param {string} str2 + * The second string to compare + * + * @return {boolean} + * Whether the TitleCase versions of the strings are equal + */ +function titleCaseEquals(str1, str2) { + return toTitleCase(str1) === toTitleCase(str2); +} - var id = _window2['default'].requestAnimationFrame(fn); - var disposeFn = function disposeFn() { - return _this2.cancelAnimationFrame(id); - }; +/** + * @file merge-options.js + * @module merge-options + */ +/** + * Deep-merge one or more options objects, recursively merging **only** plain + * object properties. + * + * @param {Object[]} sources + * One or more objects to merge into a new object. + * + * @returns {Object} + * A new object that is the merged result of all sources. + */ +function mergeOptions() { + var result = {}; - disposeFn.guid = 'vjs-raf-' + id; - this.on('dispose', disposeFn); + for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) { + sources[_key] = arguments[_key]; + } - return id; + sources.forEach(function (source) { + if (!source) { + return; } - // Fall back to using a timer. - return this.setTimeout(fn, 1000 / 60); - }; - - /** - * Cancels a queued callback passed to {@link Component#requestAnimationFrame} - * (rAF). - * - * If you queue an rAF callback via {@link Component#requestAnimationFrame}, - * use this function instead of `window.cancelAnimationFrame`. If you don't, - * your dispose listener will not get cleaned up until {@link Component#dispose}! - * - * @param {number} id - * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. - * - * @return {number} - * Returns the rAF ID that was cleared. - * - * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} - */ - - - Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { - if (this.supportsRaf_) { - _window2['default'].cancelAnimationFrame(id); + each(source, function (value, key) { + if (!isPlain(value)) { + result[key] = value; + return; + } - var disposeFn = function disposeFn() {}; + if (!isPlain(result[key])) { + result[key] = {}; + } - disposeFn.guid = 'vjs-raf-' + id; + result[key] = mergeOptions(result[key], value); + }); + }); - this.off('dispose', disposeFn); + return result; +} - return id; - } +/** + * Player Component - Base class for all UI objects + * + * @file component.js + */ +/** + * Base class for all UI Components. + * Components are UI objects which represent both a javascript object and an element + * in the DOM. They can be children of other components, and can have + * children themselves. + * + * Components can also use methods from {@link EventTarget} + */ - // Fall back to using a timer. - return this.clearTimeout(id); - }; +var Component = function () { /** - * Register a `Component` with `videojs` given the name and the component. + * A callback that is called when a component is ready. Does not have any + * paramters and any callback value will be ignored. * - * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s - * should be registered using {@link Tech.registerTech} or - * {@link videojs:videojs.registerTech}. + * @callback Component~ReadyCallback + * @this Component + */ + + /** + * Creates an instance of this class. * - * > NOTE: This function can also be seen on videojs as - * {@link videojs:videojs.registerComponent}. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @param {string} name - * The name of the `Component` to register. + * @param {Object} [options] + * The key/value store of player options. * - * @param {Component} ComponentToRegister - * The `Component` class to register. + * @param {Object[]} [options.children] + * An array of children objects to intialize this component with. Children objects have + * a name property that will be used if more than one component of the same type needs to be + * added. * - * @return {Component} - * The `Component` that was registered. + * @param {Component~ReadyCallback} [ready] + * Function that gets called when the `Component` is ready. */ + function Component(player, options, ready) { + classCallCheck(this, Component); - Component.registerComponent = function registerComponent(name, ComponentToRegister) { - if (typeof name !== 'string' || !name) { - throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); + // The component might be the player itself and we can't pass `this` to super + if (!player && this.play) { + this.player_ = player = this; // eslint-disable-line + } else { + this.player_ = player; } - var Tech = Component.getComponent('Tech'); + // Make a copy of prototype.options_ to protect against overriding defaults + this.options_ = mergeOptions({}, this.options_); - // We need to make sure this check is only done if Tech has been registered. - var isTech = Tech && Tech.isTech(ComponentToRegister); - var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); + // Updated options with supplied options + options = this.options_ = mergeOptions(this.options_, options); - if (isTech || !isComp) { - var reason = void 0; + // Get ID from options or options element if one is supplied + this.id_ = options.id || options.el && options.el.id; - if (isTech) { - reason = 'techs must be registered using Tech.registerTech()'; - } else { - reason = 'must be a Component subclass'; - } + // If there was no ID from the options, generate one + if (!this.id_) { + // Don't require the player ID function in the case of mock players + var id = player && player.id && player.id() || 'no_player'; - throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); + this.id_ = id + '_component_' + newGUID(); } - name = (0, _toTitleCase2['default'])(name); + this.name_ = options.name || null; - if (!Component.components_) { - Component.components_ = {}; + // Create element if one wasn't provided in options + if (options.el) { + this.el_ = options.el; + } else if (options.createEl !== false) { + this.el_ = this.createEl(); } - var Player = Component.getComponent('Player'); + // Make this an evented object and use `el_`, if available, as its event bus + evented(this, { eventBusKey: this.el_ ? 'el_' : null }); + stateful(this, this.constructor.defaultState); - if (name === 'Player' && Player && Player.players) { - var players = Player.players; - var playerNames = Object.keys(players); + this.children_ = []; + this.childIndex_ = {}; + this.childNameIndex_ = {}; - // If we have players that were disposed, then their name will still be - // in Players.players. So, we must loop through and verify that the value - // for each item is not null. This allows registration of the Player component - // after all players have been disposed or before any were created. - if (players && playerNames.length > 0 && playerNames.map(function (pname) { - return players[pname]; - }).every(Boolean)) { - throw new Error('Can not register Player component after player has been created.'); - } + // Add any child components in options + if (options.initChildren !== false) { + this.initChildren(); } - Component.components_[name] = ComponentToRegister; + this.ready(ready); + // Don't want to trigger ready here or it will before init is actually + // finished for all children that run this constructor - return ComponentToRegister; - }; + if (options.reportTouchActivity !== false) { + this.enableTouchActivity(); + } + } /** - * Get a `Component` based on the name it was registered with. - * - * @param {string} name - * The Name of the component to get. - * - * @return {Component} - * The `Component` that got registered under the given name. + * Dispose of the `Component` and all child components. * - * @deprecated In `videojs` 6 this will not return `Component`s that were not - * registered using {@link Component.registerComponent}. Currently we - * check the global `videojs` object for a `Component` name and - * return that if it exists. + * @fires Component#dispose */ - Component.getComponent = function getComponent(name) { - if (!name) { - return; + Component.prototype.dispose = function dispose() { + + /** + * Triggered when a `Component` is disposed. + * + * @event Component#dispose + * @type {EventTarget~Event} + * + * @property {boolean} [bubbles=false] + * set to false so that the close event does not + * bubble up + */ + this.trigger({ type: 'dispose', bubbles: false }); + + // Dispose all children. + if (this.children_) { + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i].dispose) { + this.children_[i].dispose(); + } + } } - name = (0, _toTitleCase2['default'])(name); + // Delete child references + this.children_ = null; + this.childIndex_ = null; + this.childNameIndex_ = null; - if (Component.components_ && Component.components_[name]) { - return Component.components_[name]; + if (this.el_) { + // Remove element from DOM + if (this.el_.parentNode) { + this.el_.parentNode.removeChild(this.el_); + } + + removeData(this.el_); + this.el_ = null; } }; - return Component; -}(); + /** + * Return the {@link Player} that the `Component` has attached to. + * + * @return {Player} + * The player that this `Component` has attached to. + */ -/** - * Whether or not this component supports `requestAnimationFrame`. - * - * This is exposed primarily for testing purposes. - * - * @private - * @type {Boolean} - */ - - -Component.prototype.supportsRaf_ = typeof _window2['default'].requestAnimationFrame === 'function' && typeof _window2['default'].cancelAnimationFrame === 'function'; - -Component.registerComponent('Component', Component); - -exports['default'] = Component; - -},{"100":100,"53":53,"54":54,"84":84,"85":85,"88":88,"90":90,"91":91,"92":92,"96":96}],6:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _trackButton = _dereq_(38); - -var _trackButton2 = _interopRequireDefault(_trackButton); -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _audioTrackMenuItem = _dereq_(7); - -var _audioTrackMenuItem2 = _interopRequireDefault(_audioTrackMenuItem); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + Component.prototype.player = function player() { + return this.player_; + }; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * Deep merge of options objects with new options. + * > Note: When both `obj` and `options` contain properties whose values are objects. + * The two properties get merged using {@link module:mergeOptions} + * + * @param {Object} obj + * The object that contains new options. + * + * @return {Object} + * A new object of `this.options_` and `obj` merged together. + * + * @deprecated since version 5 + */ -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file audio-track-button.js - */ + Component.prototype.options = function options(obj) { + log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0'); + if (!obj) { + return this.options_; + } -/** - * The base class for buttons that toggle specific {@link AudioTrack} types. - * - * @extends TrackButton - */ -var AudioTrackButton = function (_TrackButton) { - _inherits(AudioTrackButton, _TrackButton); + this.options_ = mergeOptions(this.options_, obj); + return this.options_; + }; /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. + * Get the `Component`s DOM element * - * @param {Object} [options={}] - * The key/value store of player options. + * @return {Element} + * The DOM element for this `Component`. */ - function AudioTrackButton(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, AudioTrackButton); - options.tracks = player.audioTracks(); - return _possibleConstructorReturn(this, _TrackButton.call(this, player, options)); - } + Component.prototype.el = function el() { + return this.el_; + }; /** - * Builds the default DOM `className`. + * Create the `Component`s DOM element. * - * @return {string} - * The DOM `className` for this object. + * @param {string} [tagName] + * Element's DOM node type. e.g. 'div' + * + * @param {Object} [properties] + * An object of properties that should be set. + * + * @param {Object} [attributes] + * An object of attributes that should be set. + * + * @return {Element} + * The element that gets created. */ - AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this); - }; - - AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this); + Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) { + return createEl(tagName, properties, attributes); }; /** - * Create a menu item for each audio track + * Localize a string given the string in english. * - * @param {AudioTrackMenuItem[]} [items=[]] - * An array of existing menu items to use. + * If tokens are provided, it'll try and run a simple token replacement on the provided string. + * The tokens it loooks for look like `{1}` with the index being 1-indexed into the tokens array. * - * @return {AudioTrackMenuItem[]} - * An array of menu items + * If a `defaultValue` is provided, it'll use that over `string`, + * if a value isn't found in provided language files. + * This is useful if you want to have a descriptive key for token replacement + * but have a succinct localized string and not require `en.json` to be included. + * + * Currently, it is used for the progress bar timing. + * ```js + * { + * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}" + * } + * ``` + * It is then used like so: + * ```js + * this.localize('progress bar timing: currentTime={1} duration{2}', + * [this.player_.currentTime(), this.player_.duration()], + * '{1} of {2}'); + * ``` + * + * Which outputs something like: `01:23 of 24:56`. + * + * + * @param {string} string + * The string to localize and the key to lookup in the language files. + * @param {string[]} [tokens] + * If the current item has token replacements, provide the tokens here. + * @param {string} [defaultValue] + * Defaults to `string`. Can be a default value to use for token replacement + * if the lookup key is needed to be separate. + * + * @return {string} + * The localized string or if no localization exists the english string. */ - AudioTrackButton.prototype.createItems = function createItems() { - var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; - - // if there's only one audio track, there no point in showing it - this.hideThreshold_ = 1; + Component.prototype.localize = function localize(string, tokens) { + var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string; - var tracks = this.player_.audioTracks(); + var code = this.player_.language && this.player_.language(); + var languages = this.player_.languages && this.player_.languages(); + var language = languages && languages[code]; + var primaryCode = code && code.split('-')[0]; + var primaryLang = languages && languages[primaryCode]; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; + var localizedString = defaultValue; - items.push(new _audioTrackMenuItem2['default'](this.player_, { - track: track, - // MenuItem is selectable - selectable: true - })); + if (language && language[string]) { + localizedString = language[string]; + } else if (primaryLang && primaryLang[string]) { + localizedString = primaryLang[string]; } - return items; - }; - - return AudioTrackButton; -}(_trackButton2['default']); + if (tokens) { + localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) { + var value = tokens[index - 1]; + var ret = value; -/** - * The text that should display over the `AudioTrackButton`s controls. Added for localization. - * - * @type {string} - * @private - */ + if (typeof value === 'undefined') { + ret = match; + } + return ret; + }); + } -AudioTrackButton.prototype.controlText_ = 'Audio Track'; -_component2['default'].registerComponent('AudioTrackButton', AudioTrackButton); -exports['default'] = AudioTrackButton; + return localizedString; + }; -},{"38":38,"5":5,"7":7}],7:[function(_dereq_,module,exports){ -'use strict'; + /** + * Return the `Component`s DOM element. This is where children get inserted. + * This will usually be the the same as the element returned in {@link Component#el}. + * + * @return {Element} + * The content element for this `Component`. + */ -exports.__esModule = true; -var _menuItem = _dereq_(51); + Component.prototype.contentEl = function contentEl() { + return this.contentEl_ || this.el_; + }; -var _menuItem2 = _interopRequireDefault(_menuItem); + /** + * Get this `Component`s ID + * + * @return {string} + * The id of this `Component` + */ -var _component = _dereq_(5); -var _component2 = _interopRequireDefault(_component); + Component.prototype.id = function id() { + return this.id_; + }; -var _fn = _dereq_(88); + /** + * Get the `Component`s name. The name gets used to reference the `Component` + * and is set during registration. + * + * @return {string} + * The name of this `Component`. + */ -var Fn = _interopRequireWildcard(_fn); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + Component.prototype.name = function name() { + return this.name_; + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + /** + * Get an array of all child components + * + * @return {Array} + * The children + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + Component.prototype.children = function children() { + return this.children_; + }; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file audio-track-menu-item.js - */ + /** + * Returns the child `Component` with the given `id`. + * + * @param {string} id + * The id of the child `Component` to get. + * + * @return {Component|undefined} + * The child `Component` with the given `id` or undefined. + */ -/** - * An {@link AudioTrack} {@link MenuItem} - * - * @extends MenuItem - */ -var AudioTrackMenuItem = function (_MenuItem) { - _inherits(AudioTrackMenuItem, _MenuItem); + Component.prototype.getChildById = function getChildById(id) { + return this.childIndex_[id]; + }; /** - * Creates an instance of this class. + * Returns the child `Component` with the given `name`. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {string} name + * The name of the child `Component` to get. * - * @param {Object} [options] - * The key/value store of player options. + * @return {Component|undefined} + * The child `Component` with the given `name` or undefined. */ - function AudioTrackMenuItem(player, options) { - _classCallCheck(this, AudioTrackMenuItem); - - var track = options.track; - var tracks = player.audioTracks(); - // Modify options for parent MenuItem class's init. - options.label = track.label || track.language || 'Unknown'; - options.selected = track.enabled; - var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + Component.prototype.getChild = function getChild(name) { + if (!name) { + return; + } - _this.track = track; + name = toTitleCase(name); - var changeHandler = Fn.bind(_this, _this.handleTracksChange); - - tracks.addEventListener('change', changeHandler); - _this.on('dispose', function () { - tracks.removeEventListener('change', changeHandler); - }); - return _this; - } + return this.childNameIndex_[name]; + }; /** - * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent} - * for more detailed information on what a click can be. + * Add a child `Component` inside the current `Component`. * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. * - * @listens tap - * @listens click - */ - - - AudioTrackMenuItem.prototype.handleClick = function handleClick(event) { - var tracks = this.player_.audioTracks(); - - _MenuItem.prototype.handleClick.call(this, event); - - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - - track.enabled = track === this.track; - } - }; - - /** - * Handle any {@link AudioTrack} change. + * @param {string|Component} child + * The name or instance of a child to add. * - * @param {EventTarget~Event} [event] - * The {@link AudioTrackList#change} event that caused this to run. + * @param {Object} [options={}] + * The key/value store of options that will get passed to children of + * the child. * - * @listens AudioTrackList#change + * @param {number} [index=this.children_.length] + * The index to attempt to add a child into. + * + * @return {Component} + * The `Component` that gets added as a child. When using a string the + * `Component` will get created by this process. */ - AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { - this.selected(this.track.enabled); - }; - - return AudioTrackMenuItem; -}(_menuItem2['default']); - -_component2['default'].registerComponent('AudioTrackMenuItem', AudioTrackMenuItem); -exports['default'] = AudioTrackMenuItem; - -},{"5":5,"51":51,"88":88}],8:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -_dereq_(12); + Component.prototype.addChild = function addChild(child) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length; -_dereq_(34); + var component = void 0; + var componentName = void 0; -_dereq_(35); + // If child is a string, create component with options + if (typeof child === 'string') { + componentName = toTitleCase(child); -_dereq_(37); + var componentClassName = options.componentClass || componentName; -_dereq_(36); + // Set name through options + options.name = componentName; -_dereq_(10); + // Create a new object & element for this controls set + // If there's no .player_, this is a player + var ComponentClass = Component.getComponent(componentClassName); -_dereq_(18); + if (!ComponentClass) { + throw new Error('Component ' + componentClassName + ' does not exist'); + } -_dereq_(9); + // data stored directly on the videojs object may be + // misidentified as a component to retain + // backwards-compatibility with 4.x. check to make sure the + // component class can be instantiated. + if (typeof ComponentClass !== 'function') { + return null; + } -_dereq_(43); + component = new ComponentClass(this.player_ || this, options); -_dereq_(25); + // child is a component instance + } else { + component = child; + } -_dereq_(27); + this.children_.splice(index, 0, component); -_dereq_(31); + if (typeof component.id === 'function') { + this.childIndex_[component.id()] = component; + } -_dereq_(24); + // If a name wasn't used to create the component, check if we can use the + // name function of the component + componentName = componentName || component.name && toTitleCase(component.name()); -_dereq_(29); + if (componentName) { + this.childNameIndex_[componentName] = component; + } -_dereq_(6); + // Add the UI object's element to the container div (box) + // Having an element is not required + if (typeof component.el === 'function' && component.el()) { + var childNodes = this.contentEl().children; + var refNode = childNodes[index] || null; -_dereq_(13); + this.contentEl().insertBefore(component.el(), refNode); + } -_dereq_(21); + // Return so it can stored on parent object if desired. + return component; + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + /** + * Remove a child `Component` from this `Component`s list of children. Also removes + * the child `Component`s element from this `Component`s element. + * + * @param {Component} component + * The child `Component` to remove. + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + Component.prototype.removeChild = function removeChild(component) { + if (typeof component === 'string') { + component = this.getChild(component); + } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file control-bar.js - */ + if (!component || !this.children_) { + return; + } + var childFound = false; -// Required children + for (var i = this.children_.length - 1; i >= 0; i--) { + if (this.children_[i] === component) { + childFound = true; + this.children_.splice(i, 1); + break; + } + } + if (!childFound) { + return; + } -/** - * Container of main controls. - * - * @extends Component - */ -var ControlBar = function (_Component) { - _inherits(ControlBar, _Component); + this.childIndex_[component.id()] = null; + this.childNameIndex_[component.name()] = null; - function ControlBar() { - _classCallCheck(this, ControlBar); + var compEl = component.el(); - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } + if (compEl && compEl.parentNode === this.contentEl()) { + this.contentEl().removeChild(component.el()); + } + }; /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. + * Add and initialize default child `Component`s based upon options. */ - ControlBar.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-control-bar', - dir: 'ltr' - }, { - // The control bar is a group, but we don't aria-label it to avoid - // over-announcing by JAWS - role: 'group' - }); - }; - return ControlBar; -}(_component2['default']); -/** - * Default options for `ControlBar` - * - * @type {Object} - * @private - */ + Component.prototype.initChildren = function initChildren() { + var _this = this; + var children = this.options_.children; -ControlBar.prototype.options_ = { - children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle'] -}; + if (children) { + // `this` is `parent` + var parentOptions = this.options_; -_component2['default'].registerComponent('ControlBar', ControlBar); -exports['default'] = ControlBar; + var handleAdd = function handleAdd(child) { + var name = child.name; + var opts = child.opts; -},{"10":10,"12":12,"13":13,"18":18,"21":21,"24":24,"25":25,"27":27,"29":29,"31":31,"34":34,"35":35,"36":36,"37":37,"43":43,"5":5,"6":6,"9":9}],9:[function(_dereq_,module,exports){ -'use strict'; + // Allow options for children to be set at the parent options + // e.g. videojs(id, { controlBar: false }); + // instead of videojs(id, { children: { controlBar: false }); + if (parentOptions[name] !== undefined) { + opts = parentOptions[name]; + } -exports.__esModule = true; + // Allow for disabling default components + // e.g. options['children']['posterImage'] = false + if (opts === false) { + return; + } -var _button = _dereq_(2); + // Allow options to be passed as a simple boolean if no configuration + // is necessary. + if (opts === true) { + opts = {}; + } -var _button2 = _interopRequireDefault(_button); + // We also want to pass the original player options + // to each component as well so they don't need to + // reach back into the player for options later. + opts.playerOptions = _this.options_.playerOptions; -var _component = _dereq_(5); + // Create and add the child component. + // Add a direct reference to the child by name on the parent instance. + // If two of the same component are used, different names should be supplied + // for each + var newChild = _this.addChild(name, opts); -var _component2 = _interopRequireDefault(_component); + if (newChild) { + _this[name] = newChild; + } + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + // Allow for an array of children details to passed in the options + var workingChildren = void 0; + var Tech = Component.getComponent('Tech'); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + if (Array.isArray(children)) { + workingChildren = children; + } else { + workingChildren = Object.keys(children); + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + workingChildren + // children that are in this.options_ but also in workingChildren would + // give us extra children we do not want. So, we want to filter them out. + .concat(Object.keys(this.options_).filter(function (child) { + return !workingChildren.some(function (wchild) { + if (typeof wchild === 'string') { + return child === wchild; + } + return child === wchild.name; + }); + })).map(function (child) { + var name = void 0; + var opts = void 0; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file fullscreen-toggle.js - */ + if (typeof child === 'string') { + name = child; + opts = children[name] || _this.options_[name] || {}; + } else { + name = child.name; + opts = child; + } + return { name: name, opts: opts }; + }).filter(function (child) { + // we have to make sure that child.name isn't in the techOrder since + // techs are registerd as Components but can't aren't compatible + // See https://github.com/videojs/video.js/issues/2772 + var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name)); -/** - * Toggle fullscreen video - * - * @extends Button - */ -var FullscreenToggle = function (_Button) { - _inherits(FullscreenToggle, _Button); + return c && !Tech.isTech(c); + }).forEach(handleAdd); + } + }; /** - * Creates an instance of this class. + * Builds the default DOM class name. Should be overriden by sub-components. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @return {string} + * The DOM class name for this object. * - * @param {Object} [options] - * The key/value store of player options. - */ - function FullscreenToggle(player, options) { - _classCallCheck(this, FullscreenToggle); - - var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); - - _this.on(player, 'fullscreenchange', _this.handleFullscreenChange); - return _this; - } - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. + * @abstract */ - FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this); + Component.prototype.buildCSSClass = function buildCSSClass() { + // Child classes can include a function that does: + // return 'CLASS NAME' + this._super(); + return ''; }; /** - * Handles fullscreenchange on the player and change control text accordingly. - * - * @param {EventTarget~Event} [event] - * The {@link Player#fullscreenchange} event that caused this function to be - * called. + * Bind a listener to the component's ready state. + * Different from event listeners in that if the ready event has already happened + * it will trigger the function immediately. * - * @listens Player#fullscreenchange + * @return {Component} + * Returns itself; method can be chained. */ - FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) { - if (this.player_.isFullscreen()) { - this.controlText('Non-Fullscreen'); - } else { - this.controlText('Fullscreen'); + Component.prototype.ready = function ready(fn) { + var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (fn) { + if (this.isReady_) { + if (sync) { + fn.call(this); + } else { + // Call the function asynchronously by default for consistency + this.setTimeout(fn, 1); + } + } else { + this.readyQueue_ = this.readyQueue_ || []; + this.readyQueue_.push(fn); + } } }; /** - * This gets called when an `FullscreenToggle` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. - * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * Trigger all the ready listeners for this `Component`. * - * @listens tap - * @listens click + * @fires Component#ready */ - FullscreenToggle.prototype.handleClick = function handleClick(event) { - if (!this.player_.isFullscreen()) { - this.player_.requestFullscreen(); - } else { - this.player_.exitFullscreen(); - } - }; - - return FullscreenToggle; -}(_button2['default']); - -/** - * The text that should display over the `FullscreenToggle`s controls. Added for localization. - * - * @type {string} - * @private - */ - - -FullscreenToggle.prototype.controlText_ = 'Fullscreen'; - -_component2['default'].registerComponent('FullscreenToggle', FullscreenToggle); -exports['default'] = FullscreenToggle; - -},{"2":2,"5":5}],10:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + Component.prototype.triggerReady = function triggerReady() { + this.isReady_ = true; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file live-display.js - */ + // Ensure ready is triggerd asynchronously + this.setTimeout(function () { + var readyQueue = this.readyQueue_; + // Reset Ready Queue + this.readyQueue_ = []; -// TODO - Future make it click to snap to live + if (readyQueue && readyQueue.length > 0) { + readyQueue.forEach(function (fn) { + fn.call(this); + }, this); + } -/** - * Displays the live indicator when duration is Infinity. - * - * @extends Component - */ -var LiveDisplay = function (_Component) { - _inherits(LiveDisplay, _Component); + // Allow for using event listeners also + /** + * Triggered when a `Component` is ready. + * + * @event Component#ready + * @type {EventTarget~Event} + */ + this.trigger('ready'); + }, 1); + }; /** - * Creates an instance of this class. + * Find a single DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelector`. * - * @param {Object} [options] - * The key/value store of player options. + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {Element|null} + * the dom element that was found, or null + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ - function LiveDisplay(player, options) { - _classCallCheck(this, LiveDisplay); - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - _this.updateShowing(); - _this.on(_this.player(), 'durationchange', _this.updateShowing); - return _this; - } + Component.prototype.$ = function $$$1(selector, context) { + return $(selector, context || this.contentEl()); + }; /** - * Create the `Component`'s DOM element + * Finds all DOM element matching a `selector`. This can be within the `Component`s + * `contentEl()` or another custom context. * - * @return {Element} - * The element that was created. + * @param {string} selector + * A valid CSS selector, which will be passed to `querySelectorAll`. + * + * @param {Element|string} [context=this.contentEl()] + * A DOM element within which to query. Can also be a selector string in + * which case the first matching element will get used as context. If + * missing `this.contentEl()` gets used. If `this.contentEl()` returns + * nothing it falls back to `document`. + * + * @return {NodeList} + * a list of dom elements that were found + * + * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors) */ - LiveDisplay.prototype.createEl = function createEl() { - var el = _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-live-control vjs-control' - }); - - this.contentEl_ = Dom.createEl('div', { - className: 'vjs-live-display', - innerHTML: '' + this.localize('Stream Type') + '' + this.localize('LIVE') - }, { - 'aria-live': 'off' - }); - - el.appendChild(this.contentEl_); - return el; + Component.prototype.$$ = function $$$$1(selector, context) { + return $$(selector, context || this.contentEl()); }; /** - * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide - * it accordingly + * Check if a component's element has a CSS class name. * - * @param {EventTarget~Event} [event] - * The {@link Player#durationchange} event that caused this function to run. + * @param {string} classToCheck + * CSS class name to check. * - * @listens Player#durationchange + * @return {boolean} + * - True if the `Component` has the class. + * - False if the `Component` does not have the class` */ - LiveDisplay.prototype.updateShowing = function updateShowing(event) { - if (this.player().duration() === Infinity) { - this.show(); - } else { - this.hide(); - } + Component.prototype.hasClass = function hasClass$$1(classToCheck) { + return hasClass(this.el_, classToCheck); }; - return LiveDisplay; -}(_component2['default']); - -_component2['default'].registerComponent('LiveDisplay', LiveDisplay); -exports['default'] = LiveDisplay; - -},{"5":5,"85":85}],11:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _button = _dereq_(2); + /** + * Add a CSS class name to the `Component`s element. + * + * @param {string} classToAdd + * CSS class name to add + */ -var _button2 = _interopRequireDefault(_button); -var _component = _dereq_(5); + Component.prototype.addClass = function addClass$$1(classToAdd) { + addClass(this.el_, classToAdd); + }; -var _component2 = _interopRequireDefault(_component); + /** + * Remove a CSS class name from the `Component`s element. + * + * @param {string} classToRemove + * CSS class name to remove + */ -var _dom = _dereq_(85); -var Dom = _interopRequireWildcard(_dom); + Component.prototype.removeClass = function removeClass$$1(classToRemove) { + removeClass(this.el_, classToRemove); + }; -var _checkVolumeSupport = _dereq_(39); + /** + * Add or remove a CSS class name from the component's element. + * - `classToToggle` gets added when {@link Component#hasClass} would return false. + * - `classToToggle` gets removed when {@link Component#hasClass} would return true. + * + * @param {string} classToToggle + * The class to add or remove based on (@link Component#hasClass} + * + * @param {boolean|Dom~predicate} [predicate] + * An {@link Dom~predicate} function or a boolean + */ -var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) { + toggleClass(this.el_, classToToggle, predicate); + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + /** + * Show the `Component`s element if it is hidden by removing the + * 'vjs-hidden' class name from it. + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + Component.prototype.show = function show() { + this.removeClass('vjs-hidden'); + }; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file mute-toggle.js - */ + /** + * Hide the `Component`s element if it is currently showing by adding the + * 'vjs-hidden` class name to it. + */ -/** - * A button component for muting the audio. - * - * @extends Button - */ -var MuteToggle = function (_Button) { - _inherits(MuteToggle, _Button); + Component.prototype.hide = function hide() { + this.addClass('vjs-hidden'); + }; /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. + * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing' + * class name to it. Used during fadeIn/fadeOut. * - * @param {Object} [options] - * The key/value store of player options. + * @private */ - function MuteToggle(player, options) { - _classCallCheck(this, MuteToggle); - // hide this control if volume support is missing - var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); - (0, _checkVolumeSupport2['default'])(_this, player); - - _this.on(player, ['loadstart', 'volumechange'], _this.update); - return _this; - } + Component.prototype.lockShowing = function lockShowing() { + this.addClass('vjs-lock-showing'); + }; /** - * Builds the default DOM `className`. + * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing' + * class name from it. Used during fadeIn/fadeOut. * - * @return {string} - * The DOM `className` for this object. + * @private */ - MuteToggle.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this); + Component.prototype.unlockShowing = function unlockShowing() { + this.removeClass('vjs-lock-showing'); }; /** - * This gets called when an `MuteToggle` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. + * Get the value of an attribute on the `Component`s element. * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * @param {string} attribute + * Name of the attribute to get the value from. * - * @listens tap - * @listens click + * @return {string|null} + * - The value of the attribute that was asked for. + * - Can be an empty string on some browsers if the attribute does not exist + * or has no value + * - Most browsers will return null if the attibute does not exist or has + * no value. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute} */ - MuteToggle.prototype.handleClick = function handleClick(event) { - var vol = this.player_.volume(); - var lastVolume = this.player_.lastVolume_(); - - if (vol === 0) { - var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume; - - this.player_.volume(volumeToSet); - this.player_.muted(false); - } else { - this.player_.muted(this.player_.muted() ? false : true); - } + Component.prototype.getAttribute = function getAttribute$$1(attribute) { + return getAttribute(this.el_, attribute); }; /** - * Update the `MuteToggle` button based on the state of `volume` and `muted` - * on the player. + * Set the value of an attribute on the `Component`'s element * - * @param {EventTarget~Event} [event] - * The {@link Player#loadstart} event if this function was called - * through an event. + * @param {string} attribute + * Name of the attribute to set. * - * @listens Player#loadstart - * @listens Player#volumechange + * @param {string} value + * Value to set the attribute to. + * + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute} */ - MuteToggle.prototype.update = function update(event) { - this.updateIcon_(); - this.updateControlText_(); + Component.prototype.setAttribute = function setAttribute$$1(attribute, value) { + setAttribute(this.el_, attribute, value); }; /** - * Update the appearance of the `MuteToggle` icon. + * Remove an attribute from the `Component`s element. * - * Possible states (given `level` variable below): - * - 0: crossed out - * - 1: zero bars of volume - * - 2: one bar of volume - * - 3: two bars of volume + * @param {string} attribute + * Name of the attribute to remove. * - * @private + * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute} */ - MuteToggle.prototype.updateIcon_ = function updateIcon_() { - var vol = this.player_.volume(); - var level = 3; - - if (vol === 0 || this.player_.muted()) { - level = 0; - } else if (vol < 0.33) { - level = 1; - } else if (vol < 0.67) { - level = 2; - } - - // TODO improve muted icon classes - for (var i = 0; i < 4; i++) { - Dom.removeClass(this.el_, 'vjs-vol-' + i); - } - Dom.addClass(this.el_, 'vjs-vol-' + level); + Component.prototype.removeAttribute = function removeAttribute$$1(attribute) { + removeAttribute(this.el_, attribute); }; /** - * If `muted` has changed on the player, update the control text - * (`title` attribute on `vjs-mute-control` element and content of - * `vjs-control-text` element). + * Get or set the width of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. * - * @private + * @param {number|string} [num] + * The width that you want to set postfixed with '%', 'px' or nothing. + * + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger + * + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. */ - MuteToggle.prototype.updateControlText_ = function updateControlText_() { - var soundOff = this.player_.muted() || this.player_.volume() === 0; - var text = soundOff ? 'Unmute' : 'Mute'; - - if (this.controlText() !== text) { - this.controlText(text); - } + Component.prototype.width = function width(num, skipListeners) { + return this.dimension('width', num, skipListeners); }; - return MuteToggle; -}(_button2['default']); - -/** - * The text that should display over the `MuteToggle`s controls. Added for localization. - * - * @type {string} - * @private - */ - - -MuteToggle.prototype.controlText_ = 'Mute'; - -_component2['default'].registerComponent('MuteToggle', MuteToggle); -exports['default'] = MuteToggle; - -},{"2":2,"39":39,"5":5,"85":85}],12:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _button = _dereq_(2); - -var _button2 = _interopRequireDefault(_button); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file play-toggle.js - */ - - -/** - * Button to toggle between play and pause. - * - * @extends Button - */ -var PlayToggle = function (_Button) { - _inherits(PlayToggle, _Button); - /** - * Creates an instance of this class. + * Get or set the height of the component based upon the CSS styles. + * See {@link Component#dimension} for more detailed information. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {number|string} [num] + * The height that you want to set postfixed with '%', 'px' or nothing. * - * @param {Object} [options] - * The key/value store of player options. - */ - function PlayToggle(player, options) { - _classCallCheck(this, PlayToggle); - - var _this = _possibleConstructorReturn(this, _Button.call(this, player, options)); - - _this.on(player, 'play', _this.handlePlay); - _this.on(player, 'pause', _this.handlePause); - _this.on(player, 'ended', _this.handleEnded); - return _this; - } - - /** - * Builds the default DOM `className`. + * @param {boolean} [skipListeners] + * Skip the componentresize event trigger * - * @return {string} - * The DOM `className` for this object. + * @return {number|string} + * The width when getting, zero if there is no width. Can be a string + * postpixed with '%' or 'px'. */ - PlayToggle.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this); + Component.prototype.height = function height(num, skipListeners) { + return this.dimension('height', num, skipListeners); }; /** - * This gets called when an `PlayToggle` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. + * Set both the width and height of the `Component` element at the same time. * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * @param {number|string} width + * Width to set the `Component`s element to. * - * @listens tap - * @listens click + * @param {number|string} height + * Height to set the `Component`s element to. */ - PlayToggle.prototype.handleClick = function handleClick(event) { - if (this.player_.paused()) { - this.player_.play(); - } else { - this.player_.pause(); - } + Component.prototype.dimensions = function dimensions(width, height) { + // Skip componentresize listeners on width for optimization + this.width(width, true); + this.height(height); }; /** - * Add the vjs-playing class to the element so it can change appearance. + * Get or set width or height of the `Component` element. This is the shared code + * for the {@link Component#width} and {@link Component#height}. * - * @param {EventTarget~Event} [event] - * The event that caused this function to run. + * Things to know: + * - If the width or height in an number this will return the number postfixed with 'px'. + * - If the width/height is a percent this will return the percent postfixed with '%' + * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function + * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`. + * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/} + * for more information + * - If you want the computed style of the component, use {@link Component#currentWidth} + * and {@link {Component#currentHeight} * - * @listens Player#play - */ - - - PlayToggle.prototype.handlePlay = function handlePlay(event) { - this.removeClass('vjs-ended'); - this.removeClass('vjs-paused'); - this.addClass('vjs-playing'); - // change the button text to "Pause" - this.controlText('Pause'); - }; - - /** - * Add the vjs-paused class to the element so it can change appearance. + * @fires Component#componentresize * - * @param {EventTarget~Event} [event] - * The event that caused this function to run. + * @param {string} widthOrHeight + 8 'width' or 'height' * - * @listens Player#pause - */ - - - PlayToggle.prototype.handlePause = function handlePause(event) { - this.removeClass('vjs-playing'); - this.addClass('vjs-paused'); - // change the button text to "Play" - this.controlText('Play'); - }; - - /** - * Add the vjs-ended class to the element so it can change appearance + * @param {number|string} [num] + 8 New dimension * + * @param {boolean} [skipListeners] + * Skip componentresize event trigger + * + * @return {number} + * The dimension when getting or 0 if unset */ - PlayToggle.prototype.handleEnded = function handleEnded(event) { - this.removeClass('vjs-playing'); - this.addClass('vjs-ended'); - // change the button text to "Replay" - this.controlText('Replay'); - }; - - return PlayToggle; -}(_button2['default']); - -/** - * The text that should display over the `PlayToggle`s controls. Added for localization. - * - * @type {string} - * @private - */ - - -PlayToggle.prototype.controlText_ = 'Play'; - -_component2['default'].registerComponent('PlayToggle', PlayToggle); -exports['default'] = PlayToggle; - -},{"2":2,"5":5}],13:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _menuButton = _dereq_(50); - -var _menuButton2 = _interopRequireDefault(_menuButton); - -var _menu = _dereq_(52); - -var _menu2 = _interopRequireDefault(_menu); - -var _playbackRateMenuItem = _dereq_(14); - -var _playbackRateMenuItem2 = _interopRequireDefault(_playbackRateMenuItem); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); + Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) { + if (num !== undefined) { + // Set to zero if null or literally NaN (NaN !== NaN) + if (num === null || num !== num) { + num = 0; + } -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + // Check if using css width/height (% or px) and adjust + if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) { + this.el_.style[widthOrHeight] = num; + } else if (num === 'auto') { + this.el_.style[widthOrHeight] = ''; + } else { + this.el_.style[widthOrHeight] = num + 'px'; + } -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + // skipListeners allows us to avoid triggering the resize event when setting both width and height + if (!skipListeners) { + /** + * Triggered when a component is resized. + * + * @event Component#componentresize + * @type {EventTarget~Event} + */ + this.trigger('componentresize'); + } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + return; + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + // Not setting a value, so getting it + // Make sure element exists + if (!this.el_) { + return 0; + } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file playback-rate-menu-button.js - */ + // Get dimension value from style + var val = this.el_.style[widthOrHeight]; + var pxIndex = val.indexOf('px'); + if (pxIndex !== -1) { + // Return the pixel value with no 'px' + return parseInt(val.slice(0, pxIndex), 10); + } -/** - * The component for controlling the playback rate. - * - * @extends MenuButton - */ -var PlaybackRateMenuButton = function (_MenuButton) { - _inherits(PlaybackRateMenuButton, _MenuButton); + // No px so using % or no style was set, so falling back to offsetWidth/height + // If component has display:none, offset will return 0 + // TODO: handle display:none and no dimension style using px + return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10); + }; /** - * Creates an instance of this class. + * Get the width or the height of the `Component` elements computed style. Uses + * `window.getComputedStyle`. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {string} widthOrHeight + * A string containing 'width' or 'height'. Whichever one you want to get. * - * @param {Object} [options] - * The key/value store of player options. + * @return {number} + * The dimension that gets asked for or 0 if nothing was set + * for that dimension. */ - function PlaybackRateMenuButton(player, options) { - _classCallCheck(this, PlaybackRateMenuButton); - var _this = _possibleConstructorReturn(this, _MenuButton.call(this, player, options)); - _this.updateVisibility(); - _this.updateLabel(); + Component.prototype.currentDimension = function currentDimension(widthOrHeight) { + var computedWidthOrHeight = 0; - _this.on(player, 'loadstart', _this.updateVisibility); - _this.on(player, 'ratechange', _this.updateLabel); - return _this; - } + if (widthOrHeight !== 'width' && widthOrHeight !== 'height') { + throw new Error('currentDimension only accepts width or height value'); + } - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ + if (typeof window_1.getComputedStyle === 'function') { + var computedStyle = window_1.getComputedStyle(this.el_); + computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight]; + } - PlaybackRateMenuButton.prototype.createEl = function createEl() { - var el = _MenuButton.prototype.createEl.call(this); + // remove 'px' from variable and parse as integer + computedWidthOrHeight = parseFloat(computedWidthOrHeight); - this.labelEl_ = Dom.createEl('div', { - className: 'vjs-playback-rate-value', - innerHTML: 1.0 - }); + // if the computed value is still 0, it's possible that the browser is lying + // and we want to check the offset values. + // This code also runs on IE8 and wherever getComputedStyle doesn't exist. + if (computedWidthOrHeight === 0) { + var rule = 'offset' + toTitleCase(widthOrHeight); - el.appendChild(this.labelEl_); + computedWidthOrHeight = this.el_[rule]; + } - return el; + return computedWidthOrHeight; }; /** - * Builds the default DOM `className`. + * An object that contains width and height values of the `Component`s + * computed style. Uses `window.getComputedStyle`. * - * @return {string} - * The DOM `className` for this object. - */ - - - PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this); - }; - - PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this); - }; - - /** - * Create the playback rate menu + * @typedef {Object} Component~DimensionObject * - * @return {Menu} - * Menu object populated with {@link PlaybackRateMenuItem}s + * @property {number} width + * The width of the `Component`s computed style. + * + * @property {number} height + * The height of the `Component`s computed style. */ - - PlaybackRateMenuButton.prototype.createMenu = function createMenu() { - var menu = new _menu2['default'](this.player()); - var rates = this.playbackRates(); - - if (rates) { - for (var i = rates.length - 1; i >= 0; i--) { - menu.addChild(new _playbackRateMenuItem2['default'](this.player(), { rate: rates[i] + 'x' })); - } - } - - return menu; - }; - /** - * Updates ARIA accessibility attributes + * Get an object that contains width and height values of the `Component`s + * computed style. + * + * @return {Component~DimensionObject} + * The dimensions of the components element */ - PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() { - // Current playback rate - this.el().setAttribute('aria-valuenow', this.player().playbackRate()); + Component.prototype.currentDimensions = function currentDimensions() { + return { + width: this.currentDimension('width'), + height: this.currentDimension('height') + }; }; /** - * This gets called when an `PlaybackRateMenuButton` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. - * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`. * - * @listens tap - * @listens click + * @return {number} width + * The width of the `Component`s computed style. */ - PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) { - // select next rate option - var currentRate = this.player().playbackRate(); - var rates = this.playbackRates(); - - // this will select first one if the last one currently selected - var newRate = rates[0]; - - for (var i = 0; i < rates.length; i++) { - if (rates[i] > currentRate) { - newRate = rates[i]; - break; - } - } - this.player().playbackRate(newRate); + Component.prototype.currentWidth = function currentWidth() { + return this.currentDimension('width'); }; /** - * Get possible playback rates + * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`. * - * @return {Array} - * All possible playback rates + * @return {number} height + * The height of the `Component`s computed style. */ - PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() { - return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates; + Component.prototype.currentHeight = function currentHeight() { + return this.currentDimension('height'); }; /** - * Get whether playback rates is supported by the tech - * and an array of playback rates exists - * - * @return {boolean} - * Whether changing playback rate is supported + * Set the focus to this component */ - PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() { - return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0; + Component.prototype.focus = function focus() { + this.el_.focus(); }; /** - * Hide playback rate controls when they're no playback rate options to select - * - * @param {EventTarget~Event} [event] - * The event that caused this function to run. - * - * @listens Player#loadstart + * Remove the focus from this component */ - PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) { - if (this.playbackRateSupported()) { - this.removeClass('vjs-hidden'); - } else { - this.addClass('vjs-hidden'); - } + Component.prototype.blur = function blur() { + this.el_.blur(); }; /** - * Update button label when rate changed - * - * @param {EventTarget~Event} [event] - * The event that caused this function to run. + * Emit a 'tap' events when touch event support gets detected. This gets used to + * support toggling the controls through a tap on the video. They get enabled + * because every sub-component would have extra overhead otherwise. * - * @listens Player#ratechange - */ + * @private + * @fires Component#tap + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchleave + * @listens Component#touchcancel + * @listens Component#touchend + */ - PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) { - if (this.playbackRateSupported()) { - this.labelEl_.innerHTML = this.player().playbackRate() + 'x'; - } - }; + Component.prototype.emitTapEvents = function emitTapEvents() { + // Track the start time so we can determine how long the touch lasted + var touchStart = 0; + var firstTouch = null; - return PlaybackRateMenuButton; -}(_menuButton2['default']); + // Maximum movement allowed during a touch event to still be considered a tap + // Other popular libs use anywhere from 2 (hammer.js) to 15, + // so 10 seems like a nice, round number. + var tapMovementThreshold = 10; -/** - * The text that should display over the `FullscreenToggle`s controls. Added for localization. - * - * @type {string} - * @private - */ + // The maximum length a touch can be while still being considered a tap + var touchTimeThreshold = 200; + var couldBeTap = void 0; -PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate'; + this.on('touchstart', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length === 1) { + // Copy pageX/pageY from the object + firstTouch = { + pageX: event.touches[0].pageX, + pageY: event.touches[0].pageY + }; + // Record start time so we can detect a tap vs. "touch and hold" + touchStart = new Date().getTime(); + // Reset couldBeTap tracking + couldBeTap = true; + } + }); -_component2['default'].registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton); -exports['default'] = PlaybackRateMenuButton; + this.on('touchmove', function (event) { + // If more than one finger, don't consider treating this as a click + if (event.touches.length > 1) { + couldBeTap = false; + } else if (firstTouch) { + // Some devices will throw touchmoves for all but the slightest of taps. + // So, if we moved only a small distance, this could still be a tap + var xdiff = event.touches[0].pageX - firstTouch.pageX; + var ydiff = event.touches[0].pageY - firstTouch.pageY; + var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff); -},{"14":14,"5":5,"50":50,"52":52,"85":85}],14:[function(_dereq_,module,exports){ -'use strict'; + if (touchDistance > tapMovementThreshold) { + couldBeTap = false; + } + } + }); -exports.__esModule = true; + var noTap = function noTap() { + couldBeTap = false; + }; -var _menuItem = _dereq_(51); + // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s + this.on('touchleave', noTap); + this.on('touchcancel', noTap); -var _menuItem2 = _interopRequireDefault(_menuItem); + // When the touch ends, measure how long it took and trigger the appropriate + // event + this.on('touchend', function (event) { + firstTouch = null; + // Proceed only if the touchmove/leave/cancel event didn't happen + if (couldBeTap === true) { + // Measure how long the touch lasted + var touchTime = new Date().getTime() - touchStart; -var _component = _dereq_(5); + // Make sure the touch was less than the threshold to be considered a tap + if (touchTime < touchTimeThreshold) { + // Don't let browser turn this into a click + event.preventDefault(); + /** + * Triggered when a `Component` is tapped. + * + * @event Component#tap + * @type {EventTarget~Event} + */ + this.trigger('tap'); + // It may be good to copy the touchend event object and change the + // type to tap, if the other event properties aren't exact after + // Events.fixEvent runs (e.g. event.target) + } + } + }); + }; -var _component2 = _interopRequireDefault(_component); + /** + * This function reports user activity whenever touch events happen. This can get + * turned off by any sub-components that wants touch events to act another way. + * + * Report user touch activity when touch events occur. User activity gets used to + * determine when controls should show/hide. It is simple when it comes to mouse + * events, because any mouse event should show the controls. So we capture mouse + * events that bubble up to the player and report activity when that happens. + * With touch events it isn't as easy as `touchstart` and `touchend` toggle player + * controls. So touch events can't help us at the player level either. + * + * User activity gets checked asynchronously. So what could happen is a tap event + * on the video turns the controls off. Then the `touchend` event bubbles up to + * the player. Which, if it reported user activity, would turn the controls right + * back on. We also don't want to completely block touch events from bubbling up. + * Furthermore a `touchmove` event and anything other than a tap, should not turn + * controls back on. + * + * @listens Component#touchstart + * @listens Component#touchmove + * @listens Component#touchend + * @listens Component#touchcancel + */ -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + Component.prototype.enableTouchActivity = function enableTouchActivity() { + // Don't continue if the root player doesn't support reporting user activity + if (!this.player() || !this.player().reportUserActivity) { + return; + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + // listener for reporting that the user is active + var report = bind(this.player(), this.player().reportUserActivity); -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file playback-rate-menu-item.js - */ + var touchHolding = void 0; + this.on('touchstart', function () { + report(); + // For as long as the they are touching the device or have their mouse down, + // we consider them active even if they're not moving their finger or mouse. + // So we want to continue to update that they are active + this.clearInterval(touchHolding); + // report at the same interval as activityCheck + touchHolding = this.setInterval(report, 250); + }); -/** - * The specific menu item type for selecting a playback rate. - * - * @extends MenuItem - */ -var PlaybackRateMenuItem = function (_MenuItem) { - _inherits(PlaybackRateMenuItem, _MenuItem); + var touchEnd = function touchEnd(event) { + report(); + // stop the interval that maintains activity if the touch is holding + this.clearInterval(touchHolding); + }; + + this.on('touchmove', report); + this.on('touchend', touchEnd); + this.on('touchcancel', touchEnd); + }; /** - * Creates an instance of this class. + * A callback that has no parameters and is bound into `Component`s context. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @callback Component~GenericCallback + * @this Component + */ + + /** + * Creates a function that runs after an `x` millisecond timeout. This function is a + * wrapper around `window.setTimeout`. There are a few reasons to use this one + * instead though: + * 1. It gets cleared via {@link Component#clearTimeout} when + * {@link Component#dispose} gets called. + * 2. The function callback will gets turned into a {@link Component~GenericCallback} * - * @param {Object} [options] - * The key/value store of player options. + * > Note: You can use `window.clearTimeout` on the id returned by this function. This + * will cause its dispose listener not to get cleaned up! Please use + * {@link Component#clearTimeout} or {@link Component#dispose}. + * + * @param {Component~GenericCallback} fn + * The function that will be run after `timeout`. + * + * @param {number} timeout + * Timeout in milliseconds to delay before executing the specified function. + * + * @return {number} + * Returns a timeout ID that gets used to identify the timeout. It can also + * get used in {@link Component#clearTimeout} to clear the timeout that + * was set. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout} */ - function PlaybackRateMenuItem(player, options) { - _classCallCheck(this, PlaybackRateMenuItem); - var label = options.rate; - var rate = parseFloat(label, 10); - // Modify options for parent MenuItem class's init. - options.label = label; - options.selected = rate === 1; - options.selectable = true; + Component.prototype.setTimeout = function setTimeout(fn, timeout) { + fn = bind(this, fn); - var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + var timeoutId = window_1.setTimeout(fn, timeout); + var disposeFn = function disposeFn() { + this.clearTimeout(timeoutId); + }; - _this.label = label; - _this.rate = rate; + disposeFn.guid = 'vjs-timeout-' + timeoutId; - _this.on(player, 'ratechange', _this.update); - return _this; - } + this.on('dispose', disposeFn); + + return timeoutId; + }; /** - * This gets called when an `PlaybackRateMenuItem` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. + * Clears a timeout that gets created via `window.setTimeout` or + * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout} + * use this function instead of `window.clearTimout`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * @param {number} timeoutId + * The id of the timeout to clear. The return value of + * {@link Component#setTimeout} or `window.setTimeout`. * - * @listens tap - * @listens click + * @return {number} + * Returns the timeout id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout} */ - PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) { - _MenuItem.prototype.handleClick.call(this); - this.player().playbackRate(this.rate); + Component.prototype.clearTimeout = function clearTimeout(timeoutId) { + window_1.clearTimeout(timeoutId); + + var disposeFn = function disposeFn() {}; + + disposeFn.guid = 'vjs-timeout-' + timeoutId; + + this.off('dispose', disposeFn); + + return timeoutId; }; /** - * Update the PlaybackRateMenuItem when the playbackrate changes. + * Creates a function that gets run every `x` milliseconds. This function is a wrapper + * around `window.setInterval`. There are a few reasons to use this one instead though. + * 1. It gets cleared via {@link Component#clearInterval} when + * {@link Component#dispose} gets called. + * 2. The function callback will be a {@link Component~GenericCallback} * - * @param {EventTarget~Event} [event] - * The `ratechange` event that caused this function to run. + * @param {Component~GenericCallback} fn + * The function to run every `x` seconds. * - * @listens Player#ratechange + * @param {number} interval + * Execute the specified function every `x` milliseconds. + * + * @return {number} + * Returns an id that can be used to identify the interval. It can also be be used in + * {@link Component#clearInterval} to clear the interval. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval} */ - PlaybackRateMenuItem.prototype.update = function update(event) { - this.selected(this.player().playbackRate() === this.rate); - }; + Component.prototype.setInterval = function setInterval(fn, interval) { + fn = bind(this, fn); - return PlaybackRateMenuItem; -}(_menuItem2['default']); + var intervalId = window_1.setInterval(fn, interval); -/** - * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization. - * - * @type {string} - * @private - */ + var disposeFn = function disposeFn() { + this.clearInterval(intervalId); + }; + disposeFn.guid = 'vjs-interval-' + intervalId; -PlaybackRateMenuItem.prototype.contentElType = 'button'; + this.on('dispose', disposeFn); + + return intervalId; + }; -_component2['default'].registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem); -exports['default'] = PlaybackRateMenuItem; + /** + * Clears an interval that gets created via `window.setInterval` or + * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval} + * use this function instead of `window.clearInterval`. If you don't your dispose + * listener will not get cleaned up until {@link Component#dispose}! + * + * @param {number} intervalId + * The id of the interval to clear. The return value of + * {@link Component#setInterval} or `window.setInterval`. + * + * @return {number} + * Returns the interval id that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval} + */ -},{"5":5,"51":51}],15:[function(_dereq_,module,exports){ -'use strict'; -exports.__esModule = true; + Component.prototype.clearInterval = function clearInterval(intervalId) { + window_1.clearInterval(intervalId); -var _component = _dereq_(5); + var disposeFn = function disposeFn() {}; -var _component2 = _interopRequireDefault(_component); + disposeFn.guid = 'vjs-interval-' + intervalId; -var _dom = _dereq_(85); + this.off('dispose', disposeFn); -var Dom = _interopRequireWildcard(_dom); + return intervalId; + }; -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + /** + * Queues up a callback to be passed to requestAnimationFrame (rAF), but + * with a few extra bonuses: + * + * - Supports browsers that do not support rAF by falling back to + * {@link Component#setTimeout}. + * + * - The callback is turned into a {@link Component~GenericCallback} (i.e. + * bound to the component). + * + * - Automatic cancellation of the rAF callback is handled if the component + * is disposed before it is called. + * + * @param {Component~GenericCallback} fn + * A function that will be bound to this component and executed just + * before the browser's next repaint. + * + * @return {number} + * Returns an rAF ID that gets used to identify the timeout. It can + * also be used in {@link Component#cancelAnimationFrame} to cancel + * the animation frame callback. + * + * @listens Component#dispose + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame} + */ -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) { + var _this2 = this; -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + if (this.supportsRaf_) { + fn = bind(this, fn); -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file load-progress-bar.js - */ + var id = window_1.requestAnimationFrame(fn); + var disposeFn = function disposeFn() { + return _this2.cancelAnimationFrame(id); + }; + disposeFn.guid = 'vjs-raf-' + id; + this.on('dispose', disposeFn); -/** - * Shows loading progress - * - * @extends Component - */ -var LoadProgressBar = function (_Component) { - _inherits(LoadProgressBar, _Component); + return id; + } + + // Fall back to using a timer. + return this.setTimeout(fn, 1000 / 60); + }; /** - * Creates an instance of this class. + * Cancels a queued callback passed to {@link Component#requestAnimationFrame} + * (rAF). * - * @param {Player} player - * The `Player` that this class should be attached to. + * If you queue an rAF callback via {@link Component#requestAnimationFrame}, + * use this function instead of `window.cancelAnimationFrame`. If you don't, + * your dispose listener will not get cleaned up until {@link Component#dispose}! * - * @param {Object} [options] - * The key/value store of player options. + * @param {number} id + * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}. + * + * @return {number} + * Returns the rAF ID that was cleared. + * + * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame} */ - function LoadProgressBar(player, options) { - _classCallCheck(this, LoadProgressBar); - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - _this.partEls_ = []; - _this.on(player, 'progress', _this.update); - return _this; - } + Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) { + if (this.supportsRaf_) { + window_1.cancelAnimationFrame(id); - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ + var disposeFn = function disposeFn() {}; + disposeFn.guid = 'vjs-raf-' + id; - LoadProgressBar.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-load-progress', - innerHTML: '' + this.localize('Loaded') + ': 0%' - }); + this.off('dispose', disposeFn); + + return id; + } + + // Fall back to using a timer. + return this.clearTimeout(id); }; /** - * Update progress bar + * Register a `Component` with `videojs` given the name and the component. * - * @param {EventTarget~Event} [event] - * The `progress` event that caused this function to run. + * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s + * should be registered using {@link Tech.registerTech} or + * {@link videojs:videojs.registerTech}. * - * @listens Player#progress + * > NOTE: This function can also be seen on videojs as + * {@link videojs:videojs.registerComponent}. + * + * @param {string} name + * The name of the `Component` to register. + * + * @param {Component} ComponentToRegister + * The `Component` class to register. + * + * @return {Component} + * The `Component` that was registered. */ - LoadProgressBar.prototype.update = function update(event) { - var buffered = this.player_.buffered(); - var duration = this.player_.duration(); - var bufferedEnd = this.player_.bufferedEnd(); - var children = this.partEls_; - - // get the percent width of a time compared to the total end - var percentify = function percentify(time, end) { - // no NaN - var percent = time / end || 0; + Component.registerComponent = function registerComponent(name, ComponentToRegister) { + if (typeof name !== 'string' || !name) { + throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.'); + } - return (percent >= 1 ? 1 : percent) * 100 + '%'; - }; + var Tech = Component.getComponent('Tech'); - // update the width of the progress bar - this.el_.style.width = percentify(bufferedEnd, duration); + // We need to make sure this check is only done if Tech has been registered. + var isTech = Tech && Tech.isTech(ComponentToRegister); + var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype); - // add child elements to represent the individual buffered time ranges - for (var i = 0; i < buffered.length; i++) { - var start = buffered.start(i); - var end = buffered.end(i); - var part = children[i]; + if (isTech || !isComp) { + var reason = void 0; - if (!part) { - part = this.el_.appendChild(Dom.createEl()); - children[i] = part; + if (isTech) { + reason = 'techs must be registered using Tech.registerTech()'; + } else { + reason = 'must be a Component subclass'; } - // set the percent based on the width of the progress bar (bufferedEnd) - part.style.left = percentify(start, bufferedEnd); - part.style.width = percentify(end - start, bufferedEnd); + throw new Error('Illegal component, "' + name + '"; ' + reason + '.'); } - // remove unused buffered range elements - for (var _i = children.length; _i > buffered.length; _i--) { - this.el_.removeChild(children[_i - 1]); - } - children.length = buffered.length; - }; + name = toTitleCase(name); - return LoadProgressBar; -}(_component2['default']); + if (!Component.components_) { + Component.components_ = {}; + } -_component2['default'].registerComponent('LoadProgressBar', LoadProgressBar); -exports['default'] = LoadProgressBar; + var Player = Component.getComponent('Player'); -},{"5":5,"85":85}],16:[function(_dereq_,module,exports){ -'use strict'; + if (name === 'Player' && Player && Player.players) { + var players = Player.players; + var playerNames = Object.keys(players); -exports.__esModule = true; + // If we have players that were disposed, then their name will still be + // in Players.players. So, we must loop through and verify that the value + // for each item is not null. This allows registration of the Player component + // after all players have been disposed or before any were created. + if (players && playerNames.length > 0 && playerNames.map(function (pname) { + return players[pname]; + }).every(Boolean)) { + throw new Error('Can not register Player component after player has been created.'); + } + } -var _component = _dereq_(5); + Component.components_[name] = ComponentToRegister; -var _component2 = _interopRequireDefault(_component); + return ComponentToRegister; + }; -var _fn = _dereq_(88); + /** + * Get a `Component` based on the name it was registered with. + * + * @param {string} name + * The Name of the component to get. + * + * @return {Component} + * The `Component` that got registered under the given name. + * + * @deprecated In `videojs` 6 this will not return `Component`s that were not + * registered using {@link Component.registerComponent}. Currently we + * check the global `videojs` object for a `Component` name and + * return that if it exists. + */ -var Fn = _interopRequireWildcard(_fn); -var _formatTime = _dereq_(89); + Component.getComponent = function getComponent(name) { + if (!name) { + return; + } -var _formatTime2 = _interopRequireDefault(_formatTime); + name = toTitleCase(name); -_dereq_(20); + if (Component.components_ && Component.components_[name]) { + return Component.components_[name]; + } + }; -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + return Component; +}(); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } +/** + * Whether or not this component supports `requestAnimationFrame`. + * + * This is exposed primarily for testing purposes. + * + * @private + * @type {Boolean} + */ -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function'; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file mouse-time-display.js - */ +Component.registerComponent('Component', Component); +/** + * @file time-ranges.js + * @module time-ranges + */ /** - * The {@link MouseTimeDisplay} component tracks mouse movement over the - * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip} - * indicating the time which is represented by a given point in the - * {@link ProgressControl}. + * Returns the time for the specified index at the start or end + * of a TimeRange object. * - * @extends Component + * @function time-ranges:indexFunction + * + * @param {number} [index=0] + * The range number to return the time for. + * + * @return {number} + * The time that offset at the specified index. + * + * @depricated index must be set to a value, in the future this will throw an error. */ -var MouseTimeDisplay = function (_Component) { - _inherits(MouseTimeDisplay, _Component); - /** - * Creates an instance of this class. - * - * @param {Player} player - * The {@link Player} that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function MouseTimeDisplay(player, options) { - _classCallCheck(this, MouseTimeDisplay); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); +/** + * An object that contains ranges of time for various reasons. + * + * @typedef {Object} TimeRange + * + * @property {number} length + * The number of time ranges represented by this Object + * + * @property {time-ranges:indexFunction} start + * Returns the time offset at which a specified time range begins. + * + * @property {time-ranges:indexFunction} end + * Returns the time offset at which a specified time range begins. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges + */ - _this.update = Fn.throttle(Fn.bind(_this, _this.update), 25); - return _this; +/** + * Check if any of the time ranges are over the maximum index. + * + * @param {string} fnName + * The function name to use for logging + * + * @param {number} index + * The index to check + * + * @param {number} maxIndex + * The maximum possible index + * + * @throws {Error} if the timeRanges provided are over the maxIndex + */ +function rangeCheck(fnName, index, maxIndex) { + if (typeof index !== 'number' || index < 0 || index > maxIndex) { + throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').'); } +} - /** - * Create the DOM element for this class. - * - * @return {Element} - * The element that was created. - */ - - - MouseTimeDisplay.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-mouse-display' - }); - }; - - /** - * Enqueues updates to its own DOM as well as the DOM of its - * {@link TimeTooltip} child. - * - * @param {Object} seekBarRect - * The `ClientRect` for the {@link SeekBar} element. - * - * @param {number} seekBarPoint - * A number from 0 to 1, representing a horizontal reference point - * from the left edge of the {@link SeekBar} - */ - - - MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) { - var _this2 = this; - - // If there is an existing rAF ID, cancel it so we don't over-queue. - if (this.rafId_) { - this.cancelAnimationFrame(this.rafId_); - } - - this.rafId_ = this.requestAnimationFrame(function () { - var duration = _this2.player_.duration(); - var content = (0, _formatTime2['default'])(seekBarPoint * duration, duration); +/** + * Check if any of the time ranges are over the maximum index. + * + * @param {string} fnName + * The function name to use for logging + * + * @param {string} valueIndex + * The proprety that should be used to get the time. should be 'start' or 'end' + * + * @param {Array} ranges + * An array of time ranges + * + * @param {Array} [rangeIndex=0] + * The index to start the search at + * + * @return {number} + * The time that offset at the specified index. + * + * + * @depricated rangeIndex must be set to a value, in the future this will throw an error. + * @throws {Error} if rangeIndex is more than the length of ranges + */ +function getRange(fnName, valueIndex, ranges, rangeIndex) { + rangeCheck(fnName, rangeIndex, ranges.length - 1); + return ranges[rangeIndex][valueIndex]; +} - _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px'; - _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content); - }); +/** + * Create a time range object givent ranges of time. + * + * @param {Array} [ranges] + * An array of time ranges. + */ +function createTimeRangesObj(ranges) { + if (ranges === undefined || ranges.length === 0) { + return { + length: 0, + start: function start() { + throw new Error('This TimeRanges object is empty'); + }, + end: function end() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + return { + length: ranges.length, + start: getRange.bind(null, 'start', 0, ranges), + end: getRange.bind(null, 'end', 1, ranges) }; - - return MouseTimeDisplay; -}(_component2['default']); +} /** - * Default options for `MouseTimeDisplay` + * Should create a fake `TimeRange` object which mimics an HTML5 time range instance. + * + * @param {number|Array} start + * The start of a single range or an array of ranges + * + * @param {number} end + * The end of a single range. * - * @type {Object} * @private */ - - -MouseTimeDisplay.prototype.options_ = { - children: ['timeTooltip'] -}; - -_component2['default'].registerComponent('MouseTimeDisplay', MouseTimeDisplay); -exports['default'] = MouseTimeDisplay; - -},{"20":20,"5":5,"88":88,"89":89}],17:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _browser = _dereq_(81); - -var _formatTime = _dereq_(89); - -var _formatTime2 = _interopRequireDefault(_formatTime); - -_dereq_(20); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file play-progress-bar.js - */ - +function createTimeRanges(start, end) { + if (Array.isArray(start)) { + return createTimeRangesObj(start); + } else if (start === undefined || end === undefined) { + return createTimeRangesObj(); + } + return createTimeRangesObj([[start, end]]); +} /** - * Used by {@link SeekBar} to display media playback progress as part of the - * {@link ProgressControl}. + * @file buffer.js + * @module buffer + */ +/** + * Compute the percentage of the media that has been buffered. * - * @extends Component + * @param {TimeRange} buffered + * The current `TimeRange` object representing buffered time ranges + * + * @param {number} duration + * Total duration of the media + * + * @return {number} + * Percent buffered of the total duration in decimal form. */ -var PlayProgressBar = function (_Component) { - _inherits(PlayProgressBar, _Component); - - function PlayProgressBar() { - _classCallCheck(this, PlayProgressBar); +function bufferedPercent(buffered, duration) { + var bufferedDuration = 0; + var start = void 0; + var end = void 0; - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); + if (!duration) { + return 0; } - /** - * Create the the DOM element for this class. - * - * @return {Element} - * The element that was created. - */ - PlayProgressBar.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-play-progress vjs-slider-bar', - innerHTML: '' + this.localize('Progress') + ': 0%' - }); - }; - - /** - * Enqueues updates to its own DOM as well as the DOM of its - * {@link TimeTooltip} child. - * - * @param {Object} seekBarRect - * The `ClientRect` for the {@link SeekBar} element. - * - * @param {number} seekBarPoint - * A number from 0 to 1, representing a horizontal reference point - * from the left edge of the {@link SeekBar} - */ - + if (!buffered || !buffered.length) { + buffered = createTimeRanges(0, 0); + } - PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) { - var _this2 = this; + for (var i = 0; i < buffered.length; i++) { + start = buffered.start(i); + end = buffered.end(i); - // If there is an existing rAF ID, cancel it so we don't over-queue. - if (this.rafId_) { - this.cancelAnimationFrame(this.rafId_); + // buffered end can be bigger than duration by a very small fraction + if (end > duration) { + end = duration; } - this.rafId_ = this.requestAnimationFrame(function () { - var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime(); - - var content = (0, _formatTime2['default'])(time, _this2.player_.duration()); - var timeTooltip = _this2.getChild('timeTooltip'); - - if (timeTooltip) { - timeTooltip.update(seekBarRect, seekBarPoint, content); - } - }); - }; + bufferedDuration += end - start; + } - return PlayProgressBar; -}(_component2['default']); + return bufferedDuration / duration; +} /** - * Default options for {@link PlayProgressBar}. + * @file fullscreen-api.js + * @module fullscreen-api + * @private + */ +/** + * Store the browser-specific methods for the fullscreen API. * * @type {Object} - * @private + * @see [Specification]{@link https://fullscreen.spec.whatwg.org} + * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} */ +var FullscreenApi = {}; +// browser API methods +var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], +// WebKit +['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], +// Old WebKit (Safari 5.1) +['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], +// Mozilla +['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], +// Microsoft +['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']]; -PlayProgressBar.prototype.options_ = { - children: [] -}; +var specApi = apiMap[0]; +var browserApi = void 0; -// Time tooltips should not be added to a player on mobile devices or IE8 -if ((!_browser.IE_VERSION || _browser.IE_VERSION > 8) && !_browser.IS_IOS && !_browser.IS_ANDROID) { - PlayProgressBar.prototype.options_.children.push('timeTooltip'); +// determine the supported set of functions +for (var i = 0; i < apiMap.length; i++) { + // check for exitFullscreen function + if (apiMap[i][1] in document_1) { + browserApi = apiMap[i]; + break; + } +} + +// map the browser API names to the spec API names +if (browserApi) { + for (var _i = 0; _i < browserApi.length; _i++) { + FullscreenApi[specApi[_i]] = browserApi[_i]; + } } -_component2['default'].registerComponent('PlayProgressBar', PlayProgressBar); -exports['default'] = PlayProgressBar; +/** + * @file media-error.js + */ +/** + * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. + * + * @param {number|string|Object|MediaError} value + * This can be of multiple types: + * - number: should be a standard error code + * - string: an error message (the code will be 0) + * - Object: arbitrary properties + * - `MediaError` (native): used to populate a video.js `MediaError` object + * - `MediaError` (video.js): will return itself if it's already a + * video.js `MediaError` object. + * + * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} + * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} + * + * @class MediaError + */ +function MediaError(value) { + + // Allow redundant calls to this constructor to avoid having `instanceof` + // checks peppered around the code. + if (value instanceof MediaError) { + return value; + } -},{"20":20,"5":5,"81":81,"89":89}],18:[function(_dereq_,module,exports){ -'use strict'; + if (typeof value === 'number') { + this.code = value; + } else if (typeof value === 'string') { + // default code is zero, so this is a custom error + this.message = value; + } else if (isObject(value)) { -exports.__esModule = true; + // We assign the `code` property manually because native `MediaError` objects + // do not expose it as an own/enumerable property of the object. + if (typeof value.code === 'number') { + this.code = value.code; + } -var _component = _dereq_(5); + assign(this, value); + } -var _component2 = _interopRequireDefault(_component); + if (!this.message) { + this.message = MediaError.defaultMessages[this.code] || ''; + } +} -var _dom = _dereq_(85); +/** + * The error code that refers two one of the defined `MediaError` types + * + * @type {Number} + */ +MediaError.prototype.code = 0; -var Dom = _interopRequireWildcard(_dom); +/** + * An optional message that to show with the error. Message is not part of the HTML5 + * video spec but allows for more informative custom errors. + * + * @type {String} + */ +MediaError.prototype.message = ''; -var _fn = _dereq_(88); +/** + * An optional status code that can be set by plugins to allow even more detail about + * the error. For example a plugin might provide a specific HTTP status code and an + * error message for that code. Then when the plugin gets that error this class will + * know how to display an error message for it. This allows a custom message to show + * up on the `Player` error overlay. + * + * @type {Array} + */ +MediaError.prototype.status = null; -_dereq_(19); +/** + * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the + * specification listed under {@link MediaError} for more information. + * + * @enum {array} + * @readonly + * @property {string} 0 - MEDIA_ERR_CUSTOM + * @property {string} 1 - MEDIA_ERR_CUSTOM + * @property {string} 2 - MEDIA_ERR_ABORTED + * @property {string} 3 - MEDIA_ERR_NETWORK + * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED + * @property {string} 5 - MEDIA_ERR_ENCRYPTED + */ +MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } +/** + * The default `MediaError` messages based on the {@link MediaError.errorTypes}. + * + * @type {Array} + * @constant + */ +MediaError.defaultMessages = { + 1: 'You aborted the media playback', + 2: 'A network error caused the media download to fail part-way.', + 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', + 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', + 5: 'The media is encrypted and we do not have the keys to decrypt it.' +}; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } +// Add types as properties on MediaError +// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; +for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { + MediaError[MediaError.errorTypes[errNum]] = errNum; + // values should be accessible on both the class and instance + MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; +} -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +var tuple = SafeParseTuple; -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +function SafeParseTuple(obj, reviver) { + var json; + var error = null; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file progress-control.js - */ + try { + json = JSON.parse(obj, reviver); + } catch (err) { + error = err; + } + return [error, json] +} /** - * The Progress Control component contains the seek bar, load progress, - * and play progress. + * @file text-track-list-converter.js Utilities for capturing text track state and + * re-creating tracks based on a capture. * - * @extends Component + * @module text-track-list-converter */ -var ProgressControl = function (_Component) { - _inherits(ProgressControl, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function ProgressControl(player, options) { - _classCallCheck(this, ProgressControl); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.handleMouseMove = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseMove), 25); - _this.on(_this.el_, 'mousemove', _this.handleMouseMove); - _this.throttledHandleMouseSeek = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseSeek), 25); - _this.on(['mousedown', 'touchstart'], _this.handleMouseDown); - return _this; - } +/** + * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that + * represents the {@link TextTrack}'s state. + * + * @param {TextTrack} track + * The text track to query. + * + * @return {Object} + * A serializable javascript representation of the TextTrack. + * @private + */ +var trackToJson_ = function trackToJson_(track) { + var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) { - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ + if (track[prop]) { + acc[prop] = track[prop]; + } + return acc; + }, { + cues: track.cues && Array.prototype.map.call(track.cues, function (cue) { + return { + startTime: cue.startTime, + endTime: cue.endTime, + text: cue.text, + id: cue.id + }; + }) + }); - ProgressControl.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-progress-control vjs-control' - }); - }; + return ret; +}; - /** - * When the mouse moves over the `ProgressControl`, the pointer position - * gets passed down to the `MouseTimeDisplay` component. - * - * @param {EventTarget~Event} event - * The `mousemove` event that caused this function to run. - * - * @listen mousemove - */ +/** + * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the + * state of all {@link TextTrack}s currently configured. The return array is compatible with + * {@link text-track-list-converter:jsonToTextTracks}. + * + * @param {Tech} tech + * The tech object to query + * + * @return {Array} + * A serializable javascript representation of the {@link Tech}s + * {@link TextTrackList}. + */ +var textTracksToJson = function textTracksToJson(tech) { + var trackEls = tech.$$('track'); - ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) { - var seekBar = this.getChild('seekBar'); - var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay'); - var seekBarEl = seekBar.el(); - var seekBarRect = Dom.getBoundingClientRect(seekBarEl); - var seekBarPoint = Dom.getPointerPosition(seekBarEl, event).x; + var trackObjs = Array.prototype.map.call(trackEls, function (t) { + return t.track; + }); + var tracks = Array.prototype.map.call(trackEls, function (trackEl) { + var json = trackToJson_(trackEl.track); - // The default skin has a gap on either side of the `SeekBar`. This means - // that it's possible to trigger this behavior outside the boundaries of - // the `SeekBar`. This ensures we stay within it at all times. - if (seekBarPoint > 1) { - seekBarPoint = 1; - } else if (seekBarPoint < 0) { - seekBarPoint = 0; + if (trackEl.src) { + json.src = trackEl.src; } + return json; + }); - if (mouseTimeDisplay) { - mouseTimeDisplay.update(seekBarRect, seekBarPoint); - } - }; - - /** - * A throttled version of the {@link ProgressControl#handleMouseSeek} listener. - * - * @method ProgressControl#throttledHandleMouseSeek - * @param {EventTarget~Event} event - * The `mousemove` event that caused this function to run. - * - * @listen mousemove - * @listen touchmove - */ - - /** - * Handle `mousemove` or `touchmove` events on the `ProgressControl`. - * - * @param {EventTarget~Event} event - * `mousedown` or `touchstart` event that triggered this function - * - * @listens mousemove - * @listens touchmove - */ - - - ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) { - var seekBar = this.getChild('seekBar'); - - seekBar.handleMouseMove(event); - }; - - /** - * Handle `mousedown` or `touchstart` events on the `ProgressControl`. - * - * @param {EventTarget~Event} event - * `mousedown` or `touchstart` event that triggered this function - * - * @listens mousedown - * @listens touchstart - */ - - - ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) { - var doc = this.el_.ownerDocument; - - this.on(doc, 'mousemove', this.throttledHandleMouseSeek); - this.on(doc, 'touchmove', this.throttledHandleMouseSeek); - this.on(doc, 'mouseup', this.handleMouseUp); - this.on(doc, 'touchend', this.handleMouseUp); - }; - - /** - * Handle `mouseup` or `touchend` events on the `ProgressControl`. - * - * @param {EventTarget~Event} event - * `mouseup` or `touchend` event that triggered this function. - * - * @listens touchend - * @listens mouseup - */ - - - ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) { - var doc = this.el_.ownerDocument; - - this.off(doc, 'mousemove', this.throttledHandleMouseSeek); - this.off(doc, 'touchmove', this.throttledHandleMouseSeek); - this.off(doc, 'mouseup', this.handleMouseUp); - this.off(doc, 'touchend', this.handleMouseUp); - }; - - return ProgressControl; -}(_component2['default']); + return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) { + return trackObjs.indexOf(track) === -1; + }).map(trackToJson_)); +}; /** - * Default options for `ProgressControl` + * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript + * object {@link TextTrack} representations. * - * @type {Object} - * @private + * @param {Array} json + * An array of `TextTrack` representation objects, like those that would be + * produced by `textTracksToJson`. + * + * @param {Tech} tech + * The `Tech` to create the `TextTrack`s on. */ +var jsonToTextTracks = function jsonToTextTracks(json, tech) { + json.forEach(function (track) { + var addedTrack = tech.addRemoteTextTrack(track).track; + if (!track.src && track.cues) { + track.cues.forEach(function (cue) { + return addedTrack.addCue(cue); + }); + } + }); -ProgressControl.prototype.options_ = { - children: ['seekBar'] + return tech.textTracks(); }; -_component2['default'].registerComponent('ProgressControl', ProgressControl); -exports['default'] = ProgressControl; - -},{"19":19,"5":5,"85":85,"88":88}],19:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _slider = _dereq_(60); - -var _slider2 = _interopRequireDefault(_slider); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _browser = _dereq_(81); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _fn = _dereq_(88); +var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ }; -var Fn = _interopRequireWildcard(_fn); - -var _formatTime = _dereq_(89); - -var _formatTime2 = _interopRequireDefault(_formatTime); - -_dereq_(15); - -_dereq_(17); - -_dereq_(16); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file seek-bar.js - */ - - -// The number of seconds the `step*` functions move the timeline. -var STEP_SECONDS = 5; +/** + * @file modal-dialog.js + */ +var MODAL_CLASS_NAME = 'vjs-modal-dialog'; +var ESC = 27; /** - * Seek bar and container for the progress bars. Uses {@link PlayProgressBar} - * as its `bar`. + * The `ModalDialog` displays over the video and its controls, which blocks + * interaction with the player until it is closed. * - * @extends Slider + * Modal dialogs include a "Close" button and will close when that button + * is activated - or when ESC is pressed anywhere. + * + * @extends Component */ -var SeekBar = function (_Slider) { - _inherits(SeekBar, _Slider); +var ModalDialog = function (_Component) { + inherits(ModalDialog, _Component); /** - * Creates an instance of this class. + * Create an instance of this class. * * @param {Player} player * The `Player` that this class should be attached to. * * @param {Object} [options] * The key/value store of player options. + * + * @param {Mixed} [options.content=undefined] + * Provide customized content for this modal. + * + * @param {string} [options.description] + * A text description for the modal, primarily for accessibility. + * + * @param {boolean} [options.fillAlways=false] + * Normally, modals are automatically filled only the first time + * they open. This tells the modal to refresh its content + * every time it opens. + * + * @param {string} [options.label] + * A text label for the modal, primarily for accessibility. + * + * @param {boolean} [options.temporary=true] + * If `true`, the modal can only be opened once; it will be + * disposed as soon as it's closed. + * + * @param {boolean} [options.uncloseable=false] + * If `true`, the user will not be able to close the modal + * through the UI in the normal ways. Programmatic closing is + * still possible. */ - function SeekBar(player, options) { - _classCallCheck(this, SeekBar); + function ModalDialog(player, options) { + classCallCheck(this, ModalDialog); - var _this = _possibleConstructorReturn(this, _Slider.call(this, player, options)); + var _this = possibleConstructorReturn(this, _Component.call(this, player, options)); - _this.update = Fn.throttle(Fn.bind(_this, _this.update), 50); - _this.on(player, ['timeupdate', 'ended'], _this.update); + _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; + + _this.closeable(!_this.options_.uncloseable); + _this.content(_this.options_.content); + + // Make sure the contentEl is defined AFTER any children are initialized + // because we only want the contents of the modal in the contentEl + // (not the UI elements like the close button). + _this.contentEl_ = createEl('div', { + className: MODAL_CLASS_NAME + '-content' + }, { + role: 'document' + }); + + _this.descEl_ = createEl('p', { + className: MODAL_CLASS_NAME + '-description vjs-control-text', + id: _this.el().getAttribute('aria-describedby') + }); + + textContent(_this.descEl_, _this.description()); + _this.el_.appendChild(_this.descEl_); + _this.el_.appendChild(_this.contentEl_); return _this; } /** - * Create the `Component`'s DOM element + * Create the `ModalDialog`'s DOM element * * @return {Element} - * The element that was created. + * The DOM element that gets created. */ - SeekBar.prototype.createEl = function createEl() { - return _Slider.prototype.createEl.call(this, 'div', { - className: 'vjs-progress-holder' + ModalDialog.prototype.createEl = function createEl$$1() { + return _Component.prototype.createEl.call(this, 'div', { + className: this.buildCSSClass(), + tabIndex: -1 }, { - 'aria-label': this.localize('Progress Bar') + 'aria-describedby': this.id() + '_description', + 'aria-hidden': 'true', + 'aria-label': this.label(), + 'role': 'dialog' }); }; /** - * Update the seek bar's UI. - * - * @param {EventTarget~Event} [event] - * The `timeupdate` or `ended` event that caused this to run. + * Builds the default DOM `className`. * - * @listens Player#timeupdate - * @listens Player#ended + * @return {string} + * The DOM `className` for this object. */ - SeekBar.prototype.update = function update() { - var percent = _Slider.prototype.update.call(this); - var duration = this.player_.duration(); - - // Allows for smooth scrubbing, when player can't keep up. - var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); - - // machine readable value of progress bar (percentage complete) - this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2)); - - // human readable value of progress bar (time complete) - this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [(0, _formatTime2['default'])(time, duration), (0, _formatTime2['default'])(duration, duration)], '{1} of {2}')); - - // Update the `PlayProgressBar`. - this.bar.update(Dom.getBoundingClientRect(this.el_), percent); - - return percent; + ModalDialog.prototype.buildCSSClass = function buildCSSClass() { + return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); }; /** - * Get the percentage of media played so far. + * Handles `keydown` events on the document, looking for ESC, which closes + * the modal. * - * @return {number} - * The percentage of media played so far (0 to 1). + * @param {EventTarget~Event} e + * The keypress that triggered this event. + * + * @listens keydown */ - SeekBar.prototype.getPercent = function getPercent() { - - // Allows for smooth scrubbing, when player can't keep up. - var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); - - var percent = time / this.player_.duration(); - - return percent >= 1 ? 1 : percent; + ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { + if (e.which === ESC && this.closeable()) { + this.close(); + } }; /** - * Handle mouse down on seek bar - * - * @param {EventTarget~Event} event - * The `mousedown` event that caused this to run. + * Returns the label string for this modal. Primarily used for accessibility. * - * @listens mousedown + * @return {string} + * the localized or raw label of this modal. */ - SeekBar.prototype.handleMouseDown = function handleMouseDown(event) { - this.player_.scrubbing(true); - - this.videoWasPlaying = !this.player_.paused(); - this.player_.pause(); - - _Slider.prototype.handleMouseDown.call(this, event); + ModalDialog.prototype.label = function label() { + return this.localize(this.options_.label || 'Modal Window'); }; /** - * Handle mouse move on seek bar - * - * @param {EventTarget~Event} event - * The `mousemove` event that caused this to run. + * Returns the description string for this modal. Primarily used for + * accessibility. * - * @listens mousemove + * @return {string} + * The localized or raw description of this modal. */ - SeekBar.prototype.handleMouseMove = function handleMouseMove(event) { - var newTime = this.calculateDistance(event) * this.player_.duration(); + ModalDialog.prototype.description = function description() { + var desc = this.options_.description || this.localize('This is a modal window.'); - // Don't let video end while scrubbing. - if (newTime === this.player_.duration()) { - newTime = newTime - 0.1; + // Append a universal closeability message if the modal is closeable. + if (this.closeable()) { + desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); } - // Set new time (tell player to seek to new time) - this.player_.currentTime(newTime); + return desc; }; /** - * Handle mouse up on seek bar - * - * @param {EventTarget~Event} event - * The `mouseup` event that caused this to run. + * Opens the modal. * - * @listens mouseup + * @fires ModalDialog#beforemodalopen + * @fires ModalDialog#modalopen */ - SeekBar.prototype.handleMouseUp = function handleMouseUp(event) { - _Slider.prototype.handleMouseUp.call(this, event); + ModalDialog.prototype.open = function open() { + if (!this.opened_) { + var player = this.player(); - this.player_.scrubbing(false); - if (this.videoWasPlaying) { - this.player_.play(); - } - }; + /** + * Fired just before a `ModalDialog` is opened. + * + * @event ModalDialog#beforemodalopen + * @type {EventTarget~Event} + */ + this.trigger('beforemodalopen'); + this.opened_ = true; - /** - * Move more quickly fast forward for keyboard-only users - */ + // Fill content if the modal has never opened before and + // never been filled. + if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { + this.fill(); + } + // If the player was playing, pause it and take note of its previously + // playing state. + this.wasPlaying_ = !player.paused(); - SeekBar.prototype.stepForward = function stepForward() { - this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); - }; + if (this.options_.pauseOnOpen && this.wasPlaying_) { + player.pause(); + } - /** - * Move more quickly rewind for keyboard-only users - */ + if (this.closeable()) { + this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); + } + player.controls(false); + this.show(); + this.conditionalFocus_(); + this.el().setAttribute('aria-hidden', 'false'); - SeekBar.prototype.stepBack = function stepBack() { - this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); + /** + * Fired just after a `ModalDialog` is opened. + * + * @event ModalDialog#modalopen + * @type {EventTarget~Event} + */ + this.trigger('modalopen'); + this.hasBeenOpened_ = true; + } }; /** - * Toggles the playback state of the player - * This gets called when enter or space is used on the seekbar + * If the `ModalDialog` is currently open or closed. * - * @param {EventTarget~Event} event - * The `keydown` event that caused this function to be called + * @param {boolean} [value] + * If given, it will open (`true`) or close (`false`) the modal. * + * @return {boolean} + * the current open state of the modaldialog */ - SeekBar.prototype.handleAction = function handleAction(event) { - if (this.player_.paused()) { - this.player_.play(); - } else { - this.player_.pause(); + ModalDialog.prototype.opened = function opened(value) { + if (typeof value === 'boolean') { + this[value ? 'open' : 'close'](); } + return this.opened_; }; /** - * Called when this SeekBar has focus and a key gets pressed down. By - * default it will call `this.handleAction` when the key is space or enter. - * - * @param {EventTarget~Event} event - * The `keydown` event that caused this function to be called. + * Closes the modal, does nothing if the `ModalDialog` is + * not open. * - * @listens keydown + * @fires ModalDialog#beforemodalclose + * @fires ModalDialog#modalclose */ - SeekBar.prototype.handleKeyPress = function handleKeyPress(event) { - - // Support Space (32) or Enter (13) key operation to fire a click event - if (event.which === 32 || event.which === 13) { - event.preventDefault(); - this.handleAction(event); - } else if (_Slider.prototype.handleKeyPress) { - - // Pass keypress handling up for unsupported keys - _Slider.prototype.handleKeyPress.call(this, event); + ModalDialog.prototype.close = function close() { + if (!this.opened_) { + return; } - }; - - return SeekBar; -}(_slider2['default']); - -/** - * Default options for the `SeekBar` - * - * @type {Object} - * @private - */ - - -SeekBar.prototype.options_ = { - children: ['loadProgressBar', 'playProgressBar'], - barName: 'playProgressBar' -}; - -// MouseTimeDisplay tooltips should not be added to a player on mobile devices or IE8 -if ((!_browser.IE_VERSION || _browser.IE_VERSION > 8) && !_browser.IS_IOS && !_browser.IS_ANDROID) { - SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay'); -} - -/** - * Call the update event for this Slider when this event happens on the player. - * - * @type {string} - */ -SeekBar.prototype.playerEvent = 'timeupdate'; - -_component2['default'].registerComponent('SeekBar', SeekBar); -exports['default'] = SeekBar; - -},{"15":15,"16":16,"17":17,"5":5,"60":60,"81":81,"85":85,"88":88,"89":89}],20:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; + var player = this.player(); -var _component = _dereq_(5); + /** + * Fired just before a `ModalDialog` is closed. + * + * @event ModalDialog#beforemodalclose + * @type {EventTarget~Event} + */ + this.trigger('beforemodalclose'); + this.opened_ = false; -var _component2 = _interopRequireDefault(_component); + if (this.wasPlaying_ && this.options_.pauseOnOpen) { + player.play(); + } -var _dom = _dereq_(85); + if (this.closeable()) { + this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress)); + } -var Dom = _interopRequireWildcard(_dom); + player.controls(true); + this.hide(); + this.el().setAttribute('aria-hidden', 'true'); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + /** + * Fired just after a `ModalDialog` is closed. + * + * @event ModalDialog#modalclose + * @type {EventTarget~Event} + */ + this.trigger('modalclose'); + this.conditionalBlur_(); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + if (this.options_.temporary) { + this.dispose(); + } + }; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * Check to see if the `ModalDialog` is closeable via the UI. + * + * @param {boolean} [value] + * If given as a boolean, it will set the `closeable` option. + * + * @return {boolean} + * Returns the final value of the closable option. + */ -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file time-tooltip.js - */ + ModalDialog.prototype.closeable = function closeable(value) { + if (typeof value === 'boolean') { + var closeable = this.closeable_ = !!value; + var close = this.getChild('closeButton'); + // If this is being made closeable and has no close button, add one. + if (closeable && !close) { -/** - * Time tooltips display a time above the progress bar. - * - * @extends Component - */ -var TimeTooltip = function (_Component) { - _inherits(TimeTooltip, _Component); + // The close button should be a child of the modal - not its + // content element, so temporarily change the content element. + var temp = this.contentEl_; - function TimeTooltip() { - _classCallCheck(this, TimeTooltip); + this.contentEl_ = this.el_; + close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); + this.contentEl_ = temp; + this.on(close, 'close', this.close); + } - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } + // If this is being made uncloseable and has a close button, remove it. + if (!closeable && close) { + this.off(close, 'close', this.close); + this.removeChild(close); + close.dispose(); + } + } + return this.closeable_; + }; /** - * Create the time tooltip DOM element - * - * @return {Element} - * The element that was created. + * Fill the modal's content element with the modal's "content" option. + * The content element will be emptied before this change takes place. */ - TimeTooltip.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-time-tooltip' - }); + + + ModalDialog.prototype.fill = function fill() { + this.fillWith(this.content()); }; /** - * Updates the position of the time tooltip relative to the `SeekBar`. + * Fill the modal's content element with arbitrary content. + * The content element will be emptied before this change takes place. * - * @param {Object} seekBarRect - * The `ClientRect` for the {@link SeekBar} element. + * @fires ModalDialog#beforemodalfill + * @fires ModalDialog#modalfill * - * @param {number} seekBarPoint - * A number from 0 to 1, representing a horizontal reference point - * from the left edge of the {@link SeekBar} + * @param {Mixed} [content] + * The same rules apply to this as apply to the `content` option. */ - TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) { - var tooltipRect = Dom.getBoundingClientRect(this.el_); - var playerRect = Dom.getBoundingClientRect(this.player_.el()); - var seekBarPointPx = seekBarRect.width * seekBarPoint; - - // do nothing if either rect isn't available - // for example, if the player isn't in the DOM for testing - if (!playerRect || !tooltipRect) { - return; - } + ModalDialog.prototype.fillWith = function fillWith(content) { + var contentEl = this.contentEl(); + var parentEl = contentEl.parentNode; + var nextSiblingEl = contentEl.nextSibling; - // This is the space left of the `seekBarPoint` available within the bounds - // of the player. We calculate any gap between the left edge of the player - // and the left edge of the `SeekBar` and add the number of pixels in the - // `SeekBar` before hitting the `seekBarPoint` - var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx; - - // This is the space right of the `seekBarPoint` available within the bounds - // of the player. We calculate the number of pixels from the `seekBarPoint` - // to the right edge of the `SeekBar` and add to that any gap between the - // right edge of the `SeekBar` and the player. - var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right); + /** + * Fired just before a `ModalDialog` is filled with content. + * + * @event ModalDialog#beforemodalfill + * @type {EventTarget~Event} + */ + this.trigger('beforemodalfill'); + this.hasBeenFilled_ = true; - // This is the number of pixels by which the tooltip will need to be pulled - // further to the right to center it over the `seekBarPoint`. - var pullTooltipBy = tooltipRect.width / 2; + // Detach the content element from the DOM before performing + // manipulation to avoid modifying the live DOM multiple times. + parentEl.removeChild(contentEl); + this.empty(); + insertContent(contentEl, content); + /** + * Fired just after a `ModalDialog` is filled with content. + * + * @event ModalDialog#modalfill + * @type {EventTarget~Event} + */ + this.trigger('modalfill'); - // Adjust the `pullTooltipBy` distance to the left or right depending on - // the results of the space calculations above. - if (spaceLeftOfPoint < pullTooltipBy) { - pullTooltipBy += pullTooltipBy - spaceLeftOfPoint; - } else if (spaceRightOfPoint < pullTooltipBy) { - pullTooltipBy = spaceRightOfPoint; + // Re-inject the re-filled content element. + if (nextSiblingEl) { + parentEl.insertBefore(contentEl, nextSiblingEl); + } else { + parentEl.appendChild(contentEl); } - // Due to the imprecision of decimal/ratio based calculations and varying - // rounding behaviors, there are cases where the spacing adjustment is off - // by a pixel or two. This adds insurance to these calculations. - if (pullTooltipBy < 0) { - pullTooltipBy = 0; - } else if (pullTooltipBy > tooltipRect.width) { - pullTooltipBy = tooltipRect.width; - } + // make sure that the close button is last in the dialog DOM + var closeButton = this.getChild('closeButton'); - this.el_.style.right = '-' + pullTooltipBy + 'px'; - Dom.textContent(this.el_, content); + if (closeButton) { + parentEl.appendChild(closeButton.el_); + } }; - return TimeTooltip; -}(_component2['default']); - -_component2['default'].registerComponent('TimeTooltip', TimeTooltip); -exports['default'] = TimeTooltip; - -},{"5":5,"85":85}],21:[function(_dereq_,module,exports){ -'use strict'; + /** + * Empties the content element. This happens anytime the modal is filled. + * + * @fires ModalDialog#beforemodalempty + * @fires ModalDialog#modalempty + */ -exports.__esModule = true; -var _spacer = _dereq_(22); + ModalDialog.prototype.empty = function empty() { + /** + * Fired just before a `ModalDialog` is emptied. + * + * @event ModalDialog#beforemodalempty + * @type {EventTarget~Event} + */ + this.trigger('beforemodalempty'); + emptyEl(this.contentEl()); -var _spacer2 = _interopRequireDefault(_spacer); + /** + * Fired just after a `ModalDialog` is emptied. + * + * @event ModalDialog#modalempty + * @type {EventTarget~Event} + */ + this.trigger('modalempty'); + }; -var _component = _dereq_(5); + /** + * Gets or sets the modal content, which gets normalized before being + * rendered into the DOM. + * + * This does not update the DOM or fill the modal, but it is called during + * that process. + * + * @param {Mixed} [value] + * If defined, sets the internal content value to be used on the + * next call(s) to `fill`. This value is normalized before being + * inserted. To "clear" the internal content value, pass `null`. + * + * @return {Mixed} + * The current content of the modal dialog + */ -var _component2 = _interopRequireDefault(_component); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + ModalDialog.prototype.content = function content(value) { + if (typeof value !== 'undefined') { + this.content_ = value; + } + return this.content_; + }; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** + * conditionally focus the modal dialog if focus was previously on the player. + * + * @private + */ -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file custom-control-spacer.js - */ + ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { + var activeEl = document_1.activeElement; + var playerEl = this.player_.el_; + this.previouslyActiveEl_ = null; -/** - * Spacer specifically meant to be used as an insertion point for new plugins, etc. - * - * @extends Spacer - */ -var CustomControlSpacer = function (_Spacer) { - _inherits(CustomControlSpacer, _Spacer); + if (playerEl.contains(activeEl) || playerEl === activeEl) { + this.previouslyActiveEl_ = activeEl; - function CustomControlSpacer() { - _classCallCheck(this, CustomControlSpacer); + this.focus(); - return _possibleConstructorReturn(this, _Spacer.apply(this, arguments)); - } + this.on(document_1, 'keydown', this.handleKeyDown); + } + }; /** - * Builds the default DOM `className`. + * conditionally blur the element and refocus the last focused element * - * @return {string} - * The DOM `className` for this object. + * @private */ - CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this); + + + ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { + if (this.previouslyActiveEl_) { + this.previouslyActiveEl_.focus(); + this.previouslyActiveEl_ = null; + } + + this.off(document_1, 'keydown', this.handleKeyDown); }; /** - * Create the `Component`'s DOM element + * Keydown handler. Attached when modal is focused. * - * @return {Element} - * The element that was created. + * @listens keydown */ - CustomControlSpacer.prototype.createEl = function createEl() { - var el = _Spacer.prototype.createEl.call(this, { - className: this.buildCSSClass() - }); + ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { + // exit early if it isn't a tab key + if (event.which !== 9) { + return; + } - // No-flex/table-cell mode requires there be some content - // in the cell to fill the remaining space of the table. - el.innerHTML = ' '; - return el; - }; + var focusableEls = this.focusableEls_(); + var activeEl = this.el_.querySelector(':focus'); + var focusIndex = void 0; - return CustomControlSpacer; -}(_spacer2['default']); + for (var i = 0; i < focusableEls.length; i++) { + if (activeEl === focusableEls[i]) { + focusIndex = i; + break; + } + } + + if (document_1.activeElement === this.el_) { + focusIndex = 0; + } -_component2['default'].registerComponent('CustomControlSpacer', CustomControlSpacer); -exports['default'] = CustomControlSpacer; + if (event.shiftKey && focusIndex === 0) { + focusableEls[focusableEls.length - 1].focus(); + event.preventDefault(); + } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { + focusableEls[0].focus(); + event.preventDefault(); + } + }; -},{"22":22,"5":5}],22:[function(_dereq_,module,exports){ -'use strict'; + /** + * get all focusable elements + * + * @private + */ -exports.__esModule = true; -var _component = _dereq_(5); + ModalDialog.prototype.focusableEls_ = function focusableEls_() { + var allChildren = this.el_.querySelectorAll('*'); -var _component2 = _interopRequireDefault(_component); + return Array.prototype.filter.call(allChildren, function (child) { + return (child instanceof window_1.HTMLAnchorElement || child instanceof window_1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window_1.HTMLInputElement || child instanceof window_1.HTMLSelectElement || child instanceof window_1.HTMLTextAreaElement || child instanceof window_1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window_1.HTMLIFrameElement || child instanceof window_1.HTMLObjectElement || child instanceof window_1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); + }); + }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + return ModalDialog; +}(Component); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +/** + * Default options for `ModalDialog` default options. + * + * @type {Object} + * @private + */ -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file spacer.js - */ +ModalDialog.prototype.options_ = { + pauseOnOpen: true, + temporary: true +}; +Component.registerComponent('ModalDialog', ModalDialog); /** - * Just an empty spacer element that can be used as an append point for plugins, etc. - * Also can be used to create space between elements when necessary. + * @file track-list.js + */ +/** + * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and + * {@link VideoTrackList} * - * @extends Component + * @extends EventTarget */ -var Spacer = function (_Component) { - _inherits(Spacer, _Component); - - function Spacer() { - _classCallCheck(this, Spacer); - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } +var TrackList = function (_EventTarget) { + inherits(TrackList, _EventTarget); /** - * Builds the default DOM `className`. + * Create an instance of this class * - * @return {string} - * The DOM `className` for this object. - */ - Spacer.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this); - }; - - /** - * Create the `Component`'s DOM element + * @param {Track[]} tracks + * A list of tracks to initialize the list with. * - * @return {Element} - * The element that was created. + * @param {Object} [list] + * The child object with inheritance done manually for ie8. + * + * @abstract */ + function TrackList() { + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var _ret; - Spacer.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: this.buildCSSClass() - }); - }; - - return Spacer; -}(_component2['default']); - -_component2['default'].registerComponent('Spacer', Spacer); - -exports['default'] = Spacer; + var list = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + classCallCheck(this, TrackList); -},{"5":5}],23:[function(_dereq_,module,exports){ -'use strict'; + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); -exports.__esModule = true; + if (!list) { + list = _this; // eslint-disable-line + if (IS_IE8) { + list = document_1.createElement('custom'); + for (var prop in TrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TrackList.prototype[prop]; + } + } + } + } -var _textTrackMenuItem = _dereq_(33); + list.tracks_ = []; -var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file caption-settings-menu-item.js - */ + /** + * @memberof TrackList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + Object.defineProperty(list, 'length', { + get: function get$$1() { + return this.tracks_.length; + } + }); + for (var i = 0; i < tracks.length; i++) { + list.addTrack(tracks[i]); + } -/** - * The menu item for caption track settings menu - * - * @extends TextTrackMenuItem - */ -var CaptionSettingsMenuItem = function (_TextTrackMenuItem) { - _inherits(CaptionSettingsMenuItem, _TextTrackMenuItem); + // must return the object, as for ie8 it will not be this + // but a reference to a document object + return _ret = list, possibleConstructorReturn(_this, _ret); + } /** - * Creates an instance of this class. + * Add a {@link Track} to the `TrackList` * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {Track} track + * The audio, video, or text track to add to the list. * - * @param {Object} [options] - * The key/value store of player options. + * @fires TrackList#addtrack */ - function CaptionSettingsMenuItem(player, options) { - _classCallCheck(this, CaptionSettingsMenuItem); - - options.track = { - player: player, - kind: options.kind, - label: options.kind + ' settings', - selectable: false, - 'default': false, - mode: 'disabled' - }; - // CaptionSettingsMenuItem has no concept of 'selected' - options.selectable = false; - options.name = 'CaptionSettingsMenuItem'; + TrackList.prototype.addTrack = function addTrack(track) { + var index = this.tracks_.length; - var _this = _possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get$$1() { + return this.tracks_[index]; + } + }); + } - _this.addClass('vjs-texttrack-settings'); - _this.controlText(', opens ' + options.kind + ' settings dialog'); - return _this; - } + // Do not add duplicate tracks + if (this.tracks_.indexOf(track) === -1) { + this.tracks_.push(track); + /** + * Triggered when a track is added to a track list. + * + * @event TrackList#addtrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was added. + */ + this.trigger({ + track: track, + type: 'addtrack' + }); + } + }; /** - * This gets called when an `CaptionSettingsMenuItem` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. + * Remove a {@link Track} from the `TrackList` * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * @param {Track} rtrack + * The audio, video, or text track to remove from the list. * - * @listens tap - * @listens click + * @fires TrackList#removetrack */ - CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) { - this.player().getChild('textTrackSettings').open(); - }; - - return CaptionSettingsMenuItem; -}(_textTrackMenuItem2['default']); - -_component2['default'].registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem); -exports['default'] = CaptionSettingsMenuItem; - -},{"33":33,"5":5}],24:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _textTrackButton = _dereq_(32); - -var _textTrackButton2 = _interopRequireDefault(_textTrackButton); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _captionSettingsMenuItem = _dereq_(23); - -var _captionSettingsMenuItem2 = _interopRequireDefault(_captionSettingsMenuItem); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file captions-button.js - */ - - -/** - * The button component for toggling and selecting captions - * - * @extends TextTrackButton - */ -var CaptionsButton = function (_TextTrackButton) { - _inherits(CaptionsButton, _TextTrackButton); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - * - * @param {Component~ReadyCallback} [ready] - * The function to call when this component is ready. - */ - function CaptionsButton(player, options, ready) { - _classCallCheck(this, CaptionsButton); + TrackList.prototype.removeTrack = function removeTrack(rtrack) { + var track = void 0; - return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); - } + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === rtrack) { + track = this[i]; + if (track.off) { + track.off(); + } - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ + this.tracks_.splice(i, 1); + break; + } + } - CaptionsButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); - }; + if (!track) { + return; + } - CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + /** + * Triggered when a track is removed from track list. + * + * @event TrackList#removetrack + * @type {EventTarget~Event} + * @property {Track} track + * A reference to track that was removed. + */ + this.trigger({ + track: track, + type: 'removetrack' + }); }; /** - * Create caption menu items + * Get a Track from the TrackList by a tracks id * - * @return {CaptionSettingsMenuItem[]} - * The array of current menu items. + * @param {String} id - the id of the track to get + * @method getTrackById + * @return {Track} + * @private */ - CaptionsButton.prototype.createItems = function createItems() { - var items = []; + TrackList.prototype.getTrackById = function getTrackById(id) { + var result = null; - if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { - items.push(new _captionSettingsMenuItem2['default'](this.player_, { kind: this.kind_ })); + for (var i = 0, l = this.length; i < l; i++) { + var track = this[i]; - this.hideThreshold_ += 1; + if (track.id === id) { + result = track; + break; + } } - return _TextTrackButton.prototype.createItems.call(this, items); + return result; }; - return CaptionsButton; -}(_textTrackButton2['default']); + return TrackList; +}(EventTarget); /** - * `kind` of TextTrack to look for to associate it with this menu. + * Triggered when a different track is selected/enabled. * - * @type {string} - * @private + * @event TrackList#change + * @type {EventTarget~Event} */ - -CaptionsButton.prototype.kind_ = 'captions'; - /** - * The text that should display over the `CaptionsButton`s controls. Added for localization. + * Events that can be called with on + eventName. See {@link EventHandler}. * - * @type {string} + * @property {Object} TrackList#allowedEvents_ * @private */ -CaptionsButton.prototype.controlText_ = 'Captions'; -_component2['default'].registerComponent('CaptionsButton', CaptionsButton); -exports['default'] = CaptionsButton; -},{"23":23,"32":32,"5":5}],25:[function(_dereq_,module,exports){ -'use strict'; +TrackList.prototype.allowedEvents_ = { + change: 'change', + addtrack: 'addtrack', + removetrack: 'removetrack' +}; -exports.__esModule = true; +// emulate attribute EventHandler support to allow for feature detection +for (var event in TrackList.prototype.allowedEvents_) { + TrackList.prototype['on' + event] = null; +} -var _textTrackButton = _dereq_(32); +/** + * @file audio-track-list.js + */ +/** + * Anywhere we call this function we diverge from the spec + * as we only support one enabled audiotrack at a time + * + * @param {AudioTrackList} list + * list to work on + * + * @param {AudioTrack} track + * The track to skip + * + * @private + */ +var disableOthers = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (track.id === list[i].id) { + continue; + } + // another audio track is enabled, disable it + list[i].enabled = false; + } +}; -var _textTrackButton2 = _interopRequireDefault(_textTrackButton); +/** + * The current list of {@link AudioTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist} + * @extends TrackList + */ -var _component = _dereq_(5); +var AudioTrackList = function (_TrackList) { + inherits(AudioTrackList, _TrackList); -var _component2 = _interopRequireDefault(_component); + /** + * Create an instance of this class. + * + * @param {AudioTrack[]} [tracks=[]] + * A list of `AudioTrack` to instantiate the list with. + */ + function AudioTrackList() { + var _this, _ret; -var _chaptersTrackMenuItem = _dereq_(26); + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, AudioTrackList); -var _chaptersTrackMenuItem2 = _interopRequireDefault(_chaptersTrackMenuItem); + var list = void 0; -var _toTitleCase = _dereq_(96); - -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].enabled) { + disableOthers(tracks, tracks[i]); + break; + } + } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file chapters-button.js - */ + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (IS_IE8) { + list = document_1.createElement('custom'); + for (var prop in TrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TrackList.prototype[prop]; + } + } + for (var _prop in AudioTrackList.prototype) { + if (_prop !== 'constructor') { + list[_prop] = AudioTrackList.prototype[_prop]; + } + } + } + list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); + list.changing_ = false; -/** - * The button component for toggling and selecting chapters - * Chapters act much differently than other text tracks - * Cues are navigation vs. other tracks of alternative languages - * - * @extends TextTrackButton - */ -var ChaptersButton = function (_TextTrackButton) { - _inherits(ChaptersButton, _TextTrackButton); + return _ret = list, possibleConstructorReturn(_this, _ret); + } /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. + * Add an {@link AudioTrack} to the `AudioTrackList`. * - * @param {Object} [options] - * The key/value store of player options. + * @param {AudioTrack} track + * The AudioTrack to add to the list * - * @param {Component~ReadyCallback} [ready] - * The function to call when this function is ready. + * @fires TrackList#addtrack */ - function ChaptersButton(player, options, ready) { - _classCallCheck(this, ChaptersButton); - return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); - } - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ + AudioTrackList.prototype.addTrack = function addTrack(track) { + var _this2 = this; + if (track.enabled) { + disableOthers(this, track); + } - ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); - }; + _TrackList.prototype.addTrack.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; + } - ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + /** + * @listens AudioTrack#enabledchange + * @fires TrackList#change + */ + track.addEventListener('enabledchange', function () { + // when we are disabling other tracks (since we don't support + // more than one track at a time) we will set changing_ + // to true so that we don't trigger additional change events + if (_this2.changing_) { + return; + } + _this2.changing_ = true; + disableOthers(_this2, track); + _this2.changing_ = false; + _this2.trigger('change'); + }); }; - /** - * Update the menu based on the current state of its items. - * - * @param {EventTarget~Event} [event] - * An event that triggered this function to run. - * - * @listens TextTrackList#addtrack - * @listens TextTrackList#removetrack - * @listens TextTrackList#change - */ - + return AudioTrackList; +}(TrackList); - ChaptersButton.prototype.update = function update(event) { - if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) { - this.setTrack(this.findChaptersTrack()); +/** + * @file video-track-list.js + */ +/** + * Un-select all other {@link VideoTrack}s that are selected. + * + * @param {VideoTrackList} list + * list to work on + * + * @param {VideoTrack} track + * The track to skip + * + * @private + */ +var disableOthers$1 = function disableOthers(list, track) { + for (var i = 0; i < list.length; i++) { + if (track.id === list[i].id) { + continue; } - _TextTrackButton.prototype.update.call(this); - }; + // another video track is enabled, disable it + list[i].selected = false; + } +}; + +/** + * The current list of {@link VideoTrack} for a video. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist} + * @extends TrackList + */ + +var VideoTrackList = function (_TrackList) { + inherits(VideoTrackList, _TrackList); /** - * Set the currently selected track for the chapters button. + * Create an instance of this class. * - * @param {TextTrack} track - * The new track to select. Nothing will change if this is the currently selected - * track. + * @param {VideoTrack[]} [tracks=[]] + * A list of `VideoTrack` to instantiate the list with. */ + function VideoTrackList() { + var _this, _ret; + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, VideoTrackList); - ChaptersButton.prototype.setTrack = function setTrack(track) { - if (this.track_ === track) { - return; - } + var list = void 0; - if (!this.updateHandler_) { - this.updateHandler_ = this.update.bind(this); + // make sure only 1 track is enabled + // sorted from last index to first index + for (var i = tracks.length - 1; i >= 0; i--) { + if (tracks[i].selected) { + disableOthers$1(tracks, tracks[i]); + break; + } } - // here this.track_ refers to the old track instance - if (this.track_) { - var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); - - if (remoteTextTrackEl) { - remoteTextTrackEl.removeEventListener('load', this.updateHandler_); + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (IS_IE8) { + list = document_1.createElement('custom'); + for (var prop in TrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TrackList.prototype[prop]; + } + } + for (var _prop in VideoTrackList.prototype) { + if (_prop !== 'constructor') { + list[_prop] = VideoTrackList.prototype[_prop]; + } } - - this.track_ = null; } - this.track_ = track; - - // here this.track_ refers to the new track instance - if (this.track_) { - this.track_.mode = 'hidden'; + list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); + list.changing_ = false; - var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_); + /** + * @member {number} VideoTrackList#selectedIndex + * The current index of the selected {@link VideoTrack`}. + */ + Object.defineProperty(list, 'selectedIndex', { + get: function get$$1() { + for (var _i = 0; _i < this.length; _i++) { + if (this[_i].selected) { + return _i; + } + } + return -1; + }, + set: function set$$1() {} + }); - if (_remoteTextTrackEl) { - _remoteTextTrackEl.addEventListener('load', this.updateHandler_); - } - } - }; + return _ret = list, possibleConstructorReturn(_this, _ret); + } /** - * Find the track object that is currently in use by this ChaptersButton + * Add a {@link VideoTrack} to the `VideoTrackList`. * - * @return {TextTrack|undefined} - * The current track or undefined if none was found. + * @param {VideoTrack} track + * The VideoTrack to add to the list + * + * @fires TrackList#addtrack */ - ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() { - var tracks = this.player_.textTracks() || []; + VideoTrackList.prototype.addTrack = function addTrack(track) { + var _this2 = this; - for (var i = tracks.length - 1; i >= 0; i--) { - // We will always choose the last track as our chaptersTrack - var track = tracks[i]; + if (track.selected) { + disableOthers$1(this, track); + } - if (track.kind === this.kind_) { - return track; - } + _TrackList.prototype.addTrack.call(this, track); + // native tracks don't have this + if (!track.addEventListener) { + return; } + + /** + * @listens VideoTrack#selectedchange + * @fires TrackList#change + */ + track.addEventListener('selectedchange', function () { + if (_this2.changing_) { + return; + } + _this2.changing_ = true; + disableOthers$1(_this2, track); + _this2.changing_ = false; + _this2.trigger('change'); + }); }; + return VideoTrackList; +}(TrackList); + +/** + * @file text-track-list.js + */ +/** + * The current list of {@link TextTrack} for a media file. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist} + * @extends TrackList + */ + +var TextTrackList = function (_TrackList) { + inherits(TextTrackList, _TrackList); + /** - * Get the caption for the ChaptersButton based on the track label. This will also - * use the current tracks localized kind as a fallback if a label does not exist. + * Create an instance of this class. * - * @return {string} - * The tracks current label or the localized track kind. + * @param {TextTrack[]} [tracks=[]] + * A list of `TextTrack` to instantiate the list with. */ + function TextTrackList() { + var _this, _ret; + var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, TextTrackList); - ChaptersButton.prototype.getMenuCaption = function getMenuCaption() { - if (this.track_ && this.track_.label) { - return this.track_.label; - } - return this.localize((0, _toTitleCase2['default'])(this.kind_)); - }; - - /** - * Create menu from chapter track - * - * @return {Menu} - * New menu for the chapter buttons - */ + var list = void 0; + // IE8 forces us to implement inheritance ourselves + // as it does not support Object.defineProperty properly + if (IS_IE8) { + list = document_1.createElement('custom'); + for (var prop in TrackList.prototype) { + if (prop !== 'constructor') { + list[prop] = TrackList.prototype[prop]; + } + } + for (var _prop in TextTrackList.prototype) { + if (_prop !== 'constructor') { + list[_prop] = TextTrackList.prototype[_prop]; + } + } + } - ChaptersButton.prototype.createMenu = function createMenu() { - this.options_.title = this.getMenuCaption(); - return _TextTrackButton.prototype.createMenu.call(this); - }; + list = (_this = possibleConstructorReturn(this, _TrackList.call(this, tracks, list)), _this); + return _ret = list, possibleConstructorReturn(_this, _ret); + } /** - * Create a menu item for each text track + * Add a {@link TextTrack} to the `TextTrackList` * - * @return {TextTrackMenuItem[]} - * Array of menu items + * @param {TextTrack} track + * The text track to add to the list. + * + * @fires TrackList#addtrack */ - ChaptersButton.prototype.createItems = function createItems() { - var items = []; - - if (!this.track_) { - return items; - } - - var cues = this.track_.cues; + TextTrackList.prototype.addTrack = function addTrack(track) { + _TrackList.prototype.addTrack.call(this, track); - if (!cues) { - return items; - } + /** + * @listens TextTrack#modechange + * @fires TrackList#change + */ + track.addEventListener('modechange', bind(this, function () { + this.trigger('change'); + })); - for (var i = 0, l = cues.length; i < l; i++) { - var cue = cues[i]; - var mi = new _chaptersTrackMenuItem2['default'](this.player_, { track: this.track_, cue: cue }); + var nonLanguageTextTrackKind = ['metadata', 'chapters']; - items.push(mi); + if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) { + track.addEventListener('modechange', bind(this, function () { + this.trigger('selectedlanguagechange'); + })); } - - return items; }; - return ChaptersButton; -}(_textTrackButton2['default']); + return TextTrackList; +}(TrackList); /** - * `kind` of TextTrack to look for to associate it with this menu. - * - * @type {string} - * @private + * @file html-track-element-list.js */ - -ChaptersButton.prototype.kind_ = 'chapters'; - /** - * The text that should display over the `ChaptersButton`s controls. Added for localization. - * - * @type {string} - * @private + * The current list of {@link HtmlTrackElement}s. */ -ChaptersButton.prototype.controlText_ = 'Chapters'; - -_component2['default'].registerComponent('ChaptersButton', ChaptersButton); -exports['default'] = ChaptersButton; - -},{"26":26,"32":32,"5":5,"96":96}],26:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; -var _menuItem = _dereq_(51); - -var _menuItem2 = _interopRequireDefault(_menuItem); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _fn = _dereq_(88); +var HtmlTrackElementList = function () { -var Fn = _interopRequireWildcard(_fn); + /** + * Create an instance of this class. + * + * @param {HtmlTrackElement[]} [tracks=[]] + * A list of `HtmlTrackElement` to instantiate the list with. + */ + function HtmlTrackElementList() { + var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + classCallCheck(this, HtmlTrackElementList); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + var list = this; // eslint-disable-line -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + if (IS_IE8) { + list = document_1.createElement('custom'); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + for (var prop in HtmlTrackElementList.prototype) { + if (prop !== 'constructor') { + list[prop] = HtmlTrackElementList.prototype[prop]; + } + } + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + list.trackElements_ = []; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file chapters-track-menu-item.js - */ + /** + * @memberof HtmlTrackElementList + * @member {number} length + * The current number of `Track`s in the this Trackist. + * @instance + */ + Object.defineProperty(list, 'length', { + get: function get$$1() { + return this.trackElements_.length; + } + }); + for (var i = 0, length = trackElements.length; i < length; i++) { + list.addTrackElement_(trackElements[i]); + } -/** - * The chapter track menu item - * - * @extends MenuItem - */ -var ChaptersTrackMenuItem = function (_MenuItem) { - _inherits(ChaptersTrackMenuItem, _MenuItem); + if (IS_IE8) { + return list; + } + } /** - * Creates an instance of this class. + * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList` * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {HtmlTrackElement} trackElement + * The track element to add to the list. * - * @param {Object} [options] - * The key/value store of player options. + * @private */ - function ChaptersTrackMenuItem(player, options) { - _classCallCheck(this, ChaptersTrackMenuItem); - var track = options.track; - var cue = options.cue; - var currentTime = player.currentTime(); - // Modify options for parent MenuItem class's init. - options.selectable = true; - options.label = cue.text; - options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; + HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) { + var index = this.trackElements_.length; - var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + if (!('' + index in this)) { + Object.defineProperty(this, index, { + get: function get$$1() { + return this.trackElements_[index]; + } + }); + } - _this.track = track; - _this.cue = cue; - track.addEventListener('cuechange', Fn.bind(_this, _this.update)); - return _this; - } + // Do not add duplicate elements + if (this.trackElements_.indexOf(trackElement) === -1) { + this.trackElements_.push(trackElement); + } + }; /** - * This gets called when an `ChaptersTrackMenuItem` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. + * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an + * {@link TextTrack}. * - * @param {EventTarget~Event} [event] - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. + * @param {TextTrack} track + * The track associated with a track element. * - * @listens tap - * @listens click + * @return {HtmlTrackElement|undefined} + * The track element that was found or undefined. + * + * @private */ - ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { - _MenuItem.prototype.handleClick.call(this); - this.player_.currentTime(this.cue.startTime); - this.update(this.cue.startTime); + HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) { + var trackElement_ = void 0; + + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (track === this.trackElements_[i].track) { + trackElement_ = this.trackElements_[i]; + + break; + } + } + + return trackElement_; }; /** - * Update chapter menu item + * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList` * - * @param {EventTarget~Event} [event] - * The `cuechange` event that caused this function to run. + * @param {HtmlTrackElement} trackElement + * The track element to remove from the list. * - * @listens TextTrack#cuechange + * @private */ - ChaptersTrackMenuItem.prototype.update = function update(event) { - var cue = this.cue; - var currentTime = this.player_.currentTime(); + HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) { + for (var i = 0, length = this.trackElements_.length; i < length; i++) { + if (trackElement === this.trackElements_[i]) { + this.trackElements_.splice(i, 1); - // vjs.log(currentTime, cue.startTime); - this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); + break; + } + } }; - return ChaptersTrackMenuItem; -}(_menuItem2['default']); - -_component2['default'].registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); -exports['default'] = ChaptersTrackMenuItem; - -},{"5":5,"51":51,"88":88}],27:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _textTrackButton = _dereq_(32); - -var _textTrackButton2 = _interopRequireDefault(_textTrackButton); + return HtmlTrackElementList; +}(); -var _component = _dereq_(5); +/** + * @file text-track-cue-list.js + */ +/** + * @typedef {Object} TextTrackCueList~TextTrackCue + * + * @property {string} id + * The unique id for this text track cue + * + * @property {number} startTime + * The start time for this text track cue + * + * @property {number} endTime + * The end time for this text track cue + * + * @property {boolean} pauseOnExit + * Pause when the end time is reached if true. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue} + */ -var _component2 = _interopRequireDefault(_component); +/** + * A List of TextTrackCues. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist} + */ -var _fn = _dereq_(88); +var TextTrackCueList = function () { -var Fn = _interopRequireWildcard(_fn); + /** + * Create an instance of this class.. + * + * @param {Array} cues + * A list of cues to be initialized with + */ + function TextTrackCueList(cues) { + classCallCheck(this, TextTrackCueList); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } + var list = this; // eslint-disable-line -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + if (IS_IE8) { + list = document_1.createElement('custom'); -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + for (var prop in TextTrackCueList.prototype) { + if (prop !== 'constructor') { + list[prop] = TextTrackCueList.prototype[prop]; + } + } + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file descriptions-button.js - */ + TextTrackCueList.prototype.setCues_.call(list, cues); + /** + * @memberof TextTrackCueList + * @member {number} length + * The current number of `TextTrackCue`s in the TextTrackCueList. + * @instance + */ + Object.defineProperty(list, 'length', { + get: function get$$1() { + return this.length_; + } + }); -/** - * The button component for toggling and selecting descriptions - * - * @extends TextTrackButton - */ -var DescriptionsButton = function (_TextTrackButton) { - _inherits(DescriptionsButton, _TextTrackButton); + if (IS_IE8) { + return list; + } + } /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. + * A setter for cues in this list. Creates getters + * an an index for the cues. * - * @param {Object} [options] - * The key/value store of player options. + * @param {Array} cues + * An array of cues to set * - * @param {Component~ReadyCallback} [ready] - * The function to call when this component is ready. + * @private */ - function DescriptionsButton(player, options, ready) { - _classCallCheck(this, DescriptionsButton); - var _this = _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); - var tracks = player.textTracks(); - var changeHandler = Fn.bind(_this, _this.handleTracksChange); + TextTrackCueList.prototype.setCues_ = function setCues_(cues) { + var oldLength = this.length || 0; + var i = 0; + var l = cues.length; - tracks.addEventListener('change', changeHandler); - _this.on('dispose', function () { - tracks.removeEventListener('change', changeHandler); - }); - return _this; - } + this.cues_ = cues; + this.length_ = cues.length; + + var defineProp = function defineProp(index) { + if (!('' + index in this)) { + Object.defineProperty(this, '' + index, { + get: function get$$1() { + return this.cues_[index]; + } + }); + } + }; + + if (oldLength < l) { + i = oldLength; + + for (; i < l; i++) { + defineProp.call(this, i); + } + } + }; /** - * Handle text track change + * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id. * - * @param {EventTarget~Event} event - * The event that caused this function to run + * @param {string} id + * The id of the cue that should be searched for. * - * @listens TextTrackList#change + * @return {TextTrackCueList~TextTrackCue|null} + * A single cue or null if none was found. */ - DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) { - var tracks = this.player().textTracks(); - var disabled = false; + TextTrackCueList.prototype.getCueById = function getCueById(id) { + var result = null; - // Check whether a track of a different kind is showing - for (var i = 0, l = tracks.length; i < l; i++) { - var track = tracks[i]; + for (var i = 0, l = this.length; i < l; i++) { + var cue = this[i]; - if (track.kind !== this.kind_ && track.mode === 'showing') { - disabled = true; + if (cue.id === id) { + result = cue; break; } } - // If another track is showing, disable this menu button - if (disabled) { - this.disable(); - } else { - this.enable(); - } - }; - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + return result; }; - DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); - }; + return TextTrackCueList; +}(); - return DescriptionsButton; -}(_textTrackButton2['default']); +/** + * @file track-kinds.js + */ /** - * `kind` of TextTrack to look for to associate it with this menu. + * All possible `VideoTrackKind`s * - * @type {string} - * @private + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind + * @typedef VideoTrack~Kind + * @enum */ - - -DescriptionsButton.prototype.kind_ = 'descriptions'; +var VideoTrackKind = { + alternative: 'alternative', + captions: 'captions', + main: 'main', + sign: 'sign', + subtitles: 'subtitles', + commentary: 'commentary' +}; /** - * The text that should display over the `DescriptionsButton`s controls. Added for localization. + * All possible `AudioTrackKind`s * - * @type {string} - * @private + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind + * @typedef AudioTrack~Kind + * @enum */ -DescriptionsButton.prototype.controlText_ = 'Descriptions'; - -_component2['default'].registerComponent('DescriptionsButton', DescriptionsButton); -exports['default'] = DescriptionsButton; - -},{"32":32,"5":5,"88":88}],28:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _textTrackMenuItem = _dereq_(33); - -var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +var AudioTrackKind = { + 'alternative': 'alternative', + 'descriptions': 'descriptions', + 'main': 'main', + 'main-desc': 'main-desc', + 'translation': 'translation', + 'commentary': 'commentary' +}; -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file off-text-track-menu-item.js - */ +/** + * All possible `TextTrackKind`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind + * @typedef TextTrack~Kind + * @enum + */ +var TextTrackKind = { + subtitles: 'subtitles', + captions: 'captions', + descriptions: 'descriptions', + chapters: 'chapters', + metadata: 'metadata' +}; +/** + * All possible `TextTrackMode`s + * + * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode + * @typedef TextTrack~Mode + * @enum + */ +var TextTrackMode = { + disabled: 'disabled', + hidden: 'hidden', + showing: 'showing' +}; /** - * A special menu item for turning of a specific type of text track + * @file track.js + */ +/** + * A Track class that contains all of the common functionality for {@link AudioTrack}, + * {@link VideoTrack}, and {@link TextTrack}. * - * @extends TextTrackMenuItem + * > Note: This class should not be used directly + * + * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html} + * @extends EventTarget + * @abstract */ -var OffTextTrackMenuItem = function (_TextTrackMenuItem) { - _inherits(OffTextTrackMenuItem, _TextTrackMenuItem); + +var Track = function (_EventTarget) { + inherits(Track, _EventTarget); /** - * Creates an instance of this class. + * Create an instance of this class. * - * @param {Player} player - * The `Player` that this class should be attached to. + * @param {Object} [options={}] + * Object of option names and values * - * @param {Object} [options] - * The key/value store of player options. + * @param {string} [options.kind=''] + * A valid kind for the track type you are creating. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @abstract */ - function OffTextTrackMenuItem(player, options) { - _classCallCheck(this, OffTextTrackMenuItem); - - // Create pseudo track info - // Requires options['kind'] - options.track = { - player: player, - kind: options.kind, - kinds: options.kinds, - 'default': false, - mode: 'disabled' - }; + function Track() { + var _ret; - if (!options.kinds) { - options.kinds = [options.kind]; - } + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, Track); - if (options.label) { - options.track.label = options.label; - } else { - options.track.label = options.kinds.join(' and ') + ' off'; - } + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); - // MenuItem is selectable - options.selectable = true; + var track = _this; // eslint-disable-line - var _this = _possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); + if (IS_IE8) { + track = document_1.createElement('custom'); + for (var prop in Track.prototype) { + if (prop !== 'constructor') { + track[prop] = Track.prototype[prop]; + } + } + } - _this.selected(true); - return _this; - } + var trackProps = { + id: options.id || 'vjs_track_' + newGUID(), + kind: options.kind || '', + label: options.label || '', + language: options.language || '' + }; - /** - * Handle text track change - * - * @param {EventTarget~Event} event - * The event that caused this function to run - */ - - - OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { - var tracks = this.player().textTracks(); - var selected = true; - - for (var i = 0, l = tracks.length; i < l; i++) { - var track = tracks[i]; - - if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { - selected = false; - break; - } - } - - this.selected(selected); - }; - - return OffTextTrackMenuItem; -}(_textTrackMenuItem2['default']); - -_component2['default'].registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); -exports['default'] = OffTextTrackMenuItem; - -},{"33":33,"5":5}],29:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _textTrackButton = _dereq_(32); - -var _textTrackButton2 = _interopRequireDefault(_textTrackButton); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _captionSettingsMenuItem = _dereq_(23); - -var _captionSettingsMenuItem2 = _interopRequireDefault(_captionSettingsMenuItem); - -var _subsCapsMenuItem = _dereq_(30); + /** + * @memberof Track + * @member {string} id + * The id of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ -var _subsCapsMenuItem2 = _interopRequireDefault(_subsCapsMenuItem); + /** + * @memberof Track + * @member {string} kind + * The kind of track that this is. Cannot be changed after creation. + * @instance + * + * @readonly + */ -var _toTitleCase = _dereq_(96); + /** + * @memberof Track + * @member {string} label + * The label of this track. Cannot be changed after creation. + * @instance + * + * @readonly + */ -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); + /** + * @memberof Track + * @member {string} language + * The two letter language code for this track. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + var _loop = function _loop(key) { + Object.defineProperty(track, key, { + get: function get$$1() { + return trackProps[key]; + }, + set: function set$$1() {} + }); + }; -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + for (var key in trackProps) { + _loop(key); + } -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + return _ret = track, possibleConstructorReturn(_this, _ret); + } -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file sub-caps-button.js - */ + return Track; +}(EventTarget); +/** + * @file url.js + * @module url + */ +/** + * @typedef {Object} url:URLObject + * + * @property {string} protocol + * The protocol of the url that was parsed. + * + * @property {string} hostname + * The hostname of the url that was parsed. + * + * @property {string} port + * The port of the url that was parsed. + * + * @property {string} pathname + * The pathname of the url that was parsed. + * + * @property {string} search + * The search query of the url that was parsed. + * + * @property {string} hash + * The hash of the url that was parsed. + * + * @property {string} host + * The host of the url that was parsed. + */ /** - * The button component for toggling and selecting captions and/or subtitles + * Resolve and parse the elements of a URL. * - * @extends TextTrackButton + * @param {String} url + * The url to parse + * + * @return {url:URLObject} + * An object of url details */ -var SubsCapsButton = function (_TextTrackButton) { - _inherits(SubsCapsButton, _TextTrackButton); +var parseUrl = function parseUrl(url) { + var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host']; - function SubsCapsButton(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + // add the url to an anchor and let the browser parse the URL + var a = document_1.createElement('a'); - _classCallCheck(this, SubsCapsButton); + a.href = url; - // Although North America uses "captions" in most cases for - // "captions and subtitles" other locales use "subtitles" - var _this = _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options)); + // IE8 (and 9?) Fix + // ie8 doesn't parse the URL correctly until the anchor is actually + // added to the body, and an innerHTML is needed to trigger the parsing + var addToBody = a.host === '' && a.protocol !== 'file:'; + var div = void 0; - _this.label_ = 'subtitles'; - if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) { - _this.label_ = 'captions'; - } - _this.menuButton_.controlText((0, _toTitleCase2['default'])(_this.label_)); - return _this; + if (addToBody) { + div = document_1.createElement('div'); + div.innerHTML = ''; + a = div.firstChild; + // prevent the div from affecting layout + div.setAttribute('style', 'display:none; position:absolute;'); + document_1.body.appendChild(div); } - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); - }; + // Copy the specific URL properties to a new object + // This is also needed for IE8 because the anchor loses its + // properties when it's removed from the dom + var details = {}; - SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); - }; + for (var i = 0; i < props.length; i++) { + details[props[i]] = a[props[i]]; + } - /** - * Create caption/subtitles menu items - * - * @return {CaptionSettingsMenuItem[]} - * The array of current menu items. - */ + // IE9 adds the port to the host property unlike everyone else. If + // a port identifier is added for standard ports, strip it. + if (details.protocol === 'http:') { + details.host = details.host.replace(/:80$/, ''); + } + if (details.protocol === 'https:') { + details.host = details.host.replace(/:443$/, ''); + } - SubsCapsButton.prototype.createItems = function createItems() { - var items = []; + if (addToBody) { + document_1.body.removeChild(div); + } - if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks)) { - items.push(new _captionSettingsMenuItem2['default'](this.player_, { kind: this.label_ })); + return details; +}; - this.hideThreshold_ += 1; - } +/** + * Get absolute version of relative URL. Used to tell flash correct URL. + * + * + * @param {string} url + * URL to make absolute + * + * @return {string} + * Absolute URL + * + * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue + */ +var getAbsoluteURL = function getAbsoluteURL(url) { + // Check if absolute URL + if (!url.match(/^https?:\/\//)) { + // Convert to absolute URL. Flash hosted off-site needs an absolute URL. + var div = document_1.createElement('div'); - items = _TextTrackButton.prototype.createItems.call(this, items, _subsCapsMenuItem2['default']); - return items; - }; + div.innerHTML = 'x'; + url = div.firstChild.href; + } - return SubsCapsButton; -}(_textTrackButton2['default']); + return url; +}; /** - * `kind`s of TextTrack to look for to associate it with this menu. + * Returns the extension of the passed file name. It will return an empty string + * if passed an invalid path. * - * @type {array} - * @private + * @param {string} path + * The fileName path like '/path/to/file.mp4' + * + * @returns {string} + * The extension in lower case or an empty string if no + * extension could be found. */ +var getFileExtension = function getFileExtension(path) { + if (typeof path === 'string') { + var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i; + var pathParts = splitPathRe.exec(path); + if (pathParts) { + return pathParts.pop().toLowerCase(); + } + } -SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles']; + return ''; +}; /** - * The text that should display over the `SubsCapsButton`s controls. + * Returns whether the url passed is a cross domain request or not. * + * @param {string} url + * The url to check. * - * @type {string} - * @private + * @return {boolean} + * Whether it is a cross domain request or not. */ -SubsCapsButton.prototype.controlText_ = 'Subtitles'; - -_component2['default'].registerComponent('SubsCapsButton', SubsCapsButton); -exports['default'] = SubsCapsButton; +var isCrossOrigin = function isCrossOrigin(url) { + var winLoc = window_1.location; + var urlInfo = parseUrl(url); -},{"23":23,"30":30,"32":32,"5":5,"96":96}],30:[function(_dereq_,module,exports){ -'use strict'; + // IE8 protocol relative urls will return ':' for protocol + var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol; -exports.__esModule = true; + // Check if url is for another domain/origin + // IE8 doesn't know location.origin, so we won't rely on it here + var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host; -var _textTrackMenuItem = _dereq_(33); + return crossOrigin; +}; -var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); +var Url = (Object.freeze || Object)({ + parseUrl: parseUrl, + getAbsoluteURL: getAbsoluteURL, + getFileExtension: getFileExtension, + isCrossOrigin: isCrossOrigin +}); -var _component = _dereq_(5); +var index$1 = isFunction; -var _component2 = _interopRequireDefault(_component); +var toString$1 = Object.prototype.toString; -var _obj = _dereq_(93); +function isFunction (fn) { + var string = toString$1.call(fn); + return string === '[object Function]' || + (typeof fn === 'function' && string !== '[object RegExp]') || + (typeof window !== 'undefined' && + // IE8 and below + (fn === window.setTimeout || + fn === window.alert || + fn === window.confirm || + fn === window.prompt)) +} -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file subs-caps-menu-item.js - */ +var index$3 = createCommonjsModule(function (module, exports) { +exports = module.exports = trim; +function trim(str){ + return str.replace(/^\s*|\s*$/g, ''); +} -/** - * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles - * in the SubsCapsMenu. - * - * @extends TextTrackMenuItem - */ -var SubsCapsMenuItem = function (_TextTrackMenuItem) { - _inherits(SubsCapsMenuItem, _TextTrackMenuItem); +exports.left = function(str){ + return str.replace(/^\s*/, ''); +}; - function SubsCapsMenuItem() { - _classCallCheck(this, SubsCapsMenuItem); +exports.right = function(str){ + return str.replace(/\s*$/, ''); +}; +}); - return _possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments)); - } +var index$5 = forEach; - SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) { - var innerHTML = '' + this.localize(this.options_.label); +var toString$2 = Object.prototype.toString; +var hasOwnProperty = Object.prototype.hasOwnProperty; - if (this.options_.track.kind === 'captions') { - innerHTML += '\n \n ' + this.localize('Captions') + '\n '; +function forEach(list, iterator, context) { + if (!index$1(iterator)) { + throw new TypeError('iterator must be a function') } - innerHTML += ''; - - var el = _TextTrackMenuItem.prototype.createEl.call(this, type, (0, _obj.assign)({ - innerHTML: innerHTML - }, props), attrs); - - return el; - }; - - return SubsCapsMenuItem; -}(_textTrackMenuItem2['default']); - -_component2['default'].registerComponent('SubsCapsMenuItem', SubsCapsMenuItem); -exports['default'] = SubsCapsMenuItem; - -},{"33":33,"5":5,"93":93}],31:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _textTrackButton = _dereq_(32); - -var _textTrackButton2 = _interopRequireDefault(_textTrackButton); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file subtitles-button.js - */ - - -/** - * The button component for toggling and selecting subtitles - * - * @extends TextTrackButton - */ -var SubtitlesButton = function (_TextTrackButton) { - _inherits(SubtitlesButton, _TextTrackButton); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - * - * @param {Component~ReadyCallback} [ready] - * The function to call when this component is ready. - */ - function SubtitlesButton(player, options, ready) { - _classCallCheck(this, SubtitlesButton); - - return _possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); - } - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); - }; - - SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); - }; - - return SubtitlesButton; -}(_textTrackButton2['default']); - -/** - * `kind` of TextTrack to look for to associate it with this menu. - * - * @type {string} - * @private - */ + if (arguments.length < 3) { + context = this; + } + + if (toString$2.call(list) === '[object Array]') + forEachArray$1(list, iterator, context); + else if (typeof list === 'string') + forEachString(list, iterator, context); + else + forEachObject(list, iterator, context); +} +function forEachArray$1(array, iterator, context) { + for (var i = 0, len = array.length; i < len; i++) { + if (hasOwnProperty.call(array, i)) { + iterator.call(context, array[i], i, array); + } + } +} -SubtitlesButton.prototype.kind_ = 'subtitles'; +function forEachString(string, iterator, context) { + for (var i = 0, len = string.length; i < len; i++) { + // no such thing as a sparse string. + iterator.call(context, string.charAt(i), i, string); + } +} -/** - * The text that should display over the `SubtitlesButton`s controls. Added for localization. - * - * @type {string} - * @private - */ -SubtitlesButton.prototype.controlText_ = 'Subtitles'; +function forEachObject(object, iterator, context) { + for (var k in object) { + if (hasOwnProperty.call(object, k)) { + iterator.call(context, object[k], k, object); + } + } +} -_component2['default'].registerComponent('SubtitlesButton', SubtitlesButton); -exports['default'] = SubtitlesButton; +var isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; -},{"32":32,"5":5}],32:[function(_dereq_,module,exports){ -'use strict'; +var parseHeaders = function (headers) { + if (!headers) + return {} -exports.__esModule = true; + var result = {}; -var _trackButton = _dereq_(38); + index$5( + index$3(headers).split('\n') + , function (row) { + var index = row.indexOf(':') + , key = index$3(row.slice(0, index)).toLowerCase() + , value = index$3(row.slice(index + 1)); -var _trackButton2 = _interopRequireDefault(_trackButton); + if (typeof(result[key]) === 'undefined') { + result[key] = value; + } else if (isArray(result[key])) { + result[key].push(value); + } else { + result[key] = [ result[key], value ]; + } + } + ); -var _component = _dereq_(5); + return result +}; -var _component2 = _interopRequireDefault(_component); +var immutable = extend; -var _textTrackMenuItem = _dereq_(33); +var hasOwnProperty$1 = Object.prototype.hasOwnProperty; -var _textTrackMenuItem2 = _interopRequireDefault(_textTrackMenuItem); +function extend() { + var target = {}; -var _offTextTrackMenuItem = _dereq_(28); + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i]; -var _offTextTrackMenuItem2 = _interopRequireDefault(_offTextTrackMenuItem); + for (var key in source) { + if (hasOwnProperty$1.call(source, key)) { + target[key] = source[key]; + } + } + } -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } + return target +} -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } +var index = createXHR; +createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop; +createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window_1.XDomainRequest; -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } +forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) { + createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) { + options = initParams(uri, options, callback); + options.method = method.toUpperCase(); + return _createXHR(options) + }; +}); -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file text-track-button.js - */ +function forEachArray(array, iterator) { + for (var i = 0; i < array.length; i++) { + iterator(array[i]); + } +} +function isEmpty(obj){ + for(var i in obj){ + if(obj.hasOwnProperty(i)) return false + } + return true +} -/** - * The base class for buttons that toggle specific text track types (e.g. subtitles) - * - * @extends MenuButton - */ -var TextTrackButton = function (_TrackButton) { - _inherits(TextTrackButton, _TrackButton); +function initParams(uri, options, callback) { + var params = uri; - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - */ - function TextTrackButton(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + if (index$1(options)) { + callback = options; + if (typeof uri === "string") { + params = {uri:uri}; + } + } else { + params = immutable(options, {uri: uri}); + } - _classCallCheck(this, TextTrackButton); + params.callback = callback; + return params +} - options.tracks = player.textTracks(); +function createXHR(uri, options, callback) { + options = initParams(uri, options, callback); + return _createXHR(options) +} - return _possibleConstructorReturn(this, _TrackButton.call(this, player, options)); - } +function _createXHR(options) { + if(typeof options.callback === "undefined"){ + throw new Error("callback argument missing") + } - /** - * Create a menu item for each text track - * - * @param {TextTrackMenuItem[]} [items=[]] - * Existing array of items to use during creation - * - * @return {TextTrackMenuItem[]} - * Array of menu items that were created - */ + var called = false; + var callback = function cbOnce(err, response, body){ + if(!called){ + called = true; + options.callback(err, response, body); + } + }; + function readystatechange() { + if (xhr.readyState === 4) { + setTimeout(loadFunc, 0); + } + } - TextTrackButton.prototype.createItems = function createItems() { - var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; - var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _textTrackMenuItem2['default']; + function getBody() { + // Chrome with requestType=blob throws errors arround when even testing access to responseText + var body = undefined; + if (xhr.response) { + body = xhr.response; + } else { + body = xhr.responseText || getXml(xhr); + } - // Label is an overide for the [track] off label - // USed to localise captions/subtitles - var label = void 0; + if (isJson) { + try { + body = JSON.parse(body); + } catch (e) {} + } - if (this.label_) { - label = this.label_ + ' off'; + return body } - // Add an OFF menu item to turn all tracks off - items.push(new _offTextTrackMenuItem2['default'](this.player_, { - kinds: this.kinds_, - kind: this.kind_, - label: label - })); - - this.hideThreshold_ += 1; - var tracks = this.player_.textTracks(); - - if (!Array.isArray(this.kinds_)) { - this.kinds_ = [this.kind_]; + function errorFunc(evt) { + clearTimeout(timeoutTimer); + if(!(evt instanceof Error)){ + evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") ); + } + evt.statusCode = 0; + return callback(evt, failureResponse) } - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - - // only add tracks that are of an appropriate kind and have a label - if (this.kinds_.indexOf(track.kind) > -1) { - var item = new TrackMenuItem(this.player_, { - track: track, - // MenuItem is selectable - selectable: true - }); - - item.addClass('vjs-' + track.kind + '-menu-item'); - items.push(item); - } - } - - return items; - }; - - return TextTrackButton; -}(_trackButton2['default']); - -_component2['default'].registerComponent('TextTrackButton', TextTrackButton); -exports['default'] = TextTrackButton; - -},{"28":28,"33":33,"38":38,"5":5}],33:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -var _menuItem = _dereq_(51); - -var _menuItem2 = _interopRequireDefault(_menuItem); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _window = _dereq_(100); - -var _window2 = _interopRequireDefault(_window); - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file text-track-menu-item.js - */ - - -/** - * The specific menu item type for selecting a language within a text track kind - * - * @extends MenuItem - */ -var TextTrackMenuItem = function (_MenuItem) { - _inherits(TextTrackMenuItem, _MenuItem); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function TextTrackMenuItem(player, options) { - _classCallCheck(this, TextTrackMenuItem); - - var track = options.track; - var tracks = player.textTracks(); - - // Modify options for parent MenuItem class's init. - options.label = track.label || track.language || 'Unknown'; - options.selected = track['default'] || track.mode === 'showing'; - - var _this = _possibleConstructorReturn(this, _MenuItem.call(this, player, options)); - - _this.track = track; - var changeHandler = Fn.bind(_this, _this.handleTracksChange); - - player.on(['loadstart', 'texttrackchange'], changeHandler); - tracks.addEventListener('change', changeHandler); - _this.on('dispose', function () { - tracks.removeEventListener('change', changeHandler); - }); - - // iOS7 doesn't dispatch change events to TextTrackLists when an - // associated track's mode changes. Without something like - // Object.observe() (also not present on iOS7), it's not - // possible to detect changes to the mode attribute and polyfill - // the change event. As a poor substitute, we manually dispatch - // change events whenever the controls modify the mode. - if (tracks.onchange === undefined) { - var event = void 0; - - _this.on(['tap', 'click'], function () { - if (_typeof(_window2['default'].Event) !== 'object') { - // Android 2.3 throws an Illegal Constructor error for window.Event - try { - event = new _window2['default'].Event('change'); - } catch (err) { - // continue regardless of error - } - } - - if (!event) { - event = _document2['default'].createEvent('Event'); - event.initEvent('change', true, true); + // will load the data & process the response in a special response object + function loadFunc() { + if (aborted) return + var status; + clearTimeout(timeoutTimer); + if(options.useXDR && xhr.status===undefined) { + //IE8 CORS GET successful response doesn't have a status field, but body is fine + status = 200; + } else { + status = (xhr.status === 1223 ? 204 : xhr.status); } + var response = failureResponse; + var err = null; - tracks.dispatchEvent(event); - }); - } - return _this; - } - - /** - * This gets called when an `TextTrackMenuItem` is "clicked". See - * {@link ClickableComponent} for more detailed information on what a click can be. - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - */ - - - TextTrackMenuItem.prototype.handleClick = function handleClick(event) { - var kind = this.track.kind; - var kinds = this.track.kinds; - var tracks = this.player_.textTracks(); - - if (!kinds) { - kinds = [kind]; - } - - _MenuItem.prototype.handleClick.call(this, event); - - if (!tracks) { - return; - } - - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - - if (track === this.track && kinds.indexOf(track.kind) > -1) { - if (track.mode !== 'showing') { - track.mode = 'showing'; + if (status !== 0){ + response = { + body: getBody(), + statusCode: status, + method: method, + headers: {}, + url: uri, + rawRequest: xhr + }; + if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE + response.headers = parseHeaders(xhr.getAllResponseHeaders()); + } + } else { + err = new Error("Internal XMLHttpRequest Error"); } - } else if (track.mode !== 'disabled') { - track.mode = 'disabled'; - } - } - }; - - /** - * Handle text track list change - * - * @param {EventTarget~Event} event - * The `change` event that caused this function to be called. - * - * @listens TextTrackList#change - */ - - - TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { - this.selected(this.track.mode === 'showing'); - }; - - return TextTrackMenuItem; -}(_menuItem2['default']); - -_component2['default'].registerComponent('TextTrackMenuItem', TextTrackMenuItem); -exports['default'] = TextTrackMenuItem; - -},{"100":100,"5":5,"51":51,"88":88,"99":99}],34:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _formatTime = _dereq_(89); - -var _formatTime2 = _interopRequireDefault(_formatTime); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file current-time-display.js - */ - - -/** - * Displays the current time - * - * @extends Component - */ -var CurrentTimeDisplay = function (_Component) { - _inherits(CurrentTimeDisplay, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function CurrentTimeDisplay(player, options) { - _classCallCheck(this, CurrentTimeDisplay); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.on(player, 'timeupdate', _this.updateContent); - return _this; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - CurrentTimeDisplay.prototype.createEl = function createEl() { - var el = _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-current-time vjs-time-control vjs-control' - }); - - this.contentEl_ = Dom.createEl('div', { - className: 'vjs-current-time-display', - // label the current time for screen reader users - innerHTML: 'Current Time ' + '0:00' - }, { - // tell screen readers not to automatically read the time as it changes - 'aria-live': 'off' - }); - - el.appendChild(this.contentEl_); - return el; - }; - - /** - * Update current time display - * - * @param {EventTarget~Event} [event] - * The `timeupdate` event that caused this function to run. - * - * @listens Player#timeupdate - */ - - - CurrentTimeDisplay.prototype.updateContent = function updateContent(event) { - // Allows for smooth scrubbing, when player can't keep up. - var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime(); - var localizedText = this.localize('Current Time'); - var formattedTime = (0, _formatTime2['default'])(time, this.player_.duration()); - - if (formattedTime !== this.formattedTime_) { - this.formattedTime_ = formattedTime; - this.contentEl_.innerHTML = '' + localizedText + ' ' + formattedTime; - } - }; - - return CurrentTimeDisplay; -}(_component2['default']); - -_component2['default'].registerComponent('CurrentTimeDisplay', CurrentTimeDisplay); -exports['default'] = CurrentTimeDisplay; - -},{"5":5,"85":85,"89":89}],35:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _formatTime = _dereq_(89); - -var _formatTime2 = _interopRequireDefault(_formatTime); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file duration-display.js - */ - - -/** - * Displays the duration - * - * @extends Component - */ -var DurationDisplay = function (_Component) { - _inherits(DurationDisplay, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function DurationDisplay(player, options) { - _classCallCheck(this, DurationDisplay); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.on(player, 'durationchange', _this.updateContent); - - // Also listen for timeupdate and loadedmetadata because removing those - // listeners could have broken dependent applications/libraries. These - // can likely be removed for 6.0. - _this.on(player, 'timeupdate', _this.updateContent); - _this.on(player, 'loadedmetadata', _this.updateContent); - return _this; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - DurationDisplay.prototype.createEl = function createEl() { - var el = _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-duration vjs-time-control vjs-control' - }); - - this.contentEl_ = Dom.createEl('div', { - className: 'vjs-duration-display', - // label the duration time for screen reader users - innerHTML: '' + this.localize('Duration Time') + ' 0:00' - }, { - // tell screen readers not to automatically read the time as it changes - 'aria-live': 'off' - }); - - el.appendChild(this.contentEl_); - return el; - }; - - /** - * Update duration time display. - * - * @param {EventTarget~Event} [event] - * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused - * this function to be called. - * - * @listens Player#durationchange - * @listens Player#timeupdate - * @listens Player#loadedmetadata - */ - - - DurationDisplay.prototype.updateContent = function updateContent(event) { - var duration = this.player_.duration(); - - if (duration && this.duration_ !== duration) { - this.duration_ = duration; - var localizedText = this.localize('Duration Time'); - var formattedTime = (0, _formatTime2['default'])(duration); - - // label the duration time for screen reader users - this.contentEl_.innerHTML = '' + localizedText + ' ' + formattedTime; - } - }; - - return DurationDisplay; -}(_component2['default']); - -_component2['default'].registerComponent('DurationDisplay', DurationDisplay); -exports['default'] = DurationDisplay; - -},{"5":5,"85":85,"89":89}],36:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _formatTime = _dereq_(89); - -var _formatTime2 = _interopRequireDefault(_formatTime); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file remaining-time-display.js - */ - - -/** - * Displays the time left in the video - * - * @extends Component - */ -var RemainingTimeDisplay = function (_Component) { - _inherits(RemainingTimeDisplay, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function RemainingTimeDisplay(player, options) { - _classCallCheck(this, RemainingTimeDisplay); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.on(player, 'timeupdate', _this.updateContent); - _this.on(player, 'durationchange', _this.updateContent); - return _this; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - RemainingTimeDisplay.prototype.createEl = function createEl() { - var el = _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-remaining-time vjs-time-control vjs-control' - }); - - this.contentEl_ = Dom.createEl('div', { - className: 'vjs-remaining-time-display', - // label the remaining time for screen reader users - innerHTML: '' + this.localize('Remaining Time') + ' -0:00' - }, { - // tell screen readers not to automatically read the time as it changes - 'aria-live': 'off' - }); - - el.appendChild(this.contentEl_); - return el; - }; - - /** - * Update remaining time display. - * - * @param {EventTarget~Event} [event] - * The `timeupdate` or `durationchange` event that caused this to run. - * - * @listens Player#timeupdate - * @listens Player#durationchange - */ - - - RemainingTimeDisplay.prototype.updateContent = function updateContent(event) { - if (this.player_.duration()) { - var localizedText = this.localize('Remaining Time'); - var formattedTime = (0, _formatTime2['default'])(this.player_.remainingTime()); - - if (formattedTime !== this.formattedTime_) { - this.formattedTime_ = formattedTime; - this.contentEl_.innerHTML = '' + localizedText + ' -' + formattedTime; - } - } - - // Allows for smooth scrubbing, when player can't keep up. - // var time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); - // this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration()); - }; - - return RemainingTimeDisplay; -}(_component2['default']); - -_component2['default'].registerComponent('RemainingTimeDisplay', RemainingTimeDisplay); -exports['default'] = RemainingTimeDisplay; - -},{"5":5,"85":85,"89":89}],37:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file time-divider.js - */ - - -/** - * The separator between the current time and duration. - * Can be hidden if it's not needed in the design. - * - * @extends Component - */ -var TimeDivider = function (_Component) { - _inherits(TimeDivider, _Component); - - function TimeDivider() { - _classCallCheck(this, TimeDivider); - - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } - - /** - * Create the component's DOM element - * - * @return {Element} - * The element that was created. - */ - TimeDivider.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-time-control vjs-time-divider', - innerHTML: '
/
' - }); - }; - - return TimeDivider; -}(_component2['default']); - -_component2['default'].registerComponent('TimeDivider', TimeDivider); -exports['default'] = TimeDivider; - -},{"5":5}],38:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _menuButton = _dereq_(50); - -var _menuButton2 = _interopRequireDefault(_menuButton); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file track-button.js - */ - - -/** - * The base class for buttons that toggle specific track types (e.g. subtitles). - * - * @extends MenuButton - */ -var TrackButton = function (_MenuButton) { - _inherits(TrackButton, _MenuButton); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function TrackButton(player, options) { - _classCallCheck(this, TrackButton); - - var tracks = options.tracks; - - var _this = _possibleConstructorReturn(this, _MenuButton.call(this, player, options)); - - if (_this.items.length <= 1) { - _this.hide(); - } - - if (!tracks) { - return _possibleConstructorReturn(_this); - } - - var updateHandler = Fn.bind(_this, _this.update); - - tracks.addEventListener('removetrack', updateHandler); - tracks.addEventListener('addtrack', updateHandler); - _this.player_.on('ready', updateHandler); - - _this.player_.on('dispose', function () { - tracks.removeEventListener('removetrack', updateHandler); - tracks.removeEventListener('addtrack', updateHandler); - }); - return _this; - } - - return TrackButton; -}(_menuButton2['default']); - -_component2['default'].registerComponent('TrackButton', TrackButton); -exports['default'] = TrackButton; - -},{"5":5,"50":50,"88":88}],39:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; -/** - * Check if volume control is supported and if it isn't hide the - * `Component` that was passed using the `vjs-hidden` class. - * - * @param {Component} self - * The component that should be hidden if volume is unsupported - * - * @param {Player} player - * A reference to the player - * - * @private - */ -var checkVolumeSupport = function checkVolumeSupport(self, player) { - // hide volume controls when they're not supported by the current tech - if (player.tech_ && !player.tech_.featuresVolumeControl) { - self.addClass('vjs-hidden'); - } - - self.on(player, 'loadstart', function () { - if (!player.tech_.featuresVolumeControl) { - self.addClass('vjs-hidden'); - } else { - self.removeClass('vjs-hidden'); - } - }); -}; - -exports['default'] = checkVolumeSupport; - -},{}],40:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _slider = _dereq_(60); - -var _slider2 = _interopRequireDefault(_slider); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -_dereq_(42); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file volume-bar.js - */ - - -// Required children - - -/** - * The bar that contains the volume level and can be clicked on to adjust the level - * - * @extends Slider - */ -var VolumeBar = function (_Slider) { - _inherits(VolumeBar, _Slider); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function VolumeBar(player, options) { - _classCallCheck(this, VolumeBar); - - var _this = _possibleConstructorReturn(this, _Slider.call(this, player, options)); - - _this.on('slideractive', _this.updateLastVolume_); - _this.on(player, 'volumechange', _this.updateARIAAttributes); - player.ready(function () { - return _this.updateARIAAttributes(); - }); - return _this; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - VolumeBar.prototype.createEl = function createEl() { - return _Slider.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-bar vjs-slider-bar' - }, { - 'aria-label': this.localize('Volume Level'), - 'aria-live': 'polite' - }); - }; - - /** - * Handle movement events on the {@link VolumeMenuButton}. - * - * @param {EventTarget~Event} event - * The event that caused this function to run. - * - * @listens mousemove - */ - - - VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) { - this.checkMuted(); - this.player_.volume(this.calculateDistance(event)); - }; - - /** - * If the player is muted unmute it. - */ - - - VolumeBar.prototype.checkMuted = function checkMuted() { - if (this.player_.muted()) { - this.player_.muted(false); - } - }; - - /** - * Get percent of volume level - * - * @return {number} - * Volume level percent as a decimal number. - */ - - - VolumeBar.prototype.getPercent = function getPercent() { - if (this.player_.muted()) { - return 0; - } - return this.player_.volume(); - }; - - /** - * Increase volume level for keyboard users - */ - - - VolumeBar.prototype.stepForward = function stepForward() { - this.checkMuted(); - this.player_.volume(this.player_.volume() + 0.1); - }; - - /** - * Decrease volume level for keyboard users - */ - - - VolumeBar.prototype.stepBack = function stepBack() { - this.checkMuted(); - this.player_.volume(this.player_.volume() - 0.1); - }; - - /** - * Update ARIA accessibility attributes - * - * @param {EventTarget~Event} [event] - * The `volumechange` event that caused this function to run. - * - * @listens Player#volumechange - */ - - - VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) { - var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_(); - - this.el_.setAttribute('aria-valuenow', ariaValue); - this.el_.setAttribute('aria-valuetext', ariaValue + '%'); - }; - - /** - * Returns the current value of the player volume as a percentage - * - * @private - */ - - - VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() { - return Math.round(this.player_.volume() * 100); - }; - - /** - * When user starts dragging the VolumeBar, store the volume and listen for - * the end of the drag. When the drag ends, if the volume was set to zero, - * set lastVolume to the stored volume. - * - * @listens slideractive - * @private - */ - - - VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() { - var _this2 = this; - - var volumeBeforeDrag = this.player_.volume(); - - this.one('sliderinactive', function () { - if (_this2.player_.volume() === 0) { - _this2.player_.lastVolume_(volumeBeforeDrag); - } - }); - }; - - return VolumeBar; -}(_slider2['default']); - -/** - * Default options for the `VolumeBar` - * - * @type {Object} - * @private - */ - - -VolumeBar.prototype.options_ = { - children: ['volumeLevel'], - barName: 'volumeLevel' -}; - -/** - * Call the update event for this Slider when this event happens on the player. - * - * @type {string} - */ -VolumeBar.prototype.playerEvent = 'volumechange'; - -_component2['default'].registerComponent('VolumeBar', VolumeBar); -exports['default'] = VolumeBar; - -},{"42":42,"5":5,"60":60}],41:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _checkVolumeSupport = _dereq_(39); - -var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); - -var _obj = _dereq_(93); - -var _fn = _dereq_(88); - -_dereq_(40); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file volume-control.js - */ - - -// Required children - - -/** - * The component for controlling the volume level - * - * @extends Component - */ -var VolumeControl = function (_Component) { - _inherits(VolumeControl, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - */ - function VolumeControl(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, VolumeControl); - - options.vertical = options.vertical || false; - - // Pass the vertical option down to the VolumeBar if - // the VolumeBar is turned on. - if (typeof options.volumeBar === 'undefined' || (0, _obj.isPlain)(options.volumeBar)) { - options.volumeBar = options.volumeBar || {}; - options.volumeBar.vertical = options.vertical; - } - - // hide this control if volume support is missing - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - (0, _checkVolumeSupport2['default'])(_this, player); - - _this.throttledHandleMouseMove = (0, _fn.throttle)((0, _fn.bind)(_this, _this.handleMouseMove), 25); - - _this.on('mousedown', _this.handleMouseDown); - _this.on('touchstart', _this.handleMouseDown); - - // while the slider is active (the mouse has been pressed down and - // is dragging) or in focus we do not want to hide the VolumeBar - _this.on(_this.volumeBar, ['focus', 'slideractive'], function () { - _this.volumeBar.addClass('vjs-slider-active'); - _this.addClass('vjs-slider-active'); - _this.trigger('slideractive'); - }); - - _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () { - _this.volumeBar.removeClass('vjs-slider-active'); - _this.removeClass('vjs-slider-active'); - _this.trigger('sliderinactive'); - }); - return _this; - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - VolumeControl.prototype.createEl = function createEl() { - var orientationClass = 'vjs-volume-horizontal'; - - if (this.options_.vertical) { - orientationClass = 'vjs-volume-vertical'; - } - - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-control vjs-control ' + orientationClass - }); - }; - - /** - * Handle `mousedown` or `touchstart` events on the `VolumeControl`. - * - * @param {EventTarget~Event} event - * `mousedown` or `touchstart` event that triggered this function - * - * @listens mousedown - * @listens touchstart - */ - - - VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) { - var doc = this.el_.ownerDocument; - - this.on(doc, 'mousemove', this.throttledHandleMouseMove); - this.on(doc, 'touchmove', this.throttledHandleMouseMove); - this.on(doc, 'mouseup', this.handleMouseUp); - this.on(doc, 'touchend', this.handleMouseUp); - }; - - /** - * Handle `mouseup` or `touchend` events on the `VolumeControl`. - * - * @param {EventTarget~Event} event - * `mouseup` or `touchend` event that triggered this function. - * - * @listens touchend - * @listens mouseup - */ - - - VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) { - var doc = this.el_.ownerDocument; - - this.off(doc, 'mousemove', this.throttledHandleMouseMove); - this.off(doc, 'touchmove', this.throttledHandleMouseMove); - this.off(doc, 'mouseup', this.handleMouseUp); - this.off(doc, 'touchend', this.handleMouseUp); - }; - - /** - * Handle `mousedown` or `touchstart` events on the `VolumeControl`. - * - * @param {EventTarget~Event} event - * `mousedown` or `touchstart` event that triggered this function - * - * @listens mousedown - * @listens touchstart - */ - - - VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) { - this.volumeBar.handleMouseMove(event); - }; - - return VolumeControl; -}(_component2['default']); - -/** - * Default options for the `VolumeControl` - * - * @type {Object} - * @private - */ - - -VolumeControl.prototype.options_ = { - children: ['volumeBar'] -}; - -_component2['default'].registerComponent('VolumeControl', VolumeControl); -exports['default'] = VolumeControl; - -},{"39":39,"40":40,"5":5,"88":88,"93":93}],42:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file volume-level.js - */ - - -/** - * Shows volume level - * - * @extends Component - */ -var VolumeLevel = function (_Component) { - _inherits(VolumeLevel, _Component); - - function VolumeLevel() { - _classCallCheck(this, VolumeLevel); - - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - VolumeLevel.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-level', - innerHTML: '' - }); - }; - - return VolumeLevel; -}(_component2['default']); - -_component2['default'].registerComponent('VolumeLevel', VolumeLevel); -exports['default'] = VolumeLevel; - -},{"5":5}],43:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _checkVolumeSupport = _dereq_(39); - -var _checkVolumeSupport2 = _interopRequireDefault(_checkVolumeSupport); - -var _obj = _dereq_(93); - -_dereq_(41); - -_dereq_(11); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file volume-control.js - */ - - -// Required children - - -/** - * A Component to contain the MuteToggle and VolumeControl so that - * they can work together. - * - * @extends Component - */ -var VolumePanel = function (_Component) { - _inherits(VolumePanel, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - */ - function VolumePanel(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, VolumePanel); - - if (typeof options.inline !== 'undefined') { - options.inline = options.inline; - } else { - options.inline = true; - } - - // pass the inline option down to the VolumeControl as vertical if - // the VolumeControl is on. - if (typeof options.volumeControl === 'undefined' || (0, _obj.isPlain)(options.volumeControl)) { - options.volumeControl = options.volumeControl || {}; - options.volumeControl.vertical = !options.inline; - } - - // hide this control if volume support is missing - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - (0, _checkVolumeSupport2['default'])(_this, player); - - // while the slider is active (the mouse has been pressed down and - // is dragging) or in focus we do not want to hide the VolumeBar - _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_); - _this.on(_this.muteToggle, 'focus', _this.sliderActive_); - - _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_); - _this.on(_this.muteToggle, 'blur', _this.sliderInactive_); - return _this; - } - - /** - * Add vjs-slider-active class to the VolumePanel - * - * @listens VolumeControl#slideractive - * @private - */ - - - VolumePanel.prototype.sliderActive_ = function sliderActive_() { - this.addClass('vjs-slider-active'); - }; - - /** - * Removes vjs-slider-active class to the VolumePanel - * - * @listens VolumeControl#sliderinactive - * @private - */ - - - VolumePanel.prototype.sliderInactive_ = function sliderInactive_() { - this.removeClass('vjs-slider-active'); - }; - - /** - * Create the `Component`'s DOM element - * - * @return {Element} - * The element that was created. - */ - - - VolumePanel.prototype.createEl = function createEl() { - var orientationClass = 'vjs-volume-panel-horizontal'; - - if (!this.options_.inline) { - orientationClass = 'vjs-volume-panel-vertical'; - } - - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-volume-panel vjs-control ' + orientationClass - }); - }; - - return VolumePanel; -}(_component2['default']); - -/** - * Default options for the `VolumeControl` - * - * @type {Object} - * @private - */ - - -VolumePanel.prototype.options_ = { - children: ['muteToggle', 'volumeControl'] -}; - -_component2['default'].registerComponent('VolumePanel', VolumePanel); -exports['default'] = VolumePanel; - -},{"11":11,"39":39,"41":41,"5":5,"93":93}],44:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _modalDialog = _dereq_(55); - -var _modalDialog2 = _interopRequireDefault(_modalDialog); - -var _mergeOptions = _dereq_(92); - -var _mergeOptions2 = _interopRequireDefault(_mergeOptions); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file error-display.js - */ - - -/** - * A display that indicates an error has occurred. This means that the video - * is unplayable. - * - * @extends ModalDialog - */ -var ErrorDisplay = function (_ModalDialog) { - _inherits(ErrorDisplay, _ModalDialog); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - */ - function ErrorDisplay(player, options) { - _classCallCheck(this, ErrorDisplay); - - var _this = _possibleConstructorReturn(this, _ModalDialog.call(this, player, options)); - - _this.on(player, 'error', _this.open); - return _this; - } - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - * - * @deprecated Since version 5. - */ - - - ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() { - return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this); - }; - - /** - * Gets the localized error message based on the `Player`s error. - * - * @return {string} - * The `Player`s error message localized or an empty string. - */ - - - ErrorDisplay.prototype.content = function content() { - var error = this.player().error(); - - return error ? this.localize(error.message) : ''; - }; - - return ErrorDisplay; -}(_modalDialog2['default']); - -/** - * The default options for an `ErrorDisplay`. - * - * @private - */ - - -ErrorDisplay.prototype.options_ = (0, _mergeOptions2['default'])(_modalDialog2['default'].prototype.options_, { - pauseOnOpen: false, - fillAlways: true, - temporary: false, - uncloseable: true -}); - -_component2['default'].registerComponent('ErrorDisplay', ErrorDisplay); -exports['default'] = ErrorDisplay; - -},{"5":5,"55":55,"92":92}],45:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -/** - * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It - * adds shorthand functions that wrap around lengthy functions. For example: - * the `on` function is a wrapper around `addEventListener`. - * - * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget} - * @class EventTarget - */ -var EventTarget = function EventTarget() {}; - -/** - * A Custom DOM event. - * - * @typedef {Object} EventTarget~Event - * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent} - */ - -/** - * All event listeners should follow the following format. - * - * @callback EventTarget~EventListener - * @this {EventTarget} - * - * @param {EventTarget~Event} event - * the event that triggered this function - * - * @param {Object} [hash] - * hash of data sent during the event - */ - -/** - * An object containing event names as keys and booleans as values. - * - * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger} - * will have extra functionality. See that function for more information. - * - * @property EventTarget.prototype.allowedEvents_ - * @private - */ -/** - * @file src/js/event-target.js - */ -EventTarget.prototype.allowedEvents_ = {}; - -/** - * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a - * function that will get called when an event with a certain name gets triggered. - * - * @param {string|string[]} type - * An event name or an array of event names. - * - * @param {EventTarget~EventListener} fn - * The function to call with `EventTarget`s - */ -EventTarget.prototype.on = function (type, fn) { - // Remove the addEventListener alias before calling Events.on - // so we don't get into an infinite type loop - var ael = this.addEventListener; - - this.addEventListener = function () {}; - Events.on(this, type, fn); - this.addEventListener = ael; -}; - -/** - * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic - * the standard DOM API. - * - * @function - * @see {@link EventTarget#on} - */ -EventTarget.prototype.addEventListener = EventTarget.prototype.on; - -/** - * Removes an `event listener` for a specific event from an instance of `EventTarget`. - * This makes it so that the `event listener` will no longer get called when the - * named event happens. - * - * @param {string|string[]} type - * An event name or an array of event names. - * - * @param {EventTarget~EventListener} fn - * The function to remove. - */ -EventTarget.prototype.off = function (type, fn) { - Events.off(this, type, fn); -}; - -/** - * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic - * the standard DOM API. - * - * @function - * @see {@link EventTarget#off} - */ -EventTarget.prototype.removeEventListener = EventTarget.prototype.off; - -/** - * This function will add an `event listener` that gets triggered only once. After the - * first trigger it will get removed. This is like adding an `event listener` - * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself. - * - * @param {string|string[]} type - * An event name or an array of event names. - * - * @param {EventTarget~EventListener} fn - * The function to be called once for each event name. - */ -EventTarget.prototype.one = function (type, fn) { - // Remove the addEventListener alialing Events.on - // so we don't get into an infinite type loop - var ael = this.addEventListener; - - this.addEventListener = function () {}; - Events.one(this, type, fn); - this.addEventListener = ael; -}; - -/** - * This function causes an event to happen. This will then cause any `event listeners` - * that are waiting for that event, to get called. If there are no `event listeners` - * for an event then nothing will happen. - * - * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`. - * Trigger will also call the `on` + `uppercaseEventName` function. - * - * Example: - * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call - * `onClick` if it exists. - * - * @param {string|EventTarget~Event|Object} event - * The name of the event, an `Event`, or an object with a key of type set to - * an event name. - */ -EventTarget.prototype.trigger = function (event) { - var type = event.type || event; - - if (typeof event === 'string') { - event = { type: type }; - } - event = Events.fixEvent(event); - - if (this.allowedEvents_[type] && this['on' + type]) { - this['on' + type](event); - } - - Events.trigger(this, event); -}; - -/** - * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic - * the standard DOM API. - * - * @function - * @see {@link EventTarget#trigger} - */ -EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger; - -exports['default'] = EventTarget; - -},{"86":86}],46:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -/** - * @file extend.js - * @module extend - */ - -/** - * A combination of node inherits and babel's inherits (after transpile). - * Both work the same but node adds `super_` to the subClass - * and Bable adds the superClass as __proto__. Both seem useful. - * - * @param {Object} subClass - * The class to inherit to - * - * @param {Object} superClass - * The class to inherit from - * - * @private - */ -var _inherits = function _inherits(subClass, superClass) { - if (typeof superClass !== 'function' && superClass !== null) { - throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass))); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - - if (superClass) { - // node - subClass.super_ = superClass; - } -}; - -/** - * Function for subclassing using the same inheritance that - * videojs uses internally - * - * @static - * @const - * - * @param {Object} superClass - * The class to inherit from - * - * @param {Object} [subClassMethods={}] - * The class to inherit to - * - * @return {Object} - * The new object with subClassMethods that inherited superClass. - */ -var extendFn = function extendFn(superClass) { - var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - var subClass = function subClass() { - superClass.apply(this, arguments); - }; - - var methods = {}; - - if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') { - if (subClassMethods.constructor !== Object.prototype.constructor) { - subClass = subClassMethods.constructor; - } - methods = subClassMethods; - } else if (typeof subClassMethods === 'function') { - subClass = subClassMethods; - } - - _inherits(subClass, superClass); - - // Extend subObj's prototype with functions and other properties from props - for (var name in methods) { - if (methods.hasOwnProperty(name)) { - subClass.prototype[name] = methods[name]; - } - } - - return subClass; -}; - -exports['default'] = extendFn; - -},{}],47:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -/** - * Store the browser-specific methods for the fullscreen API. - * - * @type {Object} - * @see [Specification]{@link https://fullscreen.spec.whatwg.org} - * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js} - */ -var FullscreenApi = {}; - -// browser API methods -/** - * @file fullscreen-api.js - * @module fullscreen-api - * @private - */ -var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'], -// WebKit -['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'], -// Old WebKit (Safari 5.1) -['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'], -// Mozilla -['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'], -// Microsoft -['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']]; - -var specApi = apiMap[0]; -var browserApi = void 0; - -// determine the supported set of functions -for (var i = 0; i < apiMap.length; i++) { - // check for exitFullscreen function - if (apiMap[i][1] in _document2['default']) { - browserApi = apiMap[i]; - break; - } -} - -// map the browser API names to the spec API names -if (browserApi) { - for (var _i = 0; _i < browserApi.length; _i++) { - FullscreenApi[specApi[_i]] = browserApi[_i]; - } -} - -exports['default'] = FullscreenApi; - -},{"99":99}],48:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file loading-spinner.js - */ - - -/** - * A loading spinner for use during waiting/loading events. - * - * @extends Component - */ -var LoadingSpinner = function (_Component) { - _inherits(LoadingSpinner, _Component); - - function LoadingSpinner() { - _classCallCheck(this, LoadingSpinner); - - return _possibleConstructorReturn(this, _Component.apply(this, arguments)); - } - - /** - * Create the `LoadingSpinner`s DOM element. - * - * @return {Element} - * The dom element that gets created. - */ - LoadingSpinner.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: 'vjs-loading-spinner', - dir: 'ltr' - }); - }; - - return LoadingSpinner; -}(_component2['default']); - -_component2['default'].registerComponent('LoadingSpinner', LoadingSpinner); -exports['default'] = LoadingSpinner; - -},{"5":5}],49:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _obj = _dereq_(93); - -/** - * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class. - * - * @param {number|string|Object|MediaError} value - * This can be of multiple types: - * - number: should be a standard error code - * - string: an error message (the code will be 0) - * - Object: arbitrary properties - * - `MediaError` (native): used to populate a video.js `MediaError` object - * - `MediaError` (video.js): will return itself if it's already a - * video.js `MediaError` object. - * - * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror} - * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes} - * - * @class MediaError - */ -function MediaError(value) { - - // Allow redundant calls to this constructor to avoid having `instanceof` - // checks peppered around the code. - if (value instanceof MediaError) { - return value; - } - - if (typeof value === 'number') { - this.code = value; - } else if (typeof value === 'string') { - // default code is zero, so this is a custom error - this.message = value; - } else if ((0, _obj.isObject)(value)) { - - // We assign the `code` property manually because native `MediaError` objects - // do not expose it as an own/enumerable property of the object. - if (typeof value.code === 'number') { - this.code = value.code; - } - - (0, _obj.assign)(this, value); - } - - if (!this.message) { - this.message = MediaError.defaultMessages[this.code] || ''; - } -} - -/** - * The error code that refers two one of the defined `MediaError` types - * - * @type {Number} - */ -/** - * @file media-error.js - */ -MediaError.prototype.code = 0; - -/** - * An optional message that to show with the error. Message is not part of the HTML5 - * video spec but allows for more informative custom errors. - * - * @type {String} - */ -MediaError.prototype.message = ''; - -/** - * An optional status code that can be set by plugins to allow even more detail about - * the error. For example a plugin might provide a specific HTTP status code and an - * error message for that code. Then when the plugin gets that error this class will - * know how to display an error message for it. This allows a custom message to show - * up on the `Player` error overlay. - * - * @type {Array} - */ -MediaError.prototype.status = null; - -/** - * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the - * specification listed under {@link MediaError} for more information. - * - * @enum {array} - * @readonly - * @property {string} 0 - MEDIA_ERR_CUSTOM - * @property {string} 1 - MEDIA_ERR_CUSTOM - * @property {string} 2 - MEDIA_ERR_ABORTED - * @property {string} 3 - MEDIA_ERR_NETWORK - * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED - * @property {string} 5 - MEDIA_ERR_ENCRYPTED - */ -MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED']; - -/** - * The default `MediaError` messages based on the {@link MediaError.errorTypes}. - * - * @type {Array} - * @constant - */ -MediaError.defaultMessages = { - 1: 'You aborted the media playback', - 2: 'A network error caused the media download to fail part-way.', - 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.', - 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.', - 5: 'The media is encrypted and we do not have the keys to decrypt it.' -}; - -// Add types as properties on MediaError -// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4; -for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) { - MediaError[MediaError.errorTypes[errNum]] = errNum; - // values should be accessible on both the class and instance - MediaError.prototype[MediaError.errorTypes[errNum]] = errNum; -} - -// jsdocs for instance/static members added above -// instance methods use `#` and static methods use `.` -/** - * W3C error code for any custom error. - * - * @member MediaError#MEDIA_ERR_CUSTOM - * @constant {number} - * @default 0 - */ -/** - * W3C error code for any custom error. - * - * @member MediaError.MEDIA_ERR_CUSTOM - * @constant {number} - * @default 0 - */ - -/** - * W3C error code for media error aborted. - * - * @member MediaError#MEDIA_ERR_ABORTED - * @constant {number} - * @default 1 - */ -/** - * W3C error code for media error aborted. - * - * @member MediaError.MEDIA_ERR_ABORTED - * @constant {number} - * @default 1 - */ - -/** - * W3C error code for any network error. - * - * @member MediaError#MEDIA_ERR_NETWORK - * @constant {number} - * @default 2 - */ -/** - * W3C error code for any network error. - * - * @member MediaError.MEDIA_ERR_NETWORK - * @constant {number} - * @default 2 - */ - -/** - * W3C error code for any decoding error. - * - * @member MediaError#MEDIA_ERR_DECODE - * @constant {number} - * @default 3 - */ -/** - * W3C error code for any decoding error. - * - * @member MediaError.MEDIA_ERR_DECODE - * @constant {number} - * @default 3 - */ - -/** - * W3C error code for any time that a source is not supported. - * - * @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED - * @constant {number} - * @default 4 - */ -/** - * W3C error code for any time that a source is not supported. - * - * @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED - * @constant {number} - * @default 4 - */ - -/** - * W3C error code for any time that a source is encrypted. - * - * @member MediaError#MEDIA_ERR_ENCRYPTED - * @constant {number} - * @default 5 - */ -/** - * W3C error code for any time that a source is encrypted. - * - * @member MediaError.MEDIA_ERR_ENCRYPTED - * @constant {number} - * @default 5 - */ - -exports['default'] = MediaError; - -},{"93":93}],50:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _button = _dereq_(2); - -var _button2 = _interopRequireDefault(_button); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _menu = _dereq_(52); - -var _menu2 = _interopRequireDefault(_menu); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -var _toTitleCase = _dereq_(96); - -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file menu-button.js - */ - - -/** - * A `MenuButton` class for any popup {@link Menu}. - * - * @extends Component - */ -var MenuButton = function (_Component) { - _inherits(MenuButton, _Component); - - /** - * Creates an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - */ - function MenuButton(player) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - _classCallCheck(this, MenuButton); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.menuButton_ = new _button2['default'](player, options); - - _this.menuButton_.controlText(_this.controlText_); - _this.menuButton_.el_.setAttribute('aria-haspopup', 'true'); - - // Add buildCSSClass values to the button, not the wrapper - var buttonClass = _button2['default'].prototype.buildCSSClass(); - - _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass; - _this.menuButton_.removeClass('vjs-control'); - - _this.addChild(_this.menuButton_); - - _this.update(); - - _this.enabled_ = true; - - _this.on(_this.menuButton_, 'tap', _this.handleClick); - _this.on(_this.menuButton_, 'click', _this.handleClick); - _this.on(_this.menuButton_, 'focus', _this.handleFocus); - _this.on(_this.menuButton_, 'blur', _this.handleBlur); - - _this.on('keydown', _this.handleSubmenuKeyPress); - return _this; - } - - /** - * Update the menu based on the current state of its items. - */ - - - MenuButton.prototype.update = function update() { - var menu = this.createMenu(); - - if (this.menu) { - this.removeChild(this.menu); - } - - this.menu = menu; - this.addChild(menu); - - /** - * Track the state of the menu button - * - * @type {Boolean} - * @private - */ - this.buttonPressed_ = false; - this.menuButton_.el_.setAttribute('aria-expanded', 'false'); - - if (this.items && this.items.length <= this.hideThreshold_) { - this.hide(); - } else { - this.show(); - } - }; - - /** - * Create the menu and add all items to it. - * - * @return {Menu} - * The constructed menu - */ - - - MenuButton.prototype.createMenu = function createMenu() { - var menu = new _menu2['default'](this.player_, { menuButton: this }); - - /** - * Hide the menu if the number of items is less than or equal to this threshold. This defaults - * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list - * it here because every time we run `createMenu` we need to reset the value. - * - * @protected - * @type {Number} - */ - this.hideThreshold_ = 0; - - // Add a title list item to the top - if (this.options_.title) { - var title = Dom.createEl('li', { - className: 'vjs-menu-title', - innerHTML: (0, _toTitleCase2['default'])(this.options_.title), - tabIndex: -1 - }); - - this.hideThreshold_ += 1; - - menu.children_.unshift(title); - Dom.prependTo(title, menu.contentEl()); - } - - this.items = this.createItems(); - - if (this.items) { - // Add menu items to the menu - for (var i = 0; i < this.items.length; i++) { - menu.addItem(this.items[i]); - } - } - - return menu; - }; - - /** - * Create the list of menu items. Specific to each subclass. - * - * @abstract - */ - - - MenuButton.prototype.createItems = function createItems() {}; - - /** - * Create the `MenuButtons`s DOM element. - * - * @return {Element} - * The element that gets created. - */ - - - MenuButton.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: this.buildWrapperCSSClass() - }, {}); - }; - - /** - * Allow sub components to stack CSS class names for the wrapper element - * - * @return {string} - * The constructed wrapper DOM `className` - */ - - - MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { - var menuButtonClass = 'vjs-menu-button'; - - // If the inline option is passed, we want to use different styles altogether. - if (this.options_.inline === true) { - menuButtonClass += '-inline'; - } else { - menuButtonClass += '-popup'; - } - - // TODO: Fix the CSS so that this isn't necessary - var buttonClass = _button2['default'].prototype.buildCSSClass(); - - return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this); - }; - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - MenuButton.prototype.buildCSSClass = function buildCSSClass() { - var menuButtonClass = 'vjs-menu-button'; - - // If the inline option is passed, we want to use different styles altogether. - if (this.options_.inline === true) { - menuButtonClass += '-inline'; - } else { - menuButtonClass += '-popup'; - } - - return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this); - }; - - /** - * Get or set the localized control text that will be used for accessibility. - * - * > NOTE: This will come from the internal `menuButton_` element. - * - * @param {string} [text] - * Control text for element. - * - * @param {Element} [el=this.menuButton_.el()] - * Element to set the title on. - * - * @return {string} - * - The control text when getting - */ - - - MenuButton.prototype.controlText = function controlText(text) { - var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el(); - - return this.menuButton_.controlText(text, el); - }; - - /** - * Handle a click on a `MenuButton`. - * See {@link ClickableComponent#handleClick} for instances where this is called. - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - */ - - - MenuButton.prototype.handleClick = function handleClick(event) { - // When you click the button it adds focus, which will show the menu. - // So we'll remove focus when the mouse leaves the button. Focus is needed - // for tab navigation. - - this.one(this.menu.contentEl(), 'mouseleave', Fn.bind(this, function (e) { - this.unpressButton(); - this.el_.blur(); - })); - if (this.buttonPressed_) { - this.unpressButton(); - } else { - this.pressButton(); - } - }; - - /** - * Set the focus to the actual button, not to this element - */ - - - MenuButton.prototype.focus = function focus() { - this.menuButton_.focus(); - }; - - /** - * Remove the focus from the actual button, not this element - */ - - - MenuButton.prototype.blur = function blur() { - this.menuButton_.blur(); - }; - - /** - * This gets called when a `MenuButton` gains focus via a `focus` event. - * Turns on listening for `keydown` events. When they happen it - * calls `this.handleKeyPress`. - * - * @param {EventTarget~Event} event - * The `focus` event that caused this function to be called. - * - * @listens focus - */ - - - MenuButton.prototype.handleFocus = function handleFocus() { - Events.on(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); - }; - - /** - * Called when a `MenuButton` loses focus. Turns off the listener for - * `keydown` events. Which Stops `this.handleKeyPress` from getting called. - * - * @param {EventTarget~Event} event - * The `blur` event that caused this function to be called. - * - * @listens blur - */ - - - MenuButton.prototype.handleBlur = function handleBlur() { - Events.off(_document2['default'], 'keydown', Fn.bind(this, this.handleKeyPress)); - }; - - /** - * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See - * {@link ClickableComponent#handleKeyPress} for instances where this is called. - * - * @param {EventTarget~Event} event - * The `keydown` event that caused this function to be called. - * - * @listens keydown - */ - - - MenuButton.prototype.handleKeyPress = function handleKeyPress(event) { - - // Escape (27) key or Tab (9) key unpress the 'button' - if (event.which === 27 || event.which === 9) { - if (this.buttonPressed_) { - this.unpressButton(); - } - // Don't preventDefault for Tab key - we still want to lose focus - if (event.which !== 9) { - event.preventDefault(); - // Set focus back to the menu button's button - this.menuButton_.el_.focus(); - } - // Up (38) key or Down (40) key press the 'button' - } else if (event.which === 38 || event.which === 40) { - if (!this.buttonPressed_) { - this.pressButton(); - event.preventDefault(); - } - } - }; - - /** - * Handle a `keydown` event on a sub-menu. The listener for this is added in - * the constructor. - * - * @param {EventTarget~Event} event - * Key press event - * - * @listens keydown - */ - - - MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) { - - // Escape (27) key or Tab (9) key unpress the 'button' - if (event.which === 27 || event.which === 9) { - if (this.buttonPressed_) { - this.unpressButton(); - } - // Don't preventDefault for Tab key - we still want to lose focus - if (event.which !== 9) { - event.preventDefault(); - // Set focus back to the menu button's button - this.menuButton_.el_.focus(); - } - } - }; - - /** - * Put the current `MenuButton` into a pressed state. - */ - - - MenuButton.prototype.pressButton = function pressButton() { - if (this.enabled_) { - this.buttonPressed_ = true; - this.menu.lockShowing(); - this.menuButton_.el_.setAttribute('aria-expanded', 'true'); - // set the focus into the submenu - this.menu.focus(); - } - }; - - /** - * Take the current `MenuButton` out of a pressed state. - */ - - - MenuButton.prototype.unpressButton = function unpressButton() { - if (this.enabled_) { - this.buttonPressed_ = false; - this.menu.unlockShowing(); - this.menuButton_.el_.setAttribute('aria-expanded', 'false'); - } - }; - - /** - * Disable the `MenuButton`. Don't allow it to be clicked. - */ - - - MenuButton.prototype.disable = function disable() { - this.unpressButton(); - - this.enabled_ = false; - this.addClass('vjs-disabled'); - - this.menuButton_.disable(); - }; - - /** - * Enable the `MenuButton`. Allow it to be clicked. - */ - - - MenuButton.prototype.enable = function enable() { - this.enabled_ = true; - this.removeClass('vjs-disabled'); - - this.menuButton_.enable(); - }; - - return MenuButton; -}(_component2['default']); - -_component2['default'].registerComponent('MenuButton', MenuButton); -exports['default'] = MenuButton; - -},{"2":2,"5":5,"52":52,"85":85,"86":86,"88":88,"96":96,"99":99}],51:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _clickableComponent = _dereq_(3); - -var _clickableComponent2 = _interopRequireDefault(_clickableComponent); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _obj = _dereq_(93); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file menu-item.js - */ - - -/** - * The component for a menu item. `
  • ` - * - * @extends ClickableComponent - */ -var MenuItem = function (_ClickableComponent) { - _inherits(MenuItem, _ClickableComponent); - - /** - * Creates an instance of the this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options={}] - * The key/value store of player options. - * - */ - function MenuItem(player, options) { - _classCallCheck(this, MenuItem); - - var _this = _possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); - - _this.selectable = options.selectable; - - _this.selected(options.selected); - - if (_this.selectable) { - // TODO: May need to be either menuitemcheckbox or menuitemradio, - // and may need logical grouping of menu items. - _this.el_.setAttribute('role', 'menuitemcheckbox'); - } else { - _this.el_.setAttribute('role', 'menuitem'); - } - return _this; - } - - /** - * Create the `MenuItem's DOM element - * - * @param {string} [type=li] - * Element's node type, not actually used, always set to `li`. - * - * @param {Object} [props={}] - * An object of properties that should be set on the element - * - * @param {Object} [attrs={}] - * An object of attributes that should be set on the element - * - * @return {Element} - * The element that gets created. - */ - - - MenuItem.prototype.createEl = function createEl(type, props, attrs) { - // The control is textual, not just an icon - this.nonIconControl = true; - - return _ClickableComponent.prototype.createEl.call(this, 'li', (0, _obj.assign)({ - className: 'vjs-menu-item', - innerHTML: '' + this.localize(this.options_.label) + '', - tabIndex: -1 - }, props), attrs); - }; - - /** - * Any click on a `MenuItem` puts int into the selected state. - * See {@link ClickableComponent#handleClick} for instances where this is called. - * - * @param {EventTarget~Event} event - * The `keydown`, `tap`, or `click` event that caused this function to be - * called. - * - * @listens tap - * @listens click - */ - - - MenuItem.prototype.handleClick = function handleClick(event) { - this.selected(true); - }; - - /** - * Set the state for this menu item as selected or not. - * - * @param {boolean} selected - * if the menu item is selected or not - */ - - - MenuItem.prototype.selected = function selected(_selected) { - if (this.selectable) { - if (_selected) { - this.addClass('vjs-selected'); - this.el_.setAttribute('aria-checked', 'true'); - // aria-checked isn't fully supported by browsers/screen readers, - // so indicate selected state to screen reader in the control text. - this.controlText(', selected'); - } else { - this.removeClass('vjs-selected'); - this.el_.setAttribute('aria-checked', 'false'); - // Indicate un-selected state to screen reader - // Note that a space clears out the selected state text - this.controlText(' '); - } - } - }; - - return MenuItem; -}(_clickableComponent2['default']); - -_component2['default'].registerComponent('MenuItem', MenuItem); -exports['default'] = MenuItem; - -},{"3":3,"5":5,"93":93}],52:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file menu.js - */ - - -/** - * The Menu component is used to build popup menus, including subtitle and - * captions selection menus. - * - * @extends Component - */ -var Menu = function (_Component) { - _inherits(Menu, _Component); - - /** - * Create an instance of this class. - * - * @param {Player} player - * the player that this component should attach to - * - * @param {Object} [options] - * Object of option names and values - * - */ - function Menu(player, options) { - _classCallCheck(this, Menu); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - if (options) { - _this.menuButton_ = options.menuButton; - } - - _this.focusedChild_ = -1; - - _this.on('keydown', _this.handleKeyPress); - return _this; - } - - /** - * Add a {@link MenuItem} to the menu. - * - * @param {Object|string} component - * The name or instance of the `MenuItem` to add. - * - */ - - - Menu.prototype.addItem = function addItem(component) { - this.addChild(component); - component.on('click', Fn.bind(this, function (event) { - // Unpress the associated MenuButton, and move focus back to it - if (this.menuButton_) { - this.menuButton_.unpressButton(); - - // don't focus menu button if item is a caption settings item - // because focus will move elsewhere and it logs an error on IE8 - if (component.name() !== 'CaptionSettingsMenuItem') { - this.menuButton_.focus(); - } - } - })); - }; - - /** - * Create the `Menu`s DOM element. - * - * @return {Element} - * the element that was created - */ - - - Menu.prototype.createEl = function createEl() { - var contentElType = this.options_.contentElType || 'ul'; - - this.contentEl_ = Dom.createEl(contentElType, { - className: 'vjs-menu-content' - }); - - this.contentEl_.setAttribute('role', 'menu'); - - var el = _Component.prototype.createEl.call(this, 'div', { - append: this.contentEl_, - className: 'vjs-menu' - }); - - el.appendChild(this.contentEl_); - - // Prevent clicks from bubbling up. Needed for Menu Buttons, - // where a click on the parent is significant - Events.on(el, 'click', function (event) { - event.preventDefault(); - event.stopImmediatePropagation(); - }); - - return el; - }; - - /** - * Handle a `keydown` event on this menu. This listener is added in the constructor. - * - * @param {EventTarget~Event} event - * A `keydown` event that happened on the menu. - * - * @listens keydown - */ - - - Menu.prototype.handleKeyPress = function handleKeyPress(event) { - // Left and Down Arrows - if (event.which === 37 || event.which === 40) { - event.preventDefault(); - this.stepForward(); - - // Up and Right Arrows - } else if (event.which === 38 || event.which === 39) { - event.preventDefault(); - this.stepBack(); - } - }; - - /** - * Move to next (lower) menu item for keyboard users. - */ - - - Menu.prototype.stepForward = function stepForward() { - var stepChild = 0; - - if (this.focusedChild_ !== undefined) { - stepChild = this.focusedChild_ + 1; - } - this.focus(stepChild); - }; - - /** - * Move to previous (higher) menu item for keyboard users. - */ - - - Menu.prototype.stepBack = function stepBack() { - var stepChild = 0; - - if (this.focusedChild_ !== undefined) { - stepChild = this.focusedChild_ - 1; - } - this.focus(stepChild); - }; - - /** - * Set focus on a {@link MenuItem} in the `Menu`. - * - * @param {Object|string} [item=0] - * Index of child item set focus on. - */ - - - Menu.prototype.focus = function focus() { - var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; - - var children = this.children().slice(); - var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className); - - if (haveTitle) { - children.shift(); - } - - if (children.length > 0) { - if (item < 0) { - item = 0; - } else if (item >= children.length) { - item = children.length - 1; - } - - this.focusedChild_ = item; - - children[item].el_.focus(); - } - }; - - return Menu; -}(_component2['default']); - -_component2['default'].registerComponent('Menu', Menu); -exports['default'] = Menu; - -},{"5":5,"85":85,"86":86,"88":88}],53:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; -exports.isEvented = undefined; - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _obj = _dereq_(93); - -var Obj = _interopRequireWildcard(_obj); - -var _eventTarget = _dereq_(45); - -var _eventTarget2 = _interopRequireDefault(_eventTarget); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -/** - * Returns whether or not an object has had the evented mixin applied. - * - * @param {Object} object - * An object to test. - * - * @return {boolean} - * Whether or not the object appears to be evented. - */ -var isEvented = function isEvented(object) { - return object instanceof _eventTarget2['default'] || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) { - return typeof object[k] === 'function'; - }); -}; - -/** - * Whether a value is a valid event type - non-empty string or array. - * - * @private - * @param {string|Array} type - * The type value to test. - * - * @return {boolean} - * Whether or not the type is a valid event type. - */ -/** - * @file mixins/evented.js - * @module evented - */ -var isValidEventType = function isValidEventType(type) { - return ( - // The regex here verifies that the `type` contains at least one non- - // whitespace character. - typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length - ); -}; - -/** - * Validates a value to determine if it is a valid event target. Throws if not. - * - * @private - * @throws {Error} - * If the target does not appear to be a valid event target. - * - * @param {Object} target - * The object to test. - */ -var validateTarget = function validateTarget(target) { - if (!target.nodeName && !isEvented(target)) { - throw new Error('Invalid target; must be a DOM node or evented object.'); - } -}; - -/** - * Validates a value to determine if it is a valid event target. Throws if not. - * - * @private - * @throws {Error} - * If the type does not appear to be a valid event type. - * - * @param {string|Array} type - * The type to test. - */ -var validateEventType = function validateEventType(type) { - if (!isValidEventType(type)) { - throw new Error('Invalid event type; must be a non-empty string or array.'); - } -}; - -/** - * Validates a value to determine if it is a valid listener. Throws if not. - * - * @private - * @throws {Error} - * If the listener is not a function. - * - * @param {Function} listener - * The listener to test. - */ -var validateListener = function validateListener(listener) { - if (typeof listener !== 'function') { - throw new Error('Invalid listener; must be a function.'); - } -}; - -/** - * Takes an array of arguments given to `on()` or `one()`, validates them, and - * normalizes them into an object. - * - * @private - * @param {Object} self - * The evented object on which `on()` or `one()` was called. This - * object will be bound as the `this` value for the listener. - * - * @param {Array} args - * An array of arguments passed to `on()` or `one()`. - * - * @return {Object} - * An object containing useful values for `on()` or `one()` calls. - */ -var normalizeListenArgs = function normalizeListenArgs(self, args) { - - // If the number of arguments is less than 3, the target is always the - // evented object itself. - var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_; - var target = void 0; - var type = void 0; - var listener = void 0; - - if (isTargetingSelf) { - target = self.eventBusEl_; - - // Deal with cases where we got 3 arguments, but we are still listening to - // the evented object itself. - if (args.length >= 3) { - args.shift(); - } - - type = args[0]; - listener = args[1]; - } else { - target = args[0]; - type = args[1]; - listener = args[2]; - } - - validateTarget(target); - validateEventType(type); - validateListener(listener); - - listener = Fn.bind(self, listener); - - return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener }; -}; - -/** - * Adds the listener to the event type(s) on the target, normalizing for - * the type of target. - * - * @private - * @param {Element|Object} target - * A DOM node or evented object. - * - * @param {string} method - * The event binding method to use ("on" or "one"). - * - * @param {string|Array} type - * One or more event type(s). - * - * @param {Function} listener - * A listener function. - */ -var listen = function listen(target, method, type, listener) { - validateTarget(target); - - if (target.nodeName) { - Events[method](target, type, listener); - } else { - target[method](type, listener); - } -}; - -/** - * Contains methods that provide event capabilites to an object which is passed - * to {@link module:evented|evented}. - * - * @mixin EventedMixin - */ -var EventedMixin = { - - /** - * Add a listener to an event (or events) on this object or another evented - * object. - * - * @param {string|Array|Element|Object} targetOrType - * If this is a string or array, it represents the event type(s) - * that will trigger the listener. - * - * Another evented object can be passed here instead, which will - * cause the listener to listen for events on _that_ object. - * - * In either case, the listener's `this` value will be bound to - * this object. - * - * @param {string|Array|Function} typeOrListener - * If the first argument was a string or array, this should be the - * listener function. Otherwise, this is a string or array of event - * type(s). - * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function. - */ - on: function on() { - var _this = this; - - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - - var _normalizeListenArgs = normalizeListenArgs(this, args), - isTargetingSelf = _normalizeListenArgs.isTargetingSelf, - target = _normalizeListenArgs.target, - type = _normalizeListenArgs.type, - listener = _normalizeListenArgs.listener; - - listen(target, 'on', type, listener); - - // If this object is listening to another evented object. - if (!isTargetingSelf) { - - // If this object is disposed, remove the listener. - var removeListenerOnDispose = function removeListenerOnDispose() { - return _this.off(target, type, listener); - }; - - // Use the same function ID as the listener so we can remove it later it - // using the ID of the original listener. - removeListenerOnDispose.guid = listener.guid; - - // Add a listener to the target's dispose event as well. This ensures - // that if the target is disposed BEFORE this object, we remove the - // removal listener that was just added. Otherwise, we create a memory leak. - var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() { - return _this.off('dispose', removeListenerOnDispose); - }; - - // Use the same function ID as the listener so we can remove it later - // it using the ID of the original listener. - removeRemoverOnTargetDispose.guid = listener.guid; - - listen(this, 'on', 'dispose', removeListenerOnDispose); - listen(target, 'on', 'dispose', removeRemoverOnTargetDispose); - } - }, - - - /** - * Add a listener to an event (or events) on this object or another evented - * object. The listener will only be called once and then removed. - * - * @param {string|Array|Element|Object} targetOrType - * If this is a string or array, it represents the event type(s) - * that will trigger the listener. - * - * Another evented object can be passed here instead, which will - * cause the listener to listen for events on _that_ object. - * - * In either case, the listener's `this` value will be bound to - * this object. - * - * @param {string|Array|Function} typeOrListener - * If the first argument was a string or array, this should be the - * listener function. Otherwise, this is a string or array of event - * type(s). - * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function. - */ - one: function one() { - var _this2 = this; - - for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { - args[_key2] = arguments[_key2]; - } - - var _normalizeListenArgs2 = normalizeListenArgs(this, args), - isTargetingSelf = _normalizeListenArgs2.isTargetingSelf, - target = _normalizeListenArgs2.target, - type = _normalizeListenArgs2.type, - listener = _normalizeListenArgs2.listener; - - // Targeting this evented object. - - - if (isTargetingSelf) { - listen(target, 'one', type, listener); - - // Targeting another evented object. - } else { - var wrapper = function wrapper() { - for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { - largs[_key3] = arguments[_key3]; - } - - _this2.off(target, type, wrapper); - listener.apply(null, largs); - }; - - // Use the same function ID as the listener so we can remove it later - // it using the ID of the original listener. - wrapper.guid = listener.guid; - listen(target, 'one', type, wrapper); - } - }, - - - /** - * Removes listener(s) from event(s) on an evented object. - * - * @param {string|Array|Element|Object} [targetOrType] - * If this is a string or array, it represents the event type(s). - * - * Another evented object can be passed here instead, in which case - * ALL 3 arguments are _required_. - * - * @param {string|Array|Function} [typeOrListener] - * If the first argument was a string or array, this may be the - * listener function. Otherwise, this is a string or array of event - * type(s). - * - * @param {Function} [listener] - * If the first argument was another evented object, this will be - * the listener function; otherwise, _all_ listeners bound to the - * event type(s) will be removed. - */ - off: function off(targetOrType, typeOrListener, listener) { - - // Targeting this evented object. - if (!targetOrType || isValidEventType(targetOrType)) { - Events.off(this.eventBusEl_, targetOrType, typeOrListener); - - // Targeting another evented object. - } else { - var target = targetOrType; - var type = typeOrListener; - - // Fail fast and in a meaningful way! - validateTarget(target); - validateEventType(type); - validateListener(listener); - - // Ensure there's at least a guid, even if the function hasn't been used - listener = Fn.bind(this, listener); - - // Remove the dispose listener on this evented object, which was given - // the same guid as the event listener in on(). - this.off('dispose', listener); - - if (target.nodeName) { - Events.off(target, type, listener); - Events.off(target, 'dispose', listener); - } else if (isEvented(target)) { - target.off(type, listener); - target.off('dispose', listener); - } - } - }, - - - /** - * Fire an event on this evented object, causing its listeners to be called. - * - * @param {string|Object} event - * An event type or an object with a type property. - * - * @param {Object} [hash] - * An additional object to pass along to listeners. - * - * @returns {boolean} - * Whether or not the default behavior was prevented. - */ - trigger: function trigger(event, hash) { - return Events.trigger(this.eventBusEl_, event, hash); - } -}; - -/** - * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object. - * - * @param {Object} target - * The object to which to add event methods. - * - * @param {Object} [options={}] - * Options for customizing the mixin behavior. - * - * @param {String} [options.eventBusKey] - * By default, adds a `eventBusEl_` DOM element to the target object, - * which is used as an event bus. If the target object already has a - * DOM element that should be used, pass its key here. - * - * @return {Object} - * The target object. - */ -function evented(target) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - var eventBusKey = options.eventBusKey; - - // Set or create the eventBusEl_. - - if (eventBusKey) { - if (!target[eventBusKey].nodeName) { - throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.'); - } - target.eventBusEl_ = target[eventBusKey]; - } else { - target.eventBusEl_ = Dom.createEl('span', { className: 'vjs-event-bus' }); - } - - Obj.assign(target, EventedMixin); - - // When any evented object is disposed, it removes all its listeners. - target.on('dispose', function () { - return target.off(); - }); - - return target; -} - -exports['default'] = evented; -exports.isEvented = isEvented; - -},{"45":45,"85":85,"86":86,"88":88,"93":93}],54:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _evented = _dereq_(53); - -var _obj = _dereq_(93); - -var Obj = _interopRequireWildcard(_obj); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -/** - * Contains methods that provide statefulness to an object which is passed - * to {@link module:stateful}. - * - * @mixin StatefulMixin - */ -/** - * @file mixins/stateful.js - * @module stateful - */ -var StatefulMixin = { - - /** - * A hash containing arbitrary keys and values representing the state of - * the object. - * - * @type {Object} - */ - state: {}, - - /** - * Set the state of an object by mutating its - * {@link module:stateful~StatefulMixin.state|state} object in place. - * - * @fires module:stateful~StatefulMixin#statechanged - * @param {Object|Function} stateUpdates - * A new set of properties to shallow-merge into the plugin state. - * Can be a plain object or a function returning a plain object. - * - * @returns {Object|undefined} - * An object containing changes that occurred. If no changes - * occurred, returns `undefined`. - */ - setState: function setState(stateUpdates) { - var _this = this; - - // Support providing the `stateUpdates` state as a function. - if (typeof stateUpdates === 'function') { - stateUpdates = stateUpdates(); - } - - var changes = void 0; - - Obj.each(stateUpdates, function (value, key) { - - // Record the change if the value is different from what's in the - // current state. - if (_this.state[key] !== value) { - changes = changes || {}; - changes[key] = { - from: _this.state[key], - to: value - }; - } - - _this.state[key] = value; - }); - - // Only trigger "statechange" if there were changes AND we have a trigger - // function. This allows us to not require that the target object be an - // evented object. - if (changes && (0, _evented.isEvented)(this)) { - - /** - * An event triggered on an object that is both - * {@link module:stateful|stateful} and {@link module:evented|evented} - * indicating that its state has changed. - * - * @event module:stateful~StatefulMixin#statechanged - * @type {Object} - * @property {Object} changes - * A hash containing the properties that were changed and - * the values they were changed `from` and `to`. - */ - this.trigger({ - changes: changes, - type: 'statechanged' - }); - } - - return changes; - } -}; - -/** - * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target - * object. - * - * If the target object is {@link module:evented|evented} and has a - * `handleStateChanged` method, that method will be automatically bound to the - * `statechanged` event on itself. - * - * @param {Object} target - * The object to be made stateful. - * - * @param {Object} [defaultState] - * A default set of properties to populate the newly-stateful object's - * `state` property. - * - * @returns {Object} - * Returns the `target`. - */ -function stateful(target, defaultState) { - Obj.assign(target, StatefulMixin); - - // This happens after the mixing-in because we need to replace the `state` - // added in that step. - target.state = Obj.assign({}, target.state, defaultState); - - // Auto-bind the `handleStateChanged` method of the target object if it exists. - if (typeof target.handleStateChanged === 'function' && (0, _evented.isEvented)(target)) { - target.on('statechanged', target.handleStateChanged); - } - - return target; -} - -exports['default'] = stateful; - -},{"53":53,"93":93}],55:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _window = _dereq_(100); - -var _window2 = _interopRequireDefault(_window); - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file modal-dialog.js - */ - - -var MODAL_CLASS_NAME = 'vjs-modal-dialog'; -var ESC = 27; - -/** - * The `ModalDialog` displays over the video and its controls, which blocks - * interaction with the player until it is closed. - * - * Modal dialogs include a "Close" button and will close when that button - * is activated - or when ESC is pressed anywhere. - * - * @extends Component - */ - -var ModalDialog = function (_Component) { - _inherits(ModalDialog, _Component); - - /** - * Create an instance of this class. - * - * @param {Player} player - * The `Player` that this class should be attached to. - * - * @param {Object} [options] - * The key/value store of player options. - * - * @param {Mixed} [options.content=undefined] - * Provide customized content for this modal. - * - * @param {string} [options.description] - * A text description for the modal, primarily for accessibility. - * - * @param {boolean} [options.fillAlways=false] - * Normally, modals are automatically filled only the first time - * they open. This tells the modal to refresh its content - * every time it opens. - * - * @param {string} [options.label] - * A text label for the modal, primarily for accessibility. - * - * @param {boolean} [options.temporary=true] - * If `true`, the modal can only be opened once; it will be - * disposed as soon as it's closed. - * - * @param {boolean} [options.uncloseable=false] - * If `true`, the user will not be able to close the modal - * through the UI in the normal ways. Programmatic closing is - * still possible. - */ - function ModalDialog(player, options) { - _classCallCheck(this, ModalDialog); - - var _this = _possibleConstructorReturn(this, _Component.call(this, player, options)); - - _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false; - - _this.closeable(!_this.options_.uncloseable); - _this.content(_this.options_.content); - - // Make sure the contentEl is defined AFTER any children are initialized - // because we only want the contents of the modal in the contentEl - // (not the UI elements like the close button). - _this.contentEl_ = Dom.createEl('div', { - className: MODAL_CLASS_NAME + '-content' - }, { - role: 'document' - }); - - _this.descEl_ = Dom.createEl('p', { - className: MODAL_CLASS_NAME + '-description vjs-control-text', - id: _this.el().getAttribute('aria-describedby') - }); - - Dom.textContent(_this.descEl_, _this.description()); - _this.el_.appendChild(_this.descEl_); - _this.el_.appendChild(_this.contentEl_); - return _this; - } - - /** - * Create the `ModalDialog`'s DOM element - * - * @return {Element} - * The DOM element that gets created. - */ - - - ModalDialog.prototype.createEl = function createEl() { - return _Component.prototype.createEl.call(this, 'div', { - className: this.buildCSSClass(), - tabIndex: -1 - }, { - 'aria-describedby': this.id() + '_description', - 'aria-hidden': 'true', - 'aria-label': this.label(), - 'role': 'dialog' - }); - }; - - /** - * Builds the default DOM `className`. - * - * @return {string} - * The DOM `className` for this object. - */ - - - ModalDialog.prototype.buildCSSClass = function buildCSSClass() { - return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this); - }; - - /** - * Handles `keydown` events on the document, looking for ESC, which closes - * the modal. - * - * @param {EventTarget~Event} e - * The keypress that triggered this event. - * - * @listens keydown - */ - - - ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) { - if (e.which === ESC && this.closeable()) { - this.close(); - } - }; - - /** - * Returns the label string for this modal. Primarily used for accessibility. - * - * @return {string} - * the localized or raw label of this modal. - */ - - - ModalDialog.prototype.label = function label() { - return this.localize(this.options_.label || 'Modal Window'); - }; - - /** - * Returns the description string for this modal. Primarily used for - * accessibility. - * - * @return {string} - * The localized or raw description of this modal. - */ - - - ModalDialog.prototype.description = function description() { - var desc = this.options_.description || this.localize('This is a modal window.'); - - // Append a universal closeability message if the modal is closeable. - if (this.closeable()) { - desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.'); - } - - return desc; - }; - - /** - * Opens the modal. - * - * @fires ModalDialog#beforemodalopen - * @fires ModalDialog#modalopen - */ - - - ModalDialog.prototype.open = function open() { - if (!this.opened_) { - var player = this.player(); - - /** - * Fired just before a `ModalDialog` is opened. - * - * @event ModalDialog#beforemodalopen - * @type {EventTarget~Event} - */ - this.trigger('beforemodalopen'); - this.opened_ = true; - - // Fill content if the modal has never opened before and - // never been filled. - if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) { - this.fill(); - } - - // If the player was playing, pause it and take note of its previously - // playing state. - this.wasPlaying_ = !player.paused(); - - if (this.options_.pauseOnOpen && this.wasPlaying_) { - player.pause(); - } - - if (this.closeable()) { - this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); - } - - player.controls(false); - this.show(); - this.conditionalFocus_(); - this.el().setAttribute('aria-hidden', 'false'); - - /** - * Fired just after a `ModalDialog` is opened. - * - * @event ModalDialog#modalopen - * @type {EventTarget~Event} - */ - this.trigger('modalopen'); - this.hasBeenOpened_ = true; - } - }; - - /** - * If the `ModalDialog` is currently open or closed. - * - * @param {boolean} [value] - * If given, it will open (`true`) or close (`false`) the modal. - * - * @return {boolean} - * the current open state of the modaldialog - */ - - - ModalDialog.prototype.opened = function opened(value) { - if (typeof value === 'boolean') { - this[value ? 'open' : 'close'](); - } - return this.opened_; - }; - - /** - * Closes the modal, does nothing if the `ModalDialog` is - * not open. - * - * @fires ModalDialog#beforemodalclose - * @fires ModalDialog#modalclose - */ - - - ModalDialog.prototype.close = function close() { - if (!this.opened_) { - return; - } - var player = this.player(); - - /** - * Fired just before a `ModalDialog` is closed. - * - * @event ModalDialog#beforemodalclose - * @type {EventTarget~Event} - */ - this.trigger('beforemodalclose'); - this.opened_ = false; - - if (this.wasPlaying_ && this.options_.pauseOnOpen) { - player.play(); - } - - if (this.closeable()) { - this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress)); - } - - player.controls(true); - this.hide(); - this.el().setAttribute('aria-hidden', 'true'); - - /** - * Fired just after a `ModalDialog` is closed. - * - * @event ModalDialog#modalclose - * @type {EventTarget~Event} - */ - this.trigger('modalclose'); - this.conditionalBlur_(); - - if (this.options_.temporary) { - this.dispose(); - } - }; - - /** - * Check to see if the `ModalDialog` is closeable via the UI. - * - * @param {boolean} [value] - * If given as a boolean, it will set the `closeable` option. - * - * @return {boolean} - * Returns the final value of the closable option. - */ - - - ModalDialog.prototype.closeable = function closeable(value) { - if (typeof value === 'boolean') { - var closeable = this.closeable_ = !!value; - var close = this.getChild('closeButton'); - - // If this is being made closeable and has no close button, add one. - if (closeable && !close) { - - // The close button should be a child of the modal - not its - // content element, so temporarily change the content element. - var temp = this.contentEl_; - - this.contentEl_ = this.el_; - close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' }); - this.contentEl_ = temp; - this.on(close, 'close', this.close); - } - - // If this is being made uncloseable and has a close button, remove it. - if (!closeable && close) { - this.off(close, 'close', this.close); - this.removeChild(close); - close.dispose(); - } - } - return this.closeable_; - }; - - /** - * Fill the modal's content element with the modal's "content" option. - * The content element will be emptied before this change takes place. - */ - - - ModalDialog.prototype.fill = function fill() { - this.fillWith(this.content()); - }; - - /** - * Fill the modal's content element with arbitrary content. - * The content element will be emptied before this change takes place. - * - * @fires ModalDialog#beforemodalfill - * @fires ModalDialog#modalfill - * - * @param {Mixed} [content] - * The same rules apply to this as apply to the `content` option. - */ - - - ModalDialog.prototype.fillWith = function fillWith(content) { - var contentEl = this.contentEl(); - var parentEl = contentEl.parentNode; - var nextSiblingEl = contentEl.nextSibling; - - /** - * Fired just before a `ModalDialog` is filled with content. - * - * @event ModalDialog#beforemodalfill - * @type {EventTarget~Event} - */ - this.trigger('beforemodalfill'); - this.hasBeenFilled_ = true; - - // Detach the content element from the DOM before performing - // manipulation to avoid modifying the live DOM multiple times. - parentEl.removeChild(contentEl); - this.empty(); - Dom.insertContent(contentEl, content); - /** - * Fired just after a `ModalDialog` is filled with content. - * - * @event ModalDialog#modalfill - * @type {EventTarget~Event} - */ - this.trigger('modalfill'); - - // Re-inject the re-filled content element. - if (nextSiblingEl) { - parentEl.insertBefore(contentEl, nextSiblingEl); - } else { - parentEl.appendChild(contentEl); - } - - // make sure that the close button is last in the dialog DOM - var closeButton = this.getChild('closeButton'); - - if (closeButton) { - parentEl.appendChild(closeButton.el_); - } - }; - - /** - * Empties the content element. This happens anytime the modal is filled. - * - * @fires ModalDialog#beforemodalempty - * @fires ModalDialog#modalempty - */ - - - ModalDialog.prototype.empty = function empty() { - /** - * Fired just before a `ModalDialog` is emptied. - * - * @event ModalDialog#beforemodalempty - * @type {EventTarget~Event} - */ - this.trigger('beforemodalempty'); - Dom.emptyEl(this.contentEl()); - - /** - * Fired just after a `ModalDialog` is emptied. - * - * @event ModalDialog#modalempty - * @type {EventTarget~Event} - */ - this.trigger('modalempty'); - }; - - /** - * Gets or sets the modal content, which gets normalized before being - * rendered into the DOM. - * - * This does not update the DOM or fill the modal, but it is called during - * that process. - * - * @param {Mixed} [value] - * If defined, sets the internal content value to be used on the - * next call(s) to `fill`. This value is normalized before being - * inserted. To "clear" the internal content value, pass `null`. - * - * @return {Mixed} - * The current content of the modal dialog - */ - - - ModalDialog.prototype.content = function content(value) { - if (typeof value !== 'undefined') { - this.content_ = value; - } - return this.content_; - }; - - /** - * conditionally focus the modal dialog if focus was previously on the player. - * - * @private - */ - - - ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() { - var activeEl = _document2['default'].activeElement; - var playerEl = this.player_.el_; - - this.previouslyActiveEl_ = null; - - if (playerEl.contains(activeEl) || playerEl === activeEl) { - this.previouslyActiveEl_ = activeEl; - - this.focus(); - - this.on(_document2['default'], 'keydown', this.handleKeyDown); - } - }; - - /** - * conditionally blur the element and refocus the last focused element - * - * @private - */ - - - ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() { - if (this.previouslyActiveEl_) { - this.previouslyActiveEl_.focus(); - this.previouslyActiveEl_ = null; - } - - this.off(_document2['default'], 'keydown', this.handleKeyDown); - }; - - /** - * Keydown handler. Attached when modal is focused. - * - * @listens keydown - */ - - - ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) { - // exit early if it isn't a tab key - if (event.which !== 9) { - return; - } - - var focusableEls = this.focusableEls_(); - var activeEl = this.el_.querySelector(':focus'); - var focusIndex = void 0; - - for (var i = 0; i < focusableEls.length; i++) { - if (activeEl === focusableEls[i]) { - focusIndex = i; - break; - } - } - - if (_document2['default'].activeElement === this.el_) { - focusIndex = 0; - } - - if (event.shiftKey && focusIndex === 0) { - focusableEls[focusableEls.length - 1].focus(); - event.preventDefault(); - } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) { - focusableEls[0].focus(); - event.preventDefault(); - } - }; - - /** - * get all focusable elements - * - * @private - */ - - - ModalDialog.prototype.focusableEls_ = function focusableEls_() { - var allChildren = this.el_.querySelectorAll('*'); - - return Array.prototype.filter.call(allChildren, function (child) { - return (child instanceof _window2['default'].HTMLAnchorElement || child instanceof _window2['default'].HTMLAreaElement) && child.hasAttribute('href') || (child instanceof _window2['default'].HTMLInputElement || child instanceof _window2['default'].HTMLSelectElement || child instanceof _window2['default'].HTMLTextAreaElement || child instanceof _window2['default'].HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof _window2['default'].HTMLIFrameElement || child instanceof _window2['default'].HTMLObjectElement || child instanceof _window2['default'].HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable'); - }); - }; - - return ModalDialog; -}(_component2['default']); - -/** - * Default options for `ModalDialog` default options. - * - * @type {Object} - * @private - */ - - -ModalDialog.prototype.options_ = { - pauseOnOpen: true, - temporary: true -}; - -_component2['default'].registerComponent('ModalDialog', ModalDialog); -exports['default'] = ModalDialog; - -},{"100":100,"5":5,"85":85,"88":88,"99":99}],56:[function(_dereq_,module,exports){ -'use strict'; - -exports.__esModule = true; - -var _templateObject = _taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']); - -var _component = _dereq_(5); - -var _component2 = _interopRequireDefault(_component); - -var _document = _dereq_(99); - -var _document2 = _interopRequireDefault(_document); - -var _window = _dereq_(100); - -var _window2 = _interopRequireDefault(_window); - -var _tsml = _dereq_(103); - -var _tsml2 = _interopRequireDefault(_tsml); - -var _evented = _dereq_(53); - -var _evented2 = _interopRequireDefault(_evented); - -var _events = _dereq_(86); - -var Events = _interopRequireWildcard(_events); - -var _dom = _dereq_(85); - -var Dom = _interopRequireWildcard(_dom); - -var _fn = _dereq_(88); - -var Fn = _interopRequireWildcard(_fn); - -var _guid = _dereq_(90); - -var Guid = _interopRequireWildcard(_guid); - -var _browser = _dereq_(81); - -var browser = _interopRequireWildcard(_browser); - -var _log = _dereq_(91); - -var _log2 = _interopRequireDefault(_log); - -var _toTitleCase = _dereq_(96); - -var _toTitleCase2 = _interopRequireDefault(_toTitleCase); - -var _timeRanges = _dereq_(95); - -var _buffer = _dereq_(82); - -var _stylesheet = _dereq_(94); - -var stylesheet = _interopRequireWildcard(_stylesheet); - -var _fullscreenApi = _dereq_(47); - -var _fullscreenApi2 = _interopRequireDefault(_fullscreenApi); - -var _mediaError = _dereq_(49); - -var _mediaError2 = _interopRequireDefault(_mediaError); - -var _tuple = _dereq_(102); - -var _tuple2 = _interopRequireDefault(_tuple); - -var _obj = _dereq_(93); - -var _mergeOptions = _dereq_(92); - -var _mergeOptions2 = _interopRequireDefault(_mergeOptions); - -var _textTrackListConverter = _dereq_(71); - -var _textTrackListConverter2 = _interopRequireDefault(_textTrackListConverter); - -var _modalDialog = _dereq_(55); - -var _modalDialog2 = _interopRequireDefault(_modalDialog); - -var _tech = _dereq_(64); - -var _tech2 = _interopRequireDefault(_tech); - -var _middleware = _dereq_(63); - -var middleware = _interopRequireWildcard(_middleware); - -var _trackTypes = _dereq_(77); - -var _filterSource = _dereq_(87); - -var _filterSource2 = _interopRequireDefault(_filterSource); - -_dereq_(62); - -_dereq_(58); - -_dereq_(70); - -_dereq_(48); - -_dereq_(1); - -_dereq_(4); - -_dereq_(8); - -_dereq_(44); - -_dereq_(73); - -_dereq_(61); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - -function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; } - -function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } - -function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } - -function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** - * @file player.js - */ -// Subclasses Component - - -// The following imports are used only to ensure that the corresponding modules -// are always included in the video.js package. Importing the modules will -// execute them and they will register themselves with video.js. - - -// Import Html5 tech, at least for disposing the original video tag. - - -// The following tech events are simply re-triggered -// on the player when they happen -var TECH_EVENTS_RETRIGGER = [ -/** - * Fired while the user agent is downloading media data. - * - * @event Player#progress - * @type {EventTarget~Event} - */ -/** - * Retrigger the `progress` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechProgress_ - * @fires Player#progress - * @listens Tech#progress - */ -'progress', - -/** - * Fires when the loading of an audio/video is aborted. - * - * @event Player#abort - * @type {EventTarget~Event} - */ -/** - * Retrigger the `abort` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechAbort_ - * @fires Player#abort - * @listens Tech#abort - */ -'abort', - -/** - * Fires when the browser is intentionally not getting media data. - * - * @event Player#suspend - * @type {EventTarget~Event} - */ -/** - * Retrigger the `suspend` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechSuspend_ - * @fires Player#suspend - * @listens Tech#suspend - */ -'suspend', - -/** - * Fires when the current playlist is empty. - * - * @event Player#emptied - * @type {EventTarget~Event} - */ -/** - * Retrigger the `emptied` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechEmptied_ - * @fires Player#emptied - * @listens Tech#emptied - */ -'emptied', -/** - * Fires when the browser is trying to get media data, but data is not available. - * - * @event Player#stalled - * @type {EventTarget~Event} - */ -/** - * Retrigger the `stalled` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechStalled_ - * @fires Player#stalled - * @listens Tech#stalled - */ -'stalled', - -/** - * Fires when the browser has loaded meta data for the audio/video. - * - * @event Player#loadedmetadata - * @type {EventTarget~Event} - */ -/** - * Retrigger the `stalled` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechLoadedmetadata_ - * @fires Player#loadedmetadata - * @listens Tech#loadedmetadata - */ -'loadedmetadata', - -/** - * Fires when the browser has loaded the current frame of the audio/video. - * - * @event player#loadeddata - * @type {event} - */ -/** - * Retrigger the `loadeddata` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechLoaddeddata_ - * @fires Player#loadeddata - * @listens Tech#loadeddata - */ -'loadeddata', - -/** - * Fires when the current playback position has changed. - * - * @event player#timeupdate - * @type {event} - */ -/** - * Retrigger the `timeupdate` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechTimeUpdate_ - * @fires Player#timeupdate - * @listens Tech#timeupdate - */ -'timeupdate', - -/** - * Fires when the playing speed of the audio/video is changed - * - * @event player#ratechange - * @type {event} - */ -/** - * Retrigger the `ratechange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechRatechange_ - * @fires Player#ratechange - * @listens Tech#ratechange - */ -'ratechange', - -/** - * Fires when the video's intrinsic dimensions change - * - * @event Player#resize - * @type {event} - */ -/** - * Retrigger the `resize` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechResize_ - * @fires Player#resize - * @listens Tech#resize - */ -'resize', - -/** - * Fires when the volume has been changed - * - * @event player#volumechange - * @type {event} - */ -/** - * Retrigger the `volumechange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechVolumechange_ - * @fires Player#volumechange - * @listens Tech#volumechange - */ -'volumechange', - -/** - * Fires when the text track has been changed - * - * @event player#texttrackchange - * @type {event} - */ -/** - * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}. - * - * @private - * @method Player#handleTechTexttrackchange_ - * @fires Player#texttrackchange - * @listens Tech#texttrackchange - */ -'texttrackchange']; - -/** - * An instance of the `Player` class is created when any of the Video.js setup methods - * are used to initialize a video. - * - * After an instance has been created it can be accessed globally in two ways: - * 1. By calling `videojs('example_video_1');` - * 2. By using it directly via `videojs.players.example_video_1;` - * - * @extends Component - */ - -var Player = function (_Component) { - _inherits(Player, _Component); - - /** - * Create an instance of this class. - * - * @param {Element} tag - * The original video DOM element used for configuring options. - * - * @param {Object} [options] - * Object of option names and values. - * - * @param {Component~ReadyCallback} [ready] - * Ready callback function. - */ - function Player(tag, options, ready) { - _classCallCheck(this, Player); - - // Make sure tag ID exists - tag.id = tag.id || 'vjs_video_' + Guid.newGUID(); - - // Set Options - // The options argument overrides options set in the video tag - // which overrides globally set options. - // This latter part coincides with the load order - // (tag must exist before Player) - options = (0, _obj.assign)(Player.getTagSettings(tag), options); - - // Delay the initialization of children because we need to set up - // player properties first, and can't use `this` before `super()` - options.initChildren = false; - - // Same with creating the element - options.createEl = false; - - // we don't want the player to report touch activity on itself - // see enableTouchActivity in Component - options.reportTouchActivity = false; + return callback(err, response, response.body) + } - // If language is not set, get the closest lang attribute - if (!options.language) { - if (typeof tag.closest === 'function') { - var closest = tag.closest('[lang]'); + var xhr = options.xhr || null; - if (closest) { - options.language = closest.getAttribute('lang'); + if (!xhr) { + if (options.cors || options.useXDR) { + xhr = new createXHR.XDomainRequest(); + }else{ + xhr = new createXHR.XMLHttpRequest(); } - } else { - var element = tag; + } - while (element && element.nodeType === 1) { - if (Dom.getAttributes(element).hasOwnProperty('lang')) { - options.language = element.getAttribute('lang'); - break; - } - element = element.parentNode; + var key; + var aborted; + var uri = xhr.url = options.uri || options.url; + var method = xhr.method = options.method || "GET"; + var body = options.body || options.data; + var headers = xhr.headers = options.headers || {}; + var sync = !!options.sync; + var isJson = false; + var timeoutTimer; + var failureResponse = { + body: undefined, + headers: {}, + statusCode: 0, + method: method, + url: uri, + rawRequest: xhr + }; + + if ("json" in options && options.json !== false) { + isJson = true; + headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user + if (method !== "GET" && method !== "HEAD") { + headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user + body = JSON.stringify(options.json === true ? body : options.json); } - } } - // Run base component initializing with new options + xhr.onreadystatechange = readystatechange; + xhr.onload = loadFunc; + xhr.onerror = errorFunc; + // IE9 must have onprogress be set to a unique function. + xhr.onprogress = function () { + // IE must die + }; + xhr.onabort = function(){ + aborted = true; + }; + xhr.ontimeout = errorFunc; + xhr.open(method, uri, !sync, options.username, options.password); + //has to be after open + if(!sync) { + xhr.withCredentials = !!options.withCredentials; + } + // Cannot set timeout with sync request + // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly + // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent + if (!sync && options.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + if (aborted) return + aborted = true;//IE9 may still call readystatechange + xhr.abort("timeout"); + var e = new Error("XMLHttpRequest timeout"); + e.code = "ETIMEDOUT"; + errorFunc(e); + }, options.timeout ); + } - // Turn off API access because we're loading a new tech that might load asynchronously - var _this = _possibleConstructorReturn(this, _Component.call(this, null, options, ready)); + if (xhr.setRequestHeader) { + for(key in headers){ + if(headers.hasOwnProperty(key)){ + xhr.setRequestHeader(key, headers[key]); + } + } + } else if (options.headers && !isEmpty(options.headers)) { + throw new Error("Headers cannot be set on an XDomainRequest object") + } - _this.isReady_ = false; + if ("responseType" in options) { + xhr.responseType = options.responseType; + } - // if the global option object was accidentally blown away by - // someone, bail early with an informative error - if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) { - throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?'); + if ("beforeSend" in options && + typeof options.beforeSend === "function" + ) { + options.beforeSend(xhr); } - // Store the original tag used to set options - _this.tag = tag; + // Microsoft Edge browser sends "undefined" when send is called with undefined value. + // XMLHttpRequest spec says to pass null as body to indicate no body + // See https://github.com/naugtur/xhr/issues/100. + xhr.send(body || null); - // Store the tag attributes used to restore html5 element - _this.tagAttributes = tag && Dom.getAttributes(tag); + return xhr - // Update current language - _this.language(_this.options_.language); - // Update Supported Languages - if (options.languages) { - // Normalise player option languages to lowercase - var languagesToLower = {}; +} - Object.getOwnPropertyNames(options.languages).forEach(function (name) { - languagesToLower[name.toLowerCase()] = options.languages[name]; - }); - _this.languages_ = languagesToLower; - } else { - _this.languages_ = Player.prototype.options_.languages; +function getXml(xhr) { + if (xhr.responseType === "document") { + return xhr.responseXML + } + var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror"; + if (xhr.responseType === "" && !firefoxBugTakenEffect) { + return xhr.responseXML } - // Cache for video property values. - _this.cache_ = {}; + return null +} - // Set poster - _this.poster_ = options.poster || ''; +function noop() {} - // Set controls - _this.controls_ = !!options.controls; +/** + * @file text-track.js + */ +/** + * Takes a webvtt file contents and parses it into cues + * + * @param {string} srcContent + * webVTT file contents + * + * @param {TextTrack} track + * TextTrack to add cues to. Cues come from the srcContent. + * + * @private + */ +var parseCues = function parseCues(srcContent, track) { + var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder()); + var errors = []; - // Set default values for lastVolume - _this.cache_.lastVolume = 1; + parser.oncue = function (cue) { + track.addCue(cue); + }; - // Original tag settings stored in options - // now remove immediately so native controls don't flash. - // May be turned back on by HTML5 tech if nativeControlsForTouch is true - tag.controls = false; + parser.onparsingerror = function (error) { + errors.push(error); + }; - /* - * Store the internal state of scrubbing - * - * @private - * @return {Boolean} True if the user is scrubbing - */ - _this.scrubbing_ = false; + parser.onflush = function () { + track.trigger({ + type: 'loadeddata', + target: track + }); + }; - _this.el_ = _this.createEl(); + parser.parse(srcContent); + if (errors.length > 0) { + if (window_1.console && window_1.console.groupCollapsed) { + window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src); + } + errors.forEach(function (error) { + return log$1.error(error); + }); + if (window_1.console && window_1.console.groupEnd) { + window_1.console.groupEnd(); + } + } - // Make this an evented object and use `el_` as its event bus. - (0, _evented2['default'])(_this, { eventBusKey: 'el_' }); + parser.flush(); +}; - // We also want to pass the original player options to each component and plugin - // as well so they don't need to reach back into the player for options later. - // We also need to do another copy of this.options_ so we don't end up with - // an infinite loop. - var playerOptionsCopy = (0, _mergeOptions2['default'])(_this.options_); +/** + * Load a `TextTrack` from a specifed url. + * + * @param {string} src + * Url to load track from. + * + * @param {TextTrack} track + * Track to add cues to. Comes from the content at the end of `url`. + * + * @private + */ +var loadTrack = function loadTrack(src, track) { + var opts = { + uri: src + }; + var crossOrigin = isCrossOrigin(src); - // Load plugins - if (options.plugins) { - var plugins = options.plugins; + if (crossOrigin) { + opts.cors = crossOrigin; + } - Object.keys(plugins).forEach(function (name) { - if (typeof this[name] === 'function') { - this[name](plugins[name]); - } else { - throw new Error('plugin "' + name + '" does not exist'); - } - }, _this); + index(opts, bind(this, function (err, response, responseBody) { + if (err) { + return log$1.error(err, response); } - _this.options_.playerOptions = playerOptionsCopy; - - _this.middleware_ = []; - - _this.initChildren(); + track.loaded_ = true; - // Set isAudio based on whether or not an audio tag was used - _this.isAudio(tag.nodeName.toLowerCase() === 'audio'); + // Make sure that vttjs has loaded, otherwise, wait till it finished loading + // NOTE: this is only used for the alt/video.novtt.js build + if (typeof window_1.WebVTT !== 'function') { + if (track.tech_) { + var loadHandler = function loadHandler() { + return parseCues(responseBody, track); + }; - // Update controls className. Can't do this when the controls are initially - // set because the element doesn't exist yet. - if (_this.controls()) { - _this.addClass('vjs-controls-enabled'); + track.tech_.on('vttjsloaded', loadHandler); + track.tech_.on('vttjserror', function () { + log$1.error('vttjs failed to load, stopping trying to process ' + track.src); + track.tech_.off('vttjsloaded', loadHandler); + }); + } } else { - _this.addClass('vjs-controls-disabled'); + parseCues(responseBody, track); } + })); +}; - // Set ARIA label and region role depending on player type - _this.el_.setAttribute('role', 'region'); - if (_this.isAudio()) { - _this.el_.setAttribute('aria-label', _this.localize('Audio Player')); - } else { - _this.el_.setAttribute('aria-label', _this.localize('Video Player')); - } +/** + * A representation of a single `TextTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack} + * @extends Track + */ + +var TextTrack = function (_Track) { + inherits(TextTrack, _Track); + + /** + * Create an instance of this class. + * + * @param {Object} options={} + * Object of option names and values + * + * @param {Tech} options.tech + * A reference to the tech that owns this TextTrack. + * + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. + * + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * vesion of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. + */ + function TextTrack() { + var _this, _ret; - if (_this.isAudio()) { - _this.addClass('vjs-audio'); + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, TextTrack); + + if (!options.tech) { + throw new Error('A tech was not provided.'); } - if (_this.flexNotSupported_()) { - _this.addClass('vjs-no-flex'); + var settings = mergeOptions(options, { + kind: TextTrackKind[options.kind] || 'subtitles', + language: options.language || options.srclang || '' + }); + var mode = TextTrackMode[settings.mode] || 'disabled'; + var default_ = settings['default']; + + if (settings.kind === 'metadata' || settings.kind === 'chapters') { + mode = 'hidden'; } + // on IE8 this will be a document element + // for every other browser this will be a normal object + var tt = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); - // TODO: Make this smarter. Toggle user state between touching/mousing - // using events, since devices can have both touch and mouse events. - // if (browser.TOUCH_ENABLED) { - // this.addClass('vjs-touch-enabled'); - // } + tt.tech_ = settings.tech; - // iOS Safari has broken hover handling - if (!browser.IS_IOS) { - _this.addClass('vjs-workinghover'); + if (IS_IE8) { + for (var prop in TextTrack.prototype) { + if (prop !== 'constructor') { + tt[prop] = TextTrack.prototype[prop]; + } + } } - // Make player easily findable by ID - Player.players[_this.id_] = _this; + tt.cues_ = []; + tt.activeCues_ = []; - // Add a major version class to aid css in plugins - var majorVersion = '6.1.0'.split('.')[0]; + var cues = new TextTrackCueList(tt.cues_); + var activeCues = new TextTrackCueList(tt.activeCues_); + var changed = false; + var timeupdateHandler = bind(tt, function () { - _this.addClass('vjs-v' + majorVersion); + // Accessing this.activeCues for the side-effects of updating itself + // due to it's nature as a getter function. Do not remove or cues will + // stop updating! + /* eslint-disable no-unused-expressions */ + this.activeCues; + /* eslint-enable no-unused-expressions */ + if (changed) { + this.trigger('cuechange'); + changed = false; + } + }); - // When the player is first initialized, trigger activity so components - // like the control bar show themselves if needed - _this.userActive(true); - _this.reportUserActivity(); - _this.listenForUserActivity_(); + if (mode !== 'disabled') { + tt.tech_.ready(function () { + tt.tech_.on('timeupdate', timeupdateHandler); + }, true); + } - _this.on('fullscreenchange', _this.handleFullscreenChange_); - _this.on('stageclick', _this.handleStageClick_); + /** + * @memberof TextTrack + * @member {boolean} default + * If this track was set to be on or off by default. Cannot be changed after + * creation. + * @instance + * + * @readonly + */ + Object.defineProperty(tt, 'default', { + get: function get$$1() { + return default_; + }, + set: function set$$1() {} + }); - _this.changingSrc_ = false; - return _this; - } + /** + * @memberof TextTrack + * @member {string} mode + * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will + * not be set if setting to an invalid mode. + * @instance + * + * @fires TextTrack#modechange + */ + Object.defineProperty(tt, 'mode', { + get: function get$$1() { + return mode; + }, + set: function set$$1(newMode) { + var _this2 = this; - /** - * Destroys the video player and does any necessary cleanup. - * - * This is especially helpful if you are dynamically adding and removing videos - * to/from the DOM. - * - * @fires Player#dispose - */ + if (!TextTrackMode[newMode]) { + return; + } + mode = newMode; + if (mode === 'showing') { + this.tech_.ready(function () { + _this2.tech_.on('timeupdate', timeupdateHandler); + }, true); + } + /** + * An event that fires when mode changes on this track. This allows + * the TextTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! + * + * @event TextTrack#modechange + * @type {EventTarget~Event} + */ + this.trigger('modechange'); + } + }); - Player.prototype.dispose = function dispose() { /** - * Called when the player is being disposed of. - * - * @event Player#dispose - * @type {EventTarget~Event} + * @memberof TextTrack + * @member {TextTrackCueList} cues + * The text track cue list for this TextTrack. + * @instance */ - this.trigger('dispose'); - // prevent dispose from being called twice - this.off('dispose'); + Object.defineProperty(tt, 'cues', { + get: function get$$1() { + if (!this.loaded_) { + return null; + } - if (this.styleEl_ && this.styleEl_.parentNode) { - this.styleEl_.parentNode.removeChild(this.styleEl_); - } + return cues; + }, + set: function set$$1() {} + }); - // Kill reference to this player - Player.players[this.id_] = null; + /** + * @memberof TextTrack + * @member {TextTrackCueList} activeCues + * The list text track cues that are currently active for this TextTrack. + * @instance + */ + Object.defineProperty(tt, 'activeCues', { + get: function get$$1() { + if (!this.loaded_) { + return null; + } - if (this.tag && this.tag.player) { - this.tag.player = null; - } + // nothing to do + if (this.cues.length === 0) { + return activeCues; + } - if (this.el_ && this.el_.player) { - this.el_.player = null; - } + var ct = this.tech_.currentTime(); + var active = []; - if (this.tech_) { - this.tech_.dispose(); - } + for (var i = 0, l = this.cues.length; i < l; i++) { + var cue = this.cues[i]; - _Component.prototype.dispose.call(this); - }; + if (cue.startTime <= ct && cue.endTime >= ct) { + active.push(cue); + } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) { + active.push(cue); + } + } - /** - * Create the `Player`'s DOM element. - * - * @return {Element} - * The DOM element that gets created. - */ + changed = false; + + if (active.length !== this.activeCues_.length) { + changed = true; + } else { + for (var _i = 0; _i < active.length; _i++) { + if (this.activeCues_.indexOf(active[_i]) === -1) { + changed = true; + } + } + } + this.activeCues_ = active; + activeCues.setCues_(this.activeCues_); - Player.prototype.createEl = function createEl() { - var tag = this.tag; - var el = void 0; - var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player'); + return activeCues; + }, + set: function set$$1() {} + }); - if (playerElIngest) { - el = this.el_ = tag.parentNode; + if (settings.src) { + tt.src = settings.src; + loadTrack(settings.src, tt); } else { - el = this.el_ = _Component.prototype.createEl.call(this, 'div'); + tt.loaded_ = true; } - // set tabindex to -1 so we could focus on the player element - tag.setAttribute('tabindex', '-1'); + return _ret = tt, possibleConstructorReturn(_this, _ret); + } - // Remove width/height attrs from tag so CSS can make it 100% width/height - tag.removeAttribute('width'); - tag.removeAttribute('height'); + /** + * Add a cue to the internal list of cues. + * + * @param {TextTrack~Cue} cue + * The cue to add to our internal list + */ - // Copy over all the attributes from the tag, including ID and class - // ID will now reference player box, not the video tag - var attrs = Dom.getAttributes(tag); - Object.getOwnPropertyNames(attrs).forEach(function (attr) { - // workaround so we don't totally break IE7 - // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7 - if (attr === 'class') { - el.className += ' ' + attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - }); + TextTrack.prototype.addCue = function addCue(originalCue) { + var cue = originalCue; - // Update tag id/class for use as HTML5 playback tech - // Might think we should do this after embedding in container so .vjs-tech class - // doesn't flash 100% width/height, but class only applies with .video-js parent - tag.playerId = tag.id; - tag.id += '_html5_api'; - tag.className = 'vjs-tech'; + if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) { + cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text); - // Make player findable on elements - tag.player = el.player = this; - // Default state of video is paused - this.addClass('vjs-paused'); + for (var prop in originalCue) { + if (!(prop in cue)) { + cue[prop] = originalCue[prop]; + } + } - // Add a style element in the player that we'll use to set the width/height - // of the player in a way that's still overrideable by CSS, just like the - // video element - if (_window2['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) { - this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions'); - var defaultsStyleEl = Dom.$('.vjs-styles-defaults'); - var head = Dom.$('head'); + // make sure that `id` is copied over + cue.id = originalCue.id; + cue.originalCue_ = originalCue; + } - head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild); + var tracks = this.tech_.textTracks(); + + for (var i = 0; i < tracks.length; i++) { + if (tracks[i] !== this) { + tracks[i].removeCue(cue); + } } - // Pass in the width/height/aspectRatio options which will update the style el - this.width(this.options_.width); - this.height(this.options_.height); - this.fluid(this.options_.fluid); - this.aspectRatio(this.options_.aspectRatio); + this.cues_.push(cue); + this.cues.setCues_(this.cues_); + }; - // Hide any links within the video/audio tag, because IE doesn't hide them completely. - var links = tag.getElementsByTagName('a'); + /** + * Remove a cue from our internal list + * + * @param {TextTrack~Cue} removeCue + * The cue to remove from our internal list + */ - for (var i = 0; i < links.length; i++) { - var linkEl = links.item(i); - Dom.addClass(linkEl, 'vjs-hidden'); - linkEl.setAttribute('hidden', 'hidden'); - } + TextTrack.prototype.removeCue = function removeCue(_removeCue) { + var i = this.cues_.length; - // insertElFirst seems to cause the networkState to flicker from 3 to 2, so - // keep track of the original for later so we can know if the source originally failed - tag.initNetworkState_ = tag.networkState; + while (i--) { + var cue = this.cues_[i]; - // Wrap video tag in div (el/box) container - if (tag.parentNode && !playerElIngest) { - tag.parentNode.insertBefore(el, tag); + if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) { + this.cues_.splice(i, 1); + this.cues.setCues_(this.cues_); + break; + } } + }; - // insert the tag as the first child of the player element - // then manually add it to the children array so that this.addChild - // will work properly for other components - // - // Breaks iPhone, fixed in HTML5 setup. - Dom.prependTo(tag, el); - this.children_.unshift(tag); + return TextTrack; +}(Track); - // Set lang attr on player to ensure CSS :lang() in consistent with player - // if it's been set to something different to the doc - this.el_.setAttribute('lang', this.language_); +/** + * cuechange - One or more cues in the track have become active or stopped being active. + */ - this.el_ = el; - return el; - }; +TextTrack.prototype.allowedEvents_ = { + cuechange: 'cuechange' +}; + +/** + * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList} + * only one `AudioTrack` in the list will be enabled at a time. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack} + * @extends Track + */ + +var AudioTrack = function (_Track) { + inherits(AudioTrack, _Track); /** - * A getter/setter for the `Player`'s width. + * Create an instance of this class. * - * @param {number} [value] - * The value to set the `Player's width to. + * @param {Object} [options={}] + * Object of option names and values * - * @return {number} - * The current width of the `Player` when getting. + * @param {AudioTrack~Kind} [options.kind=''] + * A valid audio track kind + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.enabled] + * If this track is the one that is currently playing. If this track is part of + * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled. */ + function AudioTrack() { + var _this, _ret; + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, AudioTrack); - Player.prototype.width = function width(value) { - return this.dimension('width', value); - }; + var settings = mergeOptions(options, { + kind: AudioTrackKind[options.kind] || '' + }); + // on IE8 this will be a document element + // for every other browser this will be a normal object + var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); + var enabled = false; - /** - * A getter/setter for the `Player`'s height. - * - * @param {number} [value] - * The value to set the `Player's heigth to. - * - * @return {number} - * The current height of the `Player` when getting. - */ + if (IS_IE8) { + for (var prop in AudioTrack.prototype) { + if (prop !== 'constructor') { + track[prop] = AudioTrack.prototype[prop]; + } + } + } + /** + * @memberof AudioTrack + * @member {boolean} enabled + * If this `AudioTrack` is enabled or not. When setting this will + * fire {@link AudioTrack#enabledchange} if the state of enabled is changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + Object.defineProperty(track, 'enabled', { + get: function get$$1() { + return enabled; + }, + set: function set$$1(newEnabled) { + // an invalid or unchanged value + if (typeof newEnabled !== 'boolean' || newEnabled === enabled) { + return; + } + enabled = newEnabled; + /** + * An event that fires when enabled changes on this track. This allows + * the AudioTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event AudioTrack#enabledchange + * @type {EventTarget~Event} + */ + this.trigger('enabledchange'); + } + }); - Player.prototype.height = function height(value) { - return this.dimension('height', value); - }; + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.enabled) { + track.enabled = settings.enabled; + } + track.loaded_ = true; + + return _ret = track, possibleConstructorReturn(_this, _ret); + } + + return AudioTrack; +}(Track); + +/** + * A representation of a single `VideoTrack`. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack} + * @extends Track + */ + +var VideoTrack = function (_Track) { + inherits(VideoTrack, _Track); /** - * A getter/setter for the `Player`'s width & height. + * Create an instance of this class. * - * @param {string} dimension - * This string can be: - * - 'width' - * - 'height' + * @param {Object} [options={}] + * Object of option names and values * - * @param {number} [value] - * Value for dimension specified in the first argument. + * @param {string} [options.kind=''] + * A valid {@link VideoTrack~Kind} * - * @return {number} - * The dimension arguments value when getting (width/height). + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this AudioTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {boolean} [options.selected] + * If this track is the one that is currently playing. */ + function VideoTrack() { + var _this, _ret; + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, VideoTrack); - Player.prototype.dimension = function dimension(_dimension, value) { - var privDimension = _dimension + '_'; + var settings = mergeOptions(options, { + kind: VideoTrackKind[options.kind] || '' + }); - if (value === undefined) { - return this[privDimension] || 0; + // on IE8 this will be a document element + // for every other browser this will be a normal object + var track = (_this = possibleConstructorReturn(this, _Track.call(this, settings)), _this); + var selected = false; + + if (IS_IE8) { + for (var prop in VideoTrack.prototype) { + if (prop !== 'constructor') { + track[prop] = VideoTrack.prototype[prop]; + } + } } - if (value === '') { - // If an empty string is given, reset the dimension to be automatic - this[privDimension] = undefined; - } else { - var parsedVal = parseFloat(value); + /** + * @memberof VideoTrack + * @member {boolean} selected + * If this `VideoTrack` is selected or not. When setting this will + * fire {@link VideoTrack#selectedchange} if the state of selected changed. + * @instance + * + * @fires VideoTrack#selectedchange + */ + Object.defineProperty(track, 'selected', { + get: function get$$1() { + return selected; + }, + set: function set$$1(newSelected) { + // an invalid or unchanged value + if (typeof newSelected !== 'boolean' || newSelected === selected) { + return; + } + selected = newSelected; - if (isNaN(parsedVal)) { - _log2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension); - return; + /** + * An event that fires when selected changes on this track. This allows + * the VideoTrackList that holds this track to act accordingly. + * + * > Note: This is not part of the spec! Native tracks will do + * this internally without an event. + * + * @event VideoTrack#selectedchange + * @type {EventTarget~Event} + */ + this.trigger('selectedchange'); } + }); - this[privDimension] = parsedVal; + // if the user sets this track to selected then + // set selected to that true value otherwise + // we keep it false + if (settings.selected) { + track.selected = settings.selected; } - this.updateStyleEl_(); - }; - - /** - * A getter/setter/toggler for the vjs-fluid `className` on the `Player`. - * - * @param {boolean} [bool] - * - A value of true adds the class. - * - A value of false removes the class. - * - No value will toggle the fluid class. - * - * @return {boolean|undefined} - * - The value of fluid when getting. - * - `undefined` when setting. - */ + return _ret = track, possibleConstructorReturn(_this, _ret); + } + return VideoTrack; +}(Track); - Player.prototype.fluid = function fluid(bool) { - if (bool === undefined) { - return !!this.fluid_; - } +/** + * @file html-track-element.js + */ - this.fluid_ = !!bool; +/** + * @memberof HTMLTrackElement + * @typedef {HTMLTrackElement~ReadyState} + * @enum {number} + */ +var NONE = 0; +var LOADING = 1; +var LOADED = 2; +var ERROR = 3; - if (bool) { - this.addClass('vjs-fluid'); - } else { - this.removeClass('vjs-fluid'); - } +/** + * A single track represented in the DOM. + * + * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement} + * @extends EventTarget + */ - this.updateStyleEl_(); - }; +var HTMLTrackElement = function (_EventTarget) { + inherits(HTMLTrackElement, _EventTarget); /** - * Get/Set the aspect ratio + * Create an instance of this class. * - * @param {string} [ratio] - * Aspect ratio for player + * @param {Object} options={} + * Object of option names and values * - * @return {string|undefined} - * returns the current aspect ratio when getting - */ - - /** - * A getter/setter for the `Player`'s aspect ratio. + * @param {Tech} options.tech + * A reference to the tech that owns this HTMLTrackElement. * - * @param {string} [ratio] - * The value to set the `Player's aspect ratio to. + * @param {TextTrack~Kind} [options.kind='subtitles'] + * A valid text track kind. * - * @return {string|undefined} - * - The current aspect ratio of the `Player` when getting. - * - undefined when setting + * @param {TextTrack~Mode} [options.mode='disabled'] + * A valid text track mode. + * + * @param {string} [options.id='vjs_track_' + Guid.newGUID()] + * A unique id for this TextTrack. + * + * @param {string} [options.label=''] + * The menu label for this track. + * + * @param {string} [options.language=''] + * A valid two character language code. + * + * @param {string} [options.srclang=''] + * A valid two character language code. An alternative, but deprioritized + * vesion of `options.language` + * + * @param {string} [options.src] + * A url to TextTrack cues. + * + * @param {boolean} [options.default] + * If this track should default to on or off. */ + function HTMLTrackElement() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + classCallCheck(this, HTMLTrackElement); + var _this = possibleConstructorReturn(this, _EventTarget.call(this)); - Player.prototype.aspectRatio = function aspectRatio(ratio) { - if (ratio === undefined) { - return this.aspectRatio_; - } - - // Check for width:height format - if (!/^\d+\:\d+$/.test(ratio)) { - throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.'); - } - this.aspectRatio_ = ratio; + var readyState = void 0; + var trackElement = _this; // eslint-disable-line - // We're assuming if you set an aspect ratio you want fluid mode, - // because in fixed mode you could calculate width and height yourself. - this.fluid(true); + if (IS_IE8) { + trackElement = document_1.createElement('custom'); - this.updateStyleEl_(); - }; + for (var prop in HTMLTrackElement.prototype) { + if (prop !== 'constructor') { + trackElement[prop] = HTMLTrackElement.prototype[prop]; + } + } + } - /** - * Update styles of the `Player` element (height, width and aspect ratio). - * - * @private - * @listens Tech#loadedmetadata - */ + var track = new TextTrack(options); + trackElement.kind = track.kind; + trackElement.src = track.src; + trackElement.srclang = track.language; + trackElement.label = track.label; + trackElement['default'] = track['default']; - Player.prototype.updateStyleEl_ = function updateStyleEl_() { - if (_window2['default'].VIDEOJS_NO_DYNAMIC_STYLE === true) { - var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width; - var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height; - var techEl = this.tech_ && this.tech_.el(); + /** + * @memberof HTMLTrackElement + * @member {HTMLTrackElement~ReadyState} readyState + * The current ready state of the track element. + * @instance + */ + Object.defineProperty(trackElement, 'readyState', { + get: function get$$1() { + return readyState; + } + }); - if (techEl) { - if (_width >= 0) { - techEl.width = _width; - } - if (_height >= 0) { - techEl.height = _height; - } + /** + * @memberof HTMLTrackElement + * @member {TextTrack} track + * The underlying TextTrack object. + * @instance + * + */ + Object.defineProperty(trackElement, 'track', { + get: function get$$1() { + return track; } + }); - return; - } + readyState = NONE; - var width = void 0; - var height = void 0; - var aspectRatio = void 0; - var idClass = void 0; + /** + * @listens TextTrack#loadeddata + * @fires HTMLTrackElement#load + */ + track.addEventListener('loadeddata', function () { + readyState = LOADED; - // The aspect ratio is either used directly or to calculate width and height. - if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') { - // Use any aspectRatio that's been specifically set - aspectRatio = this.aspectRatio_; - } else if (this.videoWidth() > 0) { - // Otherwise try to get the aspect ratio from the video metadata - aspectRatio = this.videoWidth() + ':' + this.videoHeight(); - } else { - // Or use a default. The video element's is 2:1, but 16:9 is more common. - aspectRatio = '16:9'; - } + trackElement.trigger({ + type: 'load', + target: trackElement + }); + }); - // Get the ratio as a decimal we can use to calculate dimensions - var ratioParts = aspectRatio.split(':'); - var ratioMultiplier = ratioParts[1] / ratioParts[0]; + if (IS_IE8) { + var _ret; - if (this.width_ !== undefined) { - // Use any width that's been specifically set - width = this.width_; - } else if (this.height_ !== undefined) { - // Or calulate the width from the aspect ratio if a height has been set - width = this.height_ / ratioMultiplier; - } else { - // Or use the video's metadata, or use the video el's default of 300 - width = this.videoWidth() || 300; + return _ret = trackElement, possibleConstructorReturn(_this, _ret); } + return _this; + } - if (this.height_ !== undefined) { - // Use any height that's been specifically set - height = this.height_; - } else { - // Otherwise calculate the height from the ratio and the width - height = width * ratioMultiplier; - } + return HTMLTrackElement; +}(EventTarget); - // Ensure the CSS class is valid by starting with an alpha character - if (/^[^a-zA-Z]/.test(this.id())) { - idClass = 'dimensions-' + this.id(); - } else { - idClass = this.id() + '-dimensions'; - } +HTMLTrackElement.prototype.allowedEvents_ = { + load: 'load' +}; - // Ensure the right class is still on the player for the style element - this.addClass(idClass); +HTMLTrackElement.NONE = NONE; +HTMLTrackElement.LOADING = LOADING; +HTMLTrackElement.LOADED = LOADED; +HTMLTrackElement.ERROR = ERROR; - stylesheet.setTextContent(this.styleEl_, '\n .' + idClass + ' {\n width: ' + width + 'px;\n height: ' + height + 'px;\n }\n\n .' + idClass + '.vjs-fluid {\n padding-top: ' + ratioMultiplier * 100 + '%;\n }\n '); - }; +/* + * This file contains all track properties that are used in + * player.js, tech.js, html5.js and possibly other techs in the future. + */ - /** - * Load/Create an instance of playback {@link Tech} including element - * and API methods. Then append the `Tech` element in `Player` as a child. - * - * @param {string} techName - * name of the playback technology - * - * @param {string} source - * video source - * - * @private - */ +var NORMAL = { + audio: { + ListClass: AudioTrackList, + TrackClass: AudioTrack, + capitalName: 'Audio' + }, + video: { + ListClass: VideoTrackList, + TrackClass: VideoTrack, + capitalName: 'Video' + }, + text: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'Text' + } +}; + +Object.keys(NORMAL).forEach(function (type) { + NORMAL[type].getterName = type + 'Tracks'; + NORMAL[type].privateName = type + 'Tracks_'; +}); +var REMOTE = { + remoteText: { + ListClass: TextTrackList, + TrackClass: TextTrack, + capitalName: 'RemoteText', + getterName: 'remoteTextTracks', + privateName: 'remoteTextTracks_' + }, + remoteTextEl: { + ListClass: HtmlTrackElementList, + TrackClass: HTMLTrackElement, + capitalName: 'RemoteTextTrackEls', + getterName: 'remoteTextTrackEls', + privateName: 'remoteTextTrackEls_' + } +}; - Player.prototype.loadTech_ = function loadTech_(techName, source) { - var _this2 = this; +var ALL = mergeOptions(NORMAL, REMOTE); - // Pause and remove current playback technology - if (this.tech_) { - this.unloadTech_(); - } +REMOTE.names = Object.keys(REMOTE); +NORMAL.names = Object.keys(NORMAL); +ALL.names = [].concat(REMOTE.names).concat(NORMAL.names); - var titleTechName = (0, _toTitleCase2['default'])(techName); - var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1); +/** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - // get rid of the HTML5 video tag as soon as we are using another tech - if (titleTechName !== 'Html5' && this.tag) { - _tech2['default'].getTech('Html5').disposeMediaElement(this.tag); - this.tag.player = null; - this.tag = null; - } +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +var _objCreate = Object.create || (function() { + function F() {} + return function(o) { + if (arguments.length !== 1) { + throw new Error('Object.create shim only accepts one parameter.'); + } + F.prototype = o; + return new F(); + }; +})(); + +// Creates a new ParserError object from an errorData object. The errorData +// object should have default code and message properties. The default message +// property can be overriden by passing in a message parameter. +// See ParsingError.Errors below for acceptable errors. +function ParsingError(errorData, message) { + this.name = "ParsingError"; + this.code = errorData.code; + this.message = message || errorData.message; +} +ParsingError.prototype = _objCreate(Error.prototype); +ParsingError.prototype.constructor = ParsingError; + +// ParsingError metadata for acceptable ParsingErrors. +ParsingError.Errors = { + BadSignature: { + code: 0, + message: "Malformed WebVTT signature." + }, + BadTimeStamp: { + code: 1, + message: "Malformed time stamp." + } +}; - this.techName_ = titleTechName; +// Try to parse input as a time stamp. +function parseTimeStamp(input) { - // Turn off API access because we're loading a new tech that might load asynchronously - this.isReady_ = false; + function computeSeconds(h, m, s, f) { + return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000; + } - // Grab tech-specific options from player options and add source and parent element to use. - var techOptions = { - source: source, - 'nativeControlsForTouch': this.options_.nativeControlsForTouch, - 'playerId': this.id(), - 'techId': this.id() + '_' + titleTechName + '_api', - 'autoplay': this.options_.autoplay, - 'playsinline': this.options_.playsinline, - 'preload': this.options_.preload, - 'loop': this.options_.loop, - 'muted': this.options_.muted, - 'poster': this.poster(), - 'language': this.language(), - 'playerElIngest': this.playerElIngest_ || false, - 'vtt.js': this.options_['vtt.js'] - }; + var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/); + if (!m) { + return null; + } - _trackTypes.ALL.names.forEach(function (name) { - var props = _trackTypes.ALL[name]; + if (m[3]) { + // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] + return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]); + } else if (m[1] > 59) { + // Timestamp takes the form of [hours]:[minutes].[milliseconds] + // First position is hours as it's over 59. + return computeSeconds(m[1], m[2], 0, m[4]); + } else { + // Timestamp takes the form of [minutes]:[seconds].[milliseconds] + return computeSeconds(0, m[1], m[2], m[4]); + } +} - techOptions[props.getterName] = _this2[props.privateName]; - }); +// A settings object holds key/value pairs and will ignore anything but the first +// assignment to a specific key. +function Settings() { + this.values = _objCreate(null); +} - (0, _obj.assign)(techOptions, this.options_[titleTechName]); - (0, _obj.assign)(techOptions, this.options_[camelTechName]); - (0, _obj.assign)(techOptions, this.options_[techName.toLowerCase()]); +Settings.prototype = { + // Only accept the first assignment to any key. + set: function(k, v) { + if (!this.get(k) && v !== "") { + this.values[k] = v; + } + }, + // Return the value for a key, or a default value. + // If 'defaultKey' is passed then 'dflt' is assumed to be an object with + // a number of possible default values as properties where 'defaultKey' is + // the key of the property that will be chosen; otherwise it's assumed to be + // a single value. + get: function(k, dflt, defaultKey) { + if (defaultKey) { + return this.has(k) ? this.values[k] : dflt[defaultKey]; + } + return this.has(k) ? this.values[k] : dflt; + }, + // Check whether we have a value for a key. + has: function(k) { + return k in this.values; + }, + // Accept a setting if its one of the given alternatives. + alt: function(k, v, a) { + for (var n = 0; n < a.length; ++n) { + if (v === a[n]) { + this.set(k, v); + break; + } + } + }, + // Accept a setting if its a valid (signed) integer. + integer: function(k, v) { + if (/^-?\d+$/.test(v)) { // integer + this.set(k, parseInt(v, 10)); + } + }, + // Accept a setting if its a valid percentage. + percent: function(k, v) { + var m; + if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) { + v = parseFloat(v); + if (v >= 0 && v <= 100) { + this.set(k, v); + return true; + } + } + return false; + } +}; - if (this.tag) { - techOptions.tag = this.tag; +// Helper function to parse input into groups separated by 'groupDelim', and +// interprete each group as a key/value pair separated by 'keyValueDelim'. +function parseOptions(input, callback, keyValueDelim, groupDelim) { + var groups = groupDelim ? input.split(groupDelim) : [input]; + for (var i in groups) { + if (typeof groups[i] !== "string") { + continue; + } + var kv = groups[i].split(keyValueDelim); + if (kv.length !== 2) { + continue; } + var k = kv[0]; + var v = kv[1]; + callback(k, v); + } +} - if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) { - techOptions.startTime = this.cache_.currentTime; +function parseCue(input, cue, regionList) { + // Remember the original input if we need to throw an error. + var oInput = input; + // 4.1 WebVTT timestamp + function consumeTimeStamp() { + var ts = parseTimeStamp(input); + if (ts === null) { + throw new ParsingError(ParsingError.Errors.BadTimeStamp, + "Malformed timestamp: " + oInput); } + // Remove time stamp from input. + input = input.replace(/^[^\sa-zA-Z-]+/, ""); + return ts; + } - // Initialize tech instance - var TechClass = _tech2['default'].getTech(techName); + // 4.4.2 WebVTT cue settings + function consumeCueSettings(input, cue) { + var settings = new Settings(); + + parseOptions(input, function (k, v) { + switch (k) { + case "region": + // Find the last region we parsed with the same region id. + for (var i = regionList.length - 1; i >= 0; i--) { + if (regionList[i].id === v) { + settings.set(k, regionList[i].region); + break; + } + } + break; + case "vertical": + settings.alt(k, v, ["rl", "lr"]); + break; + case "line": + var vals = v.split(","), + vals0 = vals[0]; + settings.integer(k, vals0); + settings.percent(k, vals0) ? settings.set("snapToLines", false) : null; + settings.alt(k, vals0, ["auto"]); + if (vals.length === 2) { + settings.alt("lineAlign", vals[1], ["start", "middle", "end"]); + } + break; + case "position": + vals = v.split(","); + settings.percent(k, vals[0]); + if (vals.length === 2) { + settings.alt("positionAlign", vals[1], ["start", "middle", "end"]); + } + break; + case "size": + settings.percent(k, v); + break; + case "align": + settings.alt(k, v, ["start", "middle", "end", "left", "right"]); + break; + } + }, /:/, /\s/); + + // Apply default values for any missing fields. + cue.region = settings.get("region", null); + cue.vertical = settings.get("vertical", ""); + cue.line = settings.get("line", "auto"); + cue.lineAlign = settings.get("lineAlign", "start"); + cue.snapToLines = settings.get("snapToLines", true); + cue.size = settings.get("size", 100); + cue.align = settings.get("align", "middle"); + cue.position = settings.get("position", { + start: 0, + left: 0, + middle: 50, + end: 100, + right: 100 + }, cue.align); + cue.positionAlign = settings.get("positionAlign", { + start: "start", + left: "start", + middle: "middle", + end: "end", + right: "end" + }, cue.align); + } - if (!TechClass) { - throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\''); - } + function skipWhitespace() { + input = input.replace(/^\s+/, ""); + } - this.tech_ = new TechClass(techOptions); + // 4.1 WebVTT cue timings. + skipWhitespace(); + cue.startTime = consumeTimeStamp(); // (1) collect cue start time + skipWhitespace(); + if (input.substr(0, 3) !== "-->") { // (3) next characters must match "-->" + throw new ParsingError(ParsingError.Errors.BadTimeStamp, + "Malformed time stamp (time stamps must be separated by '-->'): " + + oInput); + } + input = input.substr(3); + skipWhitespace(); + cue.endTime = consumeTimeStamp(); // (5) collect cue end time - // player.triggerReady is always async, so don't need this to be async - this.tech_.ready(Fn.bind(this, this.handleTechReady_), true); + // 4.1 WebVTT cue settings list. + skipWhitespace(); + consumeCueSettings(input, cue); +} - _textTrackListConverter2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_); +var ESCAPE = { + "&": "&", + "<": "<", + ">": ">", + "‎": "\u200e", + "‏": "\u200f", + " ": "\u00a0" +}; - // Listen to all HTML5-defined events and trigger them on the player - TECH_EVENTS_RETRIGGER.forEach(function (event) { - _this2.on(_this2.tech_, event, _this2['handleTech' + (0, _toTitleCase2['default'])(event) + '_']); - }); - this.on(this.tech_, 'loadstart', this.handleTechLoadStart_); - this.on(this.tech_, 'waiting', this.handleTechWaiting_); - this.on(this.tech_, 'canplay', this.handleTechCanPlay_); - this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_); - this.on(this.tech_, 'playing', this.handleTechPlaying_); - this.on(this.tech_, 'ended', this.handleTechEnded_); - this.on(this.tech_, 'seeking', this.handleTechSeeking_); - this.on(this.tech_, 'seeked', this.handleTechSeeked_); - this.on(this.tech_, 'play', this.handleTechPlay_); - this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_); - this.on(this.tech_, 'pause', this.handleTechPause_); - this.on(this.tech_, 'durationchange', this.handleTechDurationChange_); - this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_); - this.on(this.tech_, 'error', this.handleTechError_); - this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_); - this.on(this.tech_, 'posterchange', this.handleTechPosterChange_); - this.on(this.tech_, 'textdata', this.handleTechTextData_); +var TAG_NAME = { + c: "span", + i: "i", + b: "b", + u: "u", + ruby: "ruby", + rt: "rt", + v: "span", + lang: "span" +}; - this.usingNativeControls(this.techGet_('controls')); +var TAG_ANNOTATION = { + v: "title", + lang: "lang" +}; - if (this.controls() && !this.usingNativeControls()) { - this.addTechControlsListeners_(); - } +var NEEDS_PARENT = { + rt: "ruby" +}; - // Add the tech element in the DOM if it was not already there - // Make sure to not insert the original video element if using Html5 - if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) { - Dom.prependTo(this.tech_.el(), this.el()); +// Parse content into a document fragment. +function parseContent(window, input) { + function nextToken() { + // Check for end-of-string. + if (!input) { + return null; } - // Get rid of the original video tag reference after the first tech is loaded - if (this.tag) { - this.tag.player = null; - this.tag = null; + // Consume 'n' characters from the input. + function consume(result) { + input = input.substr(result.length); + return result; } - }; - /** - * Unload and dispose of the current playback {@link Tech}. - * - * @private - */ + var m = input.match(/^([^<]*)(<[^>]+>?)?/); + // If there is some text before the next tag, return it, otherwise return + // the tag. + return consume(m[1] ? m[1] : m[2]); + } + // Unescape a string 's'. + function unescape1(e) { + return ESCAPE[e]; + } + function unescape(s) { + while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) { + s = s.replace(m[0], unescape1); + } + return s; + } - Player.prototype.unloadTech_ = function unloadTech_() { - var _this3 = this; + function shouldAdd(current, element) { + return !NEEDS_PARENT[element.localName] || + NEEDS_PARENT[element.localName] === current.localName; + } - // Save the current text tracks so that we can reuse the same text tracks with the next tech - _trackTypes.ALL.names.forEach(function (name) { - var props = _trackTypes.ALL[name]; + // Create an element for this tag. + function createElement(type, annotation) { + var tagName = TAG_NAME[type]; + if (!tagName) { + return null; + } + var element = window.document.createElement(tagName); + element.localName = tagName; + var name = TAG_ANNOTATION[type]; + if (name && annotation) { + element[name] = annotation.trim(); + } + return element; + } - _this3[props.privateName] = _this3[props.getterName](); - }); - this.textTracksJson_ = _textTrackListConverter2['default'].textTracksToJson(this.tech_); + var rootDiv = window.document.createElement("div"), + current = rootDiv, + t, + tagStack = []; + + while ((t = nextToken()) !== null) { + if (t[0] === '<') { + if (t[1] === "/") { + // If the closing tag matches, move back up to the parent node. + if (tagStack.length && + tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) { + tagStack.pop(); + current = current.parentNode; + } + // Otherwise just ignore the end tag. + continue; + } + var ts = parseTimeStamp(t.substr(1, t.length - 2)); + var node; + if (ts) { + // Timestamps are lead nodes as well. + node = window.document.createProcessingInstruction("timestamp", ts); + current.appendChild(node); + continue; + } + var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/); + // If we can't parse the tag, skip to the next tag. + if (!m) { + continue; + } + // Try to construct an element, and ignore the tag if we couldn't. + node = createElement(m[1], m[3]); + if (!node) { + continue; + } + // Determine if the tag should be added based on the context of where it + // is placed in the cuetext. + if (!shouldAdd(current, node)) { + continue; + } + // Set the class list (as a list of classes, separated by space). + if (m[2]) { + node.className = m[2].substr(1).replace('.', ' '); + } + // Append the node to the current node, and enter the scope of the new + // node. + tagStack.push(m[1]); + current.appendChild(node); + current = node; + continue; + } - this.isReady_ = false; + // Text nodes are leaf nodes. + current.appendChild(window.document.createTextNode(unescape(t))); + } - this.tech_.dispose(); + return rootDiv; +} - this.tech_ = false; - }; +// This is a list of all the Unicode characters that have a strong +// right-to-left category. What this means is that these characters are +// written right-to-left for sure. It was generated by pulling all the strong +// right-to-left characters out of the Unicode data table. That table can +// found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt +var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], + [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], + [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], + [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], + [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], + [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], + [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], + [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], + [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], + [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], + [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], + [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], + [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], + [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], + [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], + [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], + [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], + [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], + [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], + [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], + [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], + [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], + [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], + [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], + [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]]; + +function isStrongRTLChar(charCode) { + for (var i = 0; i < strongRTLRanges.length; i++) { + var currentRange = strongRTLRanges[i]; + if (charCode >= currentRange[0] && charCode <= currentRange[1]) { + return true; + } + } - /** - * Return a reference to the current {@link Tech}. - * It will print a warning by default about the danger of using the tech directly - * but any argument that is passed in will silence the warning. - * - * @param {*} [safety] - * Anything passed in to silence the warning - * - * @return {Tech} - * The Tech - */ + return false; +} +function determineBidi(cueDiv) { + var nodeStack = [], + text = "", + charCode; - Player.prototype.tech = function tech(safety) { - if (safety === undefined) { - _log2['default'].warn((0, _tsml2['default'])(_templateObject)); + if (!cueDiv || !cueDiv.childNodes) { + return "ltr"; + } + + function pushNodes(nodeStack, node) { + for (var i = node.childNodes.length - 1; i >= 0; i--) { + nodeStack.push(node.childNodes[i]); } + } - return this.tech_; - }; + function nextTextNode(nodeStack) { + if (!nodeStack || !nodeStack.length) { + return null; + } - /** - * Set up click and touch listeners for the playback element - * - * - On desktops: a click on the video itself will toggle playback - * - On mobile devices: a click on the video toggles controls - * which is done by toggling the user state between active and - * inactive - * - A tap can signal that a user has become active or has become inactive - * e.g. a quick tap on an iPhone movie should reveal the controls. Another - * quick tap should hide them again (signaling the user is in an inactive - * viewing state) - * - In addition to this, we still want the user to be considered inactive after - * a few seconds of inactivity. - * - * > Note: the only part of iOS interaction we can't mimic with this setup - * is a touch and hold on the video element counting as activity in order to - * keep the controls showing, but that shouldn't be an issue. A touch and hold - * on any controls will still keep the user active - * - * @private - */ + var node = nodeStack.pop(), + text = node.textContent || node.innerText; + if (text) { + // TODO: This should match all unicode type B characters (paragraph + // separator characters). See issue #115. + var m = text.match(/^.*(\n|\r)/); + if (m) { + nodeStack.length = 0; + return m[0]; + } + return text; + } + if (node.tagName === "ruby") { + return nextTextNode(nodeStack); + } + if (node.childNodes) { + pushNodes(nodeStack, node); + return nextTextNode(nodeStack); + } + } + pushNodes(nodeStack, cueDiv); + while ((text = nextTextNode(nodeStack))) { + for (var i = 0; i < text.length; i++) { + charCode = text.charCodeAt(i); + if (isStrongRTLChar(charCode)) { + return "rtl"; + } + } + } + return "ltr"; +} - Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() { - // Make sure to remove all the previous listeners in case we are called multiple times. - this.removeTechControlsListeners_(); +function computeLinePos(cue) { + if (typeof cue.line === "number" && + (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) { + return cue.line; + } + if (!cue.track || !cue.track.textTrackList || + !cue.track.textTrackList.mediaElement) { + return -1; + } + var track = cue.track, + trackList = track.textTrackList, + count = 0; + for (var i = 0; i < trackList.length && trackList[i] !== track; i++) { + if (trackList[i].mode === "showing") { + count++; + } + } + return ++count * -1; +} - // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do - // trigger mousedown/up. - // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object - // Any touch events are set to block the mousedown event from happening - this.on(this.tech_, 'mousedown', this.handleTechClick_); +function StyleBox() { +} - // If the controls were hidden we don't want that to change without a tap event - // so we'll check if the controls were already showing before reporting user - // activity - this.on(this.tech_, 'touchstart', this.handleTechTouchStart_); - this.on(this.tech_, 'touchmove', this.handleTechTouchMove_); - this.on(this.tech_, 'touchend', this.handleTechTouchEnd_); +// Apply styles to a div. If there is no div passed then it defaults to the +// div on 'this'. +StyleBox.prototype.applyStyles = function(styles, div) { + div = div || this.div; + for (var prop in styles) { + if (styles.hasOwnProperty(prop)) { + div.style[prop] = styles[prop]; + } + } +}; - // The tap listener needs to come after the touchend listener because the tap - // listener cancels out any reportedUserActivity when setting userActive(false) - this.on(this.tech_, 'tap', this.handleTechTap_); - }; +StyleBox.prototype.formatStyle = function(val, unit) { + return val === 0 ? 0 : val + unit; +}; - /** - * Remove the listeners used for click and tap controls. This is needed for - * toggling to controls disabled, where a tap/touch should do nothing. - * - * @private - */ +// Constructs the computed display state of the cue (a div). Places the div +// into the overlay which should be a block level element (usually a div). +function CueStyleBox(window, cue, styleOptions) { + var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); + var color = "rgba(255, 255, 255, 1)"; + var backgroundColor = "rgba(0, 0, 0, 0.8)"; + if (isIE8) { + color = "rgb(255, 255, 255)"; + backgroundColor = "rgb(0, 0, 0)"; + } - Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() { - // We don't want to just use `this.off()` because there might be other needed - // listeners added by techs that extend this. - this.off(this.tech_, 'tap', this.handleTechTap_); - this.off(this.tech_, 'touchstart', this.handleTechTouchStart_); - this.off(this.tech_, 'touchmove', this.handleTechTouchMove_); - this.off(this.tech_, 'touchend', this.handleTechTouchEnd_); - this.off(this.tech_, 'mousedown', this.handleTechClick_); + StyleBox.call(this); + this.cue = cue; + + // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will + // have inline positioning and will function as the cue background box. + this.cueDiv = parseContent(window, cue.text); + var styles = { + color: color, + backgroundColor: backgroundColor, + position: "relative", + left: 0, + right: 0, + top: 0, + bottom: 0, + display: "inline" + }; + + if (!isIE8) { + styles.writingMode = cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl"; + styles.unicodeBidi = "plaintext"; + } + this.applyStyles(styles, this.cueDiv); + + // Create an absolutely positioned div that will be used to position the cue + // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS + // mirrors of them except "middle" which is "center" in CSS. + this.div = window.document.createElement("div"); + styles = { + textAlign: cue.align === "middle" ? "center" : cue.align, + font: styleOptions.font, + whiteSpace: "pre-line", + position: "absolute" + }; + + if (!isIE8) { + styles.direction = determineBidi(this.cueDiv); + styles.writingMode = cue.vertical === "" ? "horizontal-tb" + : cue.vertical === "lr" ? "vertical-lr" + : "vertical-rl". + stylesunicodeBidi = "plaintext"; + } + + this.applyStyles(styles); + + this.div.appendChild(this.cueDiv); + + // Calculate the distance from the reference edge of the viewport to the text + // position of the cue box. The reference edge will be resolved later when + // the box orientation styles are applied. + var textPos = 0; + switch (cue.positionAlign) { + case "start": + textPos = cue.position; + break; + case "middle": + textPos = cue.position - (cue.size / 2); + break; + case "end": + textPos = cue.position - cue.size; + break; + } + + // Horizontal box orientation; textPos is the distance from the left edge of the + // area to the left edge of the box and cue.size is the distance extending to + // the right from there. + if (cue.vertical === "") { + this.applyStyles({ + left: this.formatStyle(textPos, "%"), + width: this.formatStyle(cue.size, "%") + }); + // Vertical box orientation; textPos is the distance from the top edge of the + // area to the top edge of the box and cue.size is the height extending + // downwards from there. + } else { + this.applyStyles({ + top: this.formatStyle(textPos, "%"), + height: this.formatStyle(cue.size, "%") + }); + } + + this.move = function(box) { + this.applyStyles({ + top: this.formatStyle(box.top, "px"), + bottom: this.formatStyle(box.bottom, "px"), + left: this.formatStyle(box.left, "px"), + right: this.formatStyle(box.right, "px"), + height: this.formatStyle(box.height, "px"), + width: this.formatStyle(box.width, "px") + }); }; +} +CueStyleBox.prototype = _objCreate(StyleBox.prototype); +CueStyleBox.prototype.constructor = CueStyleBox; + +// Represents the co-ordinates of an Element in a way that we can easily +// compute things with such as if it overlaps or intersects with another Element. +// Can initialize it with either a StyleBox or another BoxPosition. +function BoxPosition(obj) { + var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); + + // Either a BoxPosition was passed in and we need to copy it, or a StyleBox + // was passed in and we need to copy the results of 'getBoundingClientRect' + // as the object returned is readonly. All co-ordinate values are in reference + // to the viewport origin (top left). + var lh, height, width, top; + if (obj.div) { + height = obj.div.offsetHeight; + width = obj.div.offsetWidth; + top = obj.div.offsetTop; + + var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && + rects.getClientRects && rects.getClientRects(); + obj = obj.div.getBoundingClientRect(); + // In certain cases the outter div will be slightly larger then the sum of + // the inner div's lines. This could be due to bold text, etc, on some platforms. + // In this case we should get the average line height and use that. This will + // result in the desired behaviour. + lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length) + : 0; - /** - * Player waits for the tech to be ready - * - * @private - */ + } + this.left = obj.left; + this.right = obj.right; + this.top = obj.top || top; + this.height = obj.height || height; + this.bottom = obj.bottom || (top + (obj.height || height)); + this.width = obj.width || width; + this.lineHeight = lh !== undefined ? lh : obj.lineHeight; + + if (isIE8 && !this.lineHeight) { + this.lineHeight = 13; + } +} +// Move the box along a particular axis. Optionally pass in an amount to move +// the box. If no amount is passed then the default is the line height of the +// box. +BoxPosition.prototype.move = function(axis, toMove) { + toMove = toMove !== undefined ? toMove : this.lineHeight; + switch (axis) { + case "+x": + this.left += toMove; + this.right += toMove; + break; + case "-x": + this.left -= toMove; + this.right -= toMove; + break; + case "+y": + this.top += toMove; + this.bottom += toMove; + break; + case "-y": + this.top -= toMove; + this.bottom -= toMove; + break; + } +}; - Player.prototype.handleTechReady_ = function handleTechReady_() { - this.triggerReady(); +// Check if this box overlaps another box, b2. +BoxPosition.prototype.overlaps = function(b2) { + return this.left < b2.right && + this.right > b2.left && + this.top < b2.bottom && + this.bottom > b2.top; +}; - // Keep the same volume as before - if (this.cache_.volume) { - this.techCall_('setVolume', this.cache_.volume); +// Check if this box overlaps any other boxes in boxes. +BoxPosition.prototype.overlapsAny = function(boxes) { + for (var i = 0; i < boxes.length; i++) { + if (this.overlaps(boxes[i])) { + return true; } + } + return false; +}; - // Look if the tech found a higher resolution poster while loading - this.handleTechPosterChange_(); +// Check if this box is within another box. +BoxPosition.prototype.within = function(container) { + return this.top >= container.top && + this.bottom <= container.bottom && + this.left >= container.left && + this.right <= container.right; +}; - // Update the duration if available - this.handleTechDurationChange_(); +// Check if this box is entirely within the container or it is overlapping +// on the edge opposite of the axis direction passed. For example, if "+x" is +// passed and the box is overlapping on the left edge of the container, then +// return true. +BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) { + switch (axis) { + case "+x": + return this.left < container.left; + case "-x": + return this.right > container.right; + case "+y": + return this.top < container.top; + case "-y": + return this.bottom > container.bottom; + } +}; - // Chrome and Safari both have issues with autoplay. - // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. - // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) - // This fixes both issues. Need to wait for API, so it updates displays correctly - if ((this.src() || this.currentSrc()) && this.tag && this.options_.autoplay && this.paused()) { - try { - // Chrome Fix. Fixed in Chrome v16. - delete this.tag.poster; - } catch (e) { - (0, _log2['default'])('deleting tag.poster throws in some browsers', e); - } - this.play(); - } +// Find the percentage of the area that this box is overlapping with another +// box. +BoxPosition.prototype.intersectPercentage = function(b2) { + var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)), + y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)), + intersectArea = x * y; + return intersectArea / (this.height * this.width); +}; + +// Convert the positions from this box to CSS compatible positions using +// the reference container's positions. This has to be done because this +// box's positions are in reference to the viewport origin, whereas, CSS +// values are in referecne to their respective edges. +BoxPosition.prototype.toCSSCompatValues = function(reference) { + return { + top: this.top - reference.top, + bottom: reference.bottom - this.bottom, + left: this.left - reference.left, + right: reference.right - this.right, + height: this.height, + width: this.width }; +}; - /** - * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This - * function will also trigger {@link Player#firstplay} if it is the first loadstart - * for a video. - * - * @fires Player#loadstart - * @fires Player#firstplay - * @listens Tech#loadstart - * @private - */ +// Get an object that represents the box's position without anything extra. +// Can pass a StyleBox, HTMLElement, or another BoxPositon. +BoxPosition.getSimpleBoxPosition = function(obj) { + var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0; + var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0; + var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0; + + obj = obj.div ? obj.div.getBoundingClientRect() : + obj.tagName ? obj.getBoundingClientRect() : obj; + var ret = { + left: obj.left, + right: obj.right, + top: obj.top || top, + height: obj.height || height, + bottom: obj.bottom || (top + (obj.height || height)), + width: obj.width || width + }; + return ret; +}; +// Move a StyleBox to its specified, or next best, position. The containerBox +// is the box that contains the StyleBox, such as a div. boxPositions are +// a list of other boxes that the styleBox can't overlap with. +function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) { + + // Find the best position for a cue box, b, on the video. The axis parameter + // is a list of axis, the order of which, it will move the box along. For example: + // Passing ["+x", "-x"] will move the box first along the x axis in the positive + // direction. If it doesn't find a good position for it there it will then move + // it along the x axis in the negative direction. + function findBestPosition(b, axis) { + var bestPosition, + specifiedPosition = new BoxPosition(b), + percentage = 1; // Highest possible so the first thing we get is better. + + for (var i = 0; i < axis.length; i++) { + while (b.overlapsOppositeAxis(containerBox, axis[i]) || + (b.within(containerBox) && b.overlapsAny(boxPositions))) { + b.move(axis[i]); + } + // We found a spot where we aren't overlapping anything. This is our + // best position. + if (b.within(containerBox)) { + return b; + } + var p = b.intersectPercentage(containerBox); + // If we're outside the container box less then we were on our last try + // then remember this position as the best position. + if (percentage > p) { + bestPosition = new BoxPosition(b); + percentage = p; + } + // Reset the box position to the specified position. + b = new BoxPosition(specifiedPosition); + } + return bestPosition || specifiedPosition; + } - Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() { - // TODO: Update to use `emptied` event instead. See #1277. + var boxPosition = new BoxPosition(styleBox), + cue = styleBox.cue, + linePos = computeLinePos(cue), + axis = []; + + // If we have a line number to align the cue to. + if (cue.snapToLines) { + var size; + switch (cue.vertical) { + case "": + axis = [ "+y", "-y" ]; + size = "height"; + break; + case "rl": + axis = [ "+x", "-x" ]; + size = "width"; + break; + case "lr": + axis = [ "-x", "+x" ]; + size = "width"; + break; + } - this.removeClass('vjs-ended'); - this.removeClass('vjs-seeking'); + var step = boxPosition.lineHeight, + position = step * Math.round(linePos), + maxPosition = containerBox[size] + step, + initialAxis = axis[0]; - // reset the error state - this.error(null); + // If the specified intial position is greater then the max position then + // clamp the box to the amount of steps it would take for the box to + // reach the max position. + if (Math.abs(position) > maxPosition) { + position = position < 0 ? -1 : 1; + position *= Math.ceil(maxPosition / step) * step; + } - // If it's already playing we want to trigger a firstplay event now. - // The firstplay event relies on both the play and loadstart events - // which can happen in any order for a new source - if (!this.paused()) { - /** - * Fired when the user agent begins looking for media data - * - * @event Player#loadstart - * @type {EventTarget~Event} - */ - this.trigger('loadstart'); - this.trigger('firstplay'); - } else { - // reset the hasStarted state - this.hasStarted(false); - this.trigger('loadstart'); + // If computed line position returns negative then line numbers are + // relative to the bottom of the video instead of the top. Therefore, we + // need to increase our initial position by the length or width of the + // video, depending on the writing direction, and reverse our axis directions. + if (linePos < 0) { + position += cue.vertical === "" ? containerBox.height : containerBox.width; + axis = axis.reverse(); } - }; - /** - * Add/remove the vjs-has-started class - * - * @fires Player#firstplay - * - * @param {boolean} hasStarted - * - true: adds the class - * - false: remove the class - * - * @return {boolean} - * the boolean value of hasStarted - */ + // Move the box to the specified position. This may not be its best + // position. + boxPosition.move(initialAxis, position); + } else { + // If we have a percentage line value for the cue. + var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100; - Player.prototype.hasStarted = function hasStarted(_hasStarted) { - if (_hasStarted !== undefined) { - // only update if this is a new value - if (this.hasStarted_ !== _hasStarted) { - this.hasStarted_ = _hasStarted; - if (_hasStarted) { - this.addClass('vjs-has-started'); - // trigger the firstplay event if this newly has played - this.trigger('firstplay'); - } else { - this.removeClass('vjs-has-started'); - } - } - return; + switch (cue.lineAlign) { + case "middle": + linePos -= (calculatedPercentage / 2); + break; + case "end": + linePos -= calculatedPercentage; + break; } - return !!this.hasStarted_; - }; - /** - * Fired whenever the media begins or resumes playback - * - * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play} - * @fires Player#play - * @listens Tech#play - * @private - */ + // Apply initial line position to the cue box. + switch (cue.vertical) { + case "": + styleBox.applyStyles({ + top: styleBox.formatStyle(linePos, "%") + }); + break; + case "rl": + styleBox.applyStyles({ + left: styleBox.formatStyle(linePos, "%") + }); + break; + case "lr": + styleBox.applyStyles({ + right: styleBox.formatStyle(linePos, "%") + }); + break; + } + axis = [ "+y", "-x", "+x", "-y" ]; - Player.prototype.handleTechPlay_ = function handleTechPlay_() { - this.removeClass('vjs-ended'); - this.removeClass('vjs-paused'); - this.addClass('vjs-playing'); + // Get the box position again after we've applied the specified positioning + // to it. + boxPosition = new BoxPosition(styleBox); + } - // hide the poster when the user hits play - this.hasStarted(true); - /** - * Triggered whenever an {@link Tech#play} event happens. Indicates that - * playback has started or resumed. - * - * @event Player#play - * @type {EventTarget~Event} - */ - this.trigger('play'); + var bestPosition = findBestPosition(boxPosition, axis); + styleBox.move(bestPosition.toCSSCompatValues(containerBox)); +} + +function WebVTT$1() { + // Nothing +} + +// Helper to allow strings to be decoded instead of the default binary utf8 data. +WebVTT$1.StringDecoder = function() { + return { + decode: function(data) { + if (!data) { + return ""; + } + if (typeof data !== "string") { + throw new Error("Error - expected string data."); + } + return decodeURIComponent(encodeURIComponent(data)); + } }; +}; - /** - * Retrigger the `waiting` event that was triggered by the {@link Tech}. - * - * @fires Player#waiting - * @listens Tech#waiting - * @private - */ +WebVTT$1.convertCueToDOMTree = function(window, cuetext) { + if (!window || !cuetext) { + return null; + } + return parseContent(window, cuetext); +}; +var FONT_SIZE_PERCENT = 0.05; +var FONT_STYLE = "sans-serif"; +var CUE_BACKGROUND_PADDING = "1.5%"; - Player.prototype.handleTechWaiting_ = function handleTechWaiting_() { - var _this4 = this; +// Runs the processing model over the cues and regions passed to it. +// @param overlay A block level element (usually a div) that the computed cues +// and regions will be placed into. +WebVTT$1.processCues = function(window, cues, overlay) { + if (!window || !cues || !overlay) { + return null; + } - this.addClass('vjs-waiting'); - /** - * A readyState change on the DOM element has caused playback to stop. - * - * @event Player#waiting - * @type {EventTarget~Event} - */ - this.trigger('waiting'); - this.one('timeupdate', function () { - return _this4.removeClass('vjs-waiting'); - }); + // Remove all previous children. + while (overlay.firstChild) { + overlay.removeChild(overlay.firstChild); + } + + var paddedOverlay = window.document.createElement("div"); + paddedOverlay.style.position = "absolute"; + paddedOverlay.style.left = "0"; + paddedOverlay.style.right = "0"; + paddedOverlay.style.top = "0"; + paddedOverlay.style.bottom = "0"; + paddedOverlay.style.margin = CUE_BACKGROUND_PADDING; + overlay.appendChild(paddedOverlay); + + // Determine if we need to compute the display states of the cues. This could + // be the case if a cue's state has been changed since the last computation or + // if it has not been computed yet. + function shouldCompute(cues) { + for (var i = 0; i < cues.length; i++) { + if (cues[i].hasBeenReset || !cues[i].displayState) { + return true; + } + } + return false; + } + + // We don't need to recompute the cues' display states. Just reuse them. + if (!shouldCompute(cues)) { + for (var i = 0; i < cues.length; i++) { + paddedOverlay.appendChild(cues[i].displayState); + } + return; + } + + var boxPositions = [], + containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay), + fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100; + var styleOptions = { + font: fontSize + "px " + FONT_STYLE }; - /** - * Retrigger the `canplay` event that was triggered by the {@link Tech}. - * > Note: This is not consistent between browsers. See #1351 - * - * @fires Player#canplay - * @listens Tech#canplay - * @private - */ + (function() { + var styleBox, cue; + for (var i = 0; i < cues.length; i++) { + cue = cues[i]; - Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() { - this.removeClass('vjs-waiting'); - /** - * The media has a readyState of HAVE_FUTURE_DATA or greater. - * - * @event Player#canplay - * @type {EventTarget~Event} - */ - this.trigger('canplay'); - }; + // Compute the intial position and styles of the cue div. + styleBox = new CueStyleBox(window, cue, styleOptions); + paddedOverlay.appendChild(styleBox.div); - /** - * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}. - * - * @fires Player#canplaythrough - * @listens Tech#canplaythrough - * @private - */ + // Move the cue div to it's correct line position. + moveBoxToLinePosition(window, styleBox, containerBox, boxPositions); + // Remember the computed div so that we don't have to recompute it later + // if we don't have too. + cue.displayState = styleBox.div; - Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() { - this.removeClass('vjs-waiting'); - /** - * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the - * entire media file can be played without buffering. - * - * @event Player#canplaythrough - * @type {EventTarget~Event} - */ - this.trigger('canplaythrough'); - }; + boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox)); + } + })(); +}; - /** - * Retrigger the `playing` event that was triggered by the {@link Tech}. - * - * @fires Player#playing - * @listens Tech#playing - * @private - */ +WebVTT$1.Parser = function(window, vttjs, decoder) { + if (!decoder) { + decoder = vttjs; + vttjs = {}; + } + if (!vttjs) { + vttjs = {}; + } + this.window = window; + this.vttjs = vttjs; + this.state = "INITIAL"; + this.buffer = ""; + this.decoder = decoder || new TextDecoder("utf8"); + this.regionList = []; +}; - Player.prototype.handleTechPlaying_ = function handleTechPlaying_() { - this.removeClass('vjs-waiting'); - /** - * The media is no longer blocked from playback, and has started playing. - * - * @event Player#playing - * @type {EventTarget~Event} - */ - this.trigger('playing'); - }; +WebVTT$1.Parser.prototype = { + // If the error is a ParsingError then report it to the consumer if + // possible. If it's not a ParsingError then throw it like normal. + reportOrThrowError: function(e) { + if (e instanceof ParsingError) { + this.onparsingerror && this.onparsingerror(e); + } else { + throw e; + } + }, + parse: function (data) { + var self = this; - /** - * Retrigger the `seeking` event that was triggered by the {@link Tech}. - * - * @fires Player#seeking - * @listens Tech#seeking - * @private - */ + // If there is no data then we won't decode it, but will just try to parse + // whatever is in buffer already. This may occur in circumstances, for + // example when flush() is called. + if (data) { + // Try to decode the data that we received. + self.buffer += self.decoder.decode(data, {stream: true}); + } + function collectNextLine() { + var buffer = self.buffer; + var pos = 0; + while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { + ++pos; + } + var line = buffer.substr(0, pos); + // Advance the buffer early in case we fail below. + if (buffer[pos] === '\r') { + ++pos; + } + if (buffer[pos] === '\n') { + ++pos; + } + self.buffer = buffer.substr(pos); + return line; + } - Player.prototype.handleTechSeeking_ = function handleTechSeeking_() { - this.addClass('vjs-seeking'); - /** - * Fired whenever the player is jumping to a new time - * - * @event Player#seeking - * @type {EventTarget~Event} - */ - this.trigger('seeking'); - }; + // 3.4 WebVTT region and WebVTT region settings syntax + function parseRegion(input) { + var settings = new Settings(); - /** - * Retrigger the `seeked` event that was triggered by the {@link Tech}. - * - * @fires Player#seeked - * @listens Tech#seeked - * @private - */ + parseOptions(input, function (k, v) { + switch (k) { + case "id": + settings.set(k, v); + break; + case "width": + settings.percent(k, v); + break; + case "lines": + settings.integer(k, v); + break; + case "regionanchor": + case "viewportanchor": + var xy = v.split(','); + if (xy.length !== 2) { + break; + } + // We have to make sure both x and y parse, so use a temporary + // settings object here. + var anchor = new Settings(); + anchor.percent("x", xy[0]); + anchor.percent("y", xy[1]); + if (!anchor.has("x") || !anchor.has("y")) { + break; + } + settings.set(k + "X", anchor.get("x")); + settings.set(k + "Y", anchor.get("y")); + break; + case "scroll": + settings.alt(k, v, ["up"]); + break; + } + }, /=/, /\s/); + + // Create the region, using default values for any values that were not + // specified. + if (settings.has("id")) { + var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)(); + region.width = settings.get("width", 100); + region.lines = settings.get("lines", 3); + region.regionAnchorX = settings.get("regionanchorX", 0); + region.regionAnchorY = settings.get("regionanchorY", 100); + region.viewportAnchorX = settings.get("viewportanchorX", 0); + region.viewportAnchorY = settings.get("viewportanchorY", 100); + region.scroll = settings.get("scroll", ""); + // Register the region. + self.onregion && self.onregion(region); + // Remember the VTTRegion for later in case we parse any VTTCues that + // reference it. + self.regionList.push({ + id: settings.get("id"), + region: region + }); + } + } + // draft-pantos-http-live-streaming-20 + // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5 + // 3.5 WebVTT + function parseTimestampMap(input) { + var settings = new Settings(); - Player.prototype.handleTechSeeked_ = function handleTechSeeked_() { - this.removeClass('vjs-seeking'); - /** - * Fired when the player has finished jumping to a new time - * - * @event Player#seeked - * @type {EventTarget~Event} - */ - this.trigger('seeked'); - }; + parseOptions(input, function(k, v) { + switch(k) { + case "MPEGT": + settings.integer(k + 'S', v); + break; + case "LOCA": + settings.set(k + 'L', parseTimeStamp(v)); + break; + } + }, /[^\d]:/, /,/); - /** - * Retrigger the `firstplay` event that was triggered by the {@link Tech}. - * - * @fires Player#firstplay - * @listens Tech#firstplay - * @deprecated As of 6.0 firstplay event is deprecated. - * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated. - * @private - */ + self.ontimestampmap && self.ontimestampmap({ + "MPEGTS": settings.get("MPEGTS"), + "LOCAL": settings.get("LOCAL") + }); + } + // 3.2 WebVTT metadata header syntax + function parseHeader(input) { + if (input.match(/X-TIMESTAMP-MAP/)) { + // This line contains HLS X-TIMESTAMP-MAP metadata + parseOptions(input, function(k, v) { + switch(k) { + case "X-TIMESTAMP-MAP": + parseTimestampMap(v); + break; + } + }, /=/); + } else { + parseOptions(input, function (k, v) { + switch (k) { + case "Region": + // 3.3 WebVTT region metadata header syntax + parseRegion(v); + break; + } + }, /:/); + } - Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() { - // If the first starttime attribute is specified - // then we will start at the given offset in seconds - if (this.options_.starttime) { - _log2['default'].warn('Passing the `starttime` option to the player will be deprecated in 6.0'); - this.currentTime(this.options_.starttime); } - this.addClass('vjs-has-started'); - /** - * Fired the first time a video is played. Not part of the HLS spec, and this is - * probably not the best implementation yet, so use sparingly. If you don't have a - * reason to prevent playback, use `myPlayer.one('play');` instead. - * - * @event Player#firstplay - * @deprecated As of 6.0 firstplay event is deprecated. - * @type {EventTarget~Event} - */ - this.trigger('firstplay'); - }; + // 5.1 WebVTT file parsing. + try { + var line; + if (self.state === "INITIAL") { + // We can't start parsing until we have the first line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } - /** - * Retrigger the `pause` event that was triggered by the {@link Tech}. - * - * @fires Player#pause - * @listens Tech#pause - * @private - */ + line = collectNextLine(); + var m = line.match(/^WEBVTT([ \t].*)?$/); + if (!m || !m[0]) { + throw new ParsingError(ParsingError.Errors.BadSignature); + } - Player.prototype.handleTechPause_ = function handleTechPause_() { - this.removeClass('vjs-playing'); - this.addClass('vjs-paused'); - /** - * Fired whenever the media has been paused - * - * @event Player#pause - * @type {EventTarget~Event} - */ - this.trigger('pause'); - }; + self.state = "HEADER"; + } - /** - * Retrigger the `ended` event that was triggered by the {@link Tech}. - * - * @fires Player#ended - * @listens Tech#ended - * @private - */ + var alreadyCollectedLine = false; + while (self.buffer) { + // We can't parse a line until we have the full line. + if (!/\r\n|\n/.test(self.buffer)) { + return this; + } + + if (!alreadyCollectedLine) { + line = collectNextLine(); + } else { + alreadyCollectedLine = false; + } + + switch (self.state) { + case "HEADER": + // 13-18 - Allow a header (metadata) under the WEBVTT line. + if (/:/.test(line)) { + parseHeader(line); + } else if (!line) { + // An empty line terminates the header and starts the body (cues). + self.state = "ID"; + } + continue; + case "NOTE": + // Ignore NOTE blocks. + if (!line) { + self.state = "ID"; + } + continue; + case "ID": + // Check for the start of NOTE blocks. + if (/^NOTE($|[ \t])/.test(line)) { + self.state = "NOTE"; + break; + } + // 19-29 - Allow any number of line terminators, then initialize new cue values. + if (!line) { + continue; + } + self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, ""); + self.state = "CUE"; + // 30-39 - Check if self line contains an optional identifier or timing data. + if (line.indexOf("-->") === -1) { + self.cue.id = line; + continue; + } + // Process line as start of a cue. + /*falls through*/ + case "CUE": + // 40 - Collect cue timings and settings. + try { + parseCue(line, self.cue, self.regionList); + } catch (e) { + self.reportOrThrowError(e); + // In case of an error ignore rest of the cue. + self.cue = null; + self.state = "BADCUE"; + continue; + } + self.state = "CUETEXT"; + continue; + case "CUETEXT": + var hasSubstring = line.indexOf("-->") !== -1; + // 34 - If we have an empty line then report the cue. + // 35 - If we have the special substring '-->' then report the cue, + // but do not collect the line as we need to process the current + // one as a new cue. + if (!line || hasSubstring && (alreadyCollectedLine = true)) { + // We are done parsing self cue. + self.oncue && self.oncue(self.cue); + self.cue = null; + self.state = "ID"; + continue; + } + if (self.cue.text) { + self.cue.text += "\n"; + } + self.cue.text += line; + continue; + case "BADCUE": // BADCUE + // 54-62 - Collect and discard the remaining cue. + if (!line) { + self.state = "ID"; + } + continue; + } + } + } catch (e) { + self.reportOrThrowError(e); + + // If we are currently parsing a cue, report what we have. + if (self.state === "CUETEXT" && self.cue && self.oncue) { + self.oncue(self.cue); + } + self.cue = null; + // Enter BADWEBVTT state if header was not parsed correctly otherwise + // another exception occurred so enter BADCUE state. + self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; + } + return this; + }, + flush: function () { + var self = this; + try { + // Finish decoding the stream. + self.buffer += self.decoder.decode(); + // Synthesize the end of the current cue or region. + if (self.cue || self.state === "HEADER") { + self.buffer += "\n\n"; + self.parse(); + } + // If we've flushed, parsed, and we're still on the INITIAL state then + // that means we don't have enough of the stream to parse the first + // line. + if (self.state === "INITIAL") { + throw new ParsingError(ParsingError.Errors.BadSignature); + } + } catch(e) { + self.reportOrThrowError(e); + } + self.onflush && self.onflush(); + return this; + } +}; + +var vtt$1 = WebVTT$1; + +/** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var autoKeyword = "auto"; +var directionSetting = { + "": true, + "lr": true, + "rl": true +}; +var alignSetting = { + "start": true, + "middle": true, + "end": true, + "left": true, + "right": true +}; - Player.prototype.handleTechEnded_ = function handleTechEnded_() { - this.addClass('vjs-ended'); - if (this.options_.loop) { - this.currentTime(0); - this.play(); - } else if (!this.paused()) { - this.pause(); +function findDirectionSetting(value) { + if (typeof value !== "string") { + return false; + } + var dir = directionSetting[value.toLowerCase()]; + return dir ? value.toLowerCase() : false; +} + +function findAlignSetting(value) { + if (typeof value !== "string") { + return false; + } + var align = alignSetting[value.toLowerCase()]; + return align ? value.toLowerCase() : false; +} + +function extend$1(obj) { + var i = 1; + for (; i < arguments.length; i++) { + var cobj = arguments[i]; + for (var p in cobj) { + obj[p] = cobj[p]; } + } - /** - * Fired when the end of the media resource is reached (currentTime == duration) - * - * @event Player#ended - * @type {EventTarget~Event} - */ - this.trigger('ended'); - }; + return obj; +} + +function VTTCue(startTime, endTime, text) { + var cue = this; + var isIE8 = (/MSIE\s8\.0/).test(navigator.userAgent); + var baseObj = {}; + + if (isIE8) { + cue = document.createElement('custom'); + } else { + baseObj.enumerable = true; + } /** - * Fired when the duration of the media resource is first known or changed - * - * @listens Tech#durationchange - * @private + * Shim implementation specific properties. These properties are not in + * the spec. */ - - Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() { - this.duration(this.techGet_('duration')); - }; + // Lets us know when the VTTCue's data has changed in such a way that we need + // to recompute its display state. This lets us compute its display state + // lazily. + cue.hasBeenReset = false; /** - * Handle a click on the media element to play/pause - * - * @param {EventTarget~Event} event - * the event that caused this function to trigger - * - * @listens Tech#mousedown - * @private + * VTTCue and TextTrackCue properties + * http://dev.w3.org/html5/webvtt/#vttcue-interface */ + var _id = ""; + var _pauseOnExit = false; + var _startTime = startTime; + var _endTime = endTime; + var _text = text; + var _region = null; + var _vertical = ""; + var _snapToLines = true; + var _line = "auto"; + var _lineAlign = "start"; + var _position = 50; + var _positionAlign = "middle"; + var _size = 50; + var _align = "middle"; - Player.prototype.handleTechClick_ = function handleTechClick_(event) { - // We're using mousedown to detect clicks thanks to Flash, but mousedown - // will also be triggered with right-clicks, so we need to prevent that - if (event.button !== 0) { - return; - } + Object.defineProperty(cue, + "id", extend$1({}, baseObj, { + get: function() { + return _id; + }, + set: function(value) { + _id = "" + value; + } + })); - // When controls are disabled a click should not toggle playback because - // the click is considered a control - if (this.controls()) { - if (this.paused()) { - this.play(); - } else { - this.pause(); + Object.defineProperty(cue, + "pauseOnExit", extend$1({}, baseObj, { + get: function() { + return _pauseOnExit; + }, + set: function(value) { + _pauseOnExit = !!value; } - } - }; + })); - /** - * Handle a tap on the media element. It will toggle the user - * activity state, which hides and shows the controls. - * - * @listens Tech#tap - * @private - */ + Object.defineProperty(cue, + "startTime", extend$1({}, baseObj, { + get: function() { + return _startTime; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("Start time must be set to a number."); + } + _startTime = value; + this.hasBeenReset = true; + } + })); + Object.defineProperty(cue, + "endTime", extend$1({}, baseObj, { + get: function() { + return _endTime; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("End time must be set to a number."); + } + _endTime = value; + this.hasBeenReset = true; + } + })); - Player.prototype.handleTechTap_ = function handleTechTap_() { - this.userActive(!this.userActive()); - }; + Object.defineProperty(cue, + "text", extend$1({}, baseObj, { + get: function() { + return _text; + }, + set: function(value) { + _text = "" + value; + this.hasBeenReset = true; + } + })); - /** - * Handle touch to start - * - * @listens Tech#touchstart - * @private - */ + Object.defineProperty(cue, + "region", extend$1({}, baseObj, { + get: function() { + return _region; + }, + set: function(value) { + _region = value; + this.hasBeenReset = true; + } + })); + Object.defineProperty(cue, + "vertical", extend$1({}, baseObj, { + get: function() { + return _vertical; + }, + set: function(value) { + var setting = findDirectionSetting(value); + // Have to check for false because the setting an be an empty string. + if (setting === false) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _vertical = setting; + this.hasBeenReset = true; + } + })); - Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() { - this.userWasActive = this.userActive(); - }; + Object.defineProperty(cue, + "snapToLines", extend$1({}, baseObj, { + get: function() { + return _snapToLines; + }, + set: function(value) { + _snapToLines = !!value; + this.hasBeenReset = true; + } + })); - /** - * Handle touch to move - * - * @listens Tech#touchmove - * @private - */ + Object.defineProperty(cue, + "line", extend$1({}, baseObj, { + get: function() { + return _line; + }, + set: function(value) { + if (typeof value !== "number" && value !== autoKeyword) { + throw new SyntaxError("An invalid number or illegal string was specified."); + } + _line = value; + this.hasBeenReset = true; + } + })); + Object.defineProperty(cue, + "lineAlign", extend$1({}, baseObj, { + get: function() { + return _lineAlign; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _lineAlign = setting; + this.hasBeenReset = true; + } + })); - Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() { - if (this.userWasActive) { - this.reportUserActivity(); - } - }; + Object.defineProperty(cue, + "position", extend$1({}, baseObj, { + get: function() { + return _position; + }, + set: function(value) { + if (value < 0 || value > 100) { + throw new Error("Position must be between 0 and 100."); + } + _position = value; + this.hasBeenReset = true; + } + })); - /** - * Handle touch to end - * - * @param {EventTarget~Event} event - * the touchend event that triggered - * this function - * - * @listens Tech#touchend - * @private - */ + Object.defineProperty(cue, + "positionAlign", extend$1({}, baseObj, { + get: function() { + return _positionAlign; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _positionAlign = setting; + this.hasBeenReset = true; + } + })); + Object.defineProperty(cue, + "size", extend$1({}, baseObj, { + get: function() { + return _size; + }, + set: function(value) { + if (value < 0 || value > 100) { + throw new Error("Size must be between 0 and 100."); + } + _size = value; + this.hasBeenReset = true; + } + })); - Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) { - // Stop the mouse events from also happening - event.preventDefault(); - }; + Object.defineProperty(cue, + "align", extend$1({}, baseObj, { + get: function() { + return _align; + }, + set: function(value) { + var setting = findAlignSetting(value); + if (!setting) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _align = setting; + this.hasBeenReset = true; + } + })); /** - * Fired when the player switches in or out of fullscreen mode - * - * @private - * @listens Player#fullscreenchange + * Other spec defined properties */ + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state + cue.displayState = undefined; - Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() { - if (this.isFullscreen()) { - this.addClass('vjs-fullscreen'); - } else { - this.removeClass('vjs-fullscreen'); - } - }; + if (isIE8) { + return cue; + } +} - /** - * native click events on the SWF aren't triggered on IE11, Win8.1RT - * use stageclick events triggered from inside the SWF instead - * - * @private - * @listens stageclick - */ +/** + * VTTCue methods + */ +VTTCue.prototype.getCueAsHTML = function() { + // Assume WebVTT.convertCueToDOMTree is on the global. + return WebVTT.convertCueToDOMTree(window, this.text); +}; - Player.prototype.handleStageClick_ = function handleStageClick_() { - this.reportUserActivity(); - }; +var vttcue = VTTCue; - /** - * Handle Tech Fullscreen Change - * - * @param {EventTarget~Event} event - * the fullscreenchange event that triggered this function - * - * @param {Object} data - * the data that was sent with the event - * - * @private - * @listens Tech#fullscreenchange - * @fires Player#fullscreenchange - */ +/** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var scrollSetting = { + "": true, + "up": true +}; - Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) { - if (data) { - this.isFullscreen(data.isFullscreen); - } - /** - * Fired when going in and out of fullscreen. - * - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - }; +function findScrollSetting(value) { + if (typeof value !== "string") { + return false; + } + var scroll = scrollSetting[value.toLowerCase()]; + return scroll ? value.toLowerCase() : false; +} - /** - * Fires when an error occurred during the loading of an audio/video. - * - * @private - * @listens Tech#error - */ +function isValidPercentValue(value) { + return typeof value === "number" && (value >= 0 && value <= 100); +} +// VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface +function VTTRegion() { + var _width = 100; + var _lines = 3; + var _regionAnchorX = 0; + var _regionAnchorY = 100; + var _viewportAnchorX = 0; + var _viewportAnchorY = 100; + var _scroll = ""; + + Object.defineProperties(this, { + "width": { + enumerable: true, + get: function() { + return _width; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("Width must be between 0 and 100."); + } + _width = value; + } + }, + "lines": { + enumerable: true, + get: function() { + return _lines; + }, + set: function(value) { + if (typeof value !== "number") { + throw new TypeError("Lines must be set to a number."); + } + _lines = value; + } + }, + "regionAnchorY": { + enumerable: true, + get: function() { + return _regionAnchorY; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("RegionAnchorX must be between 0 and 100."); + } + _regionAnchorY = value; + } + }, + "regionAnchorX": { + enumerable: true, + get: function() { + return _regionAnchorX; + }, + set: function(value) { + if(!isValidPercentValue(value)) { + throw new Error("RegionAnchorY must be between 0 and 100."); + } + _regionAnchorX = value; + } + }, + "viewportAnchorY": { + enumerable: true, + get: function() { + return _viewportAnchorY; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorY must be between 0 and 100."); + } + _viewportAnchorY = value; + } + }, + "viewportAnchorX": { + enumerable: true, + get: function() { + return _viewportAnchorX; + }, + set: function(value) { + if (!isValidPercentValue(value)) { + throw new Error("ViewportAnchorX must be between 0 and 100."); + } + _viewportAnchorX = value; + } + }, + "scroll": { + enumerable: true, + get: function() { + return _scroll; + }, + set: function(value) { + var setting = findScrollSetting(value); + // Have to check for false as an empty string is a legal value. + if (setting === false) { + throw new SyntaxError("An invalid or illegal string was specified."); + } + _scroll = setting; + } + } + }); +} - Player.prototype.handleTechError_ = function handleTechError_() { - var error = this.tech_.error(); +var vttregion = VTTRegion; - this.error(error); - }; +var browserIndex = createCommonjsModule(function (module) { +/** + * Copyright 2013 vtt.js Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ - /** - * Retrigger the `textdata` event that was triggered by the {@link Tech}. - * - * @fires Player#textdata - * @listens Tech#textdata - * @private - */ +// Default exports for Node. Export the extended versions of VTTCue and +// VTTRegion in Node since we likely want the capability to convert back and +// forth between JSON. If we don't then it's not that big of a deal since we're +// off browser. - Player.prototype.handleTechTextData_ = function handleTechTextData_() { - var data = null; - if (arguments.length > 1) { - data = arguments[1]; - } +var vttjs = module.exports = { + WebVTT: vtt$1, + VTTCue: vttcue, + VTTRegion: vttregion +}; - /** - * Fires when we get a textdata event from tech - * - * @event Player#textdata - * @type {EventTarget~Event} - */ - this.trigger('textdata', data); - }; +window_1.vttjs = vttjs; +window_1.WebVTT = vttjs.WebVTT; - /** - * Get object for cached values. - * - * @return {Object} - * get the current object cache - */ +var cueShim = vttjs.VTTCue; +var regionShim = vttjs.VTTRegion; +var nativeVTTCue = window_1.VTTCue; +var nativeVTTRegion = window_1.VTTRegion; +vttjs.shim = function() { + window_1.VTTCue = cueShim; + window_1.VTTRegion = regionShim; +}; - Player.prototype.getCache = function getCache() { - return this.cache_; - }; +vttjs.restore = function() { + window_1.VTTCue = nativeVTTCue; + window_1.VTTRegion = nativeVTTRegion; +}; - /** - * Pass values to the playback tech - * - * @param {string} [method] - * the method to call - * - * @param {Object} arg - * the argument to pass - * - * @private - */ +if (!window_1.VTTCue) { + vttjs.shim(); +} +}); +/** + * @file tech.js + */ - Player.prototype.techCall_ = function techCall_(method, arg) { - // If it's not ready yet, call method when it is +/** + * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string + * that just contains the src url alone. + * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};` + * `var SourceString = 'http://example.com/some-video.mp4';` + * + * @typedef {Object|string} Tech~SourceObject + * + * @property {string} src + * The url to the source + * + * @property {string} type + * The mime type of the source + */ - this.ready(function () { - if (method in middleware.allowedSetters) { - return middleware.set(this.middleware_, this.tech_, method, arg); - } +/** + * A function used by {@link Tech} to create a new {@link TextTrack}. + * + * @private + * + * @param {Tech} self + * An instance of the Tech class. + * + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) + * + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @param {Object} [options={}] + * An object with additional text track options + * + * @return {TextTrack} + * The text track that was created. + */ +function createTrackHelper(self, kind, label, language) { + var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; - try { - if (this.tech_) { - this.tech_[method](arg); - } - } catch (e) { - (0, _log2['default'])(e); - throw e; - } - }, true); - }; + var tracks = self.textTracks(); - /** - * Get calls can't wait for the tech, and sometimes don't need to. - * - * @param {string} method - * Tech method - * - * @return {Function|undefined} - * the method or undefined - * - * @private - */ + options.kind = kind; + if (label) { + options.label = label; + } + if (language) { + options.language = language; + } + options.tech = self; - Player.prototype.techGet_ = function techGet_(method) { - if (this.tech_ && this.tech_.isReady_) { + var track = new ALL.text.TrackClass(options); - if (method in middleware.allowedGetters) { - return middleware.get(this.middleware_, this.tech_, method); - } + tracks.addTrack(track); - // Flash likes to die and reload when you hide or reposition it. - // In these cases the object methods go away and we get errors. - // When that happens we'll catch the errors and inform tech that it's not ready any more. - try { - return this.tech_[method](); - } catch (e) { - // When building additional tech libs, an expected method may not be defined yet - if (this.tech_[method] === undefined) { - (0, _log2['default'])('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e); + return track; +} - // When a method isn't available on the object it throws a TypeError - } else if (e.name === 'TypeError') { - (0, _log2['default'])('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e); - this.tech_.isReady_ = false; - } else { - (0, _log2['default'])(e); - } - throw e; - } - } +/** + * This is the base class for media playback technology controllers, such as + * {@link Flash} and {@link HTML5} + * + * @extends Component + */ - return; - }; +var Tech = function (_Component) { + inherits(Tech, _Component); /** - * start media playback + * Create an instance of this Tech. * - * @return {Promise|undefined} - * Returns a `Promise` if the browser returns one, for most browsers this will - * return undefined. + * @param {Object} [options] + * The key/value store of player options. + * + * @param {Component~ReadyCallback} ready + * Callback function to call when the `HTML5` Tech is ready. */ + function Tech() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {}; + classCallCheck(this, Tech); + // we don't want the tech to report user activity automatically. + // This is done manually in addControlsListeners + options.reportTouchActivity = false; - Player.prototype.play = function play() { - if (this.changingSrc_) { - this.ready(function () { - var retval = this.techGet_('play'); + // keep track of whether the current source has played at all to + // implement a very limited played() + var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready)); - // silence errors (unhandled promise from play) - if (retval !== undefined && typeof retval.then === 'function') { - retval.then(null, function (e) {}); - } - }); + _this.hasStarted_ = false; + _this.on('playing', function () { + this.hasStarted_ = true; + }); + _this.on('loadstart', function () { + this.hasStarted_ = false; + }); - // Only calls the tech's play if we already have a src loaded - } else if (this.isReady_ && (this.src() || this.currentSrc())) { - return this.techGet_('play'); - } else { - this.ready(function () { - this.tech_.one('loadstart', function () { - var retval = this.play(); + ALL.names.forEach(function (name) { + var props = ALL[name]; - // silence errors (unhandled promise from play) - if (retval !== undefined && typeof retval.then === 'function') { - retval.then(null, function (e) {}); - } - }); - }); + if (options && options[props.getterName]) { + _this[props.privateName] = options[props.getterName]; + } + }); + + // Manually track progress in cases where the browser/flash player doesn't report it. + if (!_this.featuresProgressEvents) { + _this.manualProgressOn(); } - }; - /** - * Pause the video playback - * - * @return {Player} - * A reference to the player object this function was called on - */ + // Manually track timeupdates in cases where the browser/flash player doesn't report it. + if (!_this.featuresTimeupdateEvents) { + _this.manualTimeUpdatesOn(); + } + ['Text', 'Audio', 'Video'].forEach(function (track) { + if (options['native' + track + 'Tracks'] === false) { + _this['featuresNative' + track + 'Tracks'] = false; + } + }); - Player.prototype.pause = function pause() { - this.techCall_('pause'); - }; + if (options.nativeCaptions === false || options.nativeTextTracks === false) { + _this.featuresNativeTextTracks = false; + } else if (options.nativeCaptions === true || options.nativeTextTracks === true) { + _this.featuresNativeTextTracks = true; + } - /** - * Check if the player is paused or has yet to play - * - * @return {boolean} - * - false: if the media is currently playing - * - true: if media is not currently playing - */ + if (!_this.featuresNativeTextTracks) { + _this.emulateTextTracks(); + } + _this.autoRemoteTextTracks_ = new ALL.text.ListClass(); - Player.prototype.paused = function paused() { - // The initial state of paused should be true (in Safari it's actually false) - return this.techGet_('paused') === false ? false : true; - }; + _this.initTrackListeners(); - /** - * Get a TimeRange object representing the current ranges of time that the user - * has played. - * - * @return {TimeRange} - * A time range object that represents all the increments of time that have - * been played. - */ + // Turn on component tap events only if not using native controls + if (!options.nativeControlsForTouch) { + _this.emitTapEvents(); + } + if (_this.constructor) { + _this.name_ = _this.constructor.name || 'Unknown Tech'; + } + return _this; + } - Player.prototype.played = function played() { - return this.techGet_('played') || (0, _timeRanges.createTimeRange)(0, 0); - }; + /* Fallbacks for unsupported event types + ================================================================================ */ /** - * Returns whether or not the user is "scrubbing". Scrubbing is - * when the user has clicked the progress bar handle and is - * dragging it along the progress bar. - * - * @param {boolean} [isScrubbing] - * wether the user is or is not scrubbing + * Polyfill the `progress` event for browsers that don't support it natively. * - * @return {boolean} - * The value of scrubbing when getting + * @see {@link Tech#trackProgress} */ - Player.prototype.scrubbing = function scrubbing(isScrubbing) { - if (typeof isScrubbing === 'undefined') { - return this.scrubbing_; - } - this.scrubbing_ = !!isScrubbing; + Tech.prototype.manualProgressOn = function manualProgressOn() { + this.on('durationchange', this.onDurationChange); - if (isScrubbing) { - this.addClass('vjs-scrubbing'); - } else { - this.removeClass('vjs-scrubbing'); - } + this.manualProgress = true; + + // Trigger progress watching when a source begins loading + this.one('ready', this.trackProgress); }; /** - * Get or set the current time (in seconds) - * - * @param {number|string} [seconds] - * The time to seek to in seconds - * - * @return {number} - * - the current time in seconds when getting + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} */ - Player.prototype.currentTime = function currentTime(seconds) { - if (typeof seconds !== 'undefined') { - this.techCall_('setCurrentTime', seconds); - return; - } + Tech.prototype.manualProgressOff = function manualProgressOff() { + this.manualProgress = false; + this.stopTrackingProgress(); - // cache last currentTime and return. default to 0 seconds - // - // Caching the currentTime is meant to prevent a massive amount of reads on the tech's - // currentTime when scrubbing, but may not provide much performance benefit afterall. - // Should be tested. Also something has to read the actual current time or the cache will - // never get updated. - this.cache_.currentTime = this.techGet_('currentTime') || 0; - return this.cache_.currentTime; + this.off('durationchange', this.onDurationChange); }; /** - * Normally gets the length in time of the video in seconds; - * in all but the rarest use cases an argument will NOT be passed to the method - * - * > **NOTE**: The video must have started loading before the duration can be - * known, and in the case of Flash, may not be known until the video starts - * playing. + * This is used to trigger a `progress` event when the buffered percent changes. It + * sets an interval function that will be called every 500 milliseconds to check if the + * buffer end percent has changed. * - * @fires Player#durationchange + * > This function is called by {@link Tech#manualProgressOn} * - * @param {number} [seconds] - * The duration of the video to set in seconds + * @param {EventTarget~Event} event + * The `ready` event that caused this to run. * - * @return {number} - * - The duration of the video in seconds when getting + * @listens Tech#ready + * @fires Tech#progress */ - Player.prototype.duration = function duration(seconds) { - if (seconds === undefined) { - return this.cache_.duration || 0; - } + Tech.prototype.trackProgress = function trackProgress(event) { + this.stopTrackingProgress(); + this.progressInterval = this.setInterval(bind(this, function () { + // Don't trigger unless buffered amount is greater than last time - seconds = parseFloat(seconds) || 0; + var numBufferedPercent = this.bufferedPercent(); - // Standardize on Inifity for signaling video is live - if (seconds < 0) { - seconds = Infinity; - } + if (this.bufferedPercent_ !== numBufferedPercent) { + /** + * See {@link Player#progress} + * + * @event Tech#progress + * @type {EventTarget~Event} + */ + this.trigger('progress'); + } - if (seconds !== this.cache_.duration) { - // Cache the last set value for optimized scrubbing (esp. Flash) - this.cache_.duration = seconds; + this.bufferedPercent_ = numBufferedPercent; - if (seconds === Infinity) { - this.addClass('vjs-live'); - } else { - this.removeClass('vjs-live'); + if (numBufferedPercent === 1) { + this.stopTrackingProgress(); } - /** - * @event Player#durationchange - * @type {EventTarget~Event} - */ - this.trigger('durationchange'); - } + }), 500); }; /** - * Calculates how much time is left in the video. Not part - * of the native video API. + * Update our internal duration on a `durationchange` event by calling + * {@link Tech#duration}. * - * @return {number} - * The time remaining in seconds + * @param {EventTarget~Event} event + * The `durationchange` event that caused this to run. + * + * @listens Tech#durationchange */ - Player.prototype.remainingTime = function remainingTime() { - return this.duration() - this.currentTime(); + Tech.prototype.onDurationChange = function onDurationChange(event) { + this.duration_ = this.duration(); }; - // - // Kind of like an array of portions of the video that have been downloaded. - /** - * Get a TimeRange object with an array of the times of the video - * that have been downloaded. If you just want the percent of the - * video that's been downloaded, use bufferedPercent. - * - * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered} + * Get and create a `TimeRange` object for buffering. * * @return {TimeRange} - * A mock TimeRange object (following HTML spec) + * The time range object that was created. */ - Player.prototype.buffered = function buffered() { - var buffered = this.techGet_('buffered'); - - if (!buffered || !buffered.length) { - buffered = (0, _timeRanges.createTimeRange)(0, 0); - } - - return buffered; + Tech.prototype.buffered = function buffered() { + return createTimeRanges(0, 0); }; /** - * Get the percent (as a decimal) of the video that's been downloaded. - * This method is not a part of the native HTML video API. + * Get the percentage of the current video that is currently buffered. * * @return {number} - * A decimal between 0 and 1 representing the percent - * that is bufferred 0 being 0% and 1 being 100% - */ - - - Player.prototype.bufferedPercent = function bufferedPercent() { - return (0, _buffer.bufferedPercent)(this.buffered(), this.duration()); - }; - - /** - * Get the ending time of the last buffered time range - * This is used in the progress bar to encapsulate all time ranges. + * A number from 0 to 1 that represents the decimal percentage of the + * video that is buffered. * - * @return {number} - * The end of the last buffered time range */ - Player.prototype.bufferedEnd = function bufferedEnd() { - var buffered = this.buffered(); - var duration = this.duration(); - var end = buffered.end(buffered.length - 1); - - if (end > duration) { - end = duration; - } - - return end; + Tech.prototype.bufferedPercent = function bufferedPercent$$1() { + return bufferedPercent(this.buffered(), this.duration_); }; /** - * Get or set the current volume of the media - * - * @param {number} [percentAsDecimal] - * The new volume as a decimal percent: - * - 0 is muted/0%/off - * - 1.0 is 100%/full - * - 0.5 is half volume or 50% - * - * @return {number} - * The current volume as a percent when getting + * Turn off the polyfill for `progress` events that was created in + * {@link Tech#manualProgressOn} + * Stop manually tracking progress events by clearing the interval that was set in + * {@link Tech#trackProgress}. */ - Player.prototype.volume = function volume(percentAsDecimal) { - var vol = void 0; - - if (percentAsDecimal !== undefined) { - // Force value to between 0 and 1 - vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); - this.cache_.volume = vol; - this.techCall_('setVolume', vol); - - if (vol > 0) { - this.lastVolume_(vol); - } - - return; - } - - // Default to 1 when returning current volume. - vol = parseFloat(this.techGet_('volume')); - return isNaN(vol) ? 1 : vol; + Tech.prototype.stopTrackingProgress = function stopTrackingProgress() { + this.clearInterval(this.progressInterval); }; /** - * Get the current muted state, or turn mute on or off - * - * @param {boolean} [muted] - * - true to mute - * - false to unmute + * Polyfill the `timeupdate` event for browsers that don't support it. * - * @return {boolean} - * - true if mute is on and getting - * - false if mute is off and getting + * @see {@link Tech#trackCurrentTime} */ - Player.prototype.muted = function muted(_muted) { - if (_muted !== undefined) { - this.techCall_('setMuted', _muted); - return; - } - return this.techGet_('muted') || false; + Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() { + this.manualTimeUpdates = true; + + this.on('play', this.trackCurrentTime); + this.on('pause', this.stopTrackingCurrentTime); }; /** - * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted - * indicates the state of muted on intial playback. - * - * ```js - * var myPlayer = videojs('some-player-id'); - * - * myPlayer.src("http://www.example.com/path/to/video.mp4"); - * - * // get, should be false - * console.log(myPlayer.defaultMuted()); - * // set to true - * myPlayer.defaultMuted(true); - * // get should be true - * console.log(myPlayer.defaultMuted()); - * ``` - * - * @param {boolean} [defaultMuted] - * - true to mute - * - false to unmute - * - * @return {boolean|Player} - * - true if defaultMuted is on and getting - * - false if defaultMuted is off and getting - * - A reference to the current player when setting + * Turn off the polyfill for `timeupdate` events that was created in + * {@link Tech#manualTimeUpdatesOn} */ - Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) { - if (_defaultMuted !== undefined) { - return this.techCall_('setDefaultMuted', _defaultMuted); - } - return this.techGet_('defaultMuted') || false; + Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() { + this.manualTimeUpdates = false; + this.stopTrackingCurrentTime(); + this.off('play', this.trackCurrentTime); + this.off('pause', this.stopTrackingCurrentTime); }; /** - * Get the last volume, or set it - * - * @param {number} [percentAsDecimal] - * The new last volume as a decimal percent: - * - 0 is muted/0%/off - * - 1.0 is 100%/full - * - 0.5 is half volume or 50% - * - * @return {number} - * the current value of lastVolume as a percent when getting + * Sets up an interval function to track current time and trigger `timeupdate` every + * 250 milliseconds. * - * @private + * @listens Tech#play + * @triggers Tech#timeupdate */ - Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) { - if (percentAsDecimal !== undefined && percentAsDecimal !== 0) { - this.cache_.lastVolume = percentAsDecimal; - return; + Tech.prototype.trackCurrentTime = function trackCurrentTime() { + if (this.currentTimeInterval) { + this.stopTrackingCurrentTime(); } - return this.cache_.lastVolume; + this.currentTimeInterval = this.setInterval(function () { + /** + * Triggered at an interval of 250ms to indicated that time is passing in the video. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + + // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 + }, 250); }; /** - * Check if current tech can support native fullscreen - * (e.g. with built in controls like iOS, so not our flash swf) + * Stop the interval function created in {@link Tech#trackCurrentTime} so that the + * `timeupdate` event is no longer triggered. * - * @return {boolean} - * if native fullscreen is supported + * @listens {Tech#pause} */ - Player.prototype.supportsFullScreen = function supportsFullScreen() { - return this.techGet_('supportsFullScreen') || false; + Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { + this.clearInterval(this.currentTimeInterval); + + // #1002 - if the video ends right before the next timeupdate would happen, + // the progress bar won't make it all the way to the end + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); }; /** - * Check if the player is in fullscreen mode or tell the player that it - * is or is not in fullscreen mode. - * - * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official - * property and instead document.fullscreenElement is used. But isFullscreen is - * still a valuable property for internal player workings. - * - * @param {boolean} [isFS] - * Set the players current fullscreen state + * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, + * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. * - * @return {boolean} - * - true if fullscreen is on and getting - * - false if fullscreen is off and getting + * @fires Component#dispose */ - Player.prototype.isFullscreen = function isFullscreen(isFS) { - if (isFS !== undefined) { - this.isFullscreen_ = !!isFS; - return; + Tech.prototype.dispose = function dispose() { + + // clear out all tracks because we can't reuse them between techs + this.clearTracks(NORMAL.names); + + // Turn off any manual progress or timeupdate tracking + if (this.manualProgress) { + this.manualProgressOff(); } - return !!this.isFullscreen_; + + if (this.manualTimeUpdates) { + this.manualTimeUpdatesOff(); + } + + _Component.prototype.dispose.call(this); }; /** - * Increase the size of the video to full screen - * In some browsers, full screen is not supported natively, so it enters - * "full window mode", where the video fills the browser window. - * In browsers and devices that support native full screen, sometimes the - * browser's default controls will be shown, and not the Video.js custom skin. - * This includes most mobile devices (iOS, Android) and older versions of - * Safari. + * Clear out a single `TrackList` or an array of `TrackLists` given their names. * - * @fires Player#fullscreenchange + * > Note: Techs without source handlers should call this between sources for `video` + * & `audio` tracks. You don't want to use them between tracks! + * + * @param {string[]|string} types + * TrackList names to clear, valid names are `video`, `audio`, and + * `text`. */ - Player.prototype.requestFullscreen = function requestFullscreen() { - var fsApi = _fullscreenApi2['default']; - - this.isFullscreen(true); + Tech.prototype.clearTracks = function clearTracks(types) { + var _this2 = this; - if (fsApi.requestFullscreen) { - // the browser supports going fullscreen at the element level so we can - // take the controls fullscreen as well as the video + types = [].concat(types); + // clear out all tracks because we can't reuse them between techs + types.forEach(function (type) { + var list = _this2[type + 'Tracks']() || []; + var i = list.length; - // Trigger fullscreenchange event after change - // We have to specifically add this each time, and remove - // when canceling fullscreen. Otherwise if there's multiple - // players on a page, they would all be reacting to the same fullscreen - // events - Events.on(_document2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) { - this.isFullscreen(_document2['default'][fsApi.fullscreenElement]); + while (i--) { + var track = list[i]; - // If cancelling fullscreen, remove event listener. - if (this.isFullscreen() === false) { - Events.off(_document2['default'], fsApi.fullscreenchange, documentFullscreenChange); + if (type === 'text') { + _this2.removeRemoteTextTrack(track); } - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - })); - - this.el_[fsApi.requestFullscreen](); - } else if (this.tech_.supportsFullScreen()) { - // we can't take the video.js controls fullscreen but we can go fullscreen - // with native controls - this.techCall_('enterFullScreen'); - } else { - // fullscreen isn't supported so we'll just stretch the video element to - // fill the viewport - this.enterFullWindow(); - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); - } + list.removeTrack(track); + } + }); }; /** - * Return the video to its normal size after having been in full screen mode - * - * @fires Player#fullscreenchange + * Remove any TextTracks added via addRemoteTextTrack that are + * flagged for automatic garbage collection */ - Player.prototype.exitFullscreen = function exitFullscreen() { - var fsApi = _fullscreenApi2['default']; + Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { + var list = this.autoRemoteTextTracks_ || []; + var i = list.length; - this.isFullscreen(false); + while (i--) { + var track = list[i]; - // Check for browser element fullscreen support - if (fsApi.requestFullscreen) { - _document2['default'][fsApi.exitFullscreen](); - } else if (this.tech_.supportsFullScreen()) { - this.techCall_('exitFullScreen'); - } else { - this.exitFullWindow(); - /** - * @event Player#fullscreenchange - * @type {EventTarget~Event} - */ - this.trigger('fullscreenchange'); + this.removeRemoteTextTrack(track); } }; /** - * When fullscreen isn't supported we can stretch the - * video container to as wide as the browser will let us. + * Reset the tech, which will removes all sources and reset the internal readyState. * - * @fires Player#enterFullWindow + * @abstract */ - Player.prototype.enterFullWindow = function enterFullWindow() { - this.isFullWindow = true; + Tech.prototype.reset = function reset() {}; - // Storing original doc overflow value to return to when fullscreen is off - this.docOrigOverflow = _document2['default'].documentElement.style.overflow; + /** + * Get or set an error on the Tech. + * + * @param {MediaError} [err] + * Error to set on the Tech + * + * @return {MediaError|null} + * The current error object on the tech, or null if there isn't one. + */ - // Add listener for esc key to exit fullscreen - Events.on(_document2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey)); - // Hide any scroll bars - _document2['default'].documentElement.style.overflow = 'hidden'; + Tech.prototype.error = function error(err) { + if (err !== undefined) { + this.error_ = new MediaError(err); + this.trigger('error'); + } + return this.error_; + }; + + /** + * Returns the `TimeRange`s that have been played through for the current source. + * + * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. + * It only checks wether the source has played at all or not. + * + * @return {TimeRange} + * - A single time range if this video has played + * - An empty set of ranges if not. + */ - // Apply fullscreen styles - Dom.addClass(_document2['default'].body, 'vjs-full-window'); - /** - * @event Player#enterFullWindow - * @type {EventTarget~Event} - */ - this.trigger('enterFullWindow'); + Tech.prototype.played = function played() { + if (this.hasStarted_) { + return createTimeRanges(0, 0); + } + return createTimeRanges(); }; /** - * Check for call to either exit full window or - * full screen on ESC key + * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was + * previously called. * - * @param {string} event - * Event to check for key press + * @fires Tech#timeupdate */ - Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) { - if (event.keyCode === 27) { - if (this.isFullscreen() === true) { - this.exitFullscreen(); - } else { - this.exitFullWindow(); - } + Tech.prototype.setCurrentTime = function setCurrentTime() { + // improve the accuracy of manual timeupdates + if (this.manualTimeUpdates) { + /** + * A manual `timeupdate` event. + * + * @event Tech#timeupdate + * @type {EventTarget~Event} + */ + this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); } }; /** - * Exit full window + * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and + * {@link TextTrackList} events. * - * @fires Player#exitFullWindow + * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. + * + * @fires Tech#audiotrackchange + * @fires Tech#videotrackchange + * @fires Tech#texttrackchange */ - Player.prototype.exitFullWindow = function exitFullWindow() { - this.isFullWindow = false; - Events.off(_document2['default'], 'keydown', this.fullWindowOnEscKey); + Tech.prototype.initTrackListeners = function initTrackListeners() { + var _this3 = this; - // Unhide scroll bars. - _document2['default'].documentElement.style.overflow = this.docOrigOverflow; + /** + * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} + * + * @event Tech#audiotrackchange + * @type {EventTarget~Event} + */ - // Remove fullscreen styles - Dom.removeClass(_document2['default'].body, 'vjs-full-window'); + /** + * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} + * + * @event Tech#videotrackchange + * @type {EventTarget~Event} + */ - // Resize the box, controller, and poster to original sizes - // this.positionAll(); /** - * @event Player#exitFullWindow + * Triggered when tracks are added or removed on the Tech {@link TextTrackList} + * + * @event Tech#texttrackchange * @type {EventTarget~Event} */ - this.trigger('exitFullWindow'); + NORMAL.names.forEach(function (name) { + var props = NORMAL[name]; + var trackListChanges = function trackListChanges() { + _this3.trigger(name + 'trackchange'); + }; + + var tracks = _this3[props.getterName](); + + tracks.addEventListener('removetrack', trackListChanges); + tracks.addEventListener('addtrack', trackListChanges); + + _this3.on('dispose', function () { + tracks.removeEventListener('removetrack', trackListChanges); + tracks.removeEventListener('addtrack', trackListChanges); + }); + }); }; /** - * Check whether the player can play a given mimetype - * - * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype - * - * @param {string} type - * The mimetype to check + * Emulate TextTracks using vtt.js if necessary * - * @return {string} - * 'probably', 'maybe', or '' (empty string) + * @fires Tech#vttjsloaded + * @fires Tech#vttjserror */ - Player.prototype.canPlayType = function canPlayType(type) { - var can = void 0; + Tech.prototype.addWebVttScript_ = function addWebVttScript_() { + var _this4 = this; - // Loop through each playback technology in the options order - for (var i = 0, j = this.options_.techOrder; i < j.length; i++) { - var techName = j[i]; - var tech = _tech2['default'].getTech(techName); + if (window_1.WebVTT) { + return; + } - // Support old behavior of techs being registered as components. - // Remove once that deprecated behavior is removed. - if (!tech) { - tech = _component2['default'].getComponent(techName); - } + // Initially, Tech.el_ is a child of a dummy-div wait until the Component system + // signals that the Tech is ready at which point Tech.el_ is part of the DOM + // before inserting the WebVTT script + if (document_1.body.contains(this.el())) { - // Check if the current tech is defined before continuing - if (!tech) { - _log2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); - continue; + // load via require if available and vtt.js script location was not passed in + // as an option. novtt builds will turn the above require call into an empty object + // which will cause this if check to always fail. + if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) { + this.trigger('vttjsloaded'); + return; } - // Check if the browser supports this technology - if (tech.isSupported()) { - can = tech.canPlayType(type); + // load vtt.js via the script location option or the cdn of no location was + // passed in + var script = document_1.createElement('script'); - if (can) { - return can; - } - } + script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.4/vtt.min.js'; + script.onload = function () { + /** + * Fired when vtt.js is loaded. + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this4.trigger('vttjsloaded'); + }; + script.onerror = function () { + /** + * Fired when vtt.js was not loaded due to an error + * + * @event Tech#vttjsloaded + * @type {EventTarget~Event} + */ + _this4.trigger('vttjserror'); + }; + this.on('dispose', function () { + script.onload = null; + script.onerror = null; + }); + // but have not loaded yet and we set it to true before the inject so that + // we don't overwrite the injected window.WebVTT if it loads right away + window_1.WebVTT = true; + this.el().parentNode.appendChild(script); + } else { + this.ready(this.addWebVttScript_); } - - return ''; }; /** - * Select source based on tech-order or source-order - * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise, - * defaults to tech-order selection - * - * @param {Array} sources - * The sources for a media asset + * Emulate texttracks * - * @return {Object|boolean} - * Object of source and tech order or false */ - Player.prototype.selectSource = function selectSource(sources) { + Tech.prototype.emulateTextTracks = function emulateTextTracks() { var _this5 = this; - // Get only the techs specified in `techOrder` that exist and are supported by the - // current platform - var techs = this.options_.techOrder.map(function (techName) { - return [techName, _tech2['default'].getTech(techName)]; - }).filter(function (_ref) { - var techName = _ref[0], - tech = _ref[1]; + var tracks = this.textTracks(); + var remoteTracks = this.remoteTextTracks(); + var handleAddTrack = function handleAddTrack(e) { + return tracks.addTrack(e.track); + }; + var handleRemoveTrack = function handleRemoveTrack(e) { + return tracks.removeTrack(e.track); + }; - // Check if the current tech is defined before continuing - if (tech) { - // Check if the browser supports this technology - return tech.isSupported(); - } + remoteTracks.on('addtrack', handleAddTrack); + remoteTracks.on('removetrack', handleRemoveTrack); - _log2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.'); - return false; - }); + this.addWebVttScript_(); - // Iterate over each `innerArray` element once per `outerArray` element and execute - // `tester` with both. If `tester` returns a non-falsy value, exit early and return - // that value. - var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) { - var found = void 0; + var updateDisplay = function updateDisplay() { + return _this5.trigger('texttrackchange'); + }; - outerArray.some(function (outerChoice) { - return innerArray.some(function (innerChoice) { - found = tester(outerChoice, innerChoice); + var textTracksChanges = function textTracksChanges() { + updateDisplay(); - if (found) { - return true; - } - }); - }); + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; - return found; + track.removeEventListener('cuechange', updateDisplay); + if (track.mode === 'showing') { + track.addEventListener('cuechange', updateDisplay); + } + } }; - var foundSourceAndTech = void 0; - var flip = function flip(fn) { - return function (a, b) { - return fn(b, a); - }; - }; - var finder = function finder(_ref2, source) { - var techName = _ref2[0], - tech = _ref2[1]; + textTracksChanges(); + tracks.addEventListener('change', textTracksChanges); + tracks.addEventListener('addtrack', textTracksChanges); + tracks.addEventListener('removetrack', textTracksChanges); - if (tech.canPlaySource(source, _this5.options_[techName.toLowerCase()])) { - return { source: source, tech: techName }; - } - }; + this.on('dispose', function () { + remoteTracks.off('addtrack', handleAddTrack); + remoteTracks.off('removetrack', handleRemoveTrack); + tracks.removeEventListener('change', textTracksChanges); + tracks.removeEventListener('addtrack', textTracksChanges); + tracks.removeEventListener('removetrack', textTracksChanges); - // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources - // to select from them based on their priority. - if (this.options_.sourceOrder) { - // Source-first ordering - foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder)); - } else { - // Tech-first ordering - foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder); - } + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; - return foundSourceAndTech || false; + track.removeEventListener('cuechange', updateDisplay); + } + }); }; /** - * The source function updates the video source - * There are three types of variables you can pass as the argument. - * **URL string**: A URL to the the video file. Use this method if you are sure - * the current playback technology (HTML5/Flash) can support the source you - * provide. Currently only MP4 files can be used in both HTML5 and Flash. + * Create and returns a remote {@link TextTrack} object. * - * @param {Tech~SourceObject|Tech~SourceObject[]} [source] - * One SourceObject or an array of SourceObjects + * @param {string} kind + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) * - * @return {string} - * The current video source when getting + * @param {string} [label] + * Label to identify the text track + * + * @param {string} [language] + * Two letter language abbreviation + * + * @return {TextTrack} + * The TextTrack that gets created. */ - Player.prototype.src = function src(source) { - var _this6 = this; - - // getter usage - if (typeof source === 'undefined') { - return this.cache_.src; - } - // filter out invalid sources and turn our source into - // an array of source objects - var sources = (0, _filterSource2['default'])(source); - - // if a source was passed in then it is invalid because - // it was filtered to a zero length Array. So we have to - // show an error - if (!sources.length) { - this.setTimeout(function () { - this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); - }, 0); - return; + Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { + if (!kind) { + throw new Error('TextTrack kind is required but was not provided'); } - // intial sources - this.cache_.sources = sources; - this.changingSrc_ = true; - - // intial source - this.cache_.source = sources[0]; - - // middlewareSource is the source after it has been changed by middleware - middleware.setSource(this, sources[0], function (middlewareSource, mws) { - _this6.middleware_ = mws; - - var err = _this6.src_(middlewareSource); - - if (err) { - if (sources.length > 1) { - return _this6.src(sources.slice(1)); - } - - // We need to wrap this in a timeout to give folks a chance to add error event handlers - _this6.setTimeout(function () { - this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) }); - }, 0); - - // we could not find an appropriate tech, but let's still notify the delegate that this is it - // this needs a better comment about why this is needed - _this6.triggerReady(); + return createTrackHelper(this, kind, label, language); + }; - return; - } + /** + * Create an emulated TextTrack for use by addRemoteTextTrack + * + * This is intended to be overridden by classes that inherit from + * Tech in order to create native or custom TextTracks. + * + * @param {Object} options + * The object should contain the options to initialize the TextTrack with. + * + * @param {string} [options.kind] + * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). + * + * @param {string} [options.label]. + * Label to identify the text track + * + * @param {string} [options.language] + * Two letter language abbreviation. + * + * @return {HTMLTrackElement} + * The track element that gets created. + */ - _this6.changingSrc_ = false; - // video element listed source - _this6.cache_.src = middlewareSource.src; - middleware.setTech(mws, _this6.tech_); + Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { + var track = mergeOptions(options, { + tech: this }); + + return new REMOTE.remoteTextEl.TrackClass(track); }; /** - * Set the source object on the tech, returns a boolean that indicates wether - * there is a tech that can play the source or not + * Creates a remote text track object and returns an html track element. * - * @param {Tech~SourceObject} source - * The source object to set on the Tech + * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. * - * @return {Boolean} - * - True if there is no Tech to playback this source - * - False otherwise + * @param {Object} options + * See {@link Tech#createRemoteTextTrack} for more detailed properties. * - * @private + * @param {boolean} [manualCleanup=true] + * - When false: the TextTrack will be automatically removed from the video + * element whenever the source changes + * - When True: The TextTrack will have to be cleaned up manually + * + * @return {HTMLTrackElement} + * An Html Track Element. + * + * @deprecated The default functionality for this function will be equivalent + * to "manualCleanup=false" in the future. The manualCleanup parameter will + * also be removed. */ - Player.prototype.src_ = function src_(source) { - var sourceTech = this.selectSource([source]); + Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var manualCleanup = arguments[1]; - if (!sourceTech) { - return true; + var htmlTrackElement = this.createRemoteTextTrack(options); + + if (manualCleanup !== true && manualCleanup !== false) { + // deprecation warning + log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); + manualCleanup = true; } - if (!(0, _toTitleCase.titleCaseEquals)(sourceTech.tech, this.techName_)) { - this.changingSrc_ = true; + // store HTMLTrackElement and TextTrack to remote list + this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); + this.remoteTextTracks().addTrack(htmlTrackElement.track); - // load this technology with the chosen source - this.loadTech_(sourceTech.tech, sourceTech.source); - return false; + if (manualCleanup !== true) { + // create the TextTrackList if it doesn't exist + this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); } - // wait until the tech is ready to set the source - this.ready(function () { - - // The setSource tech method was added with source handlers - // so older techs won't support it - // We need to check the direct prototype for the case where subclasses - // of the tech do not support source handlers - if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) { - this.techCall_('setSource', source); - } else { - this.techCall_('src', source.src); - } + return htmlTrackElement; + }; - if (this.options_.preload === 'auto') { - this.load(); - } + /** + * Remove a remote text track from the remote `TextTrackList`. + * + * @param {TextTrack} track + * `TextTrack` to remove from the `TextTrackList` + */ - if (this.options_.autoplay) { - this.play(); - } - // Set the source synchronously if possible (#2326) - }, true); + Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { + var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); - return false; + // remove HTMLTrackElement and TextTrack from remote list + this.remoteTextTrackEls().removeTrackElement_(trackElement); + this.remoteTextTracks().removeTrack(track); + this.autoRemoteTextTracks_.removeTrack(track); }; /** - * Begin loading the src data. + * Gets available media playback quality metrics as specified by the W3C's Media + * Playback Quality API. + * + * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * + * @return {Object} + * An object with supported media playback quality metrics + * + * @abstract */ - Player.prototype.load = function load() { - this.techCall_('load'); + Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { + return {}; }; /** - * Reset the player. Loads the first tech in the techOrder, - * and calls `reset` on the tech`. + * A method to set a poster from a `Tech`. + * + * @abstract */ - Player.prototype.reset = function reset() { - this.loadTech_(this.options_.techOrder[0], null); - this.techCall_('reset'); - }; + Tech.prototype.setPoster = function setPoster() {}; /** - * Returns all of the current source objects. + * A method to check for the presence of the 'playsinine'
  • ` + * + * @extends ClickableComponent + */ + +var MenuItem = function (_ClickableComponent) { + inherits(MenuItem, _ClickableComponent); + + /** + * Creates an instance of the this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. + * + * @param {Object} [options={}] + * The key/value store of player options. + * + */ + function MenuItem(player, options) { + classCallCheck(this, MenuItem); + + var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options)); + + _this.selectable = options.selectable; + + _this.selected(options.selected); + + if (_this.selectable) { + // TODO: May need to be either menuitemcheckbox or menuitemradio, + // and may need logical grouping of menu items. + _this.el_.setAttribute('role', 'menuitemcheckbox'); + } else { + _this.el_.setAttribute('role', 'menuitem'); + } + return _this; + } /** - * Sets up an interval function to track current time and trigger `timeupdate` every - * 250 milliseconds. + * Create the `MenuItem's DOM element * - * @listens Tech#play - * @triggers Tech#timeupdate + * @param {string} [type=li] + * Element's node type, not actually used, always set to `li`. + * + * @param {Object} [props={}] + * An object of properties that should be set on the element + * + * @param {Object} [attrs={}] + * An object of attributes that should be set on the element + * + * @return {Element} + * The element that gets created. */ - Tech.prototype.trackCurrentTime = function trackCurrentTime() { - if (this.currentTimeInterval) { - this.stopTrackingCurrentTime(); - } - this.currentTimeInterval = this.setInterval(function () { - /** - * Triggered at an interval of 250ms to indicated that time is passing in the video. - * - * @event Tech#timeupdate - * @type {EventTarget~Event} - */ - this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + MenuItem.prototype.createEl = function createEl(type, props, attrs) { + // The control is textual, not just an icon + this.nonIconControl = true; - // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 - }, 250); + return _ClickableComponent.prototype.createEl.call(this, 'li', assign({ + className: 'vjs-menu-item', + innerHTML: '' + this.localize(this.options_.label) + '', + tabIndex: -1 + }, props), attrs); }; /** - * Stop the interval function created in {@link Tech#trackCurrentTime} so that the - * `timeupdate` event is no longer triggered. + * Any click on a `MenuItem` puts int into the selected state. + * See {@link ClickableComponent#handleClick} for instances where this is called. * - * @listens {Tech#pause} + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click */ - Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() { - this.clearInterval(this.currentTimeInterval); - - // #1002 - if the video ends right before the next timeupdate would happen, - // the progress bar won't make it all the way to the end - this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + MenuItem.prototype.handleClick = function handleClick(event) { + this.selected(true); }; /** - * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList}, - * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech. + * Set the state for this menu item as selected or not. * - * @fires Component#dispose + * @param {boolean} selected + * if the menu item is selected or not */ - Tech.prototype.dispose = function dispose() { + MenuItem.prototype.selected = function selected(_selected) { + if (this.selectable) { + if (_selected) { + this.addClass('vjs-selected'); + this.el_.setAttribute('aria-checked', 'true'); + // aria-checked isn't fully supported by browsers/screen readers, + // so indicate selected state to screen reader in the control text. + this.controlText(', selected'); + } else { + this.removeClass('vjs-selected'); + this.el_.setAttribute('aria-checked', 'false'); + // Indicate un-selected state to screen reader + // Note that a space clears out the selected state text + this.controlText(' '); + } + } + }; - // clear out all tracks because we can't reuse them between techs - this.clearTracks(TRACK_TYPES.NORMAL.names); + return MenuItem; +}(ClickableComponent); - // Turn off any manual progress or timeupdate tracking - if (this.manualProgress) { - this.manualProgressOff(); - } +Component.registerComponent('MenuItem', MenuItem); - if (this.manualTimeUpdates) { - this.manualTimeUpdatesOff(); - } +/** + * @file text-track-menu-item.js + */ +/** + * The specific menu item type for selecting a language within a text track kind + * + * @extends MenuItem + */ - _Component.prototype.dispose.call(this); - }; +var TextTrackMenuItem = function (_MenuItem) { + inherits(TextTrackMenuItem, _MenuItem); /** - * Clear out a single `TrackList` or an array of `TrackLists` given their names. + * Creates an instance of this class. * - * > Note: Techs without source handlers should call this between sources for `video` - * & `audio` tracks. You don't want to use them between tracks! + * @param {Player} player + * The `Player` that this class should be attached to. * - * @param {string[]|string} types - * TrackList names to clear, valid names are `video`, `audio`, and - * `text`. + * @param {Object} [options] + * The key/value store of player options. */ + function TextTrackMenuItem(player, options) { + classCallCheck(this, TextTrackMenuItem); + var track = options.track; + var tracks = player.textTracks(); - Tech.prototype.clearTracks = function clearTracks(types) { - var _this2 = this; + // Modify options for parent MenuItem class's init. + options.label = track.label || track.language || 'Unknown'; + options.selected = track.mode === 'showing'; - types = [].concat(types); - // clear out all tracks because we can't reuse them between techs - types.forEach(function (type) { - var list = _this2[type + 'Tracks']() || []; - var i = list.length; + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); - while (i--) { - var track = list[i]; + _this.track = track; + var changeHandler = bind(_this, _this.handleTracksChange); + var selectedLanguageChangeHandler = bind(_this, _this.handleSelectedLanguageChange); - if (type === 'text') { - _this2.removeRemoteTextTrack(track); - } - list.removeTrack(track); - } + player.on(['loadstart', 'texttrackchange'], changeHandler); + tracks.addEventListener('change', changeHandler); + tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler); + _this.on('dispose', function () { + tracks.removeEventListener('change', changeHandler); + tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler); }); - }; - - /** - * Remove any TextTracks added via addRemoteTextTrack that are - * flagged for automatic garbage collection - */ + // iOS7 doesn't dispatch change events to TextTrackLists when an + // associated track's mode changes. Without something like + // Object.observe() (also not present on iOS7), it's not + // possible to detect changes to the mode attribute and polyfill + // the change event. As a poor substitute, we manually dispatch + // change events whenever the controls modify the mode. + if (tracks.onchange === undefined) { + var event = void 0; - Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() { - var list = this.autoRemoteTextTracks_ || []; - var i = list.length; + _this.on(['tap', 'click'], function () { + if (_typeof(window_1.Event) !== 'object') { + // Android 2.3 throws an Illegal Constructor error for window.Event + try { + event = new window_1.Event('change'); + } catch (err) { + // continue regardless of error + } + } - while (i--) { - var track = list[i]; + if (!event) { + event = document_1.createEvent('Event'); + event.initEvent('change', true, true); + } - this.removeRemoteTextTrack(track); + tracks.dispatchEvent(event); + }); } - }; + return _this; + } /** - * Reset the tech, which will removes all sources and reset the internal readyState. + * This gets called when an `TextTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. * - * @abstract + * @param {EventTarget~Event} event + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. + * + * @listens tap + * @listens click */ - Tech.prototype.reset = function reset() {}; + TextTrackMenuItem.prototype.handleClick = function handleClick(event) { + var kind = this.track.kind; + var kinds = this.track.kinds; + var tracks = this.player_.textTracks(); - /** - * Get or set an error on the Tech. - * - * @param {MediaError} [err] - * Error to set on the Tech - * - * @return {MediaError|null} - * The current error object on the tech, or null if there isn't one. - */ + if (!kinds) { + kinds = [kind]; + } + _MenuItem.prototype.handleClick.call(this, event); - Tech.prototype.error = function error(err) { - if (err !== undefined) { - this.error_ = new _mediaError2['default'](err); - this.trigger('error'); + if (!tracks) { + return; + } + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + if (track === this.track && kinds.indexOf(track.kind) > -1) { + if (track.mode !== 'showing') { + track.mode = 'showing'; + } + } else if (track.mode !== 'disabled') { + track.mode = 'disabled'; + } } - return this.error_; }; /** - * Returns the `TimeRange`s that have been played through for the current source. + * Handle text track list change * - * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`. - * It only checks wether the source has played at all or not. + * @param {EventTarget~Event} event + * The `change` event that caused this function to be called. * - * @return {TimeRange} - * - A single time range if this video has played - * - An empty set of ranges if not. + * @listens TextTrackList#change */ - Tech.prototype.played = function played() { - if (this.hasStarted_) { - return (0, _timeRanges.createTimeRange)(0, 0); - } - return (0, _timeRanges.createTimeRange)(); + TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + this.selected(this.track.mode === 'showing'); }; - /** - * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was - * previously called. - * - * @fires Tech#timeupdate - */ + TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { + if (this.track.mode === 'showing') { + var selectedLanguage = this.player_.cache_.selectedLanguage; + // Don't replace the kind of track across the same language + if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) { + return; + } - Tech.prototype.setCurrentTime = function setCurrentTime() { - // improve the accuracy of manual timeupdates - if (this.manualTimeUpdates) { - /** - * A manual `timeupdate` event. - * - * @event Tech#timeupdate - * @type {EventTarget~Event} - */ - this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true }); + this.player_.cache_.selectedLanguage = { + enabled: true, + language: this.track.language, + kind: this.track.kind + }; } }; + return TextTrackMenuItem; +}(MenuItem); + +Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem); + +/** + * @file off-text-track-menu-item.js + */ +/** + * A special menu item for turning of a specific type of text track + * + * @extends TextTrackMenuItem + */ + +var OffTextTrackMenuItem = function (_TextTrackMenuItem) { + inherits(OffTextTrackMenuItem, _TextTrackMenuItem); + /** - * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and - * {@link TextTrackList} events. + * Creates an instance of this class. * - * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`. + * @param {Player} player + * The `Player` that this class should be attached to. * - * @fires Tech#audiotrackchange - * @fires Tech#videotrackchange - * @fires Tech#texttrackchange + * @param {Object} [options] + * The key/value store of player options. */ + function OffTextTrackMenuItem(player, options) { + classCallCheck(this, OffTextTrackMenuItem); + // Create pseudo track info + // Requires options['kind'] + options.track = { + player: player, + kind: options.kind, + kinds: options.kinds, + 'default': false, + mode: 'disabled' + }; - Tech.prototype.initTrackListeners = function initTrackListeners() { - var _this3 = this; - - /** - * Triggered when tracks are added or removed on the Tech {@link AudioTrackList} - * - * @event Tech#audiotrackchange - * @type {EventTarget~Event} - */ - - /** - * Triggered when tracks are added or removed on the Tech {@link VideoTrackList} - * - * @event Tech#videotrackchange - * @type {EventTarget~Event} - */ + if (!options.kinds) { + options.kinds = [options.kind]; + } - /** - * Triggered when tracks are added or removed on the Tech {@link TextTrackList} - * - * @event Tech#texttrackchange - * @type {EventTarget~Event} - */ - TRACK_TYPES.NORMAL.names.forEach(function (name) { - var props = TRACK_TYPES.NORMAL[name]; - var trackListChanges = function trackListChanges() { - _this3.trigger(name + 'trackchange'); - }; + if (options.label) { + options.track.label = options.label; + } else { + options.track.label = options.kinds.join(' and ') + ' off'; + } - var tracks = _this3[props.getterName](); + // MenuItem is selectable + options.selectable = true; - tracks.addEventListener('removetrack', trackListChanges); - tracks.addEventListener('addtrack', trackListChanges); + var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options)); - _this3.on('dispose', function () { - tracks.removeEventListener('removetrack', trackListChanges); - tracks.removeEventListener('addtrack', trackListChanges); - }); - }); - }; + _this.selected(true); + return _this; + } /** - * Emulate TextTracks using vtt.js if necessary + * Handle text track change * - * @fires Tech#vttjsloaded - * @fires Tech#vttjserror + * @param {EventTarget~Event} event + * The event that caused this function to run */ - Tech.prototype.addWebVttScript_ = function addWebVttScript_() { - var _this4 = this; + OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) { + var tracks = this.player().textTracks(); + var selected = true; - if (_window2['default'].WebVTT) { - return; + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; + + if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') { + selected = false; + break; + } } - // Initially, Tech.el_ is a child of a dummy-div wait until the Component system - // signals that the Tech is ready at which point Tech.el_ is part of the DOM - // before inserting the WebVTT script - if (_document2['default'].body.contains(this.el())) { - var vtt = _dereq_(104); + this.selected(selected); + }; - // load via require if available and vtt.js script location was not passed in - // as an option. novtt builds will turn the above require call into an empty object - // which will cause this if check to always fail. - if (!this.options_['vtt.js'] && (0, _obj.isPlain)(vtt) && Object.keys(vtt).length > 0) { - this.trigger('vttjsloaded'); - return; - } + OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) { + var tracks = this.player().textTracks(); + var allHidden = true; - // load vtt.js via the script location option or the cdn of no location was - // passed in - var script = _document2['default'].createElement('script'); + for (var i = 0, l = tracks.length; i < l; i++) { + var track = tracks[i]; - script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.3/vtt.min.js'; - script.onload = function () { - /** - * Fired when vtt.js is loaded. - * - * @event Tech#vttjsloaded - * @type {EventTarget~Event} - */ - _this4.trigger('vttjsloaded'); - }; - script.onerror = function () { - /** - * Fired when vtt.js was not loaded due to an error - * - * @event Tech#vttjsloaded - * @type {EventTarget~Event} - */ - _this4.trigger('vttjserror'); + if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') { + allHidden = false; + break; + } + } + + if (allHidden) { + this.player_.cache_.selectedLanguage = { + enabled: false }; - this.on('dispose', function () { - script.onload = null; - script.onerror = null; - }); - // but have not loaded yet and we set it to true before the inject so that - // we don't overwrite the injected window.WebVTT if it loads right away - _window2['default'].WebVTT = true; - this.el().parentNode.appendChild(script); - } else { - this.ready(this.addWebVttScript_); } }; + return OffTextTrackMenuItem; +}(TextTrackMenuItem); + +Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem); + +/** + * @file text-track-button.js + */ +/** + * The base class for buttons that toggle specific text track types (e.g. subtitles) + * + * @extends MenuButton + */ + +var TextTrackButton = function (_TrackButton) { + inherits(TextTrackButton, _TrackButton); + /** - * Emulate texttracks + * Creates an instance of this class. + * + * @param {Player} player + * The `Player` that this class should be attached to. * + * @param {Object} [options={}] + * The key/value store of player options. */ + function TextTrackButton(player) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + classCallCheck(this, TextTrackButton); + options.tracks = player.textTracks(); - Tech.prototype.emulateTextTracks = function emulateTextTracks() { - var _this5 = this; + return possibleConstructorReturn(this, _TrackButton.call(this, player, options)); + } - var tracks = this.textTracks(); - var remoteTracks = this.remoteTextTracks(); - var handleAddTrack = function handleAddTrack(e) { - return tracks.addTrack(e.track); - }; - var handleRemoveTrack = function handleRemoveTrack(e) { - return tracks.removeTrack(e.track); - }; + /** + * Create a menu item for each text track + * + * @param {TextTrackMenuItem[]} [items=[]] + * Existing array of items to use during creation + * + * @return {TextTrackMenuItem[]} + * Array of menu items that were created + */ - remoteTracks.on('addtrack', handleAddTrack); - remoteTracks.on('removetrack', handleRemoveTrack); - this.addWebVttScript_(); + TextTrackButton.prototype.createItems = function createItems() { + var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem; - var updateDisplay = function updateDisplay() { - return _this5.trigger('texttrackchange'); - }; - var textTracksChanges = function textTracksChanges() { - updateDisplay(); + // Label is an overide for the [track] off label + // USed to localise captions/subtitles + var label = void 0; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; + if (this.label_) { + label = this.label_ + ' off'; + } + // Add an OFF menu item to turn all tracks off + items.push(new OffTextTrackMenuItem(this.player_, { + kinds: this.kinds_, + kind: this.kind_, + label: label + })); - track.removeEventListener('cuechange', updateDisplay); - if (track.mode === 'showing') { - track.addEventListener('cuechange', updateDisplay); - } + this.hideThreshold_ += 1; + + var tracks = this.player_.textTracks(); + + if (!Array.isArray(this.kinds_)) { + this.kinds_ = [this.kind_]; + } + + for (var i = 0; i < tracks.length; i++) { + var track = tracks[i]; + + // only add tracks that are of an appropriate kind and have a label + if (this.kinds_.indexOf(track.kind) > -1) { + + var item = new TrackMenuItem(this.player_, { + track: track, + // MenuItem is selectable + selectable: true + }); + + item.addClass('vjs-' + track.kind + '-menu-item'); + items.push(item); } - }; + } - textTracksChanges(); - tracks.addEventListener('change', textTracksChanges); - tracks.addEventListener('addtrack', textTracksChanges); - tracks.addEventListener('removetrack', textTracksChanges); + return items; + }; - this.on('dispose', function () { - remoteTracks.off('addtrack', handleAddTrack); - remoteTracks.off('removetrack', handleRemoveTrack); - tracks.removeEventListener('change', textTracksChanges); - tracks.removeEventListener('addtrack', textTracksChanges); - tracks.removeEventListener('removetrack', textTracksChanges); + return TextTrackButton; +}(TrackButton); - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; +Component.registerComponent('TextTrackButton', TextTrackButton); + +/** + * @file chapters-track-menu-item.js + */ +/** + * The chapter track menu item + * + * @extends MenuItem + */ - track.removeEventListener('cuechange', updateDisplay); - } - }); - }; +var ChaptersTrackMenuItem = function (_MenuItem) { + inherits(ChaptersTrackMenuItem, _MenuItem); /** - * Create and returns a remote {@link TextTrack} object. - * - * @param {string} kind - * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata) - * - * @param {string} [label] - * Label to identify the text track + * Creates an instance of this class. * - * @param {string} [language] - * Two letter language abbreviation + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {TextTrack} - * The TextTrack that gets created. + * @param {Object} [options] + * The key/value store of player options. */ + function ChaptersTrackMenuItem(player, options) { + classCallCheck(this, ChaptersTrackMenuItem); + var track = options.track; + var cue = options.cue; + var currentTime = player.currentTime(); - Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) { - if (!kind) { - throw new Error('TextTrack kind is required but was not provided'); - } + // Modify options for parent MenuItem class's init. + options.selectable = true; + options.label = cue.text; + options.selected = cue.startTime <= currentTime && currentTime < cue.endTime; - return createTrackHelper(this, kind, label, language); - }; + var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options)); + + _this.track = track; + _this.cue = cue; + track.addEventListener('cuechange', bind(_this, _this.update)); + return _this; + } /** - * Create an emulated TextTrack for use by addRemoteTextTrack - * - * This is intended to be overridden by classes that inherit from - * Tech in order to create native or custom TextTracks. - * - * @param {Object} options - * The object should contain the options to initialize the TextTrack with. - * - * @param {string} [options.kind] - * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata). - * - * @param {string} [options.label]. - * Label to identify the text track + * This gets called when an `ChaptersTrackMenuItem` is "clicked". See + * {@link ClickableComponent} for more detailed information on what a click can be. * - * @param {string} [options.language] - * Two letter language abbreviation. + * @param {EventTarget~Event} [event] + * The `keydown`, `tap`, or `click` event that caused this function to be + * called. * - * @return {HTMLTrackElement} - * The track element that gets created. + * @listens tap + * @listens click */ - Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) { - var track = (0, _mergeOptions2['default'])(options, { - tech: this - }); - - return new TRACK_TYPES.REMOTE.remoteTextEl.TrackClass(track); + ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) { + _MenuItem.prototype.handleClick.call(this); + this.player_.currentTime(this.cue.startTime); + this.update(this.cue.startTime); }; /** - * Creates a remote text track object and returns an html track element. - * - * > Note: This can be an emulated {@link HTMLTrackElement} or a native one. - * - * @param {Object} options - * See {@link Tech#createRemoteTextTrack} for more detailed properties. - * - * @param {boolean} [manualCleanup=true] - * - When false: the TextTrack will be automatically removed from the video - * element whenever the source changes - * - When True: The TextTrack will have to be cleaned up manually + * Update chapter menu item * - * @return {HTMLTrackElement} - * An Html Track Element. + * @param {EventTarget~Event} [event] + * The `cuechange` event that caused this function to run. * - * @deprecated The default functionality for this function will be equivalent - * to "manualCleanup=false" in the future. The manualCleanup parameter will - * also be removed. + * @listens TextTrack#cuechange */ - Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() { - var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var manualCleanup = arguments[1]; - - var htmlTrackElement = this.createRemoteTextTrack(options); - - if (manualCleanup !== true && manualCleanup !== false) { - // deprecation warning - _log2['default'].warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js'); - manualCleanup = true; - } - - // store HTMLTrackElement and TextTrack to remote list - this.remoteTextTrackEls().addTrackElement_(htmlTrackElement); - this.remoteTextTracks().addTrack(htmlTrackElement.track); - - if (manualCleanup !== true) { - // create the TextTrackList if it doesn't exist - this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track); - } + ChaptersTrackMenuItem.prototype.update = function update(event) { + var cue = this.cue; + var currentTime = this.player_.currentTime(); - return htmlTrackElement; + // vjs.log(currentTime, cue.startTime); + this.selected(cue.startTime <= currentTime && currentTime < cue.endTime); }; - /** - * Remove a remote text track from the remote `TextTrackList`. - * - * @param {TextTrack} track - * `TextTrack` to remove from the `TextTrackList` - */ + return ChaptersTrackMenuItem; +}(MenuItem); +Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem); - Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) { - var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track); +/** + * @file chapters-button.js + */ +/** + * The button component for toggling and selecting chapters + * Chapters act much differently than other text tracks + * Cues are navigation vs. other tracks of alternative languages + * + * @extends TextTrackButton + */ - // remove HTMLTrackElement and TextTrack from remote list - this.remoteTextTrackEls().removeTrackElement_(trackElement); - this.remoteTextTracks().removeTrack(track); - this.autoRemoteTextTracks_.removeTrack(track); - }; +var ChaptersButton = function (_TextTrackButton) { + inherits(ChaptersButton, _TextTrackButton); /** - * Gets available media playback quality metrics as specified by the W3C's Media - * Playback Quality API. + * Creates an instance of this class. * - * @see [Spec]{@link https://wicg.github.io/media-playback-quality} + * @param {Player} player + * The `Player` that this class should be attached to. * - * @return {Object} - * An object with supported media playback quality metrics + * @param {Object} [options] + * The key/value store of player options. * - * @abstract + * @param {Component~ReadyCallback} [ready] + * The function to call when this function is ready. */ - - - Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() { - return {}; - }; + function ChaptersButton(player, options, ready) { + classCallCheck(this, ChaptersButton); + return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready)); + } /** - * A method to set a poster from a `Tech`. + * Builds the default DOM `className`. * - * @abstract + * @return {string} + * The DOM `className` for this object. */ - Tech.prototype.setPoster = function setPoster() {}; + ChaptersButton.prototype.buildCSSClass = function buildCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this); + }; + + ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() { + return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this); + }; /** - * A method to check for the presence of the 'playsinine'