123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536 |
- import { uuid } from '@svta/common-media-library/utils/uuid';
- import { EventEmitter } from 'eventemitter3';
- import { buildAbsoluteURL } from 'url-toolkit';
- import { enableStreamingMode, hlsDefaultConfig, mergeConfig } from './config';
- import { FragmentTracker } from './controller/fragment-tracker';
- import GapController from './controller/gap-controller';
- import ID3TrackController from './controller/id3-track-controller';
- import LatencyController from './controller/latency-controller';
- import LevelController from './controller/level-controller';
- import StreamController from './controller/stream-controller';
- import { ErrorDetails, ErrorTypes } from './errors';
- import { Events } from './events';
- import { isMSESupported, isSupported } from './is-supported';
- import KeyLoader from './loader/key-loader';
- import PlaylistLoader from './loader/playlist-loader';
- import { MetadataSchema } from './types/demuxer';
- import { type HdcpLevel, isHdcpLevel, type Level } from './types/level';
- import { PlaylistLevelType } from './types/loader';
- import { enableLogs, type ILogger } from './utils/logger';
- import { getMediaDecodingInfoPromise } from './utils/mediacapabilities-helper';
- import { getMediaSource } from './utils/mediasource-helper';
- import { getAudioTracksByGroup } from './utils/rendition-helper';
- import { version } from './version';
- import type { HlsConfig } from './config';
- import type AbrController from './controller/abr-controller';
- import type AudioStreamController from './controller/audio-stream-controller';
- import type AudioTrackController from './controller/audio-track-controller';
- import type BasePlaylistController from './controller/base-playlist-controller';
- import type { InFlightData, State } from './controller/base-stream-controller';
- import type BaseStreamController from './controller/base-stream-controller';
- import type BufferController from './controller/buffer-controller';
- import type CapLevelController from './controller/cap-level-controller';
- import type CMCDController from './controller/cmcd-controller';
- import type ContentSteeringController from './controller/content-steering-controller';
- import type EMEController from './controller/eme-controller';
- import type ErrorController from './controller/error-controller';
- import type FPSController from './controller/fps-controller';
- import type InterstitialsController from './controller/interstitials-controller';
- import type { InterstitialsManager } from './controller/interstitials-controller';
- import type { SubtitleStreamController } from './controller/subtitle-stream-controller';
- import type SubtitleTrackController from './controller/subtitle-track-controller';
- import type Decrypter from './crypt/decrypter';
- import type TransmuxerInterface from './demux/transmuxer-interface';
- import type { HlsEventEmitter, HlsListeners } from './events';
- import type FragmentLoader from './loader/fragment-loader';
- import type { LevelDetails } from './loader/level-details';
- import type M3U8Parser from './loader/m3u8-parser';
- import type TaskLoop from './task-loop';
- import type { AttachMediaSourceData } from './types/buffer';
- import type {
- AbrComponentAPI,
- ComponentAPI,
- NetworkComponentAPI,
- } from './types/component-api';
- import type { MediaAttachingData } from './types/events';
- import type {
- AudioSelectionOption,
- MediaPlaylist,
- SubtitleSelectionOption,
- VideoSelectionOption,
- } from './types/media-playlist';
- import type { BufferInfo, BufferTimeRange } from './utils/buffer-helper';
- import type Cues from './utils/cues';
- import type EwmaBandWidthEstimator from './utils/ewma-bandwidth-estimator';
- import type FetchLoader from './utils/fetch-loader';
- import type { MediaDecodingInfo } from './utils/mediacapabilities-helper';
- import type XhrLoader from './utils/xhr-loader';
- /**
- * The `Hls` class is the core of the HLS.js library used to instantiate player instances.
- * @public
- */
- export default class Hls implements HlsEventEmitter {
- private static defaultConfig: HlsConfig | undefined;
- /**
- * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`.
- */
- public readonly config: HlsConfig;
- /**
- * The configuration object provided on player instantiation.
- */
- public readonly userConfig: Partial<HlsConfig>;
- /**
- * The logger functions used by this player instance, configured on player instantiation.
- */
- public readonly logger: ILogger;
- private coreComponents: ComponentAPI[];
- private networkControllers: NetworkComponentAPI[];
- private _emitter: HlsEventEmitter = new EventEmitter();
- private _autoLevelCapping: number = -1;
- private _maxHdcpLevel: HdcpLevel = null;
- private abrController: AbrComponentAPI;
- private bufferController: BufferController;
- private capLevelController: CapLevelController;
- private latencyController: LatencyController;
- private levelController: LevelController;
- private streamController: StreamController;
- private audioStreamController?: AudioStreamController;
- private subtititleStreamController?: SubtitleStreamController;
- private audioTrackController?: AudioTrackController;
- private subtitleTrackController?: SubtitleTrackController;
- private interstitialsController?: InterstitialsController;
- private gapController: GapController;
- private emeController?: EMEController;
- private cmcdController?: CMCDController;
- private _media: HTMLMediaElement | null = null;
- private _url: string | null = null;
- private _sessionId?: string;
- private triggeringException?: boolean;
- private started: boolean = false;
- /**
- * Get the video-dev/hls.js package version.
- */
- static get version(): string {
- return version;
- }
- /**
- * Check if the required MediaSource Extensions are available.
- */
- static isMSESupported(): boolean {
- return isMSESupported();
- }
- /**
- * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs.
- */
- static isSupported(): boolean {
- return isSupported();
- }
- /**
- * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource).
- */
- static getMediaSource(): typeof MediaSource | undefined {
- return getMediaSource();
- }
- static get Events(): typeof Events {
- return Events;
- }
- static get MetadataSchema(): typeof MetadataSchema {
- return MetadataSchema;
- }
- static get ErrorTypes(): typeof ErrorTypes {
- return ErrorTypes;
- }
- static get ErrorDetails(): typeof ErrorDetails {
- return ErrorDetails;
- }
- /**
- * Get the default configuration applied to new instances.
- */
- static get DefaultConfig(): HlsConfig {
- if (!Hls.defaultConfig) {
- return hlsDefaultConfig;
- }
- return Hls.defaultConfig;
- }
- /**
- * Replace the default configuration applied to new instances.
- */
- static set DefaultConfig(defaultConfig: HlsConfig) {
- Hls.defaultConfig = defaultConfig;
- }
- /**
- * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`.
- * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
- */
- constructor(userConfig: Partial<HlsConfig> = {}) {
- const logger = (this.logger = enableLogs(
- userConfig.debug || false,
- 'Hls instance',
- userConfig.assetPlayerId,
- ));
- const config = (this.config = mergeConfig(
- Hls.DefaultConfig,
- userConfig,
- logger,
- ));
- this.userConfig = userConfig;
- if (config.progressive) {
- enableStreamingMode(config, logger);
- }
- // core controllers and network loaders
- const {
- abrController: _AbrController,
- bufferController: _BufferController,
- capLevelController: _CapLevelController,
- errorController: _ErrorController,
- fpsController: _FpsController,
- } = config;
- const errorController = new _ErrorController(this);
- const abrController = (this.abrController = new _AbrController(this));
- // FragmentTracker must be defined before StreamController because the order of event handling is important
- const fragmentTracker = new FragmentTracker(this);
- const _InterstitialsController = config.interstitialsController;
- const interstitialsController = _InterstitialsController
- ? (this.interstitialsController = new _InterstitialsController(this, Hls))
- : null;
- const bufferController = (this.bufferController = new _BufferController(
- this,
- fragmentTracker,
- ));
- const capLevelController = (this.capLevelController =
- new _CapLevelController(this));
- const fpsController = new _FpsController(this);
- const playListLoader = new PlaylistLoader(this);
- const _ContentSteeringController = config.contentSteeringController;
- // Instantiate ConentSteeringController before LevelController to receive Multivariant Playlist events first
- const contentSteering = _ContentSteeringController
- ? new _ContentSteeringController(this)
- : null;
- const levelController = (this.levelController = new LevelController(
- this,
- contentSteering,
- ));
- const id3TrackController = new ID3TrackController(this);
- const keyLoader = new KeyLoader(this.config);
- const streamController = (this.streamController = new StreamController(
- this,
- fragmentTracker,
- keyLoader,
- ));
- const gapController = (this.gapController = new GapController(
- this,
- fragmentTracker,
- ));
- // Cap level controller uses streamController to flush the buffer
- capLevelController.setStreamController(streamController);
- // fpsController uses streamController to switch when frames are being dropped
- fpsController.setStreamController(streamController);
- const networkControllers: NetworkComponentAPI[] = [
- playListLoader,
- levelController,
- streamController,
- ];
- if (interstitialsController) {
- networkControllers.splice(1, 0, interstitialsController);
- }
- if (contentSteering) {
- networkControllers.splice(1, 0, contentSteering);
- }
- this.networkControllers = networkControllers;
- const coreComponents: ComponentAPI[] = [
- abrController,
- bufferController,
- gapController,
- capLevelController,
- fpsController,
- id3TrackController,
- fragmentTracker,
- ];
- this.audioTrackController = this.createController(
- config.audioTrackController,
- networkControllers,
- );
- const AudioStreamControllerClass = config.audioStreamController;
- if (AudioStreamControllerClass) {
- networkControllers.push(
- (this.audioStreamController = new AudioStreamControllerClass(
- this,
- fragmentTracker,
- keyLoader,
- )),
- );
- }
- // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
- this.subtitleTrackController = this.createController(
- config.subtitleTrackController,
- networkControllers,
- );
- const SubtitleStreamControllerClass = config.subtitleStreamController;
- if (SubtitleStreamControllerClass) {
- networkControllers.push(
- (this.subtititleStreamController = new SubtitleStreamControllerClass(
- this,
- fragmentTracker,
- keyLoader,
- )),
- );
- }
- this.createController(config.timelineController, coreComponents);
- keyLoader.emeController = this.emeController = this.createController(
- config.emeController,
- coreComponents,
- );
- this.cmcdController = this.createController(
- config.cmcdController,
- coreComponents,
- );
- this.latencyController = this.createController(
- LatencyController,
- coreComponents,
- );
- this.coreComponents = coreComponents;
- // Error controller handles errors before and after all other controllers
- // This listener will be invoked after all other controllers error listeners
- networkControllers.push(errorController);
- const onErrorOut = errorController.onErrorOut;
- if (typeof onErrorOut === 'function') {
- this.on(Events.ERROR, onErrorOut, errorController);
- }
- // Autostart load handler
- this.on(
- Events.MANIFEST_LOADED,
- playListLoader.onManifestLoaded,
- playListLoader,
- );
- }
- createController(ControllerClass, components) {
- if (ControllerClass) {
- const controllerInstance = new ControllerClass(this);
- if (components) {
- components.push(controllerInstance);
- }
- return controllerInstance;
- }
- return null;
- }
- // Delegate the EventEmitter through the public API of Hls.js
- on<E extends keyof HlsListeners, Context = undefined>(
- event: E,
- listener: HlsListeners[E],
- context: Context = this as any,
- ) {
- this._emitter.on(event, listener, context);
- }
- once<E extends keyof HlsListeners, Context = undefined>(
- event: E,
- listener: HlsListeners[E],
- context: Context = this as any,
- ) {
- this._emitter.once(event, listener, context);
- }
- removeAllListeners<E extends keyof HlsListeners>(event?: E | undefined) {
- this._emitter.removeAllListeners(event);
- }
- off<E extends keyof HlsListeners, Context = undefined>(
- event: E,
- listener?: HlsListeners[E] | undefined,
- context: Context = this as any,
- once?: boolean | undefined,
- ) {
- this._emitter.off(event, listener, context, once);
- }
- listeners<E extends keyof HlsListeners>(event: E): HlsListeners[E][] {
- return this._emitter.listeners(event);
- }
- emit<E extends keyof HlsListeners>(
- event: E,
- name: E,
- eventObject: Parameters<HlsListeners[E]>[1],
- ): boolean {
- return this._emitter.emit(event, name, eventObject);
- }
- trigger<E extends keyof HlsListeners>(
- event: E,
- eventObject: Parameters<HlsListeners[E]>[1],
- ): boolean {
- if (this.config.debug) {
- return this.emit(event, event, eventObject);
- } else {
- try {
- return this.emit(event, event, eventObject);
- } catch (error) {
- this.logger.error(
- 'An internal error happened while handling event ' +
- event +
- '. Error message: "' +
- error.message +
- '". Here is a stacktrace:',
- error,
- );
- // Prevent recursion in error event handlers that throw #5497
- if (!this.triggeringException) {
- this.triggeringException = true;
- const fatal = event === Events.ERROR;
- this.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.INTERNAL_EXCEPTION,
- fatal,
- event,
- error,
- });
- this.triggeringException = false;
- }
- }
- }
- return false;
- }
- listenerCount<E extends keyof HlsListeners>(event: E): number {
- return this._emitter.listenerCount(event);
- }
- /**
- * Dispose of the instance
- */
- destroy() {
- this.logger.log('destroy');
- this.trigger(Events.DESTROYING, undefined);
- this.detachMedia();
- this.removeAllListeners();
- this._autoLevelCapping = -1;
- this._url = null;
- this.networkControllers.forEach((component) => component.destroy());
- this.networkControllers.length = 0;
- this.coreComponents.forEach((component) => component.destroy());
- this.coreComponents.length = 0;
- // Remove any references that could be held in config options or callbacks
- const config = this.config;
- config.xhrSetup = config.fetchSetup = undefined;
- // @ts-ignore
- this.userConfig = null;
- }
- /**
- * Attaches Hls.js to a media element
- */
- attachMedia(data: HTMLMediaElement | MediaAttachingData) {
- if (!data || ('media' in data && !data.media)) {
- const error = new Error(`attachMedia failed: invalid argument (${data})`);
- this.trigger(Events.ERROR, {
- type: ErrorTypes.OTHER_ERROR,
- details: ErrorDetails.ATTACH_MEDIA_ERROR,
- fatal: true,
- error,
- });
- return;
- }
- this.logger.log(`attachMedia`);
- if (this._media) {
- this.logger.warn(`media must be detached before attaching`);
- this.detachMedia();
- }
- const attachMediaSource = 'media' in data;
- const media = attachMediaSource ? data.media : data;
- const attachingData = attachMediaSource ? data : { media };
- this._media = media;
- this.trigger(Events.MEDIA_ATTACHING, attachingData);
- }
- /**
- * Detach Hls.js from the media
- */
- detachMedia() {
- this.logger.log('detachMedia');
- this.trigger(Events.MEDIA_DETACHING, {});
- this._media = null;
- }
- /**
- * Detach HTMLMediaElement, MediaSource, and SourceBuffers without reset, for attaching to another instance
- */
- transferMedia(): AttachMediaSourceData | null {
- this._media = null;
- const transferMedia = this.bufferController.transferMedia();
- this.trigger(Events.MEDIA_DETACHING, { transferMedia });
- return transferMedia;
- }
- /**
- * Set the source URL. Can be relative or absolute.
- */
- loadSource(url: string) {
- this.stopLoad();
- const media = this.media;
- const loadedSource = this._url;
- const loadingSource = (this._url = buildAbsoluteURL(
- self.location.href,
- url,
- {
- alwaysNormalize: true,
- },
- ));
- this._autoLevelCapping = -1;
- this._maxHdcpLevel = null;
- this.logger.log(`loadSource:${loadingSource}`);
- if (
- media &&
- loadedSource &&
- (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())
- ) {
- // Remove and re-create MediaSource
- this.detachMedia();
- this.attachMedia(media);
- }
- // when attaching to a source URL, trigger a playlist load
- this.trigger(Events.MANIFEST_LOADING, { url: url });
- }
- /**
- * Gets the currently loaded URL
- */
- public get url(): string | null {
- return this._url;
- }
- /**
- * Whether or not enough has been buffered to seek to start position or use `media.currentTime` to determine next load position
- */
- get hasEnoughToStart(): boolean {
- return this.streamController.hasEnoughToStart;
- }
- /**
- * Get the startPosition set on startLoad(position) or on autostart with config.startPosition
- */
- get startPosition(): number {
- return this.streamController.startPositionValue;
- }
- /**
- * Start loading data from the stream source.
- * Depending on default config, client starts loading automatically when a source is set.
- *
- * @param startPosition - Set the start position to stream from.
- * Defaults to -1 (None: starts from earliest point)
- */
- startLoad(startPosition: number = -1, skipSeekToStartPosition?: boolean) {
- this.logger.log(
- `startLoad(${
- startPosition +
- (skipSeekToStartPosition ? ', <skip seek to start>' : '')
- })`,
- );
- this.started = true;
- this.resumeBuffering();
- for (let i = 0; i < this.networkControllers.length; i++) {
- this.networkControllers[i].startLoad(
- startPosition,
- skipSeekToStartPosition,
- );
- if (!this.started || !this.networkControllers) {
- break;
- }
- }
- }
- /**
- * Stop loading of any stream data.
- */
- stopLoad() {
- this.logger.log('stopLoad');
- this.started = false;
- for (let i = 0; i < this.networkControllers.length; i++) {
- this.networkControllers[i].stopLoad();
- if (this.started || !this.networkControllers) {
- break;
- }
- }
- }
- /**
- * Returns whether loading, toggled with `startLoad()` and `stopLoad()`, is active or not`.
- */
- get loadingEnabled(): boolean {
- return this.started;
- }
- /**
- * Returns state of fragment loading toggled by calling `pauseBuffering()` and `resumeBuffering()`.
- */
- get bufferingEnabled(): boolean {
- return this.streamController.bufferingEnabled;
- }
- /**
- * Resumes stream controller segment loading after `pauseBuffering` has been called.
- */
- resumeBuffering() {
- if (!this.bufferingEnabled) {
- this.logger.log(`resume buffering`);
- this.networkControllers.forEach((controller) => {
- if (controller.resumeBuffering) {
- controller.resumeBuffering();
- }
- });
- }
- }
- /**
- * Prevents stream controller from loading new segments until `resumeBuffering` is called.
- * This allows for media buffering to be paused without interupting playlist loading.
- */
- pauseBuffering() {
- if (this.bufferingEnabled) {
- this.logger.log(`pause buffering`);
- this.networkControllers.forEach((controller) => {
- if (controller.pauseBuffering) {
- controller.pauseBuffering();
- }
- });
- }
- }
- get inFlightFragments(): InFlightFragments {
- const inFlightData = {
- [PlaylistLevelType.MAIN]: this.streamController.inFlightFrag,
- };
- if (this.audioStreamController) {
- inFlightData[PlaylistLevelType.AUDIO] =
- this.audioStreamController.inFlightFrag;
- }
- if (this.subtititleStreamController) {
- inFlightData[PlaylistLevelType.SUBTITLE] =
- this.subtititleStreamController.inFlightFrag;
- }
- return inFlightData;
- }
- /**
- * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
- */
- swapAudioCodec() {
- this.logger.log('swapAudioCodec');
- this.streamController.swapAudioCodec();
- }
- /**
- * When the media-element fails, this allows to detach and then re-attach it
- * as one call (convenience method).
- *
- * Automatic recovery of media-errors by this process is configurable.
- */
- recoverMediaError() {
- this.logger.log('recoverMediaError');
- const media = this._media;
- const time = media?.currentTime;
- this.detachMedia();
- if (media) {
- this.attachMedia(media);
- if (time) {
- this.startLoad(time);
- }
- }
- }
- removeLevel(levelIndex: number) {
- this.levelController.removeLevel(levelIndex);
- }
- /**
- * @returns a UUID for this player instance
- */
- get sessionId(): string {
- let _sessionId = this._sessionId;
- if (!_sessionId) {
- _sessionId = this._sessionId = uuid();
- }
- return _sessionId;
- }
- /**
- * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH
- */
- get levels(): Level[] {
- const levels = this.levelController.levels;
- return levels ? levels : [];
- }
- /**
- * @returns LevelDetails of last loaded level (variant) or `null` prior to loading a media playlist.
- */
- get latestLevelDetails(): LevelDetails | null {
- return this.streamController.getLevelDetails() || null;
- }
- /**
- * @returns Level object of selected level (variant) or `null` prior to selecting a level or once the level is removed.
- */
- get loadLevelObj(): Level | null {
- return this.levelController.loadLevelObj;
- }
- /**
- * Index of quality level (variant) currently played
- */
- get currentLevel(): number {
- return this.streamController.currentLevel;
- }
- /**
- * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
- */
- set currentLevel(newLevel: number) {
- this.logger.log(`set currentLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- this.streamController.immediateLevelSwitch();
- }
- /**
- * Index of next quality level loaded as scheduled by stream controller.
- */
- get nextLevel(): number {
- return this.streamController.nextLevel;
- }
- /**
- * Set quality level index for next loaded data.
- * This will switch the video quality asap, without interrupting playback.
- * May abort current loading of data, and flush parts of buffer (outside currently played fragment region).
- * @param newLevel - Pass -1 for automatic level selection
- */
- set nextLevel(newLevel: number) {
- this.logger.log(`set nextLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- this.streamController.nextLevelSwitch();
- }
- /**
- * Return the quality level of the currently or last (of none is loaded currently) segment
- */
- get loadLevel(): number {
- return this.levelController.level;
- }
- /**
- * Set quality level index for next loaded data in a conservative way.
- * This will switch the quality without flushing, but interrupt current loading.
- * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer.
- * @param newLevel - Pass -1 for automatic level selection
- */
- set loadLevel(newLevel: number) {
- this.logger.log(`set loadLevel:${newLevel}`);
- this.levelController.manualLevel = newLevel;
- }
- /**
- * get next quality level loaded
- */
- get nextLoadLevel(): number {
- return this.levelController.nextLoadLevel;
- }
- /**
- * Set quality level of next loaded segment in a fully "non-destructive" way.
- * Same as `loadLevel` but will wait for next switch (until current loading is done).
- */
- set nextLoadLevel(level: number) {
- this.levelController.nextLoadLevel = level;
- }
- /**
- * Return "first level": like a default level, if not set,
- * falls back to index of first level referenced in manifest
- */
- get firstLevel(): number {
- return Math.max(this.levelController.firstLevel, this.minAutoLevel);
- }
- /**
- * Sets "first-level", see getter.
- */
- set firstLevel(newLevel: number) {
- this.logger.log(`set firstLevel:${newLevel}`);
- this.levelController.firstLevel = newLevel;
- }
- /**
- * Return the desired start level for the first fragment that will be loaded.
- * The default value of -1 indicates automatic start level selection.
- * Setting hls.nextAutoLevel without setting a startLevel will result in
- * the nextAutoLevel value being used for one fragment load.
- */
- get startLevel(): number {
- const startLevel = this.levelController.startLevel;
- if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) {
- return this.abrController.forcedAutoLevel;
- }
- return startLevel;
- }
- /**
- * set start level (level of first fragment that will be played back)
- * if not overrided by user, first level appearing in manifest will be used as start level
- * if -1 : automatic start level selection, playback will start from level matching download bandwidth
- * (determined from download of first segment)
- */
- set startLevel(newLevel: number) {
- this.logger.log(`set startLevel:${newLevel}`);
- // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
- if (newLevel !== -1) {
- newLevel = Math.max(newLevel, this.minAutoLevel);
- }
- this.levelController.startLevel = newLevel;
- }
- /**
- * Whether level capping is enabled.
- * Default value is set via `config.capLevelToPlayerSize`.
- */
- get capLevelToPlayerSize(): boolean {
- return this.config.capLevelToPlayerSize;
- }
- /**
- * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called.
- */
- set capLevelToPlayerSize(shouldStartCapping: boolean) {
- const newCapLevelToPlayerSize = !!shouldStartCapping;
- if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
- if (newCapLevelToPlayerSize) {
- this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size.
- } else {
- this.capLevelController.stopCapping();
- this.autoLevelCapping = -1;
- this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap.
- }
- this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
- }
- }
- /**
- * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
- */
- get autoLevelCapping(): number {
- return this._autoLevelCapping;
- }
- /**
- * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned.
- */
- get bandwidthEstimate(): number {
- const { bwEstimator } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.getEstimate();
- }
- set bandwidthEstimate(abrEwmaDefaultEstimate: number) {
- this.abrController.resetEstimator(abrEwmaDefaultEstimate);
- }
- get abrEwmaDefaultEstimate(): number {
- const { bwEstimator } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.defaultEstimate;
- }
- /**
- * get time to first byte estimate
- * @type {number}
- */
- get ttfbEstimate(): number {
- const { bwEstimator } = this.abrController;
- if (!bwEstimator) {
- return NaN;
- }
- return bwEstimator.getEstimateTTFB();
- }
- /**
- * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
- */
- set autoLevelCapping(newLevel: number) {
- if (this._autoLevelCapping !== newLevel) {
- this.logger.log(`set autoLevelCapping:${newLevel}`);
- this._autoLevelCapping = newLevel;
- this.levelController.checkMaxAutoUpdated();
- }
- }
- get maxHdcpLevel(): HdcpLevel {
- return this._maxHdcpLevel;
- }
- set maxHdcpLevel(value: HdcpLevel) {
- if (isHdcpLevel(value) && this._maxHdcpLevel !== value) {
- this._maxHdcpLevel = value;
- this.levelController.checkMaxAutoUpdated();
- }
- }
- /**
- * True when automatic level selection enabled
- */
- get autoLevelEnabled(): boolean {
- return this.levelController.manualLevel === -1;
- }
- /**
- * Level set manually (if any)
- */
- get manualLevel(): number {
- return this.levelController.manualLevel;
- }
- /**
- * min level selectable in auto mode according to config.minAutoBitrate
- */
- get minAutoLevel(): number {
- const {
- levels,
- config: { minAutoBitrate },
- } = this;
- if (!levels) return 0;
- const len = levels.length;
- for (let i = 0; i < len; i++) {
- if (levels[i].maxBitrate >= minAutoBitrate) {
- return i;
- }
- }
- return 0;
- }
- /**
- * max level selectable in auto mode according to autoLevelCapping
- */
- get maxAutoLevel(): number {
- const { levels, autoLevelCapping, maxHdcpLevel } = this;
- let maxAutoLevel;
- if (autoLevelCapping === -1 && levels?.length) {
- maxAutoLevel = levels.length - 1;
- } else {
- maxAutoLevel = autoLevelCapping;
- }
- if (maxHdcpLevel) {
- for (let i = maxAutoLevel; i--; ) {
- const hdcpLevel = levels[i].attrs['HDCP-LEVEL'];
- if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
- return i;
- }
- }
- }
- return maxAutoLevel;
- }
- get firstAutoLevel(): number {
- return this.abrController.firstAutoLevel;
- }
- /**
- * next automatically selected quality level
- */
- get nextAutoLevel(): number {
- return this.abrController.nextAutoLevel;
- }
- /**
- * this setter is used to force next auto level.
- * this is useful to force a switch down in auto mode:
- * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example)
- * forced value is valid for one fragment. upon successful frag loading at forced level,
- * this value will be resetted to -1 by ABR controller.
- */
- set nextAutoLevel(nextLevel: number) {
- this.abrController.nextAutoLevel = nextLevel;
- }
- /**
- * get the datetime value relative to media.currentTime for the active level Program Date Time if present
- */
- public get playingDate(): Date | null {
- return this.streamController.currentProgramDateTime;
- }
- public get mainForwardBufferInfo(): BufferInfo | null {
- return this.streamController.getMainFwdBufferInfo();
- }
- public get maxBufferLength(): number {
- return this.streamController.maxBufferLength;
- }
- /**
- * Find and select the best matching audio track, making a level switch when a Group change is necessary.
- * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
- */
- public setAudioOption(
- audioOption: MediaPlaylist | AudioSelectionOption | undefined,
- ): MediaPlaylist | null {
- return this.audioTrackController?.setAudioOption(audioOption) || null;
- }
- /**
- * Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
- * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found.
- */
- public setSubtitleOption(
- subtitleOption: MediaPlaylist | SubtitleSelectionOption | undefined,
- ): MediaPlaylist | null {
- return (
- this.subtitleTrackController?.setSubtitleOption(subtitleOption) || null
- );
- }
- /**
- * Get the complete list of audio tracks across all media groups
- */
- get allAudioTracks(): MediaPlaylist[] {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.allAudioTracks : [];
- }
- /**
- * Get the list of selectable audio tracks
- */
- get audioTracks(): MediaPlaylist[] {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.audioTracks : [];
- }
- /**
- * index of the selected audio track (index in audio track lists)
- */
- get audioTrack(): number {
- const audioTrackController = this.audioTrackController;
- return audioTrackController ? audioTrackController.audioTrack : -1;
- }
- /**
- * selects an audio track, based on its index in audio track lists
- */
- set audioTrack(audioTrackId: number) {
- const audioTrackController = this.audioTrackController;
- if (audioTrackController) {
- audioTrackController.audioTrack = audioTrackId;
- }
- }
- /**
- * get the complete list of subtitle tracks across all media groups
- */
- get allSubtitleTracks(): MediaPlaylist[] {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController
- ? subtitleTrackController.allSubtitleTracks
- : [];
- }
- /**
- * get alternate subtitle tracks list from playlist
- */
- get subtitleTracks(): MediaPlaylist[] {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController
- ? subtitleTrackController.subtitleTracks
- : [];
- }
- /**
- * index of the selected subtitle track (index in subtitle track lists)
- */
- get subtitleTrack(): number {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
- }
- get media() {
- return this._media;
- }
- /**
- * select an subtitle track, based on its index in subtitle track lists
- */
- set subtitleTrack(subtitleTrackId: number) {
- const subtitleTrackController = this.subtitleTrackController;
- if (subtitleTrackController) {
- subtitleTrackController.subtitleTrack = subtitleTrackId;
- }
- }
- /**
- * Whether subtitle display is enabled or not
- */
- get subtitleDisplay(): boolean {
- const subtitleTrackController = this.subtitleTrackController;
- return subtitleTrackController
- ? subtitleTrackController.subtitleDisplay
- : false;
- }
- /**
- * Enable/disable subtitle display rendering
- */
- set subtitleDisplay(value: boolean) {
- const subtitleTrackController = this.subtitleTrackController;
- if (subtitleTrackController) {
- subtitleTrackController.subtitleDisplay = value;
- }
- }
- /**
- * get mode for Low-Latency HLS loading
- */
- get lowLatencyMode(): boolean {
- return this.config.lowLatencyMode;
- }
- /**
- * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK.
- */
- set lowLatencyMode(mode: boolean) {
- this.config.lowLatencyMode = mode;
- }
- /**
- * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```)
- * @returns null prior to loading live Playlist
- */
- get liveSyncPosition(): number | null {
- return this.latencyController.liveSyncPosition;
- }
- /**
- * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
- * @returns 0 before first playlist is loaded
- */
- get latency(): number {
- return this.latencyController.latency;
- }
- /**
- * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
- * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
- * @returns 0 before first playlist is loaded
- */
- get maxLatency(): number {
- return this.latencyController.maxLatency;
- }
- /**
- * target distance from the edge as calculated by the latency controller
- */
- get targetLatency(): number | null {
- return this.latencyController.targetLatency;
- }
- set targetLatency(latency: number) {
- this.latencyController.targetLatency = latency;
- }
- /**
- * the rate at which the edge of the current live playlist is advancing or 1 if there is none
- */
- get drift(): number | null {
- return this.latencyController.drift;
- }
- /**
- * set to true when startLoad is called before MANIFEST_PARSED event
- */
- get forceStartLoad(): boolean {
- return this.streamController.forceStartLoad;
- }
- /**
- * ContentSteering pathways getter
- */
- get pathways(): string[] {
- return this.levelController.pathways;
- }
- /**
- * ContentSteering pathwayPriority getter/setter
- */
- get pathwayPriority(): string[] | null {
- return this.levelController.pathwayPriority;
- }
- set pathwayPriority(pathwayPriority: string[]) {
- this.levelController.pathwayPriority = pathwayPriority;
- }
- /**
- * returns true when all SourceBuffers are buffered to the end
- */
- get bufferedToEnd(): boolean {
- return !!this.bufferController?.bufferedToEnd;
- }
- /**
- * returns Interstitials Program Manager
- */
- get interstitialsManager(): InterstitialsManager | null {
- return this.interstitialsController?.interstitialsManager || null;
- }
- /**
- * returns mediaCapabilities.decodingInfo for a variant/rendition
- */
- getMediaDecodingInfo(
- level: Level,
- audioTracks: MediaPlaylist[] = this.allAudioTracks,
- ): Promise<MediaDecodingInfo> {
- const audioTracksByGroup = getAudioTracksByGroup(audioTracks);
- return getMediaDecodingInfoPromise(
- level,
- audioTracksByGroup,
- navigator.mediaCapabilities,
- );
- }
- }
- export type InFlightFragments = {
- [PlaylistLevelType.MAIN]: InFlightData;
- [PlaylistLevelType.AUDIO]?: InFlightData;
- [PlaylistLevelType.SUBTITLE]?: InFlightData;
- };
- export type {
- AudioSelectionOption,
- SubtitleSelectionOption,
- VideoSelectionOption,
- MediaPlaylist,
- ErrorDetails,
- ErrorTypes,
- Events,
- Level,
- LevelDetails,
- HlsListeners,
- HlsEventEmitter,
- HlsConfig,
- BufferInfo,
- BufferTimeRange,
- HdcpLevel,
- AbrController,
- AudioStreamController,
- AudioTrackController,
- BasePlaylistController,
- BaseStreamController,
- BufferController,
- CapLevelController,
- CMCDController,
- ContentSteeringController,
- EMEController,
- ErrorController,
- FPSController,
- InterstitialsController,
- StreamController,
- SubtitleStreamController,
- SubtitleTrackController,
- EwmaBandWidthEstimator,
- InterstitialsManager,
- Decrypter,
- FragmentLoader,
- KeyLoader,
- TaskLoop,
- TransmuxerInterface,
- InFlightData,
- State,
- XhrLoader,
- FetchLoader,
- Cues,
- M3U8Parser,
- };
- export type {
- ABRControllerConfig,
- BufferControllerConfig,
- CapLevelControllerConfig,
- CMCDControllerConfig,
- EMEControllerConfig,
- DRMSystemConfiguration,
- DRMSystemsConfiguration,
- DRMSystemOptions,
- FPSControllerConfig,
- FragmentLoaderConfig,
- FragmentLoaderConstructor,
- GapControllerConfig,
- HlsLoadPolicies,
- LevelControllerConfig,
- LoaderConfig,
- LoadPolicy,
- MP4RemuxerConfig,
- PlaylistLoaderConfig,
- PlaylistLoaderConstructor,
- RetryConfig,
- SelectionPreferences,
- StreamControllerConfig,
- LatencyControllerConfig,
- MetadataControllerConfig,
- TimelineControllerConfig,
- TSDemuxerConfig,
- } from './config';
- export type { MediaKeySessionContext } from './controller/eme-controller';
- export type {
- FragmentState,
- FragmentTracker,
- } from './controller/fragment-tracker';
- export type {
- PathwayClone,
- SteeringManifest,
- UriReplacement,
- } from './controller/content-steering-controller';
- export type {
- NetworkErrorAction,
- ErrorActionFlags,
- IErrorAction,
- } from './controller/error-controller';
- export type {
- HlsAssetPlayer,
- HlsAssetPlayerConfig,
- InterstitialPlayer,
- } from './controller/interstitial-player';
- export type { PlayheadTimes } from './controller/interstitials-controller';
- export type {
- InterstitialScheduleDurations,
- InterstitialScheduleEventItem,
- InterstitialScheduleItem,
- InterstitialSchedulePrimaryItem,
- } from './controller/interstitials-schedule';
- export type { TimelineController } from './controller/timeline-controller';
- export type { DecrypterAesMode } from './crypt/decrypter-aes-mode';
- export type { DateRange, DateRangeCue } from './loader/date-range';
- export type { LoadStats } from './loader/load-stats';
- export type { LevelKey } from './loader/level-key';
- export type {
- BaseSegment,
- Fragment,
- MediaFragment,
- Part,
- ElementaryStreams,
- ElementaryStreamTypes,
- ElementaryStreamInfo,
- } from './loader/fragment';
- export type {
- FragLoadFailResult,
- FragmentLoadProgressCallback,
- LoadError,
- } from './loader/fragment-loader';
- export type { KeyLoaderInfo } from './loader/key-loader';
- export type { DecryptData } from './loader/level-key';
- export type {
- AssetListJSON,
- BaseData,
- InterstitialAssetId,
- InterstitialAssetItem,
- InterstitialEvent,
- InterstitialEventWithAssetList,
- InterstitialId,
- PlaybackRestrictions,
- SnapOptions,
- TimelineOccupancy,
- } from './loader/interstitial-event';
- export type { ParsedMultivariantPlaylist } from './loader/m3u8-parser';
- export type {
- AttachMediaSourceData,
- BaseTrack,
- BaseTrackSet,
- BufferCreatedTrack,
- BufferCreatedTrackSet,
- ExtendedSourceBuffer,
- MediaOverrides,
- ParsedTrack,
- SourceBufferName,
- SourceBufferListener,
- SourceBufferTrack,
- SourceBufferTrackSet,
- } from './types/buffer';
- export type {
- ComponentAPI,
- AbrComponentAPI,
- NetworkComponentAPI,
- } from './types/component-api';
- export type {
- TrackLoadingData,
- TrackLoadedData,
- AssetListLoadedData,
- AssetListLoadingData,
- AudioTrackLoadedData,
- AudioTrackUpdatedData,
- AudioTracksUpdatedData,
- AudioTrackSwitchedData,
- AudioTrackSwitchingData,
- BackBufferData,
- BufferAppendedData,
- BufferAppendingData,
- BufferCodecsData,
- BufferCreatedData,
- BufferEOSData,
- BufferFlushedData,
- BufferFlushingData,
- CuesParsedData,
- ErrorData,
- FPSDropData,
- FPSDropLevelCappingData,
- FragBufferedData,
- FragChangedData,
- FragDecryptedData,
- FragLoadedData,
- FragLoadEmergencyAbortedData,
- FragLoadingData,
- FragParsedData,
- FragParsingInitSegmentData,
- FragParsingMetadataData,
- FragParsingUserdataData,
- InitPTSFoundData,
- KeyLoadedData,
- KeyLoadingData,
- LevelLoadedData,
- LevelLoadingData,
- LevelPTSUpdatedData,
- LevelsUpdatedData,
- LevelSwitchedData,
- LevelSwitchingData,
- LevelUpdatedData,
- LiveBackBufferData,
- ContentSteeringOptions,
- ManifestLoadedData,
- ManifestLoadingData,
- ManifestParsedData,
- MaxAutoLevelUpdatedData,
- MediaAttachedData,
- MediaAttachingData,
- MediaDetachedData,
- MediaDetachingData,
- MediaEndedData,
- NonNativeTextTrack,
- NonNativeTextTracksData,
- PartsLoadedData,
- SteeringManifestLoadedData,
- SubtitleFragProcessedData,
- SubtitleTrackLoadedData,
- SubtitleTrackUpdatedData,
- SubtitleTracksUpdatedData,
- SubtitleTrackSwitchData,
- InterstitialsUpdatedData,
- InterstitialsBufferedToBoundaryData,
- InterstitialAssetPlayerCreatedData,
- InterstitialStartedData,
- InterstitialEndedData,
- InterstitialAssetStartedData,
- InterstitialAssetEndedData,
- InterstitialAssetErrorData,
- InterstitialsPrimaryResumed,
- } from './types/events';
- export type {
- MetadataSample,
- MetadataSchema,
- UserdataSample,
- } from './types/demuxer';
- export type {
- InitSegmentData,
- RemuxedMetadata,
- RemuxedTrack,
- RemuxedUserdata,
- RemuxerResult,
- } from './types/remuxer';
- export type { AttrList } from './utils/attr-list';
- export type { Bufferable } from './utils/buffer-helper';
- export type { CaptionScreen } from './utils/cea-608-parser';
- export type { CuesInterface } from './utils/cues';
- export type {
- CodecsParsed,
- HdcpLevels,
- HlsSkip,
- HlsUrlParameters,
- LevelAttributes,
- LevelParsed,
- VariableMap,
- VideoRange,
- VideoRangeValues,
- } from './types/level';
- export type {
- PlaylistLevelType,
- HlsChunkPerformanceTiming,
- HlsPerformanceTiming,
- HlsProgressivePerformanceTiming,
- PlaylistContextType,
- PlaylistLoaderContext,
- FragmentLoaderContext,
- KeyLoaderContext,
- Loader,
- LoaderStats,
- LoaderContext,
- LoaderResponse,
- LoaderConfiguration,
- LoaderCallbacks,
- LoaderOnProgress,
- LoaderOnAbort,
- LoaderOnError,
- LoaderOnSuccess,
- LoaderOnTimeout,
- } from './types/loader';
- export type { ILogFunction, ILogger, Logger } from './utils/logger';
- export type {
- MediaAttributes,
- MediaPlaylistType,
- MainPlaylistType,
- AudioPlaylistType,
- SubtitlePlaylistType,
- } from './types/media-playlist';
- export type { Track, TrackSet } from './types/track';
- export type { ChunkMetadata, TransmuxerResult } from './types/transmuxer';
- export type { MediaDecodingInfo } from './utils/mediacapabilities-helper';
- export type {
- MediaKeyFunc,
- KeySystems,
- KeySystemFormats,
- } from './utils/mediakeys-helper';
- export type { RationalTimestamp } from './utils/timescale-conversion';
|