3 exports.__esModule = true;
5 var _window = require('global/window');
7 var _window2 = _interopRequireDefault(_window);
9 var _dom = require('./utils/dom.js');
11 var Dom = _interopRequireWildcard(_dom);
13 var _fn = require('./utils/fn.js');
15 var Fn = _interopRequireWildcard(_fn);
17 var _guid = require('./utils/guid.js');
19 var Guid = _interopRequireWildcard(_guid);
21 var _events = require('./utils/events.js');
23 var Events = _interopRequireWildcard(_events);
25 var _log = require('./utils/log.js');
27 var _log2 = _interopRequireDefault(_log);
29 var _toTitleCase = require('./utils/to-title-case.js');
31 var _toTitleCase2 = _interopRequireDefault(_toTitleCase);
33 var _mergeOptions = require('./utils/merge-options.js');
35 var _mergeOptions2 = _interopRequireDefault(_mergeOptions);
37 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; } }
39 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
41 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
42 * Player Component - Base class for all UI objects
49 * Base class for all UI Components.
50 * Components are UI objects which represent both a javascript object and an element
51 * in the DOM. They can be children of other components, and can have
52 * children themselves.
54 * Components can also use methods from {@link EventTarget}
56 var Component = function () {
59 * A callback that is called when a component is ready. Does not have any
60 * paramters and any callback value will be ignored.
62 * @callback Component~ReadyCallback
67 * Creates an instance of this class.
69 * @param {Player} player
70 * The `Player` that this class should be attached to.
72 * @param {Object} [options]
73 * The key/value store of player options.
75 * @param {Object[]} [options.children]
76 * An array of children objects to intialize this component with. Children objects have
77 * a name property that will be used if more than one component of the same type needs to be
80 * @param {Component~ReadyCallback} [ready]
81 * Function that gets called when the `Component` is ready.
83 function Component(player, options, ready) {
84 _classCallCheck(this, Component);
86 // The component might be the player itself and we can't pass `this` to super
87 if (!player && this.play) {
88 this.player_ = player = this; // eslint-disable-line
90 this.player_ = player;
93 // Make a copy of prototype.options_ to protect against overriding defaults
94 this.options_ = (0, _mergeOptions2['default'])({}, this.options_);
96 // Updated options with supplied options
97 options = this.options_ = (0, _mergeOptions2['default'])(this.options_, options);
99 // Get ID from options or options element if one is supplied
100 this.id_ = options.id || options.el && options.el.id;
102 // If there was no ID from the options, generate one
104 // Don't require the player ID function in the case of mock players
105 var id = player && player.id && player.id() || 'no_player';
107 this.id_ = id + '_component_' + Guid.newGUID();
110 this.name_ = options.name || null;
112 // Create element if one wasn't provided in options
114 this.el_ = options.el;
115 } else if (options.createEl !== false) {
116 this.el_ = this.createEl();
120 this.childIndex_ = {};
121 this.childNameIndex_ = {};
123 // Add any child components in options
124 if (options.initChildren !== false) {
129 // Don't want to trigger ready here or it will before init is actually
130 // finished for all children that run this constructor
132 if (options.reportTouchActivity !== false) {
133 this.enableTouchActivity();
138 * Dispose of the `Component` and all child components.
140 * @fires Component#dispose
144 Component.prototype.dispose = function dispose() {
147 * Triggered when a `Component` is disposed.
149 * @event Component#dispose
150 * @type {EventTarget~Event}
152 * @property {boolean} [bubbles=false]
153 * set to false so that the close event does not
156 this.trigger({ type: 'dispose', bubbles: false });
158 // Dispose all children.
159 if (this.children_) {
160 for (var i = this.children_.length - 1; i >= 0; i--) {
161 if (this.children_[i].dispose) {
162 this.children_[i].dispose();
167 // Delete child references
168 this.children_ = null;
169 this.childIndex_ = null;
170 this.childNameIndex_ = null;
172 // Remove all event listeners.
175 // Remove element from DOM
176 if (this.el_.parentNode) {
177 this.el_.parentNode.removeChild(this.el_);
180 Dom.removeElData(this.el_);
185 * Return the {@link Player} that the `Component` has attached to.
188 * The player that this `Component` has attached to.
192 Component.prototype.player = function player() {
197 * Deep merge of options objects with new options.
198 * > Note: When both `obj` and `options` contain properties whose values are objects.
199 * The two properties get merged using {@link module:mergeOptions}
201 * @param {Object} obj
202 * The object that contains new options.
205 * A new object of `this.options_` and `obj` merged together.
207 * @deprecated since version 5
211 Component.prototype.options = function options(obj) {
212 _log2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
215 return this.options_;
218 this.options_ = (0, _mergeOptions2['default'])(this.options_, obj);
219 return this.options_;
223 * Get the `Component`s DOM element
226 * The DOM element for this `Component`.
230 Component.prototype.el = function el() {
235 * Create the `Component`s DOM element.
237 * @param {string} [tagName]
238 * Element's DOM node type. e.g. 'div'
240 * @param {Object} [properties]
241 * An object of properties that should be set.
243 * @param {Object} [attributes]
244 * An object of attributes that should be set.
247 * The element that gets created.
251 Component.prototype.createEl = function createEl(tagName, properties, attributes) {
252 return Dom.createEl(tagName, properties, attributes);
256 * Localize a string given the string in english.
258 * @param {string} string
259 * The string to localize.
262 * The localized string or if no localization exists the english string.
266 Component.prototype.localize = function localize(string) {
267 var code = this.player_.language && this.player_.language();
268 var languages = this.player_.languages && this.player_.languages();
270 if (!code || !languages) {
274 var language = languages[code];
276 if (language && language[string]) {
277 return language[string];
280 var primaryCode = code.split('-')[0];
281 var primaryLang = languages[primaryCode];
283 if (primaryLang && primaryLang[string]) {
284 return primaryLang[string];
291 * Return the `Component`s DOM element. This is where children get inserted.
292 * This will usually be the the same as the element returned in {@link Component#el}.
295 * The content element for this `Component`.
299 Component.prototype.contentEl = function contentEl() {
300 return this.contentEl_ || this.el_;
304 * Get this `Component`s ID
307 * The id of this `Component`
311 Component.prototype.id = function id() {
316 * Get the `Component`s name. The name gets used to reference the `Component`
317 * and is set during registration.
320 * The name of this `Component`.
324 Component.prototype.name = function name() {
329 * Get an array of all child components
336 Component.prototype.children = function children() {
337 return this.children_;
341 * Returns the child `Component` with the given `id`.
344 * The id of the child `Component` to get.
346 * @return {Component|undefined}
347 * The child `Component` with the given `id` or undefined.
351 Component.prototype.getChildById = function getChildById(id) {
352 return this.childIndex_[id];
356 * Returns the child `Component` with the given `name`.
358 * @param {string} name
359 * The name of the child `Component` to get.
361 * @return {Component|undefined}
362 * The child `Component` with the given `name` or undefined.
366 Component.prototype.getChild = function getChild(name) {
371 name = (0, _toTitleCase2['default'])(name);
373 return this.childNameIndex_[name];
377 * Add a child `Component` inside the current `Component`.
380 * @param {string|Component} child
381 * The name or instance of a child to add.
383 * @param {Object} [options={}]
384 * The key/value store of options that will get passed to children of
387 * @param {number} [index=this.children_.length]
388 * The index to attempt to add a child into.
390 * @return {Component}
391 * The `Component` that gets added as a child. When using a string the
392 * `Component` will get created by this process.
396 Component.prototype.addChild = function addChild(child) {
397 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
398 var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length;
400 var component = void 0;
401 var componentName = void 0;
403 // If child is a string, create component with options
404 if (typeof child === 'string') {
405 componentName = (0, _toTitleCase2['default'])(child);
407 // Options can also be specified as a boolean,
408 // so convert to an empty object if false.
413 // Same as above, but true is deprecated so show a warning.
414 if (options === true) {
415 _log2['default'].warn('Initializing a child component with `true` is deprecated.' + 'Children should be defined in an array when possible, ' + 'but if necessary use an object instead of `true`.');
419 var componentClassName = options.componentClass || componentName;
421 // Set name through options
422 options.name = componentName;
424 // Create a new object & element for this controls set
425 // If there's no .player_, this is a player
426 var ComponentClass = Component.getComponent(componentClassName);
428 if (!ComponentClass) {
429 throw new Error('Component ' + componentClassName + ' does not exist');
432 // data stored directly on the videojs object may be
433 // misidentified as a component to retain
434 // backwards-compatibility with 4.x. check to make sure the
435 // component class can be instantiated.
436 if (typeof ComponentClass !== 'function') {
440 component = new ComponentClass(this.player_ || this, options);
442 // child is a component instance
447 this.children_.splice(index, 0, component);
449 if (typeof component.id === 'function') {
450 this.childIndex_[component.id()] = component;
453 // If a name wasn't used to create the component, check if we can use the
454 // name function of the component
455 componentName = componentName || component.name && (0, _toTitleCase2['default'])(component.name());
458 this.childNameIndex_[componentName] = component;
461 // Add the UI object's element to the container div (box)
462 // Having an element is not required
463 if (typeof component.el === 'function' && component.el()) {
464 var childNodes = this.contentEl().children;
465 var refNode = childNodes[index] || null;
467 this.contentEl().insertBefore(component.el(), refNode);
470 // Return so it can stored on parent object if desired.
475 * Remove a child `Component` from this `Component`s list of children. Also removes
476 * the child `Component`s element from this `Component`s element.
478 * @param {Component} component
479 * The child `Component` to remove.
483 Component.prototype.removeChild = function removeChild(component) {
484 if (typeof component === 'string') {
485 component = this.getChild(component);
488 if (!component || !this.children_) {
492 var childFound = false;
494 for (var i = this.children_.length - 1; i >= 0; i--) {
495 if (this.children_[i] === component) {
497 this.children_.splice(i, 1);
506 this.childIndex_[component.id()] = null;
507 this.childNameIndex_[component.name()] = null;
509 var compEl = component.el();
511 if (compEl && compEl.parentNode === this.contentEl()) {
512 this.contentEl().removeChild(component.el());
517 * Add and initialize default child `Component`s based upon options.
521 Component.prototype.initChildren = function initChildren() {
524 var children = this.options_.children;
527 // `this` is `parent`
528 var parentOptions = this.options_;
530 var handleAdd = function handleAdd(child) {
531 var name = child.name;
532 var opts = child.opts;
534 // Allow options for children to be set at the parent options
535 // e.g. videojs(id, { controlBar: false });
536 // instead of videojs(id, { children: { controlBar: false });
537 if (parentOptions[name] !== undefined) {
538 opts = parentOptions[name];
541 // Allow for disabling default components
542 // e.g. options['children']['posterImage'] = false
543 if (opts === false) {
547 // Allow options to be passed as a simple boolean if no configuration
553 // We also want to pass the original player options
554 // to each component as well so they don't need to
555 // reach back into the player for options later.
556 opts.playerOptions = _this.options_.playerOptions;
558 // Create and add the child component.
559 // Add a direct reference to the child by name on the parent instance.
560 // If two of the same component are used, different names should be supplied
562 var newChild = _this.addChild(name, opts);
565 _this[name] = newChild;
569 // Allow for an array of children details to passed in the options
570 var workingChildren = void 0;
571 var Tech = Component.getComponent('Tech');
573 if (Array.isArray(children)) {
574 workingChildren = children;
576 workingChildren = Object.keys(children);
580 // children that are in this.options_ but also in workingChildren would
581 // give us extra children we do not want. So, we want to filter them out.
582 .concat(Object.keys(this.options_).filter(function (child) {
583 return !workingChildren.some(function (wchild) {
584 if (typeof wchild === 'string') {
585 return child === wchild;
587 return child === wchild.name;
589 })).map(function (child) {
593 if (typeof child === 'string') {
595 opts = children[name] || _this.options_[name] || {};
601 return { name: name, opts: opts };
602 }).filter(function (child) {
603 // we have to make sure that child.name isn't in the techOrder since
604 // techs are registerd as Components but can't aren't compatible
605 // See https://github.com/videojs/video.js/issues/2772
606 var c = Component.getComponent(child.opts.componentClass || (0, _toTitleCase2['default'])(child.name));
608 return c && !Tech.isTech(c);
609 }).forEach(handleAdd);
614 * Builds the default DOM class name. Should be overriden by sub-components.
617 * The DOM class name for this object.
623 Component.prototype.buildCSSClass = function buildCSSClass() {
624 // Child classes can include a function that does:
625 // return 'CLASS NAME' + this._super();
630 * Add an `event listener` to this `Component`s element.
632 * The benefit of using this over the following:
633 * - `VjsEvents.on(otherElement, 'eventName', myFunc)`
634 * - `otherComponent.on('eventName', myFunc)`
636 * 1. Is that the listeners will get cleaned up when either component gets disposed.
637 * 1. It will also bind `myComponent` as the context of `myFunc`.
638 * > NOTE: If you remove the element from the DOM that has used `on` you need to
639 * clean up references using: `myComponent.trigger(el, 'dispose')`
640 * This will also allow the browser to garbage collect it. In special
641 * cases such as with `window` and `document`, which are both permanent,
642 * this is not necessary.
644 * @param {string|Component|string[]} [first]
645 * The event name, and array of event names, or another `Component`.
647 * @param {EventTarget~EventListener|string|string[]} [second]
648 * The listener function, an event name, or an Array of events names.
650 * @param {EventTarget~EventListener} [third]
651 * The event handler if `first` is a `Component` and `second` is an event name
652 * or an Array of event names.
654 * @return {Component}
655 * Returns itself; method can be chained.
657 * @listens Component#dispose
661 Component.prototype.on = function on(first, second, third) {
664 if (typeof first === 'string' || Array.isArray(first)) {
665 Events.on(this.el_, first, Fn.bind(this, second));
667 // Targeting another component or element
671 var fn = Fn.bind(this, third);
673 // When this component is disposed, remove the listener from the other component
674 var removeOnDispose = function removeOnDispose() {
675 return _this2.off(target, type, fn);
678 // Use the same function ID so we can remove it later it using the ID
679 // of the original listener
680 removeOnDispose.guid = fn.guid;
681 this.on('dispose', removeOnDispose);
683 // If the other component is disposed first we need to clean the reference
684 // to the other component in this component's removeOnDispose listener
685 // Otherwise we create a memory leak.
686 var cleanRemover = function cleanRemover() {
687 return _this2.off('dispose', removeOnDispose);
690 // Add the same function ID so we can easily remove it later
691 cleanRemover.guid = fn.guid;
693 // Check if this is a DOM node
694 if (first.nodeName) {
695 // Add the listener to the other element
696 Events.on(target, type, fn);
697 Events.on(target, 'dispose', cleanRemover);
699 // Should be a component
700 // Not using `instanceof Component` because it makes mock players difficult
701 } else if (typeof first.on === 'function') {
702 // Add the listener to the other component
704 target.on('dispose', cleanRemover);
712 * Remove an event listener from this `Component`s element. If the second argument is
713 * exluded all listeners for the type passed in as the first argument will be removed.
715 * @param {string|Component|string[]} [first]
716 * The event name, and array of event names, or another `Component`.
718 * @param {EventTarget~EventListener|string|string[]} [second]
719 * The listener function, an event name, or an Array of events names.
721 * @param {EventTarget~EventListener} [third]
722 * The event handler if `first` is a `Component` and `second` is an event name
723 * or an Array of event names.
725 * @return {Component}
726 * Returns itself; method can be chained.
730 Component.prototype.off = function off(first, second, third) {
731 if (!first || typeof first === 'string' || Array.isArray(first)) {
732 Events.off(this.el_, first, second);
736 // Ensure there's at least a guid, even if the function hasn't been used
737 var fn = Fn.bind(this, third);
739 // Remove the dispose listener on this component,
740 // which was given the same guid as the event listener
741 this.off('dispose', fn);
743 if (first.nodeName) {
744 // Remove the listener
745 Events.off(target, type, fn);
746 // Remove the listener for cleaning the dispose listener
747 Events.off(target, 'dispose', fn);
749 target.off(type, fn);
750 target.off('dispose', fn);
758 * Add an event listener that gets triggered only once and then gets removed.
760 * @param {string|Component|string[]} [first]
761 * The event name, and array of event names, or another `Component`.
763 * @param {EventTarget~EventListener|string|string[]} [second]
764 * The listener function, an event name, or an Array of events names.
766 * @param {EventTarget~EventListener} [third]
767 * The event handler if `first` is a `Component` and `second` is an event name
768 * or an Array of event names.
770 * @return {Component}
771 * Returns itself; method can be chained.
775 Component.prototype.one = function one(first, second, third) {
777 _arguments = arguments;
779 if (typeof first === 'string' || Array.isArray(first)) {
780 Events.one(this.el_, first, Fn.bind(this, second));
784 var fn = Fn.bind(this, third);
786 var newFunc = function newFunc() {
787 _this3.off(target, type, newFunc);
788 fn.apply(null, _arguments);
791 // Keep the same function ID so we can remove it later
792 newFunc.guid = fn.guid;
794 this.on(target, type, newFunc);
801 * Trigger an event on an element.
803 * @param {EventTarget~Event|Object|string} event
804 * The event name, and Event, or an event-like object with a type attribute
805 * set to the event name.
807 * @param {Object} [hash]
808 * Data hash to pass along with the event
810 * @return {Component}
811 * Returns itself; method can be chained.
815 Component.prototype.trigger = function trigger(event, hash) {
816 Events.trigger(this.el_, event, hash);
821 * Bind a listener to the component's ready state. If the ready event has already
822 * happened it will trigger the function immediately.
824 * @param {Component~ReadyCallback} fn
825 * A function to call when ready is triggered.
827 * @param {boolean} [sync=false]
828 * Execute the listener synchronously if `Component` is ready.
830 * @return {Component}
831 * Returns itself; method can be chained.
835 Component.prototype.ready = function ready(fn) {
836 var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
843 // Call the function asynchronously by default for consistency
844 this.setTimeout(fn, 1);
847 this.readyQueue_ = this.readyQueue_ || [];
848 this.readyQueue_.push(fn);
855 * Trigger all the ready listeners for this `Component`.
857 * @fires Component#ready
861 Component.prototype.triggerReady = function triggerReady() {
862 this.isReady_ = true;
864 // Ensure ready is triggerd asynchronously
865 this.setTimeout(function () {
866 var readyQueue = this.readyQueue_;
869 this.readyQueue_ = [];
871 if (readyQueue && readyQueue.length > 0) {
872 readyQueue.forEach(function (fn) {
877 // Allow for using event listeners also
879 * Triggered when a `Component` is ready.
881 * @event Component#ready
882 * @type {EventTarget~Event}
884 this.trigger('ready');
889 * Find a single DOM element matching a `selector`. This can be within the `Component`s
890 * `contentEl()` or another custom context.
892 * @param {string} selector
893 * A valid CSS selector, which will be passed to `querySelector`.
895 * @param {Element|string} [context=this.contentEl()]
896 * A DOM element within which to query. Can also be a selector string in
897 * which case the first matching element will get used as context. If
898 * missing `this.contentEl()` gets used. If `this.contentEl()` returns
899 * nothing it falls back to `document`.
901 * @return {Element|null}
902 * the dom element that was found, or null
904 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
908 Component.prototype.$ = function $(selector, context) {
909 return Dom.$(selector, context || this.contentEl());
913 * Finds all DOM element matching a `selector`. This can be within the `Component`s
914 * `contentEl()` or another custom context.
916 * @param {string} selector
917 * A valid CSS selector, which will be passed to `querySelectorAll`.
919 * @param {Element|string} [context=this.contentEl()]
920 * A DOM element within which to query. Can also be a selector string in
921 * which case the first matching element will get used as context. If
922 * missing `this.contentEl()` gets used. If `this.contentEl()` returns
923 * nothing it falls back to `document`.
926 * a list of dom elements that were found
928 * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
932 Component.prototype.$$ = function $$(selector, context) {
933 return Dom.$$(selector, context || this.contentEl());
937 * Check if a component's element has a CSS class name.
939 * @param {string} classToCheck
940 * CSS class name to check.
943 * - True if the `Component` has the class.
944 * - False if the `Component` does not have the class`
948 Component.prototype.hasClass = function hasClass(classToCheck) {
949 return Dom.hasElClass(this.el_, classToCheck);
953 * Add a CSS class name to the `Component`s element.
955 * @param {string} classToAdd
956 * CSS class name to add
958 * @return {Component}
959 * Returns itself; method can be chained.
963 Component.prototype.addClass = function addClass(classToAdd) {
964 Dom.addElClass(this.el_, classToAdd);
969 * Remove a CSS class name from the `Component`s element.
971 * @param {string} classToRemove
972 * CSS class name to remove
974 * @return {Component}
975 * Returns itself; method can be chained.
979 Component.prototype.removeClass = function removeClass(classToRemove) {
980 Dom.removeElClass(this.el_, classToRemove);
985 * Add or remove a CSS class name from the component's element.
986 * - `classToToggle` gets added when {@link Component#hasClass} would return false.
987 * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
989 * @param {string} classToToggle
990 * The class to add or remove based on (@link Component#hasClass}
992 * @param {boolean|Dom~predicate} [predicate]
993 * An {@link Dom~predicate} function or a boolean
995 * @return {Component}
996 * Returns itself; method can be chained.
1000 Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) {
1001 Dom.toggleElClass(this.el_, classToToggle, predicate);
1006 * Show the `Component`s element if it is hidden by removing the
1007 * 'vjs-hidden' class name from it.
1009 * @return {Component}
1010 * Returns itself; method can be chained.
1014 Component.prototype.show = function show() {
1015 this.removeClass('vjs-hidden');
1020 * Hide the `Component`s element if it is currently showing by adding the
1021 * 'vjs-hidden` class name to it.
1023 * @return {Component}
1024 * Returns itself; method can be chained.
1028 Component.prototype.hide = function hide() {
1029 this.addClass('vjs-hidden');
1034 * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
1035 * class name to it. Used during fadeIn/fadeOut.
1037 * @return {Component}
1038 * Returns itself; method can be chained.
1044 Component.prototype.lockShowing = function lockShowing() {
1045 this.addClass('vjs-lock-showing');
1050 * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
1051 * class name from it. Used during fadeIn/fadeOut.
1053 * @return {Component}
1054 * Returns itself; method can be chained.
1060 Component.prototype.unlockShowing = function unlockShowing() {
1061 this.removeClass('vjs-lock-showing');
1066 * Get the value of an attribute on the `Component`s element.
1068 * @param {string} attribute
1069 * Name of the attribute to get the value from.
1071 * @return {string|null}
1072 * - The value of the attribute that was asked for.
1073 * - Can be an empty string on some browsers if the attribute does not exist
1075 * - Most browsers will return null if the attibute does not exist or has
1078 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
1082 Component.prototype.getAttribute = function getAttribute(attribute) {
1083 return Dom.getAttribute(this.el_, attribute);
1087 * Set the value of an attribute on the `Component`'s element
1089 * @param {string} attribute
1090 * Name of the attribute to set.
1092 * @param {string} value
1093 * Value to set the attribute to.
1095 * @return {Component}
1096 * Returns itself; method can be chained.
1098 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
1102 Component.prototype.setAttribute = function setAttribute(attribute, value) {
1103 Dom.setAttribute(this.el_, attribute, value);
1108 * Remove an attribute from the `Component`s element.
1110 * @param {string} attribute
1111 * Name of the attribute to remove.
1113 * @return {Component}
1114 * Returns itself; method can be chained.
1116 * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
1120 Component.prototype.removeAttribute = function removeAttribute(attribute) {
1121 Dom.removeAttribute(this.el_, attribute);
1126 * Get or set the width of the component based upon the CSS styles.
1127 * See {@link Component#dimension} for more detailed information.
1129 * @param {number|string} [num]
1130 * The width that you want to set postfixed with '%', 'px' or nothing.
1132 * @param {boolean} [skipListeners]
1133 * Skip the resize event trigger
1135 * @return {Component|number|string}
1136 * - The width when getting, zero if there is no width. Can be a string
1137 * postpixed with '%' or 'px'.
1138 * - Returns itself when setting; method can be chained.
1142 Component.prototype.width = function width(num, skipListeners) {
1143 return this.dimension('width', num, skipListeners);
1147 * Get or set the height of the component based upon the CSS styles.
1148 * See {@link Component#dimension} for more detailed information.
1150 * @param {number|string} [num]
1151 * The height that you want to set postfixed with '%', 'px' or nothing.
1153 * @param {boolean} [skipListeners]
1154 * Skip the resize event trigger
1156 * @return {Component|number|string}
1157 * - The width when getting, zero if there is no width. Can be a string
1158 * postpixed with '%' or 'px'.
1159 * - Returns itself when setting; method can be chained.
1163 Component.prototype.height = function height(num, skipListeners) {
1164 return this.dimension('height', num, skipListeners);
1168 * Set both the width and height of the `Component` element at the same time.
1170 * @param {number|string} width
1171 * Width to set the `Component`s element to.
1173 * @param {number|string} height
1174 * Height to set the `Component`s element to.
1176 * @return {Component}
1177 * Returns itself; method can be chained.
1181 Component.prototype.dimensions = function dimensions(width, height) {
1182 // Skip resize listeners on width for optimization
1183 return this.width(width, true).height(height);
1187 * Get or set width or height of the `Component` element. This is the shared code
1188 * for the {@link Component#width} and {@link Component#height}.
1191 * - If the width or height in an number this will return the number postfixed with 'px'.
1192 * - If the width/height is a percent this will return the percent postfixed with '%'
1193 * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
1194 * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
1195 * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
1196 * for more information
1197 * - If you want the computed style of the component, use {@link Component#currentWidth}
1198 * and {@link {Component#currentHeight}
1200 * @fires Component#resize
1202 * @param {string} widthOrHeight
1203 8 'width' or 'height'
1205 * @param {number|string} [num]
1208 * @param {boolean} [skipListeners]
1209 * Skip resize event trigger
1211 * @return {Component}
1212 * - the dimension when getting or 0 if unset
1213 * - Returns itself when setting; method can be chained.
1217 Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
1218 if (num !== undefined) {
1219 // Set to zero if null or literally NaN (NaN !== NaN)
1220 if (num === null || num !== num) {
1224 // Check if using css width/height (% or px) and adjust
1225 if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
1226 this.el_.style[widthOrHeight] = num;
1227 } else if (num === 'auto') {
1228 this.el_.style[widthOrHeight] = '';
1230 this.el_.style[widthOrHeight] = num + 'px';
1233 // skipListeners allows us to avoid triggering the resize event when setting both width and height
1234 if (!skipListeners) {
1236 * Triggered when a component is resized.
1238 * @event Component#resize
1239 * @type {EventTarget~Event}
1241 this.trigger('resize');
1248 // Not setting a value, so getting it
1249 // Make sure element exists
1254 // Get dimension value from style
1255 var val = this.el_.style[widthOrHeight];
1256 var pxIndex = val.indexOf('px');
1258 if (pxIndex !== -1) {
1259 // Return the pixel value with no 'px'
1260 return parseInt(val.slice(0, pxIndex), 10);
1263 // No px so using % or no style was set, so falling back to offsetWidth/height
1264 // If component has display:none, offset will return 0
1265 // TODO: handle display:none and no dimension style using px
1266 return parseInt(this.el_['offset' + (0, _toTitleCase2['default'])(widthOrHeight)], 10);
1270 * Get the width or the height of the `Component` elements computed style. Uses
1271 * `window.getComputedStyle`.
1273 * @param {string} widthOrHeight
1274 * A string containing 'width' or 'height'. Whichever one you want to get.
1277 * The dimension that gets asked for or 0 if nothing was set
1278 * for that dimension.
1282 Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
1283 var computedWidthOrHeight = 0;
1285 if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
1286 throw new Error('currentDimension only accepts width or height value');
1289 if (typeof _window2['default'].getComputedStyle === 'function') {
1290 var computedStyle = _window2['default'].getComputedStyle(this.el_);
1292 computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
1295 // remove 'px' from variable and parse as integer
1296 computedWidthOrHeight = parseFloat(computedWidthOrHeight);
1298 // if the computed value is still 0, it's possible that the browser is lying
1299 // and we want to check the offset values.
1300 // This code also runs on IE8 and wherever getComputedStyle doesn't exist.
1301 if (computedWidthOrHeight === 0) {
1302 var rule = 'offset' + (0, _toTitleCase2['default'])(widthOrHeight);
1304 computedWidthOrHeight = this.el_[rule];
1307 return computedWidthOrHeight;
1311 * An object that contains width and height values of the `Component`s
1312 * computed style. Uses `window.getComputedStyle`.
1314 * @typedef {Object} Component~DimensionObject
1316 * @property {number} width
1317 * The width of the `Component`s computed style.
1319 * @property {number} height
1320 * The height of the `Component`s computed style.
1324 * Get an object that contains width and height values of the `Component`s
1327 * @return {Component~DimensionObject}
1328 * The dimensions of the components element
1332 Component.prototype.currentDimensions = function currentDimensions() {
1334 width: this.currentDimension('width'),
1335 height: this.currentDimension('height')
1340 * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`.
1342 * @return {number} width
1343 * The width of the `Component`s computed style.
1347 Component.prototype.currentWidth = function currentWidth() {
1348 return this.currentDimension('width');
1352 * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`.
1354 * @return {number} height
1355 * The height of the `Component`s computed style.
1359 Component.prototype.currentHeight = function currentHeight() {
1360 return this.currentDimension('height');
1364 * Set the focus to this component
1368 Component.prototype.focus = function focus() {
1373 * Remove the focus from this component
1377 Component.prototype.blur = function blur() {
1382 * Emit a 'tap' events when touch event support gets detected. This gets used to
1383 * support toggling the controls through a tap on the video. They get enabled
1384 * because every sub-component would have extra overhead otherwise.
1387 * @fires Component#tap
1388 * @listens Component#touchstart
1389 * @listens Component#touchmove
1390 * @listens Component#touchleave
1391 * @listens Component#touchcancel
1392 * @listens Component#touchend
1396 Component.prototype.emitTapEvents = function emitTapEvents() {
1397 // Track the start time so we can determine how long the touch lasted
1399 var firstTouch = null;
1401 // Maximum movement allowed during a touch event to still be considered a tap
1402 // Other popular libs use anywhere from 2 (hammer.js) to 15,
1403 // so 10 seems like a nice, round number.
1404 var tapMovementThreshold = 10;
1406 // The maximum length a touch can be while still being considered a tap
1407 var touchTimeThreshold = 200;
1409 var couldBeTap = void 0;
1411 this.on('touchstart', function (event) {
1412 // If more than one finger, don't consider treating this as a click
1413 if (event.touches.length === 1) {
1414 // Copy pageX/pageY from the object
1416 pageX: event.touches[0].pageX,
1417 pageY: event.touches[0].pageY
1419 // Record start time so we can detect a tap vs. "touch and hold"
1420 touchStart = new Date().getTime();
1421 // Reset couldBeTap tracking
1426 this.on('touchmove', function (event) {
1427 // If more than one finger, don't consider treating this as a click
1428 if (event.touches.length > 1) {
1430 } else if (firstTouch) {
1431 // Some devices will throw touchmoves for all but the slightest of taps.
1432 // So, if we moved only a small distance, this could still be a tap
1433 var xdiff = event.touches[0].pageX - firstTouch.pageX;
1434 var ydiff = event.touches[0].pageY - firstTouch.pageY;
1435 var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
1437 if (touchDistance > tapMovementThreshold) {
1443 var noTap = function noTap() {
1447 // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
1448 this.on('touchleave', noTap);
1449 this.on('touchcancel', noTap);
1451 // When the touch ends, measure how long it took and trigger the appropriate
1453 this.on('touchend', function (event) {
1455 // Proceed only if the touchmove/leave/cancel event didn't happen
1456 if (couldBeTap === true) {
1457 // Measure how long the touch lasted
1458 var touchTime = new Date().getTime() - touchStart;
1460 // Make sure the touch was less than the threshold to be considered a tap
1461 if (touchTime < touchTimeThreshold) {
1462 // Don't let browser turn this into a click
1463 event.preventDefault();
1465 * Triggered when a `Component` is tapped.
1467 * @event Component#tap
1468 * @type {EventTarget~Event}
1470 this.trigger('tap');
1471 // It may be good to copy the touchend event object and change the
1472 // type to tap, if the other event properties aren't exact after
1473 // Events.fixEvent runs (e.g. event.target)
1480 * This function reports user activity whenever touch events happen. This can get
1481 * turned off by any sub-components that wants touch events to act another way.
1483 * Report user touch activity when touch events occur. User activity gets used to
1484 * determine when controls should show/hide. It is simple when it comes to mouse
1485 * events, because any mouse event should show the controls. So we capture mouse
1486 * events that bubble up to the player and report activity when that happens.
1487 * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
1488 * controls. So touch events can't help us at the player level either.
1490 * User activity gets checked asynchronously. So what could happen is a tap event
1491 * on the video turns the controls off. Then the `touchend` event bubbles up to
1492 * the player. Which, if it reported user activity, would turn the controls right
1493 * back on. We also don't want to completely block touch events from bubbling up.
1494 * Furthermore a `touchmove` event and anything other than a tap, should not turn
1497 * @listens Component#touchstart
1498 * @listens Component#touchmove
1499 * @listens Component#touchend
1500 * @listens Component#touchcancel
1504 Component.prototype.enableTouchActivity = function enableTouchActivity() {
1505 // Don't continue if the root player doesn't support reporting user activity
1506 if (!this.player() || !this.player().reportUserActivity) {
1510 // listener for reporting that the user is active
1511 var report = Fn.bind(this.player(), this.player().reportUserActivity);
1513 var touchHolding = void 0;
1515 this.on('touchstart', function () {
1517 // For as long as the they are touching the device or have their mouse down,
1518 // we consider them active even if they're not moving their finger or mouse.
1519 // So we want to continue to update that they are active
1520 this.clearInterval(touchHolding);
1521 // report at the same interval as activityCheck
1522 touchHolding = this.setInterval(report, 250);
1525 var touchEnd = function touchEnd(event) {
1527 // stop the interval that maintains activity if the touch is holding
1528 this.clearInterval(touchHolding);
1531 this.on('touchmove', report);
1532 this.on('touchend', touchEnd);
1533 this.on('touchcancel', touchEnd);
1537 * A callback that has no parameters and is bound into `Component`s context.
1539 * @callback Component~GenericCallback
1544 * Creates a function that runs after an `x` millisecond timeout. This function is a
1545 * wrapper around `window.setTimeout`. There are a few reasons to use this one
1547 * 1. It gets cleared via {@link Component#clearTimeout} when
1548 * {@link Component#dispose} gets called.
1549 * 2. The function callback will gets turned into a {@link Component~GenericCallback}
1551 * > Note: You can use `window.clearTimeout` on the id returned by this function. This
1552 * will cause its dispose listener not to get cleaned up! Please use
1553 * {@link Component#clearTimeout} or {@link Component#dispose}.
1555 * @param {Component~GenericCallback} fn
1556 * The function that will be run after `timeout`.
1558 * @param {number} timeout
1559 * Timeout in milliseconds to delay before executing the specified function.
1562 * Returns a timeout ID that gets used to identify the timeout. It can also
1563 * get used in {@link Component#clearTimeout} to clear the timeout that
1566 * @listens Component#dispose
1567 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
1571 Component.prototype.setTimeout = function setTimeout(fn, timeout) {
1572 fn = Fn.bind(this, fn);
1574 var timeoutId = _window2['default'].setTimeout(fn, timeout);
1575 var disposeFn = function disposeFn() {
1576 this.clearTimeout(timeoutId);
1579 disposeFn.guid = 'vjs-timeout-' + timeoutId;
1581 this.on('dispose', disposeFn);
1587 * Clears a timeout that gets created via `window.setTimeout` or
1588 * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
1589 * use this function instead of `window.clearTimout`. If you don't your dispose
1590 * listener will not get cleaned up until {@link Component#dispose}!
1592 * @param {number} timeoutId
1593 * The id of the timeout to clear. The return value of
1594 * {@link Component#setTimeout} or `window.setTimeout`.
1597 * Returns the timeout id that was cleared.
1599 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
1603 Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
1604 _window2['default'].clearTimeout(timeoutId);
1606 var disposeFn = function disposeFn() {};
1608 disposeFn.guid = 'vjs-timeout-' + timeoutId;
1610 this.off('dispose', disposeFn);
1616 * Creates a function that gets run every `x` milliseconds. This function is a wrapper
1617 * around `window.setInterval`. There are a few reasons to use this one instead though.
1618 * 1. It gets cleared via {@link Component#clearInterval} when
1619 * {@link Component#dispose} gets called.
1620 * 2. The function callback will be a {@link Component~GenericCallback}
1622 * @param {Component~GenericCallback} fn
1623 * The function to run every `x` seconds.
1625 * @param {number} interval
1626 * Execute the specified function every `x` milliseconds.
1629 * Returns an id that can be used to identify the interval. It can also be be used in
1630 * {@link Component#clearInterval} to clear the interval.
1632 * @listens Component#dispose
1633 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
1637 Component.prototype.setInterval = function setInterval(fn, interval) {
1638 fn = Fn.bind(this, fn);
1640 var intervalId = _window2['default'].setInterval(fn, interval);
1642 var disposeFn = function disposeFn() {
1643 this.clearInterval(intervalId);
1646 disposeFn.guid = 'vjs-interval-' + intervalId;
1648 this.on('dispose', disposeFn);
1654 * Clears an interval that gets created via `window.setInterval` or
1655 * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
1656 * use this function instead of `window.clearInterval`. If you don't your dispose
1657 * listener will not get cleaned up until {@link Component#dispose}!
1659 * @param {number} intervalId
1660 * The id of the interval to clear. The return value of
1661 * {@link Component#setInterval} or `window.setInterval`.
1664 * Returns the interval id that was cleared.
1666 * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
1670 Component.prototype.clearInterval = function clearInterval(intervalId) {
1671 _window2['default'].clearInterval(intervalId);
1673 var disposeFn = function disposeFn() {};
1675 disposeFn.guid = 'vjs-interval-' + intervalId;
1677 this.off('dispose', disposeFn);
1683 * Register a `Component` with `videojs` given the name and the component.
1685 * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
1686 * should be registered using {@link Tech.registerTech} or
1687 * {@link videojs:videojs.registerTech}.
1689 * > NOTE: This function can also be seen on videojs as
1690 * {@link videojs:videojs.registerComponent}.
1692 * @param {string} name
1693 * The name of the `Component` to register.
1695 * @param {Component} comp
1696 * The `Component` class to register.
1698 * @return {Component}
1699 * The `Component` that was registered.
1703 Component.registerComponent = function registerComponent(name, comp) {
1708 name = (0, _toTitleCase2['default'])(name);
1710 if (!Component.components_) {
1711 Component.components_ = {};
1714 if (name === 'Player' && Component.components_[name]) {
1715 var Player = Component.components_[name];
1717 // If we have players that were disposed, then their name will still be
1718 // in Players.players. So, we must loop through and verify that the value
1719 // for each item is not null. This allows registration of the Player component
1720 // after all players have been disposed or before any were created.
1721 if (Player.players && Object.keys(Player.players).length > 0 && Object.keys(Player.players).map(function (playerName) {
1722 return Player.players[playerName];
1723 }).every(Boolean)) {
1724 throw new Error('Can not register Player component after player has been created');
1728 Component.components_[name] = comp;
1734 * Get a `Component` based on the name it was registered with.
1736 * @param {string} name
1737 * The Name of the component to get.
1739 * @return {Component}
1740 * The `Component` that got registered under the given name.
1742 * @deprecated In `videojs` 6 this will not return `Component`s that were not
1743 * registered using {@link Component.registerComponent}. Currently we
1744 * check the global `videojs` object for a `Component` name and
1745 * return that if it exists.
1749 Component.getComponent = function getComponent(name) {
1754 name = (0, _toTitleCase2['default'])(name);
1756 if (Component.components_ && Component.components_[name]) {
1757 return Component.components_[name];
1760 if (_window2['default'] && _window2['default'].videojs && _window2['default'].videojs[name]) {
1761 _log2['default'].warn('The ' + name + ' component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)');
1763 return _window2['default'].videojs[name];
1768 * Sets up the constructor using the supplied init method or uses the init of the
1771 * @param {Object} [props={}]
1772 * An object of properties.
1775 * the extended object.
1777 * @deprecated since version 5
1781 Component.extend = function extend(props) {
1782 props = props || {};
1784 _log2['default'].warn('Component.extend({}) has been deprecated, ' + ' use videojs.extend(Component, {}) instead');
1786 // Set up the constructor using the supplied init method
1787 // or using the init of the parent object
1788 // Make sure to check the unobfuscated version for external libs
1789 var init = props.init || props.init || this.prototype.init || this.prototype.init || function () {};
1790 // In Resig's simple class inheritance (previously used) the constructor
1791 // is a function that calls `this.init.apply(arguments)`
1792 // However that would prevent us from using `ParentObject.call(this);`
1793 // in a Child constructor because the `this` in `this.init`
1794 // would still refer to the Child and cause an infinite loop.
1795 // We would instead have to do
1796 // `ParentObject.prototype.init.apply(this, arguments);`
1797 // Bleh. We're not creating a _super() function, so it's good to keep
1798 // the parent constructor reference simple.
1799 var subObj = function subObj() {
1800 init.apply(this, arguments);
1803 // Inherit from this object's prototype
1804 subObj.prototype = Object.create(this.prototype);
1805 // Reset the constructor property for subObj otherwise
1806 // instances of subObj would have the constructor of the parent Object
1807 subObj.prototype.constructor = subObj;
1809 // Make the class extendable
1810 subObj.extend = Component.extend;
1812 // Extend subObj's prototype with functions and other properties from props
1813 for (var name in props) {
1814 if (props.hasOwnProperty(name)) {
1815 subObj.prototype[name] = props[name];
1825 Component.registerComponent('Component', Component);
1826 exports['default'] = Component;