tech.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _component = require('../component');
  4. var _component2 = _interopRequireDefault(_component);
  5. var _htmlTrackElement = require('../tracks/html-track-element');
  6. var _htmlTrackElement2 = _interopRequireDefault(_htmlTrackElement);
  7. var _htmlTrackElementList = require('../tracks/html-track-element-list');
  8. var _htmlTrackElementList2 = _interopRequireDefault(_htmlTrackElementList);
  9. var _mergeOptions = require('../utils/merge-options.js');
  10. var _mergeOptions2 = _interopRequireDefault(_mergeOptions);
  11. var _textTrack = require('../tracks/text-track');
  12. var _textTrack2 = _interopRequireDefault(_textTrack);
  13. var _textTrackList = require('../tracks/text-track-list');
  14. var _textTrackList2 = _interopRequireDefault(_textTrackList);
  15. var _videoTrackList = require('../tracks/video-track-list');
  16. var _videoTrackList2 = _interopRequireDefault(_videoTrackList);
  17. var _audioTrackList = require('../tracks/audio-track-list');
  18. var _audioTrackList2 = _interopRequireDefault(_audioTrackList);
  19. var _fn = require('../utils/fn.js');
  20. var Fn = _interopRequireWildcard(_fn);
  21. var _log = require('../utils/log.js');
  22. var _log2 = _interopRequireDefault(_log);
  23. var _timeRanges = require('../utils/time-ranges.js');
  24. var _buffer = require('../utils/buffer.js');
  25. var _mediaError = require('../media-error.js');
  26. var _mediaError2 = _interopRequireDefault(_mediaError);
  27. var _window = require('global/window');
  28. var _window2 = _interopRequireDefault(_window);
  29. var _document = require('global/document');
  30. var _document2 = _interopRequireDefault(_document);
  31. var _obj = require('../utils/obj');
  32. 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; } }
  33. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
  34. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  35. function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
  36. function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
  37. * @file tech.js
  38. */
  39. /**
  40. * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
  41. * that just contains the src url alone.
  42. * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
  43. * `var SourceString = 'http://example.com/some-video.mp4';`
  44. *
  45. * @typedef {Object|string} Tech~SourceObject
  46. *
  47. * @property {string} src
  48. * The url to the source
  49. *
  50. * @property {string} type
  51. * The mime type of the source
  52. */
  53. /**
  54. * A function used by {@link Tech} to create a new {@link TextTrack}.
  55. *
  56. * @param {Tech} self
  57. * An instance of the Tech class.
  58. *
  59. * @param {string} kind
  60. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  61. *
  62. * @param {string} [label]
  63. * Label to identify the text track
  64. *
  65. * @param {string} [language]
  66. * Two letter language abbreviation
  67. *
  68. * @param {Object} [options={}]
  69. * An object with additional text track options
  70. *
  71. * @return {TextTrack}
  72. * The text track that was created.
  73. */
  74. function createTrackHelper(self, kind, label, language) {
  75. var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
  76. var tracks = self.textTracks();
  77. options.kind = kind;
  78. if (label) {
  79. options.label = label;
  80. }
  81. if (language) {
  82. options.language = language;
  83. }
  84. options.tech = self;
  85. var track = new _textTrack2['default'](options);
  86. tracks.addTrack_(track);
  87. return track;
  88. }
  89. /**
  90. * This is the base class for media playback technology controllers, such as
  91. * {@link Flash} and {@link HTML5}
  92. *
  93. * @extends Component
  94. */
  95. var Tech = function (_Component) {
  96. _inherits(Tech, _Component);
  97. /**
  98. * Create an instance of this Tech.
  99. *
  100. * @param {Object} [options]
  101. * The key/value store of player options.
  102. *
  103. * @param {Component~ReadyCallback} ready
  104. * Callback function to call when the `HTML5` Tech is ready.
  105. */
  106. function Tech() {
  107. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  108. var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
  109. _classCallCheck(this, Tech);
  110. // we don't want the tech to report user activity automatically.
  111. // This is done manually in addControlsListeners
  112. options.reportTouchActivity = false;
  113. // keep track of whether the current source has played at all to
  114. // implement a very limited played()
  115. var _this = _possibleConstructorReturn(this, _Component.call(this, null, options, ready));
  116. _this.hasStarted_ = false;
  117. _this.on('playing', function () {
  118. this.hasStarted_ = true;
  119. });
  120. _this.on('loadstart', function () {
  121. this.hasStarted_ = false;
  122. });
  123. _this.textTracks_ = options.textTracks;
  124. _this.videoTracks_ = options.videoTracks;
  125. _this.audioTracks_ = options.audioTracks;
  126. // Manually track progress in cases where the browser/flash player doesn't report it.
  127. if (!_this.featuresProgressEvents) {
  128. _this.manualProgressOn();
  129. }
  130. // Manually track timeupdates in cases where the browser/flash player doesn't report it.
  131. if (!_this.featuresTimeupdateEvents) {
  132. _this.manualTimeUpdatesOn();
  133. }
  134. ['Text', 'Audio', 'Video'].forEach(function (track) {
  135. if (options['native' + track + 'Tracks'] === false) {
  136. _this['featuresNative' + track + 'Tracks'] = false;
  137. }
  138. });
  139. if (options.nativeCaptions === false) {
  140. _this.featuresNativeTextTracks = false;
  141. }
  142. if (!_this.featuresNativeTextTracks) {
  143. _this.emulateTextTracks();
  144. }
  145. _this.autoRemoteTextTracks_ = new _textTrackList2['default']();
  146. _this.initTextTrackListeners();
  147. _this.initTrackListeners();
  148. // Turn on component tap events only if not using native controls
  149. if (!options.nativeControlsForTouch) {
  150. _this.emitTapEvents();
  151. }
  152. if (_this.constructor) {
  153. _this.name_ = _this.constructor.name || 'Unknown Tech';
  154. }
  155. return _this;
  156. }
  157. /* Fallbacks for unsupported event types
  158. ================================================================================ */
  159. /**
  160. * Polyfill the `progress` event for browsers that don't support it natively.
  161. *
  162. * @see {@link Tech#trackProgress}
  163. */
  164. Tech.prototype.manualProgressOn = function manualProgressOn() {
  165. this.on('durationchange', this.onDurationChange);
  166. this.manualProgress = true;
  167. // Trigger progress watching when a source begins loading
  168. this.one('ready', this.trackProgress);
  169. };
  170. /**
  171. * Turn off the polyfill for `progress` events that was created in
  172. * {@link Tech#manualProgressOn}
  173. */
  174. Tech.prototype.manualProgressOff = function manualProgressOff() {
  175. this.manualProgress = false;
  176. this.stopTrackingProgress();
  177. this.off('durationchange', this.onDurationChange);
  178. };
  179. /**
  180. * This is used to trigger a `progress` event when the buffered percent changes. It
  181. * sets an interval function that will be called every 500 milliseconds to check if the
  182. * buffer end percent has changed.
  183. *
  184. * > This function is called by {@link Tech#manualProgressOn}
  185. *
  186. * @param {EventTarget~Event} event
  187. * The `ready` event that caused this to run.
  188. *
  189. * @listens Tech#ready
  190. * @fires Tech#progress
  191. */
  192. Tech.prototype.trackProgress = function trackProgress(event) {
  193. this.stopTrackingProgress();
  194. this.progressInterval = this.setInterval(Fn.bind(this, function () {
  195. // Don't trigger unless buffered amount is greater than last time
  196. var numBufferedPercent = this.bufferedPercent();
  197. if (this.bufferedPercent_ !== numBufferedPercent) {
  198. /**
  199. * See {@link Player#progress}
  200. *
  201. * @event Tech#progress
  202. * @type {EventTarget~Event}
  203. */
  204. this.trigger('progress');
  205. }
  206. this.bufferedPercent_ = numBufferedPercent;
  207. if (numBufferedPercent === 1) {
  208. this.stopTrackingProgress();
  209. }
  210. }), 500);
  211. };
  212. /**
  213. * Update our internal duration on a `durationchange` event by calling
  214. * {@link Tech#duration}.
  215. *
  216. * @param {EventTarget~Event} event
  217. * The `durationchange` event that caused this to run.
  218. *
  219. * @listens Tech#durationchange
  220. */
  221. Tech.prototype.onDurationChange = function onDurationChange(event) {
  222. this.duration_ = this.duration();
  223. };
  224. /**
  225. * Get and create a `TimeRange` object for buffering.
  226. *
  227. * @return {TimeRange}
  228. * The time range object that was created.
  229. */
  230. Tech.prototype.buffered = function buffered() {
  231. return (0, _timeRanges.createTimeRange)(0, 0);
  232. };
  233. /**
  234. * Get the percentage of the current video that is currently buffered.
  235. *
  236. * @return {number}
  237. * A number from 0 to 1 that represents the decimal percentage of the
  238. * video that is buffered.
  239. *
  240. */
  241. Tech.prototype.bufferedPercent = function bufferedPercent() {
  242. return (0, _buffer.bufferedPercent)(this.buffered(), this.duration_);
  243. };
  244. /**
  245. * Turn off the polyfill for `progress` events that was created in
  246. * {@link Tech#manualProgressOn}
  247. * Stop manually tracking progress events by clearing the interval that was set in
  248. * {@link Tech#trackProgress}.
  249. */
  250. Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
  251. this.clearInterval(this.progressInterval);
  252. };
  253. /**
  254. * Polyfill the `timeupdate` event for browsers that don't support it.
  255. *
  256. * @see {@link Tech#trackCurrentTime}
  257. */
  258. Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
  259. this.manualTimeUpdates = true;
  260. this.on('play', this.trackCurrentTime);
  261. this.on('pause', this.stopTrackingCurrentTime);
  262. };
  263. /**
  264. * Turn off the polyfill for `timeupdate` events that was created in
  265. * {@link Tech#manualTimeUpdatesOn}
  266. */
  267. Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
  268. this.manualTimeUpdates = false;
  269. this.stopTrackingCurrentTime();
  270. this.off('play', this.trackCurrentTime);
  271. this.off('pause', this.stopTrackingCurrentTime);
  272. };
  273. /**
  274. * Sets up an interval function to track current time and trigger `timeupdate` every
  275. * 250 milliseconds.
  276. *
  277. * @listens Tech#play
  278. * @triggers Tech#timeupdate
  279. */
  280. Tech.prototype.trackCurrentTime = function trackCurrentTime() {
  281. if (this.currentTimeInterval) {
  282. this.stopTrackingCurrentTime();
  283. }
  284. this.currentTimeInterval = this.setInterval(function () {
  285. /**
  286. * Triggered at an interval of 250ms to indicated that time is passing in the video.
  287. *
  288. * @event Tech#timeupdate
  289. * @type {EventTarget~Event}
  290. */
  291. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  292. // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  293. }, 250);
  294. };
  295. /**
  296. * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
  297. * `timeupdate` event is no longer triggered.
  298. *
  299. * @listens {Tech#pause}
  300. */
  301. Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
  302. this.clearInterval(this.currentTimeInterval);
  303. // #1002 - if the video ends right before the next timeupdate would happen,
  304. // the progress bar won't make it all the way to the end
  305. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  306. };
  307. /**
  308. * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
  309. * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
  310. *
  311. * @fires Component#dispose
  312. */
  313. Tech.prototype.dispose = function dispose() {
  314. // clear out all tracks because we can't reuse them between techs
  315. this.clearTracks(['audio', 'video', 'text']);
  316. // Turn off any manual progress or timeupdate tracking
  317. if (this.manualProgress) {
  318. this.manualProgressOff();
  319. }
  320. if (this.manualTimeUpdates) {
  321. this.manualTimeUpdatesOff();
  322. }
  323. _Component.prototype.dispose.call(this);
  324. };
  325. /**
  326. * Clear out a single `TrackList` or an array of `TrackLists` given their names.
  327. *
  328. * > Note: Techs without source handlers should call this between sources for `video`
  329. * & `audio` tracks. You don't want to use them between tracks!
  330. *
  331. * @param {string[]|string} types
  332. * TrackList names to clear, valid names are `video`, `audio`, and
  333. * `text`.
  334. */
  335. Tech.prototype.clearTracks = function clearTracks(types) {
  336. var _this2 = this;
  337. types = [].concat(types);
  338. // clear out all tracks because we can't reuse them between techs
  339. types.forEach(function (type) {
  340. var list = _this2[type + 'Tracks']() || [];
  341. var i = list.length;
  342. while (i--) {
  343. var track = list[i];
  344. if (type === 'text') {
  345. _this2.removeRemoteTextTrack(track);
  346. }
  347. list.removeTrack_(track);
  348. }
  349. });
  350. };
  351. /**
  352. * Remove any TextTracks added via addRemoteTextTrack that are
  353. * flagged for automatic garbage collection
  354. */
  355. Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
  356. var list = this.autoRemoteTextTracks_ || [];
  357. var i = list.length;
  358. while (i--) {
  359. var track = list[i];
  360. this.removeRemoteTextTrack(track);
  361. }
  362. };
  363. /**
  364. * Reset the tech, which will removes all sources and reset the internal readyState.
  365. *
  366. * @abstract
  367. */
  368. Tech.prototype.reset = function reset() {};
  369. /**
  370. * Get or set an error on the Tech.
  371. *
  372. * @param {MediaError} [err]
  373. * Error to set on the Tech
  374. *
  375. * @return {MediaError|null}
  376. * The current error object on the tech, or null if there isn't one.
  377. */
  378. Tech.prototype.error = function error(err) {
  379. if (err !== undefined) {
  380. this.error_ = new _mediaError2['default'](err);
  381. this.trigger('error');
  382. }
  383. return this.error_;
  384. };
  385. /**
  386. * Returns the `TimeRange`s that have been played through for the current source.
  387. *
  388. * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
  389. * It only checks wether the source has played at all or not.
  390. *
  391. * @return {TimeRange}
  392. * - A single time range if this video has played
  393. * - An empty set of ranges if not.
  394. */
  395. Tech.prototype.played = function played() {
  396. if (this.hasStarted_) {
  397. return (0, _timeRanges.createTimeRange)(0, 0);
  398. }
  399. return (0, _timeRanges.createTimeRange)();
  400. };
  401. /**
  402. * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
  403. * previously called.
  404. *
  405. * @fires Tech#timeupdate
  406. */
  407. Tech.prototype.setCurrentTime = function setCurrentTime() {
  408. // improve the accuracy of manual timeupdates
  409. if (this.manualTimeUpdates) {
  410. /**
  411. * A manual `timeupdate` event.
  412. *
  413. * @event Tech#timeupdate
  414. * @type {EventTarget~Event}
  415. */
  416. this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
  417. }
  418. };
  419. /**
  420. * Turn on listeners for {@link TextTrackList} events. This adds
  421. * {@link EventTarget~EventListeners} for `texttrackchange`, `addtrack` and
  422. * `removetrack`.
  423. *
  424. * @fires Tech#texttrackchange
  425. */
  426. Tech.prototype.initTextTrackListeners = function initTextTrackListeners() {
  427. var textTrackListChanges = Fn.bind(this, function () {
  428. /**
  429. * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
  430. *
  431. * @event Tech#texttrackchange
  432. * @type {EventTarget~Event}
  433. */
  434. this.trigger('texttrackchange');
  435. });
  436. var tracks = this.textTracks();
  437. if (!tracks) {
  438. return;
  439. }
  440. tracks.addEventListener('removetrack', textTrackListChanges);
  441. tracks.addEventListener('addtrack', textTrackListChanges);
  442. this.on('dispose', Fn.bind(this, function () {
  443. tracks.removeEventListener('removetrack', textTrackListChanges);
  444. tracks.removeEventListener('addtrack', textTrackListChanges);
  445. }));
  446. };
  447. /**
  448. * Turn on listeners for {@link VideoTrackList} and {@link {AudioTrackList} events.
  449. * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
  450. *
  451. * @fires Tech#audiotrackchange
  452. * @fires Tech#videotrackchange
  453. */
  454. Tech.prototype.initTrackListeners = function initTrackListeners() {
  455. var _this3 = this;
  456. var trackTypes = ['video', 'audio'];
  457. trackTypes.forEach(function (type) {
  458. /**
  459. * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
  460. *
  461. * @event Tech#audiotrackchange
  462. * @type {EventTarget~Event}
  463. */
  464. /**
  465. * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
  466. *
  467. * @event Tech#videotrackchange
  468. * @type {EventTarget~Event}
  469. */
  470. var trackListChanges = function trackListChanges() {
  471. _this3.trigger(type + 'trackchange');
  472. };
  473. var tracks = _this3[type + 'Tracks']();
  474. tracks.addEventListener('removetrack', trackListChanges);
  475. tracks.addEventListener('addtrack', trackListChanges);
  476. _this3.on('dispose', function () {
  477. tracks.removeEventListener('removetrack', trackListChanges);
  478. tracks.removeEventListener('addtrack', trackListChanges);
  479. });
  480. });
  481. };
  482. /**
  483. * Emulate TextTracks using vtt.js if necessary
  484. *
  485. * @fires Tech#vttjsloaded
  486. * @fires Tech#vttjserror
  487. */
  488. Tech.prototype.addWebVttScript_ = function addWebVttScript_() {
  489. var _this4 = this;
  490. if (_window2['default'].WebVTT) {
  491. return;
  492. }
  493. // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
  494. // signals that the Tech is ready at which point Tech.el_ is part of the DOM
  495. // before inserting the WebVTT script
  496. if (_document2['default'].body.contains(this.el())) {
  497. var vtt = require('videojs-vtt.js');
  498. // load via require if available and vtt.js script location was not passed in
  499. // as an option. novtt builds will turn the above require call into an empty object
  500. // which will cause this if check to always fail.
  501. if (!this.options_['vtt.js'] && (0, _obj.isPlain)(vtt) && Object.keys(vtt).length > 0) {
  502. this.trigger('vttjsloaded');
  503. return;
  504. }
  505. // load vtt.js via the script location option or the cdn of no location was
  506. // passed in
  507. var script = _document2['default'].createElement('script');
  508. script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.12.3/vtt.min.js';
  509. script.onload = function () {
  510. /**
  511. * Fired when vtt.js is loaded.
  512. *
  513. * @event Tech#vttjsloaded
  514. * @type {EventTarget~Event}
  515. */
  516. _this4.trigger('vttjsloaded');
  517. };
  518. script.onerror = function () {
  519. /**
  520. * Fired when vtt.js was not loaded due to an error
  521. *
  522. * @event Tech#vttjsloaded
  523. * @type {EventTarget~Event}
  524. */
  525. _this4.trigger('vttjserror');
  526. };
  527. this.on('dispose', function () {
  528. script.onload = null;
  529. script.onerror = null;
  530. });
  531. // but have not loaded yet and we set it to true before the inject so that
  532. // we don't overwrite the injected window.WebVTT if it loads right away
  533. _window2['default'].WebVTT = true;
  534. this.el().parentNode.appendChild(script);
  535. } else {
  536. this.ready(this.addWebVttScript_);
  537. }
  538. };
  539. /**
  540. * Emulate texttracks
  541. *
  542. * @method emulateTextTracks
  543. */
  544. Tech.prototype.emulateTextTracks = function emulateTextTracks() {
  545. var _this5 = this;
  546. var tracks = this.textTracks();
  547. if (!tracks) {
  548. return;
  549. }
  550. var remoteTracks = this.remoteTextTracks();
  551. var handleAddTrack = function handleAddTrack(e) {
  552. return tracks.addTrack_(e.track);
  553. };
  554. var handleRemoveTrack = function handleRemoveTrack(e) {
  555. return tracks.removeTrack_(e.track);
  556. };
  557. remoteTracks.on('addtrack', handleAddTrack);
  558. remoteTracks.on('removetrack', handleRemoveTrack);
  559. this.addWebVttScript_();
  560. var updateDisplay = function updateDisplay() {
  561. return _this5.trigger('texttrackchange');
  562. };
  563. var textTracksChanges = function textTracksChanges() {
  564. updateDisplay();
  565. for (var i = 0; i < tracks.length; i++) {
  566. var track = tracks[i];
  567. track.removeEventListener('cuechange', updateDisplay);
  568. if (track.mode === 'showing') {
  569. track.addEventListener('cuechange', updateDisplay);
  570. }
  571. }
  572. };
  573. textTracksChanges();
  574. tracks.addEventListener('change', textTracksChanges);
  575. tracks.addEventListener('addtrack', textTracksChanges);
  576. tracks.addEventListener('removetrack', textTracksChanges);
  577. this.on('dispose', function () {
  578. remoteTracks.off('addtrack', handleAddTrack);
  579. remoteTracks.off('removetrack', handleRemoveTrack);
  580. tracks.removeEventListener('change', textTracksChanges);
  581. tracks.removeEventListener('addtrack', textTracksChanges);
  582. tracks.removeEventListener('removetrack', textTracksChanges);
  583. for (var i = 0; i < tracks.length; i++) {
  584. var track = tracks[i];
  585. track.removeEventListener('cuechange', updateDisplay);
  586. }
  587. });
  588. };
  589. /**
  590. * Get the `Tech`s {@link VideoTrackList}.
  591. *
  592. * @return {VideoTrackList}
  593. * The video track list that the Tech is currently using.
  594. */
  595. Tech.prototype.videoTracks = function videoTracks() {
  596. this.videoTracks_ = this.videoTracks_ || new _videoTrackList2['default']();
  597. return this.videoTracks_;
  598. };
  599. /**
  600. * Get the `Tech`s {@link AudioTrackList}.
  601. *
  602. * @return {AudioTrackList}
  603. * The audio track list that the Tech is currently using.
  604. */
  605. Tech.prototype.audioTracks = function audioTracks() {
  606. this.audioTracks_ = this.audioTracks_ || new _audioTrackList2['default']();
  607. return this.audioTracks_;
  608. };
  609. /**
  610. * Get the `Tech`s {@link TextTrackList}.
  611. *
  612. * @return {TextTrackList}
  613. * The text track list that the Tech is currently using.
  614. */
  615. Tech.prototype.textTracks = function textTracks() {
  616. this.textTracks_ = this.textTracks_ || new _textTrackList2['default']();
  617. return this.textTracks_;
  618. };
  619. /**
  620. * Get the `Tech`s remote {@link TextTrackList}, which is created from elements
  621. * that were added to the DOM.
  622. *
  623. * @return {TextTrackList}
  624. * The remote text track list that the Tech is currently using.
  625. */
  626. Tech.prototype.remoteTextTracks = function remoteTextTracks() {
  627. this.remoteTextTracks_ = this.remoteTextTracks_ || new _textTrackList2['default']();
  628. return this.remoteTextTracks_;
  629. };
  630. /**
  631. * Get The `Tech`s {HTMLTrackElementList}, which are the elements in the DOM that are
  632. * being used as TextTracks.
  633. *
  634. * @return {HTMLTrackElementList}
  635. * The current HTML track elements that exist for the tech.
  636. */
  637. Tech.prototype.remoteTextTrackEls = function remoteTextTrackEls() {
  638. this.remoteTextTrackEls_ = this.remoteTextTrackEls_ || new _htmlTrackElementList2['default']();
  639. return this.remoteTextTrackEls_;
  640. };
  641. /**
  642. * Create and returns a remote {@link TextTrack} object.
  643. *
  644. * @param {string} kind
  645. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
  646. *
  647. * @param {string} [label]
  648. * Label to identify the text track
  649. *
  650. * @param {string} [language]
  651. * Two letter language abbreviation
  652. *
  653. * @return {TextTrack}
  654. * The TextTrack that gets created.
  655. */
  656. Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
  657. if (!kind) {
  658. throw new Error('TextTrack kind is required but was not provided');
  659. }
  660. return createTrackHelper(this, kind, label, language);
  661. };
  662. /**
  663. * Create an emulated TextTrack for use by addRemoteTextTrack
  664. *
  665. * This is intended to be overridden by classes that inherit from
  666. * Tech in order to create native or custom TextTracks.
  667. *
  668. * @param {Object} options
  669. * The object should contain the options to initialize the TextTrack with.
  670. *
  671. * @param {string} [options.kind]
  672. * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
  673. *
  674. * @param {string} [options.label].
  675. * Label to identify the text track
  676. *
  677. * @param {string} [options.language]
  678. * Two letter language abbreviation.
  679. *
  680. * @return {HTMLTrackElement}
  681. * The track element that gets created.
  682. */
  683. Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
  684. var track = (0, _mergeOptions2['default'])(options, {
  685. tech: this
  686. });
  687. return new _htmlTrackElement2['default'](track);
  688. };
  689. /**
  690. * Creates a remote text track object and returns an html track element.
  691. *
  692. * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
  693. *
  694. * @param {Object} options
  695. * See {@link Tech#createRemoteTextTrack} for more detailed properties.
  696. *
  697. * @param {boolean} [manualCleanup=true]
  698. * - When false: the TextTrack will be automatically removed from the video
  699. * element whenever the source changes
  700. * - When True: The TextTrack will have to be cleaned up manually
  701. *
  702. * @return {HTMLTrackElement}
  703. * An Html Track Element.
  704. *
  705. * @deprecated The default functionality for this function will be equivalent
  706. * to "manualCleanup=false" in the future. The manualCleanup parameter will
  707. * also be removed.
  708. */
  709. Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
  710. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  711. var manualCleanup = arguments[1];
  712. var htmlTrackElement = this.createRemoteTextTrack(options);
  713. if (manualCleanup !== true && manualCleanup !== false) {
  714. // deprecation warning
  715. _log2['default'].warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
  716. manualCleanup = true;
  717. }
  718. // store HTMLTrackElement and TextTrack to remote list
  719. this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
  720. this.remoteTextTracks().addTrack_(htmlTrackElement.track);
  721. if (manualCleanup !== true) {
  722. // create the TextTrackList if it doesn't exist
  723. this.autoRemoteTextTracks_.addTrack_(htmlTrackElement.track);
  724. }
  725. return htmlTrackElement;
  726. };
  727. /**
  728. * Remove a remote text track from the remote `TextTrackList`.
  729. *
  730. * @param {TextTrack} track
  731. * `TextTrack` to remove from the `TextTrackList`
  732. */
  733. Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
  734. var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
  735. // remove HTMLTrackElement and TextTrack from remote list
  736. this.remoteTextTrackEls().removeTrackElement_(trackElement);
  737. this.remoteTextTracks().removeTrack_(track);
  738. this.autoRemoteTextTracks_.removeTrack_(track);
  739. };
  740. /**
  741. * A method to set a poster from a `Tech`.
  742. *
  743. * @abstract
  744. */
  745. Tech.prototype.setPoster = function setPoster() {};
  746. /*
  747. * Check if the tech can support the given mime-type.
  748. *
  749. * The base tech does not support any type, but source handlers might
  750. * overwrite this.
  751. *
  752. * @param {string} type
  753. * The mimetype to check for support
  754. *
  755. * @return {string}
  756. * 'probably', 'maybe', or empty string
  757. *
  758. * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
  759. *
  760. * @abstract
  761. */
  762. Tech.prototype.canPlayType = function canPlayType() {
  763. return '';
  764. };
  765. /*
  766. * Return whether the argument is a Tech or not.
  767. * Can be passed either a Class like `Html5` or a instance like `player.tech_`
  768. *
  769. * @param {Object} component
  770. * The item to check
  771. *
  772. * @return {boolean}
  773. * Whether it is a tech or not
  774. * - True if it is a tech
  775. * - False if it is not
  776. */
  777. Tech.isTech = function isTech(component) {
  778. return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
  779. };
  780. /**
  781. * Registers a `Tech` into a shared list for videojs.
  782. *
  783. * @param {string} name
  784. * Name of the `Tech` to register.
  785. *
  786. * @param {Object} tech
  787. * The `Tech` class to register.
  788. */
  789. Tech.registerTech = function registerTech(name, tech) {
  790. if (!Tech.techs_) {
  791. Tech.techs_ = {};
  792. }
  793. if (!Tech.isTech(tech)) {
  794. throw new Error('Tech ' + name + ' must be a Tech');
  795. }
  796. Tech.techs_[name] = tech;
  797. return tech;
  798. };
  799. /**
  800. * Get a `Tech` from the shared list by name.
  801. *
  802. * @param {string} name
  803. * Name of the component to get
  804. *
  805. * @return {Tech|undefined}
  806. * The `Tech` or undefined if there was no tech with the name requsted.
  807. */
  808. Tech.getTech = function getTech(name) {
  809. if (Tech.techs_ && Tech.techs_[name]) {
  810. return Tech.techs_[name];
  811. }
  812. if (_window2['default'] && _window2['default'].videojs && _window2['default'].videojs[name]) {
  813. _log2['default'].warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
  814. return _window2['default'].videojs[name];
  815. }
  816. };
  817. return Tech;
  818. }(_component2['default']);
  819. /**
  820. * List of associated text tracks.
  821. *
  822. * @type {TextTrackList}
  823. * @private
  824. */
  825. Tech.prototype.textTracks_; // eslint-disable-line
  826. /**
  827. * List of associated audio tracks.
  828. *
  829. * @type {AudioTrackList}
  830. * @private
  831. */
  832. Tech.prototype.audioTracks_; // eslint-disable-line
  833. /**
  834. * List of associated video tracks.
  835. *
  836. * @type {VideoTrackList}
  837. * @private
  838. */
  839. Tech.prototype.videoTracks_; // eslint-disable-line
  840. /**
  841. * Boolean indicating wether the `Tech` supports volume control.
  842. *
  843. * @type {boolean}
  844. * @default
  845. */
  846. Tech.prototype.featuresVolumeControl = true;
  847. /**
  848. * Boolean indicating wether the `Tech` support fullscreen resize control.
  849. * Resizing plugins using request fullscreen reloads the plugin
  850. *
  851. * @type {boolean}
  852. * @default
  853. */
  854. Tech.prototype.featuresFullscreenResize = false;
  855. /**
  856. * Boolean indicating wether the `Tech` supports changing the speed at which the video
  857. * plays. Examples:
  858. * - Set player to play 2x (twice) as fast
  859. * - Set player to play 0.5x (half) as fast
  860. *
  861. * @type {boolean}
  862. * @default
  863. */
  864. Tech.prototype.featuresPlaybackRate = false;
  865. /**
  866. * Boolean indicating wether the `Tech` supports the `progress` event. This is currently
  867. * not triggered by video-js-swf. This will be used to determine if
  868. * {@link Tech#manualProgressOn} should be called.
  869. *
  870. * @type {boolean}
  871. * @default
  872. */
  873. Tech.prototype.featuresProgressEvents = false;
  874. /**
  875. * Boolean indicating wether the `Tech` supports the `timeupdate` event. This is currently
  876. * not triggered by video-js-swf. This will be used to determine if
  877. * {@link Tech#manualTimeUpdates} should be called.
  878. *
  879. * @type {boolean}
  880. * @default
  881. */
  882. Tech.prototype.featuresTimeupdateEvents = false;
  883. /**
  884. * Boolean indicating wether the `Tech` supports the native `TextTrack`s.
  885. * This will help us integrate with native `TextTrack`s if the browser supports them.
  886. *
  887. * @type {boolean}
  888. * @default
  889. */
  890. Tech.prototype.featuresNativeTextTracks = false;
  891. /**
  892. * A functional mixin for techs that want to use the Source Handler pattern.
  893. * Source handlers are scripts for handling specific formats.
  894. * The source handler pattern is used for adaptive formats (HLS, DASH) that
  895. * manually load video data and feed it into a Source Buffer (Media Source Extensions)
  896. * Example: `Tech.withSourceHandlers.call(MyTech);`
  897. *
  898. * @param {Tech} _Tech
  899. * The tech to add source handler functions to.
  900. *
  901. * @mixes Tech~SourceHandlerAdditions
  902. */
  903. Tech.withSourceHandlers = function (_Tech) {
  904. /**
  905. * Register a source handler
  906. *
  907. * @param {Function} handler
  908. * The source handler class
  909. *
  910. * @param {number} [index]
  911. * Register it at the following index
  912. */
  913. _Tech.registerSourceHandler = function (handler, index) {
  914. var handlers = _Tech.sourceHandlers;
  915. if (!handlers) {
  916. handlers = _Tech.sourceHandlers = [];
  917. }
  918. if (index === undefined) {
  919. // add to the end of the list
  920. index = handlers.length;
  921. }
  922. handlers.splice(index, 0, handler);
  923. };
  924. /**
  925. * Check if the tech can support the given type. Also checks the
  926. * Techs sourceHandlers.
  927. *
  928. * @param {string} type
  929. * The mimetype to check.
  930. *
  931. * @return {string}
  932. * 'probably', 'maybe', or '' (empty string)
  933. */
  934. _Tech.canPlayType = function (type) {
  935. var handlers = _Tech.sourceHandlers || [];
  936. var can = void 0;
  937. for (var i = 0; i < handlers.length; i++) {
  938. can = handlers[i].canPlayType(type);
  939. if (can) {
  940. return can;
  941. }
  942. }
  943. return '';
  944. };
  945. /**
  946. * Returns the first source handler that supports the source.
  947. *
  948. * TODO: Answer question: should 'probably' be prioritized over 'maybe'
  949. *
  950. * @param {Tech~SourceObject} source
  951. * The source object
  952. *
  953. * @param {Object} options
  954. * The options passed to the tech
  955. *
  956. * @return {SourceHandler|null}
  957. * The first source handler that supports the source or null if
  958. * no SourceHandler supports the source
  959. */
  960. _Tech.selectSourceHandler = function (source, options) {
  961. var handlers = _Tech.sourceHandlers || [];
  962. var can = void 0;
  963. for (var i = 0; i < handlers.length; i++) {
  964. can = handlers[i].canHandleSource(source, options);
  965. if (can) {
  966. return handlers[i];
  967. }
  968. }
  969. return null;
  970. };
  971. /**
  972. * Check if the tech can support the given source.
  973. *
  974. * @param {Tech~SourceObject} srcObj
  975. * The source object
  976. *
  977. * @param {Object} options
  978. * The options passed to the tech
  979. *
  980. * @return {string}
  981. * 'probably', 'maybe', or '' (empty string)
  982. */
  983. _Tech.canPlaySource = function (srcObj, options) {
  984. var sh = _Tech.selectSourceHandler(srcObj, options);
  985. if (sh) {
  986. return sh.canHandleSource(srcObj, options);
  987. }
  988. return '';
  989. };
  990. /**
  991. * When using a source handler, prefer its implementation of
  992. * any function normally provided by the tech.
  993. */
  994. var deferrable = ['seekable', 'duration'];
  995. /**
  996. * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
  997. * function if it exists, with a fallback to the Techs seekable function.
  998. *
  999. * @method _Tech.seekable
  1000. */
  1001. /**
  1002. * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
  1003. * function if it exists, otherwise it will fallback to the techs duration function.
  1004. *
  1005. * @method _Tech.duration
  1006. */
  1007. deferrable.forEach(function (fnName) {
  1008. var originalFn = this[fnName];
  1009. if (typeof originalFn !== 'function') {
  1010. return;
  1011. }
  1012. this[fnName] = function () {
  1013. if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
  1014. return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
  1015. }
  1016. return originalFn.apply(this, arguments);
  1017. };
  1018. }, _Tech.prototype);
  1019. /**
  1020. * Create a function for setting the source using a source object
  1021. * and source handlers.
  1022. * Should never be called unless a source handler was found.
  1023. *
  1024. * @param {Tech~SourceObject} source
  1025. * A source object with src and type keys
  1026. *
  1027. * @return {Tech}
  1028. * Returns itself; this method is chainable
  1029. */
  1030. _Tech.prototype.setSource = function (source) {
  1031. var sh = _Tech.selectSourceHandler(source, this.options_);
  1032. if (!sh) {
  1033. // Fall back to a native source hander when unsupported sources are
  1034. // deliberately set
  1035. if (_Tech.nativeSourceHandler) {
  1036. sh = _Tech.nativeSourceHandler;
  1037. } else {
  1038. _log2['default'].error('No source hander found for the current source.');
  1039. }
  1040. }
  1041. // Dispose any existing source handler
  1042. this.disposeSourceHandler();
  1043. this.off('dispose', this.disposeSourceHandler);
  1044. if (sh !== _Tech.nativeSourceHandler) {
  1045. this.currentSource_ = source;
  1046. // Catch if someone replaced the src without calling setSource.
  1047. // If they do, set currentSource_ to null and dispose our source handler.
  1048. this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
  1049. this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
  1050. this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
  1051. }
  1052. this.sourceHandler_ = sh.handleSource(source, this, this.options_);
  1053. this.on('dispose', this.disposeSourceHandler);
  1054. return this;
  1055. };
  1056. /**
  1057. * Called once for the first loadstart of a video.
  1058. *
  1059. * @listens Tech#loadstart
  1060. */
  1061. _Tech.prototype.firstLoadStartListener_ = function () {
  1062. this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
  1063. };
  1064. // On successive loadstarts when setSource has not been called again
  1065. /**
  1066. * Called after the first loadstart for a video occurs.
  1067. *
  1068. * @listens Tech#loadstart
  1069. */
  1070. _Tech.prototype.successiveLoadStartListener_ = function () {
  1071. this.disposeSourceHandler();
  1072. this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
  1073. };
  1074. /**
  1075. * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
  1076. *
  1077. * @listens Tech#dispose
  1078. */
  1079. _Tech.prototype.disposeSourceHandler = function () {
  1080. // if we have a source and get another one
  1081. // then we are loading something new
  1082. // than clear all of our current tracks
  1083. if (this.currentSource_) {
  1084. this.clearTracks(['audio', 'video']);
  1085. this.currentSource_ = null;
  1086. }
  1087. // always clean up auto-text tracks
  1088. this.cleanupAutoTextTracks();
  1089. if (this.sourceHandler_) {
  1090. this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
  1091. this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
  1092. if (this.sourceHandler_.dispose) {
  1093. this.sourceHandler_.dispose();
  1094. }
  1095. this.sourceHandler_ = null;
  1096. }
  1097. };
  1098. };
  1099. _component2['default'].registerComponent('Tech', Tech);
  1100. // Old name for Tech
  1101. // @deprecated
  1102. _component2['default'].registerComponent('MediaTechController', Tech);
  1103. Tech.registerTech('Tech', Tech);
  1104. exports['default'] = Tech;