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 * as Cues from './utils/cues';
import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
import { logger } from './utils/logger';
import type { MediaKeyFunc } from './utils/mediakeys-helper';
import type {
FragmentLoaderContext,
Loader,
LoaderContext,
PlaylistLoaderContext,
} from './types/loader';
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;
liveDurationInfinity: boolean;
liveBackBufferLength: number;
};
type CapLevelControllerConfig = {
capLevelToPlayerSize: boolean;
};
export type DRMSystemOptions = {
audioRobustness?: string;
videoRobustness?: string;
};
export type EMEControllerConfig = {
licenseXhrSetup?: (xhr: XMLHttpRequest, url: string) => void;
emeEnabled: boolean;
widevineLicenseUrl?: string;
drmSystemOptions: DRMSystemOptions;
requestMediaKeySystemAccessFunc: MediaKeyFunc | null;
};
type FragmentLoaderConfig = {
fLoader?: { new (confg: HlsConfig): Loader<FragmentLoaderContext> };
fragLoadingTimeOut: number;
fragLoadingMaxRetry: number;
fragLoadingRetryDelay: number;
fragLoadingMaxRetryTimeout: number;
};
type FPSControllerConfig = {
capLevelOnFPSDrop: boolean;
fpsDroppedMonitoringPeriod: number;
fpsDroppedMonitoringThreshold: number;
};
type LevelControllerConfig = {
startLevel?: number;
};
export type MP4RemuxerConfig = {
stretchShortVideoTrack: boolean;
maxAudioFramesDrift: number;
};
type PlaylistLoaderConfig = {
pLoader?: { new (confg: HlsConfig): Loader<PlaylistLoaderContext> };
manifestLoadingTimeOut: number;
manifestLoadingMaxRetry: number;
manifestLoadingRetryDelay: number;
manifestLoadingMaxRetryTimeout: number;
levelLoadingTimeOut: number;
levelLoadingMaxRetry: number;
levelLoadingRetryDelay: number;
levelLoadingMaxRetryTimeout: number;
};
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;
};
type LatencyControllerConfig = {
liveSyncDurationCount: number;
liveMaxLatencyDurationCount: number;
liveSyncDuration?: number;
liveMaxLatencyDuration?: number;
maxLiveSyncPlaybackRate: number;
};
type TimelineControllerConfig = {
cueHandler: Cues.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;
};
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
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.25, // used by latency-controller
liveDurationInfinity: false, // used by buffer-controller
liveBackBufferLength: Infinity, // 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
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');
}
}
}