Home Reference Source

src/hls.ts

  1. import * as URLToolkit from 'url-toolkit';
  2.  
  3. import { ErrorTypes, ErrorDetails } from './errors';
  4.  
  5. import PlaylistLoader from './loader/playlist-loader';
  6. import KeyLoader from './loader/key-loader';
  7.  
  8. import { FragmentTracker } from './controller/fragment-tracker';
  9. import StreamController from './controller/stream-controller';
  10. import LevelController from './controller/level-controller';
  11.  
  12. import { isSupported } from './is-supported';
  13. import { logger, enableLogs } from './utils/logger';
  14. import {
  15. enableStreamingMode,
  16. HlsConfig,
  17. hlsDefaultConfig,
  18. mergeConfig,
  19. } from './config';
  20.  
  21. import { Events } from './events';
  22. import { EventEmitter } from 'eventemitter3';
  23. import { Level } from './types/level';
  24. import { MediaPlaylist } from './types/media-playlist';
  25. import AudioTrackController from './controller/audio-track-controller';
  26. import SubtitleTrackController from './controller/subtitle-track-controller';
  27. import ID3TrackController from './controller/id3-track-controller';
  28. import EMEController from './controller/eme-controller';
  29. import CapLevelController from './controller/cap-level-controller';
  30. import AbrController from './controller/abr-controller';
  31. import LatencyController from './controller/latency-controller';
  32. import { ComponentAPI, NetworkComponentAPI } from './types/component-api';
  33. import type { HlsEventEmitter, HlsListeners } from './events';
  34.  
  35. /**
  36. * @module Hls
  37. * @class
  38. * @constructor
  39. */
  40. export default class Hls implements HlsEventEmitter {
  41. private static defaultConfig?: HlsConfig;
  42.  
  43. public readonly config: HlsConfig;
  44. public readonly userConfig: Partial<HlsConfig>;
  45.  
  46. private coreComponents: ComponentAPI[];
  47. private networkControllers: NetworkComponentAPI[];
  48.  
  49. private _emitter: HlsEventEmitter = new EventEmitter();
  50. private _autoLevelCapping: number;
  51. private abrController: AbrController;
  52. private capLevelController: CapLevelController;
  53. private latencyController: LatencyController;
  54. private levelController: LevelController;
  55. private streamController: StreamController;
  56. private audioTrackController: AudioTrackController;
  57. private subtitleTrackController: SubtitleTrackController;
  58. private emeController: EMEController;
  59.  
  60. private _media: HTMLMediaElement | null = null;
  61. private url: string | null = null;
  62.  
  63. static get version(): string {
  64. return __VERSION__;
  65. }
  66.  
  67. static isSupported(): boolean {
  68. return isSupported();
  69. }
  70.  
  71. static get Events() {
  72. return Events;
  73. }
  74.  
  75. static get ErrorTypes() {
  76. return ErrorTypes;
  77. }
  78.  
  79. static get ErrorDetails() {
  80. return ErrorDetails;
  81. }
  82.  
  83. static get DefaultConfig(): HlsConfig {
  84. if (!Hls.defaultConfig) {
  85. return hlsDefaultConfig;
  86. }
  87.  
  88. return Hls.defaultConfig;
  89. }
  90.  
  91. /**
  92. * @type {HlsConfig}
  93. */
  94. static set DefaultConfig(defaultConfig: HlsConfig) {
  95. Hls.defaultConfig = defaultConfig;
  96. }
  97.  
  98. /**
  99. * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`.
  100. *
  101. * @constructs Hls
  102. * @param {HlsConfig} config
  103. */
  104. constructor(userConfig: Partial<HlsConfig> = {}) {
  105. const config = (this.config = mergeConfig(Hls.DefaultConfig, userConfig));
  106. this.userConfig = userConfig;
  107. enableLogs(config.debug);
  108.  
  109. this._autoLevelCapping = -1;
  110.  
  111. if (config.progressive) {
  112. enableStreamingMode(config);
  113. }
  114.  
  115. // core controllers and network loaders
  116. const abrController = (this.abrController = new config.abrController(this)); // eslint-disable-line new-cap
  117. const bufferController = new config.bufferController(this); // eslint-disable-line new-cap
  118. const capLevelController = (this.capLevelController = new config.capLevelController(
  119. this
  120. )); // eslint-disable-line new-cap
  121. const fpsController = new config.fpsController(this); // eslint-disable-line new-cap
  122. const playListLoader = new PlaylistLoader(this);
  123. const keyLoader = new KeyLoader(this);
  124. const id3TrackController = new ID3TrackController(this);
  125.  
  126. // network controllers
  127. const levelController = (this.levelController = new LevelController(this));
  128. // FragmentTracker must be defined before StreamController because the order of event handling is important
  129. const fragmentTracker = new FragmentTracker(this);
  130. const streamController = (this.streamController = new StreamController(
  131. this,
  132. fragmentTracker
  133. ));
  134.  
  135. // Level Controller initiates loading after all controllers have received MANIFEST_PARSED
  136. levelController.onParsedComplete = () => {
  137. if (config.autoStartLoad || streamController.forceStartLoad) {
  138. this.startLoad(config.startPosition);
  139. }
  140. };
  141.  
  142. // Cap level controller uses streamController to flush the buffer
  143. capLevelController.setStreamController(streamController);
  144. // fpsController uses streamController to switch when frames are being dropped
  145. fpsController.setStreamController(streamController);
  146.  
  147. const networkControllers = [levelController, streamController];
  148.  
  149. this.networkControllers = networkControllers;
  150. const coreComponents = [
  151. playListLoader,
  152. keyLoader,
  153. abrController,
  154. bufferController,
  155. capLevelController,
  156. fpsController,
  157. id3TrackController,
  158. fragmentTracker,
  159. ];
  160.  
  161. this.audioTrackController = this.createController(
  162. config.audioTrackController,
  163. null,
  164. networkControllers
  165. );
  166. this.createController(
  167. config.audioStreamController,
  168. fragmentTracker,
  169. networkControllers
  170. );
  171. // subtitleTrackController must be defined before because the order of event handling is important
  172. this.subtitleTrackController = this.createController(
  173. config.subtitleTrackController,
  174. null,
  175. networkControllers
  176. );
  177. this.createController(
  178. config.subtitleStreamController,
  179. fragmentTracker,
  180. networkControllers
  181. );
  182. this.createController(config.timelineController, null, coreComponents);
  183. this.emeController = this.createController(
  184. config.emeController,
  185. null,
  186. coreComponents
  187. );
  188. this.latencyController = this.createController(
  189. LatencyController,
  190. null,
  191. coreComponents
  192. );
  193.  
  194. this.coreComponents = coreComponents;
  195. }
  196.  
  197. createController(ControllerClass, fragmentTracker, components) {
  198. if (ControllerClass) {
  199. const controllerInstance = fragmentTracker
  200. ? new ControllerClass(this, fragmentTracker)
  201. : new ControllerClass(this);
  202. if (components) {
  203. components.push(controllerInstance);
  204. }
  205. return controllerInstance;
  206. }
  207. return null;
  208. }
  209.  
  210. // Delegate the EventEmitter through the public API of Hls.js
  211. on<E extends keyof HlsListeners, Context = undefined>(
  212. event: E,
  213. listener: HlsListeners[E],
  214. context?: Context
  215. ) {
  216. const hlsjs = this;
  217. this._emitter.on(
  218. event,
  219. function (this: Context, ...args: unknown[]) {
  220. if (hlsjs.config.debug) {
  221. listener.apply(this, args);
  222. } else {
  223. try {
  224. listener.apply(this, args);
  225. } catch (e) {
  226. logger.error(
  227. 'An internal error happened while handling event ' +
  228. event +
  229. '. Error message: "' +
  230. e.message +
  231. '". Here is a stacktrace:',
  232. e
  233. );
  234. hlsjs.trigger(Events.ERROR, {
  235. type: ErrorTypes.OTHER_ERROR,
  236. details: ErrorDetails.INTERNAL_EXCEPTION,
  237. fatal: false,
  238. event: event,
  239. error: e,
  240. });
  241. }
  242. }
  243. },
  244. context
  245. );
  246. }
  247.  
  248. once<E extends keyof HlsListeners, Context = undefined>(
  249. event: E,
  250. listener: HlsListeners[E],
  251. context?: Context
  252. ) {
  253. const hlsjs = this;
  254. this._emitter.once(
  255. event,
  256. function (this: Context, ...args: unknown[]) {
  257. if (hlsjs.config.debug) {
  258. listener.apply(this, args);
  259. } else {
  260. try {
  261. listener.apply(this, args);
  262. } catch (e) {
  263. logger.error(
  264. 'An internal error happened while handling event ' +
  265. event +
  266. '. Error message: "' +
  267. e.message +
  268. '". Here is a stacktrace:',
  269. e
  270. );
  271. hlsjs.trigger(Events.ERROR, {
  272. type: ErrorTypes.OTHER_ERROR,
  273. details: ErrorDetails.INTERNAL_EXCEPTION,
  274. fatal: false,
  275. event: event,
  276. error: e,
  277. });
  278. }
  279. }
  280. },
  281. context
  282. );
  283. }
  284.  
  285. removeAllListeners<E extends keyof HlsListeners>(event?: E | undefined) {
  286. this._emitter.removeAllListeners(event);
  287. }
  288.  
  289. off<E extends keyof HlsListeners, Context = undefined>(
  290. event: E,
  291. listener?: HlsListeners[E] | undefined,
  292. context?: Context,
  293. once?: boolean | undefined
  294. ) {
  295. this._emitter.off(event, listener, context, once);
  296. }
  297.  
  298. listeners<E extends keyof HlsListeners>(event: E): HlsListeners[E][] {
  299. return this._emitter.listeners(event);
  300. }
  301.  
  302. emit<E extends keyof HlsListeners>(
  303. event: E,
  304. name: E,
  305. eventObject: Parameters<HlsListeners[E]>[1]
  306. ): boolean {
  307. return this._emitter.emit(event, name, eventObject);
  308. }
  309.  
  310. trigger<E extends keyof HlsListeners>(
  311. event: E,
  312. eventObject: Parameters<HlsListeners[E]>[1]
  313. ): boolean {
  314. return this._emitter.emit(event, event, eventObject);
  315. }
  316.  
  317. listenerCount<E extends keyof HlsListeners>(event: E): number {
  318. return this._emitter.listenerCount(event);
  319. }
  320.  
  321. /**
  322. * Dispose of the instance
  323. */
  324. destroy() {
  325. logger.log('destroy');
  326. this.trigger(Events.DESTROYING, undefined);
  327. this.detachMedia();
  328. this.networkControllers.forEach((component) => component.destroy());
  329. this.coreComponents.forEach((component) => component.destroy());
  330. this.url = null;
  331. this.removeAllListeners();
  332. this._autoLevelCapping = -1;
  333. }
  334.  
  335. /**
  336. * Attaches Hls.js to a media element
  337. * @param {HTMLMediaElement} media
  338. */
  339. attachMedia(media: HTMLMediaElement) {
  340. logger.log('attachMedia');
  341. this._media = media;
  342. this.trigger(Events.MEDIA_ATTACHING, { media: media });
  343. }
  344.  
  345. /**
  346. * Detach Hls.js from the media
  347. */
  348. detachMedia() {
  349. logger.log('detachMedia');
  350. this.trigger(Events.MEDIA_DETACHING, undefined);
  351. this._media = null;
  352. }
  353.  
  354. /**
  355. * Set the source URL. Can be relative or absolute.
  356. * @param {string} url
  357. */
  358. loadSource(url: string) {
  359. this.stopLoad();
  360. const media = this.media;
  361. if (media && this.url) {
  362. this.detachMedia();
  363. this.attachMedia(media);
  364. }
  365. url = URLToolkit.buildAbsoluteURL(self.location.href, url, {
  366. alwaysNormalize: true,
  367. });
  368. logger.log(`loadSource:${url}`);
  369. this.url = url;
  370. // when attaching to a source URL, trigger a playlist load
  371. this.trigger(Events.MANIFEST_LOADING, { url: url });
  372. }
  373.  
  374. /**
  375. * Start loading data from the stream source.
  376. * Depending on default config, client starts loading automatically when a source is set.
  377. *
  378. * @param {number} startPosition Set the start position to stream from
  379. * @default -1 None (from earliest point)
  380. */
  381. startLoad(startPosition: number = -1) {
  382. logger.log(`startLoad(${startPosition})`);
  383. this.networkControllers.forEach((controller) => {
  384. controller.startLoad(startPosition);
  385. });
  386. }
  387.  
  388. /**
  389. * Stop loading of any stream data.
  390. */
  391. stopLoad() {
  392. logger.log('stopLoad');
  393. this.networkControllers.forEach((controller) => {
  394. controller.stopLoad();
  395. });
  396. }
  397.  
  398. /**
  399. * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
  400. */
  401. swapAudioCodec() {
  402. logger.log('swapAudioCodec');
  403. this.streamController.swapAudioCodec();
  404. }
  405.  
  406. /**
  407. * When the media-element fails, this allows to detach and then re-attach it
  408. * as one call (convenience method).
  409. *
  410. * Automatic recovery of media-errors by this process is configurable.
  411. */
  412. recoverMediaError() {
  413. logger.log('recoverMediaError');
  414. const media = this._media;
  415. this.detachMedia();
  416. if (media) {
  417. this.attachMedia(media);
  418. }
  419. }
  420.  
  421. removeLevel(levelIndex, urlId = 0) {
  422. this.levelController.removeLevel(levelIndex, urlId);
  423. }
  424.  
  425. /**
  426. * @type {Level[]}
  427. */
  428. get levels(): Array<Level> {
  429. return this.levelController.levels ? this.levelController.levels : [];
  430. }
  431.  
  432. /**
  433. * Index of quality level currently played
  434. * @type {number}
  435. */
  436. get currentLevel(): number {
  437. return this.streamController.currentLevel;
  438. }
  439.  
  440. /**
  441. * Set quality level index immediately .
  442. * This will flush the current buffer to replace the quality asap.
  443. * That means playback will interrupt at least shortly to re-buffer and re-sync eventually.
  444. * @type {number} -1 for automatic level selection
  445. */
  446. set currentLevel(newLevel: number) {
  447. logger.log(`set currentLevel:${newLevel}`);
  448. this.loadLevel = newLevel;
  449. this.streamController.immediateLevelSwitch();
  450. }
  451.  
  452. /**
  453. * Index of next quality level loaded as scheduled by stream controller.
  454. * @type {number}
  455. */
  456. get nextLevel(): number {
  457. return this.streamController.nextLevel;
  458. }
  459.  
  460. /**
  461. * Set quality level index for next loaded data.
  462. * This will switch the video quality asap, without interrupting playback.
  463. * May abort current loading of data, and flush parts of buffer (outside currently played fragment region).
  464. * @type {number} -1 for automatic level selection
  465. */
  466. set nextLevel(newLevel: number) {
  467. logger.log(`set nextLevel:${newLevel}`);
  468. this.levelController.manualLevel = newLevel;
  469. this.streamController.nextLevelSwitch();
  470. }
  471.  
  472. /**
  473. * Return the quality level of the currently or last (of none is loaded currently) segment
  474. * @type {number}
  475. */
  476. get loadLevel(): number {
  477. return this.levelController.level;
  478. }
  479.  
  480. /**
  481. * Set quality level index for next loaded data in a conservative way.
  482. * This will switch the quality without flushing, but interrupt current loading.
  483. * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer.
  484. * @type {number} newLevel -1 for automatic level selection
  485. */
  486. set loadLevel(newLevel: number) {
  487. logger.log(`set loadLevel:${newLevel}`);
  488. this.levelController.manualLevel = newLevel;
  489. }
  490.  
  491. /**
  492. * get next quality level loaded
  493. * @type {number}
  494. */
  495. get nextLoadLevel(): number {
  496. return this.levelController.nextLoadLevel;
  497. }
  498.  
  499. /**
  500. * Set quality level of next loaded segment in a fully "non-destructive" way.
  501. * Same as `loadLevel` but will wait for next switch (until current loading is done).
  502. * @type {number} level
  503. */
  504. set nextLoadLevel(level: number) {
  505. this.levelController.nextLoadLevel = level;
  506. }
  507.  
  508. /**
  509. * Return "first level": like a default level, if not set,
  510. * falls back to index of first level referenced in manifest
  511. * @type {number}
  512. */
  513. get firstLevel(): number {
  514. return Math.max(this.levelController.firstLevel, this.minAutoLevel);
  515. }
  516.  
  517. /**
  518. * Sets "first-level", see getter.
  519. * @type {number}
  520. */
  521. set firstLevel(newLevel: number) {
  522. logger.log(`set firstLevel:${newLevel}`);
  523. this.levelController.firstLevel = newLevel;
  524. }
  525.  
  526. /**
  527. * Return start level (level of first fragment that will be played back)
  528. * if not overrided by user, first level appearing in manifest will be used as start level
  529. * if -1 : automatic start level selection, playback will start from level matching download bandwidth
  530. * (determined from download of first segment)
  531. * @type {number}
  532. */
  533. get startLevel(): number {
  534. return this.levelController.startLevel;
  535. }
  536.  
  537. /**
  538. * set start level (level of first fragment that will be played back)
  539. * if not overrided by user, first level appearing in manifest will be used as start level
  540. * if -1 : automatic start level selection, playback will start from level matching download bandwidth
  541. * (determined from download of first segment)
  542. * @type {number} newLevel
  543. */
  544. set startLevel(newLevel: number) {
  545. logger.log(`set startLevel:${newLevel}`);
  546. // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
  547. if (newLevel !== -1) {
  548. newLevel = Math.max(newLevel, this.minAutoLevel);
  549. }
  550.  
  551. this.levelController.startLevel = newLevel;
  552. }
  553.  
  554. /**
  555. * Get the current setting for capLevelToPlayerSize
  556. *
  557. * @type {boolean}
  558. */
  559. get capLevelToPlayerSize(): boolean {
  560. return this.config.capLevelToPlayerSize;
  561. }
  562.  
  563. /**
  564. * set dynamically set capLevelToPlayerSize against (`CapLevelController`)
  565. *
  566. * @type {boolean}
  567. */
  568. set capLevelToPlayerSize(shouldStartCapping: boolean) {
  569. const newCapLevelToPlayerSize = !!shouldStartCapping;
  570.  
  571. if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
  572. if (newCapLevelToPlayerSize) {
  573. this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size.
  574. } else {
  575. this.capLevelController.stopCapping();
  576. this.autoLevelCapping = -1;
  577. this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap.
  578. }
  579.  
  580. this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
  581. }
  582. }
  583.  
  584. /**
  585. * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
  586. * @type {number}
  587. */
  588. get autoLevelCapping(): number {
  589. return this._autoLevelCapping;
  590. }
  591.  
  592. /**
  593. * get bandwidth estimate
  594. * @type {number}
  595. */
  596. get bandwidthEstimate(): number {
  597. return this.abrController.bwEstimator.getEstimate();
  598. }
  599.  
  600. /**
  601. * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
  602. * @type {number}
  603. */
  604. set autoLevelCapping(newLevel: number) {
  605. if (this._autoLevelCapping !== newLevel) {
  606. logger.log(`set autoLevelCapping:${newLevel}`);
  607. this._autoLevelCapping = newLevel;
  608. }
  609. }
  610.  
  611. /**
  612. * True when automatic level selection enabled
  613. * @type {boolean}
  614. */
  615. get autoLevelEnabled(): boolean {
  616. return this.levelController.manualLevel === -1;
  617. }
  618.  
  619. /**
  620. * Level set manually (if any)
  621. * @type {number}
  622. */
  623. get manualLevel(): number {
  624. return this.levelController.manualLevel;
  625. }
  626.  
  627. /**
  628. * min level selectable in auto mode according to config.minAutoBitrate
  629. * @type {number}
  630. */
  631. get minAutoLevel(): number {
  632. const {
  633. levels,
  634. config: { minAutoBitrate },
  635. } = this;
  636. if (!levels) return 0;
  637.  
  638. const len = levels.length;
  639. for (let i = 0; i < len; i++) {
  640. if (levels[i].maxBitrate > minAutoBitrate) {
  641. return i;
  642. }
  643. }
  644.  
  645. return 0;
  646. }
  647.  
  648. /**
  649. * max level selectable in auto mode according to autoLevelCapping
  650. * @type {number}
  651. */
  652. get maxAutoLevel(): number {
  653. const { levels, autoLevelCapping } = this;
  654.  
  655. let maxAutoLevel;
  656. if (autoLevelCapping === -1 && levels && levels.length) {
  657. maxAutoLevel = levels.length - 1;
  658. } else {
  659. maxAutoLevel = autoLevelCapping;
  660. }
  661.  
  662. return maxAutoLevel;
  663. }
  664.  
  665. /**
  666. * next automatically selected quality level
  667. * @type {number}
  668. */
  669. get nextAutoLevel(): number {
  670. // ensure next auto level is between min and max auto level
  671. return Math.min(
  672. Math.max(this.abrController.nextAutoLevel, this.minAutoLevel),
  673. this.maxAutoLevel
  674. );
  675. }
  676.  
  677. /**
  678. * this setter is used to force next auto level.
  679. * this is useful to force a switch down in auto mode:
  680. * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example)
  681. * forced value is valid for one fragment. upon succesful frag loading at forced level,
  682. * this value will be resetted to -1 by ABR controller.
  683. * @type {number}
  684. */
  685. set nextAutoLevel(nextLevel: number) {
  686. this.abrController.nextAutoLevel = Math.max(this.minAutoLevel, nextLevel);
  687. }
  688.  
  689. /**
  690. * @type {AudioTrack[]}
  691. */
  692. get audioTracks(): Array<MediaPlaylist> {
  693. const audioTrackController = this.audioTrackController;
  694. return audioTrackController ? audioTrackController.audioTracks : [];
  695. }
  696.  
  697. /**
  698. * index of the selected audio track (index in audio track lists)
  699. * @type {number}
  700. */
  701. get audioTrack(): number {
  702. const audioTrackController = this.audioTrackController;
  703. return audioTrackController ? audioTrackController.audioTrack : -1;
  704. }
  705.  
  706. /**
  707. * selects an audio track, based on its index in audio track lists
  708. * @type {number}
  709. */
  710. set audioTrack(audioTrackId: number) {
  711. const audioTrackController = this.audioTrackController;
  712. if (audioTrackController) {
  713. audioTrackController.audioTrack = audioTrackId;
  714. }
  715. }
  716.  
  717. /**
  718. * get alternate subtitle tracks list from playlist
  719. * @type {MediaPlaylist[]}
  720. */
  721. get subtitleTracks(): Array<MediaPlaylist> {
  722. const subtitleTrackController = this.subtitleTrackController;
  723. return subtitleTrackController
  724. ? subtitleTrackController.subtitleTracks
  725. : [];
  726. }
  727.  
  728. /**
  729. * index of the selected subtitle track (index in subtitle track lists)
  730. * @type {number}
  731. */
  732. get subtitleTrack(): number {
  733. const subtitleTrackController = this.subtitleTrackController;
  734. return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
  735. }
  736.  
  737. get media() {
  738. return this._media;
  739. }
  740.  
  741. /**
  742. * select an subtitle track, based on its index in subtitle track lists
  743. * @type {number}
  744. */
  745. set subtitleTrack(subtitleTrackId: number) {
  746. const subtitleTrackController = this.subtitleTrackController;
  747. if (subtitleTrackController) {
  748. subtitleTrackController.subtitleTrack = subtitleTrackId;
  749. }
  750. }
  751.  
  752. /**
  753. * @type {boolean}
  754. */
  755. get subtitleDisplay(): boolean {
  756. const subtitleTrackController = this.subtitleTrackController;
  757. return subtitleTrackController
  758. ? subtitleTrackController.subtitleDisplay
  759. : false;
  760. }
  761.  
  762. /**
  763. * Enable/disable subtitle display rendering
  764. * @type {boolean}
  765. */
  766. set subtitleDisplay(value: boolean) {
  767. const subtitleTrackController = this.subtitleTrackController;
  768. if (subtitleTrackController) {
  769. subtitleTrackController.subtitleDisplay = value;
  770. }
  771. }
  772.  
  773. /**
  774. * get mode for Low-Latency HLS loading
  775. * @type {boolean}
  776. */
  777. get lowLatencyMode() {
  778. return this.config.lowLatencyMode;
  779. }
  780.  
  781. /**
  782. * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK.
  783. * @type {boolean}
  784. */
  785. set lowLatencyMode(mode: boolean) {
  786. this.config.lowLatencyMode = mode;
  787. }
  788.  
  789. /**
  790. * position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```)
  791. * @type {number}
  792. */
  793. get liveSyncPosition(): number | null {
  794. return this.latencyController.liveSyncPosition;
  795. }
  796.  
  797. /**
  798. * estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
  799. * returns 0 before first playlist is loaded
  800. * @type {number}
  801. */
  802. get latency() {
  803. return this.latencyController.latency;
  804. }
  805.  
  806. /**
  807. * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
  808. * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
  809. * returns 0 before first playlist is loaded
  810. * @type {number}
  811. */
  812. get maxLatency(): number {
  813. return this.latencyController.maxLatency;
  814. }
  815.  
  816. /**
  817. * target distance from the edge as calculated by the latency controller
  818. * @type {number}
  819. */
  820. get targetLatency(): number | null {
  821. return this.latencyController.targetLatency;
  822. }
  823. }