Home Reference Source

src/config.ts

import AbrController from './controller/abr-controller';
import AudioStreamController from './controller/audio-stream-controller';
import AudioTrackController from './controller/audio-track-controller';
import { SubtitleStreamController } from './controller/subtitle-stream-controller';
import SubtitleTrackController from './controller/subtitle-track-controller';
import BufferController from './controller/buffer-controller';
import { TimelineController } from './controller/timeline-controller';
import CapLevelController from './controller/cap-level-controller';
import FPSController from './controller/fps-controller';
import EMEController from './controller/eme-controller';
import XhrLoader from './utils/xhr-loader';
import FetchLoader, { fetchSupported } from './utils/fetch-loader';
import Cues from './utils/cues';
import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
import { logger } from './utils/logger';

import type { CuesInterface } from './utils/cues';
import type { MediaKeyFunc } from './utils/mediakeys-helper';
import type {
  FragmentLoaderContext,
  Loader,
  LoaderContext,
  PlaylistLoaderContext,
} from './types/loader';

export type ABRControllerConfig = {
  abrEwmaFastLive: number;
  abrEwmaSlowLive: number;
  abrEwmaFastVoD: number;
  abrEwmaSlowVoD: number;
  abrEwmaDefaultEstimate: number;
  abrBandWidthFactor: number;
  abrBandWidthUpFactor: number;
  abrMaxWithRealBitrate: boolean;
  maxStarvationDelay: number;
  maxLoadingDelay: number;
};

export type BufferControllerConfig = {
  appendErrorMaxRetry: number;
  backBufferLength: number;
  liveDurationInfinity: boolean;
  liveBackBufferLength: number | null;
};

export type CapLevelControllerConfig = {
  capLevelToPlayerSize: boolean;
};

export type DRMSystemOptions = {
  audioRobustness?: string;
  videoRobustness?: string;
};

export type EMEControllerConfig = {
  licenseXhrSetup?: (xhr: XMLHttpRequest, url: string) => void;
  licenseResponseCallback?: (xhr: XMLHttpRequest, url: string) => ArrayBuffer;
  emeEnabled: boolean;
  widevineLicenseUrl?: string;
  drmSystemOptions: DRMSystemOptions;
  requestMediaKeySystemAccessFunc: MediaKeyFunc | null;
};

export type FragmentLoaderConfig = {
  fLoader?: { new (confg: HlsConfig): Loader<FragmentLoaderContext> };

  fragLoadingTimeOut: number;
  fragLoadingMaxRetry: number;
  fragLoadingRetryDelay: number;
  fragLoadingMaxRetryTimeout: number;
};

export type FPSControllerConfig = {
  capLevelOnFPSDrop: boolean;
  fpsDroppedMonitoringPeriod: number;
  fpsDroppedMonitoringThreshold: number;
};

export type LevelControllerConfig = {
  startLevel?: number;
};

export type MP4RemuxerConfig = {
  stretchShortVideoTrack: boolean;
  maxAudioFramesDrift: number;
};

export type PlaylistLoaderConfig = {
  pLoader?: { new (confg: HlsConfig): Loader<PlaylistLoaderContext> };

  manifestLoadingTimeOut: number;
  manifestLoadingMaxRetry: number;
  manifestLoadingRetryDelay: number;
  manifestLoadingMaxRetryTimeout: number;

  levelLoadingTimeOut: number;
  levelLoadingMaxRetry: number;
  levelLoadingRetryDelay: number;
  levelLoadingMaxRetryTimeout: number;
};

export type StreamControllerConfig = {
  autoStartLoad: boolean;
  startPosition: number;
  defaultAudioCodec?: string;
  initialLiveManifestSize: number;
  maxBufferLength: number;
  maxBufferSize: number;
  maxBufferHole: number;
  highBufferWatchdogPeriod: number;
  nudgeOffset: number;
  nudgeMaxRetry: number;
  maxFragLookUpTolerance: number;
  maxMaxBufferLength: number;
  startFragPrefetch: boolean;
  testBandwidth: boolean;
};

export type LatencyControllerConfig = {
  liveSyncDurationCount: number;
  liveMaxLatencyDurationCount: number;
  liveSyncDuration?: number;
  liveMaxLatencyDuration?: number;
  maxLiveSyncPlaybackRate: number;
};

export type TimelineControllerConfig = {
  cueHandler: CuesInterface;
  enableCEA708Captions: boolean;
  enableWebVTT: boolean;
  enableIMSC1: boolean;
  captionsTextTrack1Label: string;
  captionsTextTrack1LanguageCode: string;
  captionsTextTrack2Label: string;
  captionsTextTrack2LanguageCode: string;
  captionsTextTrack3Label: string;
  captionsTextTrack3LanguageCode: string;
  captionsTextTrack4Label: string;
  captionsTextTrack4LanguageCode: string;
  renderTextTracksNatively: boolean;
};

export type TSDemuxerConfig = {
  forceKeyFrameOnDiscontinuity: boolean;
};

export type HlsConfig = {
  debug: boolean;
  enableWorker: boolean;
  enableSoftwareAES: boolean;
  minAutoBitrate: number;
  loader: { new (confg: HlsConfig): Loader<LoaderContext> };
  xhrSetup?: (xhr: XMLHttpRequest, url: string) => void;

  // Alt Audio
  audioStreamController?: typeof AudioStreamController;
  audioTrackController?: typeof AudioTrackController;
  // Subtitle
  subtitleStreamController?: typeof SubtitleStreamController;
  subtitleTrackController?: typeof SubtitleTrackController;
  timelineController?: typeof TimelineController;
  // EME
  emeController?: typeof EMEController;

  abrController: typeof AbrController;
  bufferController: typeof BufferController;
  capLevelController: typeof CapLevelController;
  fpsController: typeof FPSController;
  progressive: boolean;
  lowLatencyMode: boolean;
} & ABRControllerConfig &
  BufferControllerConfig &
  CapLevelControllerConfig &
  EMEControllerConfig &
  FPSControllerConfig &
  FragmentLoaderConfig &
  LevelControllerConfig &
  MP4RemuxerConfig &
  PlaylistLoaderConfig &
  StreamControllerConfig &
  LatencyControllerConfig &
  TimelineControllerConfig &
  TSDemuxerConfig;

// If possible, keep hlsDefaultConfig shallow
// It is cloned whenever a new Hls instance is created, by keeping the config
// shallow the properties are cloned, and we don't end up manipulating the default
export const hlsDefaultConfig: HlsConfig = {
  autoStartLoad: true, // used by stream-controller
  startPosition: -1, // used by stream-controller
  defaultAudioCodec: undefined, // used by stream-controller
  debug: false, // used by logger
  capLevelOnFPSDrop: false, // used by fps-controller
  capLevelToPlayerSize: false, // used by cap-level-controller
  initialLiveManifestSize: 1, // used by stream-controller
  maxBufferLength: 30, // used by stream-controller
  backBufferLength: Infinity, // used by buffer-controller
  maxBufferSize: 60 * 1000 * 1000, // used by stream-controller
  maxBufferHole: 0.1, // used by stream-controller
  highBufferWatchdogPeriod: 2, // used by stream-controller
  nudgeOffset: 0.1, // used by stream-controller
  nudgeMaxRetry: 3, // used by stream-controller
  maxFragLookUpTolerance: 0.25, // used by stream-controller
  liveSyncDurationCount: 3, // used by latency-controller
  liveMaxLatencyDurationCount: Infinity, // used by latency-controller
  liveSyncDuration: undefined, // used by latency-controller
  liveMaxLatencyDuration: undefined, // used by latency-controller
  maxLiveSyncPlaybackRate: 1, // used by latency-controller
  liveDurationInfinity: false, // used by buffer-controller
  liveBackBufferLength: null, // used by buffer-controller
  maxMaxBufferLength: 600, // used by stream-controller
  enableWorker: true, // used by demuxer
  enableSoftwareAES: true, // used by decrypter
  manifestLoadingTimeOut: 10000, // used by playlist-loader
  manifestLoadingMaxRetry: 1, // used by playlist-loader
  manifestLoadingRetryDelay: 1000, // used by playlist-loader
  manifestLoadingMaxRetryTimeout: 64000, // used by playlist-loader
  startLevel: undefined, // used by level-controller
  levelLoadingTimeOut: 10000, // used by playlist-loader
  levelLoadingMaxRetry: 4, // used by playlist-loader
  levelLoadingRetryDelay: 1000, // used by playlist-loader
  levelLoadingMaxRetryTimeout: 64000, // used by playlist-loader
  fragLoadingTimeOut: 20000, // used by fragment-loader
  fragLoadingMaxRetry: 6, // used by fragment-loader
  fragLoadingRetryDelay: 1000, // used by fragment-loader
  fragLoadingMaxRetryTimeout: 64000, // used by fragment-loader
  startFragPrefetch: false, // used by stream-controller
  fpsDroppedMonitoringPeriod: 5000, // used by fps-controller
  fpsDroppedMonitoringThreshold: 0.2, // used by fps-controller
  appendErrorMaxRetry: 3, // used by buffer-controller
  loader: XhrLoader,
  // loader: FetchLoader,
  fLoader: undefined, // used by fragment-loader
  pLoader: undefined, // used by playlist-loader
  xhrSetup: undefined, // used by xhr-loader
  licenseXhrSetup: undefined, // used by eme-controller
  licenseResponseCallback: undefined, // used by eme-controller
  abrController: AbrController,
  bufferController: BufferController,
  capLevelController: CapLevelController,
  fpsController: FPSController,
  stretchShortVideoTrack: false, // used by mp4-remuxer
  maxAudioFramesDrift: 1, // used by mp4-remuxer
  forceKeyFrameOnDiscontinuity: true, // used by ts-demuxer
  abrEwmaFastLive: 3, // used by abr-controller
  abrEwmaSlowLive: 9, // used by abr-controller
  abrEwmaFastVoD: 3, // used by abr-controller
  abrEwmaSlowVoD: 9, // used by abr-controller
  abrEwmaDefaultEstimate: 5e5, // 500 kbps  // used by abr-controller
  abrBandWidthFactor: 0.95, // used by abr-controller
  abrBandWidthUpFactor: 0.7, // used by abr-controller
  abrMaxWithRealBitrate: false, // used by abr-controller
  maxStarvationDelay: 4, // used by abr-controller
  maxLoadingDelay: 4, // used by abr-controller
  minAutoBitrate: 0, // used by hls
  emeEnabled: false, // used by eme-controller
  widevineLicenseUrl: undefined, // used by eme-controller
  drmSystemOptions: {}, // used by eme-controller
  requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess, // used by eme-controller
  testBandwidth: true,
  progressive: false,
  lowLatencyMode: true,

  // Dynamic Modules
  ...timelineConfig(),
  subtitleStreamController: __USE_SUBTITLES__
    ? SubtitleStreamController
    : undefined,
  subtitleTrackController: __USE_SUBTITLES__
    ? SubtitleTrackController
    : undefined,
  timelineController: __USE_SUBTITLES__ ? TimelineController : undefined,
  audioStreamController: __USE_ALT_AUDIO__ ? AudioStreamController : undefined,
  audioTrackController: __USE_ALT_AUDIO__ ? AudioTrackController : undefined,
  emeController: __USE_EME_DRM__ ? EMEController : undefined,
};

function timelineConfig(): TimelineControllerConfig {
  return {
    cueHandler: Cues, // used by timeline-controller
    enableCEA708Captions: __USE_SUBTITLES__, // used by timeline-controller
    enableWebVTT: __USE_SUBTITLES__, // used by timeline-controller
    enableIMSC1: __USE_SUBTITLES__, // used by timeline-controller
    captionsTextTrack1Label: 'English', // used by timeline-controller
    captionsTextTrack1LanguageCode: 'en', // used by timeline-controller
    captionsTextTrack2Label: 'Spanish', // used by timeline-controller
    captionsTextTrack2LanguageCode: 'es', // used by timeline-controller
    captionsTextTrack3Label: 'Unknown CC', // used by timeline-controller
    captionsTextTrack3LanguageCode: '', // used by timeline-controller
    captionsTextTrack4Label: 'Unknown CC', // used by timeline-controller
    captionsTextTrack4LanguageCode: '', // used by timeline-controller
    renderTextTracksNatively: true,
  };
}

export function mergeConfig(
  defaultConfig: HlsConfig,
  userConfig: Partial<HlsConfig>
): HlsConfig {
  if (
    (userConfig.liveSyncDurationCount ||
      userConfig.liveMaxLatencyDurationCount) &&
    (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)
  ) {
    throw new Error(
      "Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration"
    );
  }

  if (
    userConfig.liveMaxLatencyDurationCount !== undefined &&
    (userConfig.liveSyncDurationCount === undefined ||
      userConfig.liveMaxLatencyDurationCount <=
        userConfig.liveSyncDurationCount)
  ) {
    throw new Error(
      'Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"'
    );
  }

  if (
    userConfig.liveMaxLatencyDuration !== undefined &&
    (userConfig.liveSyncDuration === undefined ||
      userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)
  ) {
    throw new Error(
      'Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"'
    );
  }

  return Object.assign({}, defaultConfig, userConfig);
}

export function enableStreamingMode(config) {
  const currentLoader = config.loader;
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
    // If a developer has configured their own loader, respect that choice
    logger.log(
      '[config]: Custom loader detected, cannot enable progressive streaming'
    );
    config.progressive = false;
  } else {
    const canStreamProgressively = fetchSupported();
    if (canStreamProgressively) {
      config.loader = FetchLoader;
      config.progressive = true;
      config.enableSoftwareAES = true;
      logger.log('[config]: Progressive streaming enabled, using FetchLoader');
    }
  }
}