component.js 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _window = require('global/window');
  4. var _window2 = _interopRequireDefault(_window);
  5. var _dom = require('./utils/dom.js');
  6. var Dom = _interopRequireWildcard(_dom);
  7. var _fn = require('./utils/fn.js');
  8. var Fn = _interopRequireWildcard(_fn);
  9. var _guid = require('./utils/guid.js');
  10. var Guid = _interopRequireWildcard(_guid);
  11. var _events = require('./utils/events.js');
  12. var Events = _interopRequireWildcard(_events);
  13. var _log = require('./utils/log.js');
  14. var _log2 = _interopRequireDefault(_log);
  15. var _toTitleCase = require('./utils/to-title-case.js');
  16. var _toTitleCase2 = _interopRequireDefault(_toTitleCase);
  17. var _mergeOptions = require('./utils/merge-options.js');
  18. var _mergeOptions2 = _interopRequireDefault(_mergeOptions);
  19. 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; } }
  20. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  21. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /**
  22. * Player Component - Base class for all UI objects
  23. *
  24. * @file component.js
  25. */
  26. /**
  27. * Base class for all UI Components.
  28. * Components are UI objects which represent both a javascript object and an element
  29. * in the DOM. They can be children of other components, and can have
  30. * children themselves.
  31. *
  32. * Components can also use methods from {@link EventTarget}
  33. */
  34. var Component = function () {
  35. /**
  36. * A callback that is called when a component is ready. Does not have any
  37. * paramters and any callback value will be ignored.
  38. *
  39. * @callback Component~ReadyCallback
  40. * @this Component
  41. */
  42. /**
  43. * Creates an instance of this class.
  44. *
  45. * @param {Player} player
  46. * The `Player` that this class should be attached to.
  47. *
  48. * @param {Object} [options]
  49. * The key/value store of player options.
  50. #
  51. * @param {Object[]} [options.children]
  52. * An array of children objects to intialize this component with. Children objects have
  53. * a name property that will be used if more than one component of the same type needs to be
  54. * added.
  55. *
  56. * @param {Component~ReadyCallback} [ready]
  57. * Function that gets called when the `Component` is ready.
  58. */
  59. function Component(player, options, ready) {
  60. _classCallCheck(this, Component);
  61. // The component might be the player itself and we can't pass `this` to super
  62. if (!player && this.play) {
  63. this.player_ = player = this; // eslint-disable-line
  64. } else {
  65. this.player_ = player;
  66. }
  67. // Make a copy of prototype.options_ to protect against overriding defaults
  68. this.options_ = (0, _mergeOptions2['default'])({}, this.options_);
  69. // Updated options with supplied options
  70. options = this.options_ = (0, _mergeOptions2['default'])(this.options_, options);
  71. // Get ID from options or options element if one is supplied
  72. this.id_ = options.id || options.el && options.el.id;
  73. // If there was no ID from the options, generate one
  74. if (!this.id_) {
  75. // Don't require the player ID function in the case of mock players
  76. var id = player && player.id && player.id() || 'no_player';
  77. this.id_ = id + '_component_' + Guid.newGUID();
  78. }
  79. this.name_ = options.name || null;
  80. // Create element if one wasn't provided in options
  81. if (options.el) {
  82. this.el_ = options.el;
  83. } else if (options.createEl !== false) {
  84. this.el_ = this.createEl();
  85. }
  86. this.children_ = [];
  87. this.childIndex_ = {};
  88. this.childNameIndex_ = {};
  89. // Add any child components in options
  90. if (options.initChildren !== false) {
  91. this.initChildren();
  92. }
  93. this.ready(ready);
  94. // Don't want to trigger ready here or it will before init is actually
  95. // finished for all children that run this constructor
  96. if (options.reportTouchActivity !== false) {
  97. this.enableTouchActivity();
  98. }
  99. }
  100. /**
  101. * Dispose of the `Component` and all child components.
  102. *
  103. * @fires Component#dispose
  104. */
  105. Component.prototype.dispose = function dispose() {
  106. /**
  107. * Triggered when a `Component` is disposed.
  108. *
  109. * @event Component#dispose
  110. * @type {EventTarget~Event}
  111. *
  112. * @property {boolean} [bubbles=false]
  113. * set to false so that the close event does not
  114. * bubble up
  115. */
  116. this.trigger({ type: 'dispose', bubbles: false });
  117. // Dispose all children.
  118. if (this.children_) {
  119. for (var i = this.children_.length - 1; i >= 0; i--) {
  120. if (this.children_[i].dispose) {
  121. this.children_[i].dispose();
  122. }
  123. }
  124. }
  125. // Delete child references
  126. this.children_ = null;
  127. this.childIndex_ = null;
  128. this.childNameIndex_ = null;
  129. // Remove all event listeners.
  130. this.off();
  131. // Remove element from DOM
  132. if (this.el_.parentNode) {
  133. this.el_.parentNode.removeChild(this.el_);
  134. }
  135. Dom.removeElData(this.el_);
  136. this.el_ = null;
  137. };
  138. /**
  139. * Return the {@link Player} that the `Component` has attached to.
  140. *
  141. * @return {Player}
  142. * The player that this `Component` has attached to.
  143. */
  144. Component.prototype.player = function player() {
  145. return this.player_;
  146. };
  147. /**
  148. * Deep merge of options objects with new options.
  149. * > Note: When both `obj` and `options` contain properties whose values are objects.
  150. * The two properties get merged using {@link module:mergeOptions}
  151. *
  152. * @param {Object} obj
  153. * The object that contains new options.
  154. *
  155. * @return {Object}
  156. * A new object of `this.options_` and `obj` merged together.
  157. *
  158. * @deprecated since version 5
  159. */
  160. Component.prototype.options = function options(obj) {
  161. _log2['default'].warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
  162. if (!obj) {
  163. return this.options_;
  164. }
  165. this.options_ = (0, _mergeOptions2['default'])(this.options_, obj);
  166. return this.options_;
  167. };
  168. /**
  169. * Get the `Component`s DOM element
  170. *
  171. * @return {Element}
  172. * The DOM element for this `Component`.
  173. */
  174. Component.prototype.el = function el() {
  175. return this.el_;
  176. };
  177. /**
  178. * Create the `Component`s DOM element.
  179. *
  180. * @param {string} [tagName]
  181. * Element's DOM node type. e.g. 'div'
  182. *
  183. * @param {Object} [properties]
  184. * An object of properties that should be set.
  185. *
  186. * @param {Object} [attributes]
  187. * An object of attributes that should be set.
  188. *
  189. * @return {Element}
  190. * The element that gets created.
  191. */
  192. Component.prototype.createEl = function createEl(tagName, properties, attributes) {
  193. return Dom.createEl(tagName, properties, attributes);
  194. };
  195. /**
  196. * Localize a string given the string in english.
  197. *
  198. * @param {string} string
  199. * The string to localize.
  200. *
  201. * @return {string}
  202. * The localized string or if no localization exists the english string.
  203. */
  204. Component.prototype.localize = function localize(string) {
  205. var code = this.player_.language && this.player_.language();
  206. var languages = this.player_.languages && this.player_.languages();
  207. if (!code || !languages) {
  208. return string;
  209. }
  210. var language = languages[code];
  211. if (language && language[string]) {
  212. return language[string];
  213. }
  214. var primaryCode = code.split('-')[0];
  215. var primaryLang = languages[primaryCode];
  216. if (primaryLang && primaryLang[string]) {
  217. return primaryLang[string];
  218. }
  219. return string;
  220. };
  221. /**
  222. * Return the `Component`s DOM element. This is where children get inserted.
  223. * This will usually be the the same as the element returned in {@link Component#el}.
  224. *
  225. * @return {Element}
  226. * The content element for this `Component`.
  227. */
  228. Component.prototype.contentEl = function contentEl() {
  229. return this.contentEl_ || this.el_;
  230. };
  231. /**
  232. * Get this `Component`s ID
  233. *
  234. * @return {string}
  235. * The id of this `Component`
  236. */
  237. Component.prototype.id = function id() {
  238. return this.id_;
  239. };
  240. /**
  241. * Get the `Component`s name. The name gets used to reference the `Component`
  242. * and is set during registration.
  243. *
  244. * @return {string}
  245. * The name of this `Component`.
  246. */
  247. Component.prototype.name = function name() {
  248. return this.name_;
  249. };
  250. /**
  251. * Get an array of all child components
  252. *
  253. * @return {Array}
  254. * The children
  255. */
  256. Component.prototype.children = function children() {
  257. return this.children_;
  258. };
  259. /**
  260. * Returns the child `Component` with the given `id`.
  261. *
  262. * @param {string} id
  263. * The id of the child `Component` to get.
  264. *
  265. * @return {Component|undefined}
  266. * The child `Component` with the given `id` or undefined.
  267. */
  268. Component.prototype.getChildById = function getChildById(id) {
  269. return this.childIndex_[id];
  270. };
  271. /**
  272. * Returns the child `Component` with the given `name`.
  273. *
  274. * @param {string} name
  275. * The name of the child `Component` to get.
  276. *
  277. * @return {Component|undefined}
  278. * The child `Component` with the given `name` or undefined.
  279. */
  280. Component.prototype.getChild = function getChild(name) {
  281. if (!name) {
  282. return;
  283. }
  284. name = (0, _toTitleCase2['default'])(name);
  285. return this.childNameIndex_[name];
  286. };
  287. /**
  288. * Add a child `Component` inside the current `Component`.
  289. *
  290. *
  291. * @param {string|Component} child
  292. * The name or instance of a child to add.
  293. *
  294. * @param {Object} [options={}]
  295. * The key/value store of options that will get passed to children of
  296. * the child.
  297. *
  298. * @param {number} [index=this.children_.length]
  299. * The index to attempt to add a child into.
  300. *
  301. * @return {Component}
  302. * The `Component` that gets added as a child. When using a string the
  303. * `Component` will get created by this process.
  304. */
  305. Component.prototype.addChild = function addChild(child) {
  306. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  307. var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length;
  308. var component = void 0;
  309. var componentName = void 0;
  310. // If child is a string, create component with options
  311. if (typeof child === 'string') {
  312. componentName = (0, _toTitleCase2['default'])(child);
  313. // Options can also be specified as a boolean,
  314. // so convert to an empty object if false.
  315. if (!options) {
  316. options = {};
  317. }
  318. // Same as above, but true is deprecated so show a warning.
  319. if (options === true) {
  320. _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`.');
  321. options = {};
  322. }
  323. var componentClassName = options.componentClass || componentName;
  324. // Set name through options
  325. options.name = componentName;
  326. // Create a new object & element for this controls set
  327. // If there's no .player_, this is a player
  328. var ComponentClass = Component.getComponent(componentClassName);
  329. if (!ComponentClass) {
  330. throw new Error('Component ' + componentClassName + ' does not exist');
  331. }
  332. // data stored directly on the videojs object may be
  333. // misidentified as a component to retain
  334. // backwards-compatibility with 4.x. check to make sure the
  335. // component class can be instantiated.
  336. if (typeof ComponentClass !== 'function') {
  337. return null;
  338. }
  339. component = new ComponentClass(this.player_ || this, options);
  340. // child is a component instance
  341. } else {
  342. component = child;
  343. }
  344. this.children_.splice(index, 0, component);
  345. if (typeof component.id === 'function') {
  346. this.childIndex_[component.id()] = component;
  347. }
  348. // If a name wasn't used to create the component, check if we can use the
  349. // name function of the component
  350. componentName = componentName || component.name && (0, _toTitleCase2['default'])(component.name());
  351. if (componentName) {
  352. this.childNameIndex_[componentName] = component;
  353. }
  354. // Add the UI object's element to the container div (box)
  355. // Having an element is not required
  356. if (typeof component.el === 'function' && component.el()) {
  357. var childNodes = this.contentEl().children;
  358. var refNode = childNodes[index] || null;
  359. this.contentEl().insertBefore(component.el(), refNode);
  360. }
  361. // Return so it can stored on parent object if desired.
  362. return component;
  363. };
  364. /**
  365. * Remove a child `Component` from this `Component`s list of children. Also removes
  366. * the child `Component`s element from this `Component`s element.
  367. *
  368. * @param {Component} component
  369. * The child `Component` to remove.
  370. */
  371. Component.prototype.removeChild = function removeChild(component) {
  372. if (typeof component === 'string') {
  373. component = this.getChild(component);
  374. }
  375. if (!component || !this.children_) {
  376. return;
  377. }
  378. var childFound = false;
  379. for (var i = this.children_.length - 1; i >= 0; i--) {
  380. if (this.children_[i] === component) {
  381. childFound = true;
  382. this.children_.splice(i, 1);
  383. break;
  384. }
  385. }
  386. if (!childFound) {
  387. return;
  388. }
  389. this.childIndex_[component.id()] = null;
  390. this.childNameIndex_[component.name()] = null;
  391. var compEl = component.el();
  392. if (compEl && compEl.parentNode === this.contentEl()) {
  393. this.contentEl().removeChild(component.el());
  394. }
  395. };
  396. /**
  397. * Add and initialize default child `Component`s based upon options.
  398. */
  399. Component.prototype.initChildren = function initChildren() {
  400. var _this = this;
  401. var children = this.options_.children;
  402. if (children) {
  403. // `this` is `parent`
  404. var parentOptions = this.options_;
  405. var handleAdd = function handleAdd(child) {
  406. var name = child.name;
  407. var opts = child.opts;
  408. // Allow options for children to be set at the parent options
  409. // e.g. videojs(id, { controlBar: false });
  410. // instead of videojs(id, { children: { controlBar: false });
  411. if (parentOptions[name] !== undefined) {
  412. opts = parentOptions[name];
  413. }
  414. // Allow for disabling default components
  415. // e.g. options['children']['posterImage'] = false
  416. if (opts === false) {
  417. return;
  418. }
  419. // Allow options to be passed as a simple boolean if no configuration
  420. // is necessary.
  421. if (opts === true) {
  422. opts = {};
  423. }
  424. // We also want to pass the original player options
  425. // to each component as well so they don't need to
  426. // reach back into the player for options later.
  427. opts.playerOptions = _this.options_.playerOptions;
  428. // Create and add the child component.
  429. // Add a direct reference to the child by name on the parent instance.
  430. // If two of the same component are used, different names should be supplied
  431. // for each
  432. var newChild = _this.addChild(name, opts);
  433. if (newChild) {
  434. _this[name] = newChild;
  435. }
  436. };
  437. // Allow for an array of children details to passed in the options
  438. var workingChildren = void 0;
  439. var Tech = Component.getComponent('Tech');
  440. if (Array.isArray(children)) {
  441. workingChildren = children;
  442. } else {
  443. workingChildren = Object.keys(children);
  444. }
  445. workingChildren
  446. // children that are in this.options_ but also in workingChildren would
  447. // give us extra children we do not want. So, we want to filter them out.
  448. .concat(Object.keys(this.options_).filter(function (child) {
  449. return !workingChildren.some(function (wchild) {
  450. if (typeof wchild === 'string') {
  451. return child === wchild;
  452. }
  453. return child === wchild.name;
  454. });
  455. })).map(function (child) {
  456. var name = void 0;
  457. var opts = void 0;
  458. if (typeof child === 'string') {
  459. name = child;
  460. opts = children[name] || _this.options_[name] || {};
  461. } else {
  462. name = child.name;
  463. opts = child;
  464. }
  465. return { name: name, opts: opts };
  466. }).filter(function (child) {
  467. // we have to make sure that child.name isn't in the techOrder since
  468. // techs are registerd as Components but can't aren't compatible
  469. // See https://github.com/videojs/video.js/issues/2772
  470. var c = Component.getComponent(child.opts.componentClass || (0, _toTitleCase2['default'])(child.name));
  471. return c && !Tech.isTech(c);
  472. }).forEach(handleAdd);
  473. }
  474. };
  475. /**
  476. * Builds the default DOM class name. Should be overriden by sub-components.
  477. *
  478. * @return {string}
  479. * The DOM class name for this object.
  480. *
  481. * @abstract
  482. */
  483. Component.prototype.buildCSSClass = function buildCSSClass() {
  484. // Child classes can include a function that does:
  485. // return 'CLASS NAME' + this._super();
  486. return '';
  487. };
  488. /**
  489. * Add an `event listener` to this `Component`s element.
  490. *
  491. * The benefit of using this over the following:
  492. * - `VjsEvents.on(otherElement, 'eventName', myFunc)`
  493. * - `otherComponent.on('eventName', myFunc)`
  494. *
  495. * 1. Is that the listeners will get cleaned up when either component gets disposed.
  496. * 1. It will also bind `myComponent` as the context of `myFunc`.
  497. * > NOTE: If you remove the element from the DOM that has used `on` you need to
  498. * clean up references using: `myComponent.trigger(el, 'dispose')`
  499. * This will also allow the browser to garbage collect it. In special
  500. * cases such as with `window` and `document`, which are both permanent,
  501. * this is not necessary.
  502. *
  503. * @param {string|Component|string[]} [first]
  504. * The event name, and array of event names, or another `Component`.
  505. *
  506. * @param {EventTarget~EventListener|string|string[]} [second]
  507. * The listener function, an event name, or an Array of events names.
  508. *
  509. * @param {EventTarget~EventListener} [third]
  510. * The event handler if `first` is a `Component` and `second` is an event name
  511. * or an Array of event names.
  512. *
  513. * @return {Component}
  514. * Returns itself; method can be chained.
  515. *
  516. * @listens Component#dispose
  517. */
  518. Component.prototype.on = function on(first, second, third) {
  519. var _this2 = this;
  520. if (typeof first === 'string' || Array.isArray(first)) {
  521. Events.on(this.el_, first, Fn.bind(this, second));
  522. // Targeting another component or element
  523. } else {
  524. var target = first;
  525. var type = second;
  526. var fn = Fn.bind(this, third);
  527. // When this component is disposed, remove the listener from the other component
  528. var removeOnDispose = function removeOnDispose() {
  529. return _this2.off(target, type, fn);
  530. };
  531. // Use the same function ID so we can remove it later it using the ID
  532. // of the original listener
  533. removeOnDispose.guid = fn.guid;
  534. this.on('dispose', removeOnDispose);
  535. // If the other component is disposed first we need to clean the reference
  536. // to the other component in this component's removeOnDispose listener
  537. // Otherwise we create a memory leak.
  538. var cleanRemover = function cleanRemover() {
  539. return _this2.off('dispose', removeOnDispose);
  540. };
  541. // Add the same function ID so we can easily remove it later
  542. cleanRemover.guid = fn.guid;
  543. // Check if this is a DOM node
  544. if (first.nodeName) {
  545. // Add the listener to the other element
  546. Events.on(target, type, fn);
  547. Events.on(target, 'dispose', cleanRemover);
  548. // Should be a component
  549. // Not using `instanceof Component` because it makes mock players difficult
  550. } else if (typeof first.on === 'function') {
  551. // Add the listener to the other component
  552. target.on(type, fn);
  553. target.on('dispose', cleanRemover);
  554. }
  555. }
  556. return this;
  557. };
  558. /**
  559. * Remove an event listener from this `Component`s element. If the second argument is
  560. * exluded all listeners for the type passed in as the first argument will be removed.
  561. *
  562. * @param {string|Component|string[]} [first]
  563. * The event name, and array of event names, or another `Component`.
  564. *
  565. * @param {EventTarget~EventListener|string|string[]} [second]
  566. * The listener function, an event name, or an Array of events names.
  567. *
  568. * @param {EventTarget~EventListener} [third]
  569. * The event handler if `first` is a `Component` and `second` is an event name
  570. * or an Array of event names.
  571. *
  572. * @return {Component}
  573. * Returns itself; method can be chained.
  574. */
  575. Component.prototype.off = function off(first, second, third) {
  576. if (!first || typeof first === 'string' || Array.isArray(first)) {
  577. Events.off(this.el_, first, second);
  578. } else {
  579. var target = first;
  580. var type = second;
  581. // Ensure there's at least a guid, even if the function hasn't been used
  582. var fn = Fn.bind(this, third);
  583. // Remove the dispose listener on this component,
  584. // which was given the same guid as the event listener
  585. this.off('dispose', fn);
  586. if (first.nodeName) {
  587. // Remove the listener
  588. Events.off(target, type, fn);
  589. // Remove the listener for cleaning the dispose listener
  590. Events.off(target, 'dispose', fn);
  591. } else {
  592. target.off(type, fn);
  593. target.off('dispose', fn);
  594. }
  595. }
  596. return this;
  597. };
  598. /**
  599. * Add an event listener that gets triggered only once and then gets removed.
  600. *
  601. * @param {string|Component|string[]} [first]
  602. * The event name, and array of event names, or another `Component`.
  603. *
  604. * @param {EventTarget~EventListener|string|string[]} [second]
  605. * The listener function, an event name, or an Array of events names.
  606. *
  607. * @param {EventTarget~EventListener} [third]
  608. * The event handler if `first` is a `Component` and `second` is an event name
  609. * or an Array of event names.
  610. *
  611. * @return {Component}
  612. * Returns itself; method can be chained.
  613. */
  614. Component.prototype.one = function one(first, second, third) {
  615. var _this3 = this,
  616. _arguments = arguments;
  617. if (typeof first === 'string' || Array.isArray(first)) {
  618. Events.one(this.el_, first, Fn.bind(this, second));
  619. } else {
  620. var target = first;
  621. var type = second;
  622. var fn = Fn.bind(this, third);
  623. var newFunc = function newFunc() {
  624. _this3.off(target, type, newFunc);
  625. fn.apply(null, _arguments);
  626. };
  627. // Keep the same function ID so we can remove it later
  628. newFunc.guid = fn.guid;
  629. this.on(target, type, newFunc);
  630. }
  631. return this;
  632. };
  633. /**
  634. * Trigger an event on an element.
  635. *
  636. * @param {EventTarget~Event|Object|string} event
  637. * The event name, and Event, or an event-like object with a type attribute
  638. * set to the event name.
  639. *
  640. * @param {Object} [hash]
  641. * Data hash to pass along with the event
  642. *
  643. * @return {Component}
  644. * Returns itself; method can be chained.
  645. */
  646. Component.prototype.trigger = function trigger(event, hash) {
  647. Events.trigger(this.el_, event, hash);
  648. return this;
  649. };
  650. /**
  651. * Bind a listener to the component's ready state. If the ready event has already
  652. * happened it will trigger the function immediately.
  653. *
  654. * @param {Component~ReadyCallback} fn
  655. * A function to call when ready is triggered.
  656. *
  657. * @param {boolean} [sync=false]
  658. * Execute the listener synchronously if `Component` is ready.
  659. *
  660. * @return {Component}
  661. * Returns itself; method can be chained.
  662. */
  663. Component.prototype.ready = function ready(fn) {
  664. var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  665. if (fn) {
  666. if (this.isReady_) {
  667. if (sync) {
  668. fn.call(this);
  669. } else {
  670. // Call the function asynchronously by default for consistency
  671. this.setTimeout(fn, 1);
  672. }
  673. } else {
  674. this.readyQueue_ = this.readyQueue_ || [];
  675. this.readyQueue_.push(fn);
  676. }
  677. }
  678. return this;
  679. };
  680. /**
  681. * Trigger all the ready listeners for this `Component`.
  682. *
  683. * @fires Component#ready
  684. */
  685. Component.prototype.triggerReady = function triggerReady() {
  686. this.isReady_ = true;
  687. // Ensure ready is triggerd asynchronously
  688. this.setTimeout(function () {
  689. var readyQueue = this.readyQueue_;
  690. // Reset Ready Queue
  691. this.readyQueue_ = [];
  692. if (readyQueue && readyQueue.length > 0) {
  693. readyQueue.forEach(function (fn) {
  694. fn.call(this);
  695. }, this);
  696. }
  697. // Allow for using event listeners also
  698. /**
  699. * Triggered when a `Component` is ready.
  700. *
  701. * @event Component#ready
  702. * @type {EventTarget~Event}
  703. */
  704. this.trigger('ready');
  705. }, 1);
  706. };
  707. /**
  708. * Find a single DOM element matching a `selector`. This can be within the `Component`s
  709. * `contentEl()` or another custom context.
  710. *
  711. * @param {string} selector
  712. * A valid CSS selector, which will be passed to `querySelector`.
  713. *
  714. * @param {Element|string} [context=this.contentEl()]
  715. * A DOM element within which to query. Can also be a selector string in
  716. * which case the first matching element will get used as context. If
  717. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  718. * nothing it falls back to `document`.
  719. *
  720. * @return {Element|null}
  721. * the dom element that was found, or null
  722. *
  723. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  724. */
  725. Component.prototype.$ = function $(selector, context) {
  726. return Dom.$(selector, context || this.contentEl());
  727. };
  728. /**
  729. * Finds all DOM element matching a `selector`. This can be within the `Component`s
  730. * `contentEl()` or another custom context.
  731. *
  732. * @param {string} selector
  733. * A valid CSS selector, which will be passed to `querySelectorAll`.
  734. *
  735. * @param {Element|string} [context=this.contentEl()]
  736. * A DOM element within which to query. Can also be a selector string in
  737. * which case the first matching element will get used as context. If
  738. * missing `this.contentEl()` gets used. If `this.contentEl()` returns
  739. * nothing it falls back to `document`.
  740. *
  741. * @return {NodeList}
  742. * a list of dom elements that were found
  743. *
  744. * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
  745. */
  746. Component.prototype.$$ = function $$(selector, context) {
  747. return Dom.$$(selector, context || this.contentEl());
  748. };
  749. /**
  750. * Check if a component's element has a CSS class name.
  751. *
  752. * @param {string} classToCheck
  753. * CSS class name to check.
  754. *
  755. * @return {boolean}
  756. * - True if the `Component` has the class.
  757. * - False if the `Component` does not have the class`
  758. */
  759. Component.prototype.hasClass = function hasClass(classToCheck) {
  760. return Dom.hasElClass(this.el_, classToCheck);
  761. };
  762. /**
  763. * Add a CSS class name to the `Component`s element.
  764. *
  765. * @param {string} classToAdd
  766. * CSS class name to add
  767. *
  768. * @return {Component}
  769. * Returns itself; method can be chained.
  770. */
  771. Component.prototype.addClass = function addClass(classToAdd) {
  772. Dom.addElClass(this.el_, classToAdd);
  773. return this;
  774. };
  775. /**
  776. * Remove a CSS class name from the `Component`s element.
  777. *
  778. * @param {string} classToRemove
  779. * CSS class name to remove
  780. *
  781. * @return {Component}
  782. * Returns itself; method can be chained.
  783. */
  784. Component.prototype.removeClass = function removeClass(classToRemove) {
  785. Dom.removeElClass(this.el_, classToRemove);
  786. return this;
  787. };
  788. /**
  789. * Add or remove a CSS class name from the component's element.
  790. * - `classToToggle` gets added when {@link Component#hasClass} would return false.
  791. * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
  792. *
  793. * @param {string} classToToggle
  794. * The class to add or remove based on (@link Component#hasClass}
  795. *
  796. * @param {boolean|Dom~predicate} [predicate]
  797. * An {@link Dom~predicate} function or a boolean
  798. *
  799. * @return {Component}
  800. * Returns itself; method can be chained.
  801. */
  802. Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) {
  803. Dom.toggleElClass(this.el_, classToToggle, predicate);
  804. return this;
  805. };
  806. /**
  807. * Show the `Component`s element if it is hidden by removing the
  808. * 'vjs-hidden' class name from it.
  809. *
  810. * @return {Component}
  811. * Returns itself; method can be chained.
  812. */
  813. Component.prototype.show = function show() {
  814. this.removeClass('vjs-hidden');
  815. return this;
  816. };
  817. /**
  818. * Hide the `Component`s element if it is currently showing by adding the
  819. * 'vjs-hidden` class name to it.
  820. *
  821. * @return {Component}
  822. * Returns itself; method can be chained.
  823. */
  824. Component.prototype.hide = function hide() {
  825. this.addClass('vjs-hidden');
  826. return this;
  827. };
  828. /**
  829. * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
  830. * class name to it. Used during fadeIn/fadeOut.
  831. *
  832. * @return {Component}
  833. * Returns itself; method can be chained.
  834. *
  835. * @private
  836. */
  837. Component.prototype.lockShowing = function lockShowing() {
  838. this.addClass('vjs-lock-showing');
  839. return this;
  840. };
  841. /**
  842. * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
  843. * class name from it. Used during fadeIn/fadeOut.
  844. *
  845. * @return {Component}
  846. * Returns itself; method can be chained.
  847. *
  848. * @private
  849. */
  850. Component.prototype.unlockShowing = function unlockShowing() {
  851. this.removeClass('vjs-lock-showing');
  852. return this;
  853. };
  854. /**
  855. * Get the value of an attribute on the `Component`s element.
  856. *
  857. * @param {string} attribute
  858. * Name of the attribute to get the value from.
  859. *
  860. * @return {string|null}
  861. * - The value of the attribute that was asked for.
  862. * - Can be an empty string on some browsers if the attribute does not exist
  863. * or has no value
  864. * - Most browsers will return null if the attibute does not exist or has
  865. * no value.
  866. *
  867. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
  868. */
  869. Component.prototype.getAttribute = function getAttribute(attribute) {
  870. return Dom.getAttribute(this.el_, attribute);
  871. };
  872. /**
  873. * Set the value of an attribute on the `Component`'s element
  874. *
  875. * @param {string} attribute
  876. * Name of the attribute to set.
  877. *
  878. * @param {string} value
  879. * Value to set the attribute to.
  880. *
  881. * @return {Component}
  882. * Returns itself; method can be chained.
  883. *
  884. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
  885. */
  886. Component.prototype.setAttribute = function setAttribute(attribute, value) {
  887. Dom.setAttribute(this.el_, attribute, value);
  888. return this;
  889. };
  890. /**
  891. * Remove an attribute from the `Component`s element.
  892. *
  893. * @param {string} attribute
  894. * Name of the attribute to remove.
  895. *
  896. * @return {Component}
  897. * Returns itself; method can be chained.
  898. *
  899. * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
  900. */
  901. Component.prototype.removeAttribute = function removeAttribute(attribute) {
  902. Dom.removeAttribute(this.el_, attribute);
  903. return this;
  904. };
  905. /**
  906. * Get or set the width of the component based upon the CSS styles.
  907. * See {@link Component#dimension} for more detailed information.
  908. *
  909. * @param {number|string} [num]
  910. * The width that you want to set postfixed with '%', 'px' or nothing.
  911. *
  912. * @param {boolean} [skipListeners]
  913. * Skip the resize event trigger
  914. *
  915. * @return {Component|number|string}
  916. * - The width when getting, zero if there is no width. Can be a string
  917. * postpixed with '%' or 'px'.
  918. * - Returns itself when setting; method can be chained.
  919. */
  920. Component.prototype.width = function width(num, skipListeners) {
  921. return this.dimension('width', num, skipListeners);
  922. };
  923. /**
  924. * Get or set the height of the component based upon the CSS styles.
  925. * See {@link Component#dimension} for more detailed information.
  926. *
  927. * @param {number|string} [num]
  928. * The height that you want to set postfixed with '%', 'px' or nothing.
  929. *
  930. * @param {boolean} [skipListeners]
  931. * Skip the resize event trigger
  932. *
  933. * @return {Component|number|string}
  934. * - The width when getting, zero if there is no width. Can be a string
  935. * postpixed with '%' or 'px'.
  936. * - Returns itself when setting; method can be chained.
  937. */
  938. Component.prototype.height = function height(num, skipListeners) {
  939. return this.dimension('height', num, skipListeners);
  940. };
  941. /**
  942. * Set both the width and height of the `Component` element at the same time.
  943. *
  944. * @param {number|string} width
  945. * Width to set the `Component`s element to.
  946. *
  947. * @param {number|string} height
  948. * Height to set the `Component`s element to.
  949. *
  950. * @return {Component}
  951. * Returns itself; method can be chained.
  952. */
  953. Component.prototype.dimensions = function dimensions(width, height) {
  954. // Skip resize listeners on width for optimization
  955. return this.width(width, true).height(height);
  956. };
  957. /**
  958. * Get or set width or height of the `Component` element. This is the shared code
  959. * for the {@link Component#width} and {@link Component#height}.
  960. *
  961. * Things to know:
  962. * - If the width or height in an number this will return the number postfixed with 'px'.
  963. * - If the width/height is a percent this will return the percent postfixed with '%'
  964. * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
  965. * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
  966. * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
  967. * for more information
  968. * - If you want the computed style of the component, use {@link Component#currentWidth}
  969. * and {@link {Component#currentHeight}
  970. *
  971. * @fires Component#resize
  972. *
  973. * @param {string} widthOrHeight
  974. 8 'width' or 'height'
  975. *
  976. * @param {number|string} [num]
  977. 8 New dimension
  978. *
  979. * @param {boolean} [skipListeners]
  980. * Skip resize event trigger
  981. *
  982. * @return {Component}
  983. * - the dimension when getting or 0 if unset
  984. * - Returns itself when setting; method can be chained.
  985. */
  986. Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
  987. if (num !== undefined) {
  988. // Set to zero if null or literally NaN (NaN !== NaN)
  989. if (num === null || num !== num) {
  990. num = 0;
  991. }
  992. // Check if using css width/height (% or px) and adjust
  993. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  994. this.el_.style[widthOrHeight] = num;
  995. } else if (num === 'auto') {
  996. this.el_.style[widthOrHeight] = '';
  997. } else {
  998. this.el_.style[widthOrHeight] = num + 'px';
  999. }
  1000. // skipListeners allows us to avoid triggering the resize event when setting both width and height
  1001. if (!skipListeners) {
  1002. /**
  1003. * Triggered when a component is resized.
  1004. *
  1005. * @event Component#resize
  1006. * @type {EventTarget~Event}
  1007. */
  1008. this.trigger('resize');
  1009. }
  1010. // Return component
  1011. return this;
  1012. }
  1013. // Not setting a value, so getting it
  1014. // Make sure element exists
  1015. if (!this.el_) {
  1016. return 0;
  1017. }
  1018. // Get dimension value from style
  1019. var val = this.el_.style[widthOrHeight];
  1020. var pxIndex = val.indexOf('px');
  1021. if (pxIndex !== -1) {
  1022. // Return the pixel value with no 'px'
  1023. return parseInt(val.slice(0, pxIndex), 10);
  1024. }
  1025. // No px so using % or no style was set, so falling back to offsetWidth/height
  1026. // If component has display:none, offset will return 0
  1027. // TODO: handle display:none and no dimension style using px
  1028. return parseInt(this.el_['offset' + (0, _toTitleCase2['default'])(widthOrHeight)], 10);
  1029. };
  1030. /**
  1031. * Get the width or the height of the `Component` elements computed style. Uses
  1032. * `window.getComputedStyle`.
  1033. *
  1034. * @param {string} widthOrHeight
  1035. * A string containing 'width' or 'height'. Whichever one you want to get.
  1036. *
  1037. * @return {number}
  1038. * The dimension that gets asked for or 0 if nothing was set
  1039. * for that dimension.
  1040. */
  1041. Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
  1042. var computedWidthOrHeight = 0;
  1043. if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
  1044. throw new Error('currentDimension only accepts width or height value');
  1045. }
  1046. if (typeof _window2['default'].getComputedStyle === 'function') {
  1047. var computedStyle = _window2['default'].getComputedStyle(this.el_);
  1048. computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
  1049. }
  1050. // remove 'px' from variable and parse as integer
  1051. computedWidthOrHeight = parseFloat(computedWidthOrHeight);
  1052. // if the computed value is still 0, it's possible that the browser is lying
  1053. // and we want to check the offset values.
  1054. // This code also runs on IE8 and wherever getComputedStyle doesn't exist.
  1055. if (computedWidthOrHeight === 0) {
  1056. var rule = 'offset' + (0, _toTitleCase2['default'])(widthOrHeight);
  1057. computedWidthOrHeight = this.el_[rule];
  1058. }
  1059. return computedWidthOrHeight;
  1060. };
  1061. /**
  1062. * An object that contains width and height values of the `Component`s
  1063. * computed style. Uses `window.getComputedStyle`.
  1064. *
  1065. * @typedef {Object} Component~DimensionObject
  1066. *
  1067. * @property {number} width
  1068. * The width of the `Component`s computed style.
  1069. *
  1070. * @property {number} height
  1071. * The height of the `Component`s computed style.
  1072. */
  1073. /**
  1074. * Get an object that contains width and height values of the `Component`s
  1075. * computed style.
  1076. *
  1077. * @return {Component~DimensionObject}
  1078. * The dimensions of the components element
  1079. */
  1080. Component.prototype.currentDimensions = function currentDimensions() {
  1081. return {
  1082. width: this.currentDimension('width'),
  1083. height: this.currentDimension('height')
  1084. };
  1085. };
  1086. /**
  1087. * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`.
  1088. *
  1089. * @return {number} width
  1090. * The width of the `Component`s computed style.
  1091. */
  1092. Component.prototype.currentWidth = function currentWidth() {
  1093. return this.currentDimension('width');
  1094. };
  1095. /**
  1096. * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`.
  1097. *
  1098. * @return {number} height
  1099. * The height of the `Component`s computed style.
  1100. */
  1101. Component.prototype.currentHeight = function currentHeight() {
  1102. return this.currentDimension('height');
  1103. };
  1104. /**
  1105. * Set the focus to this component
  1106. */
  1107. Component.prototype.focus = function focus() {
  1108. this.el_.focus();
  1109. };
  1110. /**
  1111. * Remove the focus from this component
  1112. */
  1113. Component.prototype.blur = function blur() {
  1114. this.el_.blur();
  1115. };
  1116. /**
  1117. * Emit a 'tap' events when touch event support gets detected. This gets used to
  1118. * support toggling the controls through a tap on the video. They get enabled
  1119. * because every sub-component would have extra overhead otherwise.
  1120. *
  1121. * @private
  1122. * @fires Component#tap
  1123. * @listens Component#touchstart
  1124. * @listens Component#touchmove
  1125. * @listens Component#touchleave
  1126. * @listens Component#touchcancel
  1127. * @listens Component#touchend
  1128. */
  1129. Component.prototype.emitTapEvents = function emitTapEvents() {
  1130. // Track the start time so we can determine how long the touch lasted
  1131. var touchStart = 0;
  1132. var firstTouch = null;
  1133. // Maximum movement allowed during a touch event to still be considered a tap
  1134. // Other popular libs use anywhere from 2 (hammer.js) to 15,
  1135. // so 10 seems like a nice, round number.
  1136. var tapMovementThreshold = 10;
  1137. // The maximum length a touch can be while still being considered a tap
  1138. var touchTimeThreshold = 200;
  1139. var couldBeTap = void 0;
  1140. this.on('touchstart', function (event) {
  1141. // If more than one finger, don't consider treating this as a click
  1142. if (event.touches.length === 1) {
  1143. // Copy pageX/pageY from the object
  1144. firstTouch = {
  1145. pageX: event.touches[0].pageX,
  1146. pageY: event.touches[0].pageY
  1147. };
  1148. // Record start time so we can detect a tap vs. "touch and hold"
  1149. touchStart = new Date().getTime();
  1150. // Reset couldBeTap tracking
  1151. couldBeTap = true;
  1152. }
  1153. });
  1154. this.on('touchmove', function (event) {
  1155. // If more than one finger, don't consider treating this as a click
  1156. if (event.touches.length > 1) {
  1157. couldBeTap = false;
  1158. } else if (firstTouch) {
  1159. // Some devices will throw touchmoves for all but the slightest of taps.
  1160. // So, if we moved only a small distance, this could still be a tap
  1161. var xdiff = event.touches[0].pageX - firstTouch.pageX;
  1162. var ydiff = event.touches[0].pageY - firstTouch.pageY;
  1163. var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  1164. if (touchDistance > tapMovementThreshold) {
  1165. couldBeTap = false;
  1166. }
  1167. }
  1168. });
  1169. var noTap = function noTap() {
  1170. couldBeTap = false;
  1171. };
  1172. // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  1173. this.on('touchleave', noTap);
  1174. this.on('touchcancel', noTap);
  1175. // When the touch ends, measure how long it took and trigger the appropriate
  1176. // event
  1177. this.on('touchend', function (event) {
  1178. firstTouch = null;
  1179. // Proceed only if the touchmove/leave/cancel event didn't happen
  1180. if (couldBeTap === true) {
  1181. // Measure how long the touch lasted
  1182. var touchTime = new Date().getTime() - touchStart;
  1183. // Make sure the touch was less than the threshold to be considered a tap
  1184. if (touchTime < touchTimeThreshold) {
  1185. // Don't let browser turn this into a click
  1186. event.preventDefault();
  1187. /**
  1188. * Triggered when a `Component` is tapped.
  1189. *
  1190. * @event Component#tap
  1191. * @type {EventTarget~Event}
  1192. */
  1193. this.trigger('tap');
  1194. // It may be good to copy the touchend event object and change the
  1195. // type to tap, if the other event properties aren't exact after
  1196. // Events.fixEvent runs (e.g. event.target)
  1197. }
  1198. }
  1199. });
  1200. };
  1201. /**
  1202. * This function reports user activity whenever touch events happen. This can get
  1203. * turned off by any sub-components that wants touch events to act another way.
  1204. *
  1205. * Report user touch activity when touch events occur. User activity gets used to
  1206. * determine when controls should show/hide. It is simple when it comes to mouse
  1207. * events, because any mouse event should show the controls. So we capture mouse
  1208. * events that bubble up to the player and report activity when that happens.
  1209. * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
  1210. * controls. So touch events can't help us at the player level either.
  1211. *
  1212. * User activity gets checked asynchronously. So what could happen is a tap event
  1213. * on the video turns the controls off. Then the `touchend` event bubbles up to
  1214. * the player. Which, if it reported user activity, would turn the controls right
  1215. * back on. We also don't want to completely block touch events from bubbling up.
  1216. * Furthermore a `touchmove` event and anything other than a tap, should not turn
  1217. * controls back on.
  1218. *
  1219. * @listens Component#touchstart
  1220. * @listens Component#touchmove
  1221. * @listens Component#touchend
  1222. * @listens Component#touchcancel
  1223. */
  1224. Component.prototype.enableTouchActivity = function enableTouchActivity() {
  1225. // Don't continue if the root player doesn't support reporting user activity
  1226. if (!this.player() || !this.player().reportUserActivity) {
  1227. return;
  1228. }
  1229. // listener for reporting that the user is active
  1230. var report = Fn.bind(this.player(), this.player().reportUserActivity);
  1231. var touchHolding = void 0;
  1232. this.on('touchstart', function () {
  1233. report();
  1234. // For as long as the they are touching the device or have their mouse down,
  1235. // we consider them active even if they're not moving their finger or mouse.
  1236. // So we want to continue to update that they are active
  1237. this.clearInterval(touchHolding);
  1238. // report at the same interval as activityCheck
  1239. touchHolding = this.setInterval(report, 250);
  1240. });
  1241. var touchEnd = function touchEnd(event) {
  1242. report();
  1243. // stop the interval that maintains activity if the touch is holding
  1244. this.clearInterval(touchHolding);
  1245. };
  1246. this.on('touchmove', report);
  1247. this.on('touchend', touchEnd);
  1248. this.on('touchcancel', touchEnd);
  1249. };
  1250. /**
  1251. * A callback that has no parameters and is bound into `Component`s context.
  1252. *
  1253. * @callback Component~GenericCallback
  1254. * @this Component
  1255. */
  1256. /**
  1257. * Creates a function that runs after an `x` millisecond timeout. This function is a
  1258. * wrapper around `window.setTimeout`. There are a few reasons to use this one
  1259. * instead though:
  1260. * 1. It gets cleared via {@link Component#clearTimeout} when
  1261. * {@link Component#dispose} gets called.
  1262. * 2. The function callback will gets turned into a {@link Component~GenericCallback}
  1263. *
  1264. * > Note: You can use `window.clearTimeout` on the id returned by this function. This
  1265. * will cause its dispose listener not to get cleaned up! Please use
  1266. * {@link Component#clearTimeout} or {@link Component#dispose}.
  1267. *
  1268. * @param {Component~GenericCallback} fn
  1269. * The function that will be run after `timeout`.
  1270. *
  1271. * @param {number} timeout
  1272. * Timeout in milliseconds to delay before executing the specified function.
  1273. *
  1274. * @return {number}
  1275. * Returns a timeout ID that gets used to identify the timeout. It can also
  1276. * get used in {@link Component#clearTimeout} to clear the timeout that
  1277. * was set.
  1278. *
  1279. * @listens Component#dispose
  1280. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
  1281. */
  1282. Component.prototype.setTimeout = function setTimeout(fn, timeout) {
  1283. fn = Fn.bind(this, fn);
  1284. var timeoutId = _window2['default'].setTimeout(fn, timeout);
  1285. var disposeFn = function disposeFn() {
  1286. this.clearTimeout(timeoutId);
  1287. };
  1288. disposeFn.guid = 'vjs-timeout-' + timeoutId;
  1289. this.on('dispose', disposeFn);
  1290. return timeoutId;
  1291. };
  1292. /**
  1293. * Clears a timeout that gets created via `window.setTimeout` or
  1294. * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
  1295. * use this function instead of `window.clearTimout`. If you don't your dispose
  1296. * listener will not get cleaned up until {@link Component#dispose}!
  1297. *
  1298. * @param {number} timeoutId
  1299. * The id of the timeout to clear. The return value of
  1300. * {@link Component#setTimeout} or `window.setTimeout`.
  1301. *
  1302. * @return {number}
  1303. * Returns the timeout id that was cleared.
  1304. *
  1305. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
  1306. */
  1307. Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
  1308. _window2['default'].clearTimeout(timeoutId);
  1309. var disposeFn = function disposeFn() {};
  1310. disposeFn.guid = 'vjs-timeout-' + timeoutId;
  1311. this.off('dispose', disposeFn);
  1312. return timeoutId;
  1313. };
  1314. /**
  1315. * Creates a function that gets run every `x` milliseconds. This function is a wrapper
  1316. * around `window.setInterval`. There are a few reasons to use this one instead though.
  1317. * 1. It gets cleared via {@link Component#clearInterval} when
  1318. * {@link Component#dispose} gets called.
  1319. * 2. The function callback will be a {@link Component~GenericCallback}
  1320. *
  1321. * @param {Component~GenericCallback} fn
  1322. * The function to run every `x` seconds.
  1323. *
  1324. * @param {number} interval
  1325. * Execute the specified function every `x` milliseconds.
  1326. *
  1327. * @return {number}
  1328. * Returns an id that can be used to identify the interval. It can also be be used in
  1329. * {@link Component#clearInterval} to clear the interval.
  1330. *
  1331. * @listens Component#dispose
  1332. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
  1333. */
  1334. Component.prototype.setInterval = function setInterval(fn, interval) {
  1335. fn = Fn.bind(this, fn);
  1336. var intervalId = _window2['default'].setInterval(fn, interval);
  1337. var disposeFn = function disposeFn() {
  1338. this.clearInterval(intervalId);
  1339. };
  1340. disposeFn.guid = 'vjs-interval-' + intervalId;
  1341. this.on('dispose', disposeFn);
  1342. return intervalId;
  1343. };
  1344. /**
  1345. * Clears an interval that gets created via `window.setInterval` or
  1346. * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
  1347. * use this function instead of `window.clearInterval`. If you don't your dispose
  1348. * listener will not get cleaned up until {@link Component#dispose}!
  1349. *
  1350. * @param {number} intervalId
  1351. * The id of the interval to clear. The return value of
  1352. * {@link Component#setInterval} or `window.setInterval`.
  1353. *
  1354. * @return {number}
  1355. * Returns the interval id that was cleared.
  1356. *
  1357. * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
  1358. */
  1359. Component.prototype.clearInterval = function clearInterval(intervalId) {
  1360. _window2['default'].clearInterval(intervalId);
  1361. var disposeFn = function disposeFn() {};
  1362. disposeFn.guid = 'vjs-interval-' + intervalId;
  1363. this.off('dispose', disposeFn);
  1364. return intervalId;
  1365. };
  1366. /**
  1367. * Register a `Component` with `videojs` given the name and the component.
  1368. *
  1369. * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
  1370. * should be registered using {@link Tech.registerTech} or
  1371. * {@link videojs:videojs.registerTech}.
  1372. *
  1373. * > NOTE: This function can also be seen on videojs as
  1374. * {@link videojs:videojs.registerComponent}.
  1375. *
  1376. * @param {string} name
  1377. * The name of the `Component` to register.
  1378. *
  1379. * @param {Component} comp
  1380. * The `Component` class to register.
  1381. *
  1382. * @return {Component}
  1383. * The `Component` that was registered.
  1384. */
  1385. Component.registerComponent = function registerComponent(name, comp) {
  1386. if (!name) {
  1387. return;
  1388. }
  1389. name = (0, _toTitleCase2['default'])(name);
  1390. if (!Component.components_) {
  1391. Component.components_ = {};
  1392. }
  1393. if (name === 'Player' && Component.components_[name]) {
  1394. var Player = Component.components_[name];
  1395. // If we have players that were disposed, then their name will still be
  1396. // in Players.players. So, we must loop through and verify that the value
  1397. // for each item is not null. This allows registration of the Player component
  1398. // after all players have been disposed or before any were created.
  1399. if (Player.players && Object.keys(Player.players).length > 0 && Object.keys(Player.players).map(function (playerName) {
  1400. return Player.players[playerName];
  1401. }).every(Boolean)) {
  1402. throw new Error('Can not register Player component after player has been created');
  1403. }
  1404. }
  1405. Component.components_[name] = comp;
  1406. return comp;
  1407. };
  1408. /**
  1409. * Get a `Component` based on the name it was registered with.
  1410. *
  1411. * @param {string} name
  1412. * The Name of the component to get.
  1413. *
  1414. * @return {Component}
  1415. * The `Component` that got registered under the given name.
  1416. *
  1417. * @deprecated In `videojs` 6 this will not return `Component`s that were not
  1418. * registered using {@link Component.registerComponent}. Currently we
  1419. * check the global `videojs` object for a `Component` name and
  1420. * return that if it exists.
  1421. */
  1422. Component.getComponent = function getComponent(name) {
  1423. if (!name) {
  1424. return;
  1425. }
  1426. name = (0, _toTitleCase2['default'])(name);
  1427. if (Component.components_ && Component.components_[name]) {
  1428. return Component.components_[name];
  1429. }
  1430. if (_window2['default'] && _window2['default'].videojs && _window2['default'].videojs[name]) {
  1431. _log2['default'].warn('The ' + name + ' component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)');
  1432. return _window2['default'].videojs[name];
  1433. }
  1434. };
  1435. /**
  1436. * Sets up the constructor using the supplied init method or uses the init of the
  1437. * parent object.
  1438. *
  1439. * @param {Object} [props={}]
  1440. * An object of properties.
  1441. *
  1442. * @return {Object}
  1443. * the extended object.
  1444. *
  1445. * @deprecated since version 5
  1446. */
  1447. Component.extend = function extend(props) {
  1448. props = props || {};
  1449. _log2['default'].warn('Component.extend({}) has been deprecated, ' + ' use videojs.extend(Component, {}) instead');
  1450. // Set up the constructor using the supplied init method
  1451. // or using the init of the parent object
  1452. // Make sure to check the unobfuscated version for external libs
  1453. var init = props.init || props.init || this.prototype.init || this.prototype.init || function () {};
  1454. // In Resig's simple class inheritance (previously used) the constructor
  1455. // is a function that calls `this.init.apply(arguments)`
  1456. // However that would prevent us from using `ParentObject.call(this);`
  1457. // in a Child constructor because the `this` in `this.init`
  1458. // would still refer to the Child and cause an infinite loop.
  1459. // We would instead have to do
  1460. // `ParentObject.prototype.init.apply(this, arguments);`
  1461. // Bleh. We're not creating a _super() function, so it's good to keep
  1462. // the parent constructor reference simple.
  1463. var subObj = function subObj() {
  1464. init.apply(this, arguments);
  1465. };
  1466. // Inherit from this object's prototype
  1467. subObj.prototype = Object.create(this.prototype);
  1468. // Reset the constructor property for subObj otherwise
  1469. // instances of subObj would have the constructor of the parent Object
  1470. subObj.prototype.constructor = subObj;
  1471. // Make the class extendable
  1472. subObj.extend = Component.extend;
  1473. // Extend subObj's prototype with functions and other properties from props
  1474. for (var name in props) {
  1475. if (props.hasOwnProperty(name)) {
  1476. subObj.prototype[name] = props[name];
  1477. }
  1478. }
  1479. return subObj;
  1480. };
  1481. return Component;
  1482. }();
  1483. Component.registerComponent('Component', Component);
  1484. exports['default'] = Component;