diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 33a139ef9f1..ff7844a4160 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -25,7 +25,7 @@ export interface AbrComponentAPI extends ComponentAPI { // Warning: (ae-missing-release-tag) "AbrController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class AbrController implements AbrComponentAPI { +export class AbrController extends Logger implements AbrComponentAPI { constructor(hls: Hls); // (undocumented) bwEstimator: EwmaBandWidthEstimator; @@ -162,6 +162,8 @@ export class AudioStreamController extends BaseStreamController implements Netwo // (undocumented) onBufferReset(): void; // (undocumented) + protected onError(event: Events.ERROR, data: ErrorData): void; + // (undocumented) onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData): void; // (undocumented) protected onHandlerDestroying(): void; @@ -176,9 +178,13 @@ export class AudioStreamController extends BaseStreamController implements Netwo // (undocumented) protected onTickEnd(): void; // (undocumented) + protected registerListeners(): void; + // (undocumented) protected resetLoadingState(): void; // (undocumented) startLoad(startPosition: number): void; + // (undocumented) + protected unregisterListeners(): void; } // Warning: (ae-missing-release-tag) "AudioTrackController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -250,7 +256,7 @@ export interface BackBufferData { // Warning: (ae-missing-release-tag) "BasePlaylistController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class BasePlaylistController implements NetworkComponentAPI { +export class BasePlaylistController extends Logger implements NetworkComponentAPI { constructor(hls: Hls, logPrefix: string); // (undocumented) protected canLoad: boolean; @@ -265,8 +271,6 @@ export class BasePlaylistController implements NetworkComponentAPI { // (undocumented) protected loadPlaylist(hlsUrlParameters?: HlsUrlParameters): void; // (undocumented) - protected log: (msg: any) => void; - // (undocumented) protected playlistLoaded(index: number, data: LevelLoadedData | AudioTrackLoadedData | TrackLoadedData, previousDetails?: LevelDetails): void; // (undocumented) protected requestScheduled: number; @@ -282,8 +286,6 @@ export class BasePlaylistController implements NetworkComponentAPI { protected switchParams(playlistUri: string, previous: LevelDetails | undefined): HlsUrlParameters | undefined; // (undocumented) protected timer: number; - // (undocumented) - protected warn: (msg: any) => void; } // Warning: (ae-missing-release-tag) "BaseSegment" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -419,8 +421,6 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected _loadInitSegment(frag: Fragment, level: Level): void; // (undocumented) - protected log: (msg: any) => void; - // (undocumented) mapToInitFragWhenRequired(frag: Fragment | null): typeof frag; // (undocumented) protected media: HTMLMediaElement | null; @@ -431,6 +431,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected nextLoadPosition: number; // (undocumented) + protected onError(event: Events.ERROR, data: ErrorData): void; + // (undocumented) protected onFragmentOrKeyLoadError(filterType: PlaylistLevelType, data: ErrorData): void; // (undocumented) protected onHandlerDestroyed(): void; @@ -439,6 +441,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected onManifestLoaded(event: Events.MANIFEST_LOADED, data: ManifestLoadedData): void; // (undocumented) + protected onManifestLoading(): void; + // (undocumented) protected onMediaAttached(event: Events.MEDIA_ATTACHED, data: MediaAttachedData): void; // (undocumented) protected onMediaDetaching(): void; @@ -457,6 +461,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected reduceMaxBufferLength(threshold: number): boolean; // (undocumented) + protected registerListeners(): void; + // (undocumented) protected removeUnbufferedFrags(start?: number): void; // (undocumented) protected resetFragmentErrors(filterType: PlaylistLevelType): void; @@ -500,9 +506,9 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected transmuxer: TransmuxerInterface | null; // (undocumented) - protected waitForCdnTuneIn(details: LevelDetails): boolean | 0; + protected unregisterListeners(): void; // (undocumented) - protected warn: (msg: any) => void; + protected waitForCdnTuneIn(details: LevelDetails): boolean | 0; } // Warning: (ae-missing-release-tag) "BufferAppendedData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -554,7 +560,7 @@ export interface BufferCodecsData { // Warning: (ae-missing-release-tag) "BufferController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class BufferController implements ComponentAPI { +export class BufferController extends Logger implements ComponentAPI { constructor(hls: Hls); // (undocumented) protected appendChangeType(type: any, mimeType: any): void; @@ -573,16 +579,12 @@ export class BufferController implements ComponentAPI { // (undocumented) destroy(): void; // (undocumented) - protected error: (msg: any, obj?: any) => void; - // (undocumented) flushBackBuffer(currentTime: number, targetDuration: number, targetBackBufferPosition: number): void; // (undocumented) flushFrontBuffer(currentTime: number, targetDuration: number, targetFrontBufferPosition: number): void; // (undocumented) hasSourceTypes(): boolean; // (undocumented) - protected log: (msg: any) => void; - // (undocumented) media: HTMLMediaElement | null; // (undocumented) mediaSource: MediaSource | null; @@ -622,8 +624,6 @@ export class BufferController implements ComponentAPI { protected unregisterListeners(): void; // (undocumented) updateSeekableRange(levelDetails: any): void; - // (undocumented) - protected warn: (msg: any, obj?: any) => void; } // Warning: (ae-missing-release-tag) "BufferControllerConfig" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -795,7 +795,7 @@ export interface ComponentAPI { // Warning: (ae-missing-release-tag) "ContentSteeringController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class ContentSteeringController implements NetworkComponentAPI { +export class ContentSteeringController extends Logger implements NetworkComponentAPI { constructor(hls: Hls); // (undocumented) clearTimeout(): void; @@ -922,7 +922,7 @@ export const enum ElementaryStreamTypes { // Warning: (ae-missing-release-tag) "EMEController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export class EMEController implements ComponentAPI { +export class EMEController extends Logger implements ComponentAPI { constructor(hls: Hls); // (undocumented) static CDMCleanupPromise: Promise | void; @@ -964,7 +964,7 @@ export const enum ErrorActionFlags { // Warning: (ae-missing-release-tag) "ErrorController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class ErrorController implements NetworkComponentAPI { +export class ErrorController extends Logger implements NetworkComponentAPI { constructor(hls: Hls); // (undocumented) destroy(): void; @@ -1604,6 +1604,7 @@ class Hls implements HlsEventEmitter { // Warning: (ae-setter-with-docs) The doc comment for the property "loadLevel" must appear on the getter, not the setter. set loadLevel(newLevel: number); loadSource(url: string): void; + readonly logger: ILogger; get lowLatencyMode(): boolean; // Warning: (ae-setter-with-docs) The doc comment for the property "lowLatencyMode" must appear on the getter, not the setter. set lowLatencyMode(mode: boolean); @@ -2649,6 +2650,25 @@ export class LoadStats implements LoaderStats { total: number; } +// Warning: (ae-missing-release-tag) "Logger" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class Logger implements ILogger { + constructor(label: string, logger: ILogger); + // (undocumented) + debug: ILogFunction; + // (undocumented) + error: ILogFunction; + // (undocumented) + info: ILogFunction; + // (undocumented) + log: ILogFunction; + // (undocumented) + trace: ILogFunction; + // (undocumented) + warn: ILogFunction; +} + // Warning: (ae-missing-release-tag) "MainPlaylistType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3207,7 +3227,11 @@ export class SubtitleStreamController extends BaseStreamController implements Ne // (undocumented) onSubtitleTrackSwitch(event: Events.SUBTITLE_TRACK_SWITCH, data: TrackSwitchedData): void; // (undocumented) + protected registerListeners(): void; + // (undocumented) startLoad(startPosition: number): void; + // (undocumented) + protected unregisterListeners(): void; } // Warning: (ae-missing-release-tag) "SubtitleTrackController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/config.ts b/src/config.ts index 05b3e52938a..39462a3fa34 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,10 +17,10 @@ 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 { ILogger, logger } from './utils/logger'; import type Hls from './hls'; import type { CuesInterface } from './utils/cues'; +import type { ILogger } from './utils/logger'; import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper'; import type { FragmentLoaderContext, @@ -558,6 +558,7 @@ function timelineConfig(): TimelineControllerConfig { export function mergeConfig( defaultConfig: HlsConfig, userConfig: Partial, + logger: ILogger, ): HlsConfig { if ( (userConfig.liveSyncDurationCount || @@ -664,7 +665,7 @@ function deepCpy(obj: any): any { /** * @ignore */ -export function enableStreamingMode(config) { +export function enableStreamingMode(config: HlsConfig, logger: ILogger) { const currentLoader = config.loader; if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) { // If a developer has configured their own loader, respect that choice diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index c62234e2dff..6be49100f70 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -2,7 +2,7 @@ import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator'; import { Events } from '../events'; import { ErrorDetails } from '../errors'; import { PlaylistLevelType } from '../types/loader'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import { SUPPORTED_INFO_DEFAULT, getMediaDecodingInfoPromise, @@ -31,7 +31,7 @@ import type { } from '../types/events'; import type { AbrComponentAPI } from '../types/component-api'; -class AbrController implements AbrComponentAPI { +class AbrController extends Logger implements AbrComponentAPI { protected hls: Hls; private lastLevelLoadSec: number = 0; private lastLoadedFragLevel: number = -1; @@ -48,6 +48,7 @@ class AbrController implements AbrComponentAPI { public bwEstimator: EwmaBandWidthEstimator; constructor(hls: Hls) { + super('abr', hls.logger); this.hls = hls; this.bwEstimator = this.initEstimator(); this.registerListeners(); @@ -55,7 +56,7 @@ class AbrController implements AbrComponentAPI { public resetEstimator(abrEwmaDefaultEstimate?: number) { if (abrEwmaDefaultEstimate) { - logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`); + this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`); this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate; } this.firstSelection = -1; @@ -355,7 +356,7 @@ class AbrController implements AbrComponentAPI { } this.clearTimer(); - logger.warn(`[abr] Fragment ${frag.sn}${ + this.warn(`Fragment ${frag.sn}${ part ? ' part ' + part.index : '' } of level ${frag.level} is loading too slowly; Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s @@ -479,8 +480,8 @@ class AbrController implements AbrComponentAPI { } const firstLevel = this.hls.firstLevel; const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel); - logger.warn( - `[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`, + this.warn( + `Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`, ); return clamped; } @@ -591,8 +592,8 @@ class AbrController implements AbrComponentAPI { ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay; maxStarvationDelay = maxLoadingDelay - bitrateTestDelay; - logger.info( - `[abr] bitrate test took ${Math.round( + this.info( + `bitrate test took ${Math.round( 1000 * bitrateTestDelay, )}ms, set first fragment max fetchDuration to ${Math.round( 1000 * maxStarvationDelay, @@ -611,8 +612,8 @@ class AbrController implements AbrComponentAPI { bwFactor, bwUpFactor, ); - logger.info( - `[abr] ${ + this.info( + `${ bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty' }, optimal quality level ${bestLevel}`, ); @@ -691,7 +692,7 @@ class AbrController implements AbrComponentAPI { : videoRanges[0]; currentFrameRate = minFramerate; currentBw = Math.max(currentBw, minBitrate); - logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`); + this.log(`picked start tier ${JSON.stringify(startTier)}`); } else { currentCodecSet = level?.codecSet; currentVideoRange = level?.videoRange; @@ -741,19 +742,19 @@ class AbrController implements AbrComponentAPI { const levels = this.hls.levels; const index = levels.indexOf(levelInfo); if (decodingInfo.error) { - logger.warn( - `[abr] MediaCapabilities decodingInfo error: "${ + this.warn( + `MediaCapabilities decodingInfo error: "${ decodingInfo.error }" for level ${index} ${JSON.stringify(decodingInfo)}`, ); } else if (!decodingInfo.supported) { - logger.warn( - `[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify( + this.warn( + `Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify( decodingInfo, )}`, ); if (index > -1 && levels.length > 1) { - logger.log(`[abr] Removing unsupported level ${index}`); + this.log(`Removing unsupported level ${index}`); this.hls.removeLevel(index); } } @@ -831,8 +832,8 @@ class AbrController implements AbrComponentAPI { (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel) ) { if (levelsSkipped.length) { - logger.trace( - `[abr] Skipped level(s) ${levelsSkipped.join( + this.trace( + `Skipped level(s) ${levelsSkipped.join( ',', )} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${ levels[levelsSkipped[0]].codecs @@ -841,8 +842,8 @@ class AbrController implements AbrComponentAPI { }" ${currentVideoRange}`, ); } - logger.info( - `[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round( + this.info( + `switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round( adjustedbw, )})-bitrate=${Math.round( adjustedbw - bitrate, diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index 4730c5578d7..6c4c50dfb47 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -71,30 +71,27 @@ class AudioStreamController hls, fragmentTracker, keyLoader, - '[audio-stream-controller]', + 'audio-stream-controller', PlaylistLevelType.AUDIO, ); - this._registerListeners(); + this.registerListeners(); } protected onHandlerDestroying() { - this._unregisterListeners(); + this.unregisterListeners(); super.onHandlerDestroying(); this.mainDetails = null; this.bufferedTrack = null; this.switchingTrack = null; } - private _registerListeners() { + protected registerListeners() { + super.registerListeners(); const { hls } = this; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.on(Events.ERROR, this.onError, this); hls.on(Events.BUFFER_RESET, this.onBufferReset, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); @@ -103,16 +100,16 @@ class AudioStreamController hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } - private _unregisterListeners() { + protected unregisterListeners() { const { hls } = this; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); + if (!hls) { + return; + } + super.unregisterListeners(); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.off(Events.ERROR, this.onError, this); hls.off(Events.BUFFER_RESET, this.onBufferReset, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); @@ -713,7 +710,7 @@ class AudioStreamController this.fragBufferedComplete(frag, part); } - private onError(event: Events.ERROR, data: ErrorData) { + protected onError(event: Events.ERROR, data: ErrorData) { if (data.fatal) { this.state = State.ERROR; return; diff --git a/src/controller/audio-track-controller.ts b/src/controller/audio-track-controller.ts index 2cb4b65f416..bf90a409e75 100644 --- a/src/controller/audio-track-controller.ts +++ b/src/controller/audio-track-controller.ts @@ -33,7 +33,7 @@ class AudioTrackController extends BasePlaylistController { private selectDefaultTrack: boolean = true; constructor(hls: Hls) { - super(hls, '[audio-track-controller]'); + super(hls, 'audio-track-controller'); this.registerListeners(); } diff --git a/src/controller/base-playlist-controller.ts b/src/controller/base-playlist-controller.ts index dde6a5f7fe7..6ba3a6d0815 100644 --- a/src/controller/base-playlist-controller.ts +++ b/src/controller/base-playlist-controller.ts @@ -5,7 +5,7 @@ import { computeReloadInterval, mergeDetails } from '../utils/level-helper'; import { ErrorData } from '../types/events'; import { getRetryDelay, isTimeoutError } from '../utils/error-helper'; import { NetworkErrorAction } from './error-controller'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import type { LevelDetails } from '../loader/level-details'; import type { MediaPlaylist } from '../types/media-playlist'; import type { @@ -14,17 +14,17 @@ import type { TrackLoadedData, } from '../types/events'; -export default class BasePlaylistController implements NetworkComponentAPI { +export default class BasePlaylistController + extends Logger + implements NetworkComponentAPI +{ protected hls: Hls; protected timer: number = -1; protected requestScheduled: number = -1; protected canLoad: boolean = false; - protected log: (msg: any) => void; - protected warn: (msg: any) => void; constructor(hls: Hls, logPrefix: string) { - this.log = logger.log.bind(logger, `${logPrefix}:`); - this.warn = logger.warn.bind(logger, `${logPrefix}:`); + super(logPrefix, hls.logger); this.hls = hls; } @@ -65,7 +65,7 @@ export default class BasePlaylistController implements NetworkComponentAPI { try { uri = new self.URL(attr.URI, previous.url).href; } catch (error) { - logger.warn( + this.warn( `Could not construct new URL for Rendition Report: ${error}`, ); uri = attr.URI || ''; diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 0d50dfb3a1f..18c6289ac00 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -1,7 +1,6 @@ import TaskLoop from '../task-loop'; import { FragmentState } from './fragment-tracker'; import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper'; -import { logger } from '../utils/logger'; import { Events } from '../events'; import { ErrorDetails, ErrorTypes } from '../errors'; import { ChunkMetadata } from '../types/transmuxer'; @@ -98,10 +97,6 @@ export default class BaseStreamController protected decrypter: Decrypter; protected initPTS: RationalTimestamp[] = []; - private readonly logPrefix: string = ''; - protected log: (msg: any) => void; - protected warn: (msg: any) => void; - constructor( hls: Hls, fragmentTracker: FragmentTracker, @@ -109,18 +104,32 @@ export default class BaseStreamController logPrefix: string, playlistType: PlaylistLevelType, ) { - super(); + super(logPrefix, hls.logger); this.playlistType = playlistType; - this.logPrefix = logPrefix; - this.log = logger.log.bind(logger, `${logPrefix}:`); - this.warn = logger.warn.bind(logger, `${logPrefix}:`); this.hls = hls; this.fragmentLoader = new FragmentLoader(hls.config); this.keyLoader = keyLoader; this.fragmentTracker = fragmentTracker; this.config = hls.config; this.decrypter = new Decrypter(hls.config); + } + + protected registerListeners() { + const { hls } = this; + hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); + hls.on(Events.ERROR, this.onError, this); + } + + protected unregisterListeners() { + const { hls } = this; + hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); + hls.off(Events.ERROR, this.onError, this); } protected doTick() { @@ -224,6 +233,10 @@ export default class BaseStreamController this.stopLoad(); } + protected onManifestLoading() {} + + protected onError(event: Events.ERROR, data: ErrorData) {} + protected onMediaSeeking = () => { const { config, fragCurrent, media, mediaBuffer, state } = this; const currentTime: number = media ? media.currentTime : 0; @@ -644,7 +657,7 @@ export default class BaseStreamController if (frag.encrypted && !frag.decryptdata?.key) { this.log( `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${ - this.logPrefix === '[stream-controller]' ? 'level' : 'track' + this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track' } ${frag.level}`, ); this.state = State.KEY_LOADING; @@ -684,7 +697,7 @@ export default class BaseStreamController } of playlist [${details.startSN}-${ details.endSN }] parts [0-${partIndex}-${partList.length - 1}] ${ - this.logPrefix === '[stream-controller]' ? 'level' : 'track' + this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track' }: ${frag.level}, target: ${parseFloat( targetBufferTime.toFixed(3), )}`, @@ -743,7 +756,7 @@ export default class BaseStreamController this.log( `Loading fragment ${frag.sn} cc: ${frag.cc} ${ details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : '' - }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${ + }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${ frag.level }, target: ${parseFloat(targetBufferTime.toFixed(3))}`, ); @@ -1545,7 +1558,7 @@ export default class BaseStreamController errorAction.resolved = true; } } else { - logger.warn( + this.warn( `${data.details} reached or exceeded max retry (${retryCount})`, ); return; diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index efbbe4415cb..99c0454e4dd 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -1,5 +1,5 @@ import { Events } from '../events'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import { ErrorDetails, ErrorTypes } from '../errors'; import { BufferHelper } from '../utils/buffer-helper'; import { @@ -42,7 +42,7 @@ interface BufferedChangeEvent extends Event { readonly removedRanges?: TimeRanges; } -export default class BufferController implements ComponentAPI { +export default class BufferController extends Logger implements ComponentAPI { // The level details used to determine duration, target-duration and live private details: LevelDetails | null = null; // cache the self generated object url to detect hijack of video tag @@ -82,17 +82,10 @@ export default class BufferController implements ComponentAPI { public pendingTracks: TrackSet = {}; public sourceBuffer!: SourceBuffers; - protected log: (msg: any) => void; - protected warn: (msg: any, obj?: any) => void; - protected error: (msg: any, obj?: any) => void; - constructor(hls: Hls) { + super('buffer-controller', hls.logger); this.hls = hls; - const logPrefix = '[buffer-controller]'; this.appendSource = hls.config.preferManagedMediaSource; - this.log = logger.log.bind(logger, logPrefix); - this.warn = logger.warn.bind(logger, logPrefix); - this.error = logger.error.bind(logger, logPrefix); this._initSourceBuffer(); this.registerListeners(); } @@ -1010,7 +1003,7 @@ export default class BufferController implements ComponentAPI { private _onMediaEmptied = () => { const { mediaSrc, _objectUrl } = this; if (mediaSrc !== _objectUrl) { - logger.error( + this.error( `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`, ); } diff --git a/src/controller/cap-level-controller.ts b/src/controller/cap-level-controller.ts index 6613780b7dd..2404716096f 100644 --- a/src/controller/cap-level-controller.ts +++ b/src/controller/cap-level-controller.ts @@ -12,7 +12,6 @@ import type { LevelsUpdatedData, } from '../types/events'; import StreamController from './stream-controller'; -import { logger } from '../utils/logger'; import type { ComponentAPI } from '../types/component-api'; import type Hls from '../hls'; @@ -152,7 +151,7 @@ class CapLevelController implements ComponentAPI { const hls = this.hls; const maxLevel = this.getMaxLevel(levels.length - 1); if (maxLevel !== this.autoLevelCapping) { - logger.log( + hls.logger.log( `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`, ); } diff --git a/src/controller/content-steering-controller.ts b/src/controller/content-steering-controller.ts index 6289bbb6b5b..276aff7379c 100644 --- a/src/controller/content-steering-controller.ts +++ b/src/controller/content-steering-controller.ts @@ -3,7 +3,7 @@ import { Level } from '../types/level'; import { reassignFragmentLevelIndexes } from '../utils/level-helper'; import { AttrList } from '../utils/attr-list'; import { ErrorActionFlags, NetworkErrorAction } from './error-controller'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import { PlaylistContextType, type Loader, @@ -48,9 +48,11 @@ export type UriReplacement = { const PATHWAY_PENALTY_DURATION_MS = 300000; -export default class ContentSteeringController implements NetworkComponentAPI { +export default class ContentSteeringController + extends Logger + implements NetworkComponentAPI +{ private readonly hls: Hls; - private log: (msg: any) => void; private loader: Loader | null = null; private uri: string | null = null; private pathwayId: string = '.'; @@ -66,8 +68,8 @@ export default class ContentSteeringController implements NetworkComponentAPI { private penalizedPathways: { [pathwayId: string]: number } = {}; constructor(hls: Hls) { + super('content-steering', hls.logger); this.hls = hls; - this.log = logger.log.bind(logger, `[content-steering]:`); this.registerListeners(); } @@ -203,7 +205,7 @@ export default class ContentSteeringController implements NetworkComponentAPI { errorAction.resolved = this.pathwayId !== errorPathway; } if (!errorAction.resolved) { - logger.warn( + this.warn( `Could not resolve ${data.details} ("${ data.error.message }") with content-steering for Pathway: ${errorPathway} levels: ${ @@ -442,7 +444,7 @@ export default class ContentSteeringController implements NetworkComponentAPI { ) => { this.log(`Loaded steering manifest: "${url}"`); const steeringData = response.data as SteeringManifest; - if (steeringData.VERSION !== 1) { + if (steeringData?.VERSION !== 1) { this.log(`Steering VERSION ${steeringData.VERSION} not supported!`); return; } diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index 27303a5f698..f74cc22e653 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -5,7 +5,7 @@ */ import { Events } from '../events'; import { ErrorTypes, ErrorDetails } from '../errors'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, getSupportedMediaKeySystemConfigurations, @@ -41,9 +41,6 @@ import type { LoaderConfiguration, LoaderContext, } from '../types/loader'; - -const LOGGER_PREFIX = '[eme]'; - interface KeySystemAccessPromises { keySystemAccess: Promise; mediaKeys?: Promise; @@ -68,7 +65,7 @@ export interface MediaKeySessionContext { * @class * @constructor */ -class EMEController implements ComponentAPI { +class EMEController extends Logger implements ComponentAPI { public static CDMCleanupPromise: Promise | void; private readonly hls: Hls; @@ -91,12 +88,8 @@ class EMEController implements ComponentAPI { ? [EMEController.CDMCleanupPromise] : []; - private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX); - private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX); - private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX); - private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX); - constructor(hls: Hls) { + super('eme', hls.logger); this.hls = hls; this.config = hls.config; this.registerListeners(); diff --git a/src/controller/error-controller.ts b/src/controller/error-controller.ts index bc6f8810b95..64558dd8a08 100644 --- a/src/controller/error-controller.ts +++ b/src/controller/error-controller.ts @@ -8,7 +8,7 @@ import { } from '../utils/error-helper'; import { findFragmentByPTS } from './fragment-finders'; import { HdcpLevel, HdcpLevels } from '../types/level'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import type Hls from '../hls'; import type { RetryConfig } from '../config'; import type { NetworkComponentAPI } from '../types/component-api'; @@ -50,19 +50,17 @@ type PenalizedRendition = { type PenalizedRenditions = { [key: number]: PenalizedRendition }; -export default class ErrorController implements NetworkComponentAPI { +export default class ErrorController + extends Logger + implements NetworkComponentAPI +{ private readonly hls: Hls; private playlistError: number = 0; private penalizedRenditions: PenalizedRenditions = {}; - private log: (msg: any) => void; - private warn: (msg: any) => void; - private error: (msg: any) => void; constructor(hls: Hls) { + super('error-controller', hls.logger); this.hls = hls; - this.log = logger.log.bind(logger, `[info]:`); - this.warn = logger.warn.bind(logger, `[warning]:`); - this.error = logger.error.bind(logger, `[error]:`); this.registerListeners(); } diff --git a/src/controller/fps-controller.ts b/src/controller/fps-controller.ts index 8419f3f9cc4..1bc34fe6d69 100644 --- a/src/controller/fps-controller.ts +++ b/src/controller/fps-controller.ts @@ -1,5 +1,4 @@ import { Events } from '../events'; -import { logger } from '../utils/logger'; import type { ComponentAPI } from '../types/component-api'; import type Hls from '../hls'; import type { MediaAttachingData } from '../types/events'; @@ -84,13 +83,13 @@ class FPSController implements ComponentAPI { totalDroppedFrames: droppedFrames, }); if (droppedFPS > 0) { - // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod)); + // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod)); if ( currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded ) { let currentLevel = hls.currentLevel; - logger.warn( + hls.logger.warn( 'drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel, ); diff --git a/src/controller/gap-controller.ts b/src/controller/gap-controller.ts index 012c8956a4a..0e6ee6ff946 100644 --- a/src/controller/gap-controller.ts +++ b/src/controller/gap-controller.ts @@ -1,10 +1,10 @@ -import type { BufferInfo } from '../utils/buffer-helper'; import { BufferHelper } from '../utils/buffer-helper'; import { ErrorTypes, ErrorDetails } from '../errors'; import { PlaylistLevelType } from '../types/loader'; import { Events } from '../events'; -import { logger } from '../utils/logger'; +import { Logger } from '../utils/logger'; import type Hls from '../hls'; +import type { BufferInfo } from '../utils/buffer-helper'; import type { HlsConfig } from '../config'; import type { Fragment } from '../loader/fragment'; import type { FragmentTracker } from './fragment-tracker'; @@ -14,7 +14,7 @@ export const MAX_START_GAP_JUMP = 2.0; export const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1; export const SKIP_BUFFER_RANGE_START = 0.05; -export default class GapController { +export default class GapController extends Logger { private config: HlsConfig; private media: HTMLMediaElement | null = null; private fragmentTracker: FragmentTracker; @@ -25,7 +25,13 @@ export default class GapController { private moved: boolean = false; private seeking: boolean = false; - constructor(config, media, fragmentTracker, hls) { + constructor( + config: HlsConfig, + media: HTMLMediaElement, + fragmentTracker: FragmentTracker, + hls: Hls, + ) { + super('gap-controller', hls.logger); this.config = config; this.media = media; this.fragmentTracker = fragmentTracker; @@ -65,7 +71,7 @@ export default class GapController { // The playhead is now moving, but was previously stalled if (this.stallReported) { const stalledDuration = self.performance.now() - stalled; - logger.warn( + this.warn( `playback not stuck anymore @${currentTime}, after ${Math.round( stalledDuration, )}ms`, @@ -206,7 +212,7 @@ export default class GapController { bufferInfo.nextStart - currentTime < config.maxBufferHole)) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000 ) { - logger.warn('Trying to nudge playhead over buffer-hole'); + this.warn('Trying to nudge playhead over buffer-hole'); // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds // We only try to jump the hole if it's under the configured size // Reset stalled so to rearm watchdog timer @@ -230,7 +236,7 @@ export default class GapController { media.currentTime } due to low buffer (${JSON.stringify(bufferInfo)})`, ); - logger.warn(error.message); + this.warn(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, @@ -305,7 +311,7 @@ export default class GapController { startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS, ); - logger.warn( + this.warn( `skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`, ); this.moved = true; @@ -348,7 +354,7 @@ export default class GapController { const error = new Error( `Nudging 'currentTime' from ${currentTime} to ${targetTime}`, ); - logger.warn(error.message); + this.warn(error.message); media.currentTime = targetTime; hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, @@ -360,7 +366,7 @@ export default class GapController { const error = new Error( `Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`, ); - logger.error(error.message); + this.error(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, diff --git a/src/controller/latency-controller.ts b/src/controller/latency-controller.ts index 77830588c0e..a39a398d586 100644 --- a/src/controller/latency-controller.ts +++ b/src/controller/latency-controller.ts @@ -6,7 +6,6 @@ import type { LevelUpdatedData, MediaAttachingData, } from '../types/events'; -import { logger } from '../utils/logger'; import type { ComponentAPI } from '../types/component-api'; import type Hls from '../hls'; import type { HlsConfig } from '../config'; @@ -125,7 +124,7 @@ export default class LatencyController implements ComponentAPI { this.onMediaDetaching(); this.levelDetails = null; // @ts-ignore - this.hls = this.onTimeupdate = null; + this.hls = null; } private registerListeners() { @@ -184,8 +183,8 @@ export default class LatencyController implements ComponentAPI { } this.stallCount++; if (this.levelDetails?.live) { - logger.warn( - '[playback-rate-controller]: Stall detected, adjusting target latency', + this.hls.logger.warn( + '[latency-controller]: Stall detected, adjusting target latency', ); } } diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index 1eb35d313a4..1ae879456e0 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -45,7 +45,7 @@ export default class LevelController extends BasePlaylistController { hls: Hls, contentSteeringController: ContentSteeringController | null, ) { - super(hls, '[level-controller]'); + super(hls, 'level-controller'); this.steering = contentSteeringController; this._registerListeners(); } diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index be0714af0e2..278321b2610 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -64,17 +64,15 @@ export default class StreamController hls, fragmentTracker, keyLoader, - '[stream-controller]', + 'stream-controller', PlaylistLevelType.MAIN, ); - this._registerListeners(); + this.registerListeners(); } - private _registerListeners() { + protected registerListeners() { + super.registerListeners(); const { hls } = this; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); @@ -83,7 +81,6 @@ export default class StreamController this.onFragLoadEmergencyAborted, this, ); - hls.on(Events.ERROR, this.onError, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); @@ -92,11 +89,9 @@ export default class StreamController hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } - protected _unregisterListeners() { + protected unregisterListeners() { + super.unregisterListeners(); const { hls } = this; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); hls.off( @@ -104,7 +99,6 @@ export default class StreamController this.onFragLoadEmergencyAborted, this, ); - hls.off(Events.ERROR, this.onError, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); @@ -114,9 +108,9 @@ export default class StreamController } protected onHandlerDestroying() { - this._unregisterListeners(); // @ts-ignore this.onMediaPlaying = this.onMediaSeeked = null; + this.unregisterListeners(); super.onHandlerDestroying(); } @@ -567,7 +561,7 @@ export default class StreamController this.tick(); }; - private onManifestLoading() { + protected onManifestLoading() { // reset buffer on manifest loading this.log('Trigger BUFFER_RESET'); this.hls.trigger(Events.BUFFER_RESET, undefined); @@ -880,7 +874,7 @@ export default class StreamController this.fragBufferedComplete(frag, part); } - private onError(event: Events.ERROR, data: ErrorData) { + protected onError(event: Events.ERROR, data: ErrorData) { if (data.fatal) { this.state = State.ERROR; return; diff --git a/src/controller/subtitle-stream-controller.ts b/src/controller/subtitle-stream-controller.ts index 4b08534e49e..dfc070bf63e 100644 --- a/src/controller/subtitle-stream-controller.ts +++ b/src/controller/subtitle-stream-controller.ts @@ -51,25 +51,22 @@ export class SubtitleStreamController hls, fragmentTracker, keyLoader, - '[subtitle-stream-controller]', + 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE, ); - this._registerListeners(); + this.registerListeners(); } protected onHandlerDestroying() { - this._unregisterListeners(); + this.unregisterListeners(); super.onHandlerDestroying(); this.mainDetails = null; } - private _registerListeners() { + protected registerListeners() { + super.registerListeners(); const { hls } = this; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.ERROR, this.onError, this); hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); @@ -78,13 +75,10 @@ export class SubtitleStreamController hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); } - private _unregisterListeners() { + protected unregisterListeners() { + super.unregisterListeners(); const { hls } = this; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.ERROR, this.onError, this); hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); diff --git a/src/controller/subtitle-track-controller.ts b/src/controller/subtitle-track-controller.ts index 1c8616f9bdd..982dc718f06 100644 --- a/src/controller/subtitle-track-controller.ts +++ b/src/controller/subtitle-track-controller.ts @@ -42,7 +42,7 @@ class SubtitleTrackController extends BasePlaylistController { private asyncPollTrackChange = () => this.pollTrackChange(0); constructor(hls: Hls) { - super(hls, '[subtitle-track-controller]'); + super(hls, 'subtitle-track-controller'); this.registerListeners(); } diff --git a/src/controller/timeline-controller.ts b/src/controller/timeline-controller.ts index dc5c05192a5..3bc9589f29b 100644 --- a/src/controller/timeline-controller.ts +++ b/src/controller/timeline-controller.ts @@ -28,7 +28,6 @@ import type { BufferFlushingData, FragLoadingData, } from '../types/events'; -import { logger } from '../utils/logger'; import type Hls from '../hls'; import type { ComponentAPI } from '../types/component-api'; import type { HlsConfig } from '../config'; @@ -410,7 +409,7 @@ export class TimelineController implements ComponentAPI { .filter((t) => t !== null) .map((t) => (t as TextTrack).label); if (unusedTextTracks.length) { - logger.warn( + this.hls.logger.warn( `Media element contains unused subtitle tracks: ${unusedTextTracks.join( ', ', )}. Replace media element for each source to clear TextTracks and captions menu.`, @@ -550,7 +549,7 @@ export class TimelineController implements ComponentAPI { }); }, (error) => { - logger.log(`Failed to parse IMSC1: ${error}`); + hls.logger.log(`Failed to parse IMSC1: ${error}`); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag: frag, @@ -597,7 +596,7 @@ export class TimelineController implements ComponentAPI { this._fallbackToIMSC1(frag, payload); } // Something went wrong while parsing. Trigger event with success false. - logger.log(`Failed to parse VTT cue: ${error}`); + hls.logger.log(`Failed to parse VTT cue: ${error}`); if (missingInitPTS && maxAvCC > frag.cc) { return; } diff --git a/src/demux/transmuxer-worker.ts b/src/demux/transmuxer-worker.ts index f150c48395b..df7f6af7e40 100644 --- a/src/demux/transmuxer-worker.ts +++ b/src/demux/transmuxer-worker.ts @@ -1,6 +1,6 @@ import Transmuxer, { isPromise } from '../demux/transmuxer'; import { Events } from '../events'; -import { ILogFunction, enableLogs, logger } from '../utils/logger'; +import { enableLogs, type ILogFunction, type ILogger } from '../utils/logger'; import { EventEmitter } from 'eventemitter3'; import { ErrorDetails, ErrorTypes } from '../errors'; import type { RemuxedTrack, RemuxerResult } from '../types/remuxer'; @@ -21,7 +21,7 @@ function startWorker(self) { observer.on(Events.ERROR, forwardMessage); // forward logger events to main thread - const forwardWorkerLogs = () => { + const forwardWorkerLogs = (logger: ILogger) => { for (const logFn in logger) { const func: ILogFunction = (message?) => { forwardMessage('workerLog', { @@ -46,8 +46,8 @@ function startWorker(self) { data.vendor, data.id, ); - enableLogs(config.debug, data.id); - forwardWorkerLogs(); + const logger = enableLogs(config.debug, data.id); + forwardWorkerLogs(logger); forwardMessage('init', null); break; } diff --git a/src/hls.ts b/src/hls.ts index e04e1aa318f..9445ee44a40 100644 --- a/src/hls.ts +++ b/src/hls.ts @@ -8,7 +8,7 @@ import KeyLoader from './loader/key-loader'; import StreamController from './controller/stream-controller'; import { isMSESupported, isSupported } from './is-supported'; import { getMediaSource } from './utils/mediasource-helper'; -import { logger, enableLogs } from './utils/logger'; +import { enableLogs, type ILogger } from './utils/logger'; import { enableStreamingMode, hlsDefaultConfig, mergeConfig } from './config'; import { EventEmitter } from 'eventemitter3'; import { Events } from './events'; @@ -59,6 +59,11 @@ export default class Hls implements HlsEventEmitter { */ public readonly userConfig: Partial; + /** + * The logger functions used by this player instance, configured on player instantiation. + */ + public readonly logger: ILogger; + private coreComponents: ComponentAPI[]; private networkControllers: NetworkComponentAPI[]; private started: boolean = false; @@ -142,12 +147,19 @@ export default class Hls implements HlsEventEmitter { * @param userConfig - Configuration options applied over `Hls.DefaultConfig` */ constructor(userConfig: Partial = {}) { - enableLogs(userConfig.debug || false, 'Hls instance'); - const config = (this.config = mergeConfig(Hls.DefaultConfig, userConfig)); + const logger = (this.logger = enableLogs( + userConfig.debug || false, + 'Hls instance', + )); + const config = (this.config = mergeConfig( + Hls.DefaultConfig, + userConfig, + logger, + )); this.userConfig = userConfig; if (config.progressive) { - enableStreamingMode(config); + enableStreamingMode(config, logger); } // core controllers and network loaders @@ -320,7 +332,7 @@ export default class Hls implements HlsEventEmitter { try { return this.emit(event, event, eventObject); } catch (error) { - logger.error( + this.logger.error( 'An internal error happened while handling event ' + event + '. Error message: "' + @@ -354,7 +366,7 @@ export default class Hls implements HlsEventEmitter { * Dispose of the instance */ destroy() { - logger.log('destroy'); + this.logger.log('destroy'); this.trigger(Events.DESTROYING, undefined); this.detachMedia(); this.removeAllListeners(); @@ -377,7 +389,7 @@ export default class Hls implements HlsEventEmitter { * Attaches Hls.js to a media element */ attachMedia(media: HTMLMediaElement) { - logger.log('attachMedia'); + this.logger.log('attachMedia'); this._media = media; this.trigger(Events.MEDIA_ATTACHING, { media: media }); } @@ -386,7 +398,7 @@ export default class Hls implements HlsEventEmitter { * Detach Hls.js from the media */ detachMedia() { - logger.log('detachMedia'); + this.logger.log('detachMedia'); this.trigger(Events.MEDIA_DETACHING, undefined); this._media = null; } @@ -407,7 +419,7 @@ export default class Hls implements HlsEventEmitter { )); this._autoLevelCapping = -1; this._maxHdcpLevel = null; - logger.log(`loadSource:${loadingSource}`); + this.logger.log(`loadSource:${loadingSource}`); if ( media && loadedSource && @@ -428,7 +440,7 @@ export default class Hls implements HlsEventEmitter { * Defaults to -1 (None: starts from earliest point) */ startLoad(startPosition: number = -1) { - logger.log(`startLoad(${startPosition})`); + this.logger.log(`startLoad(${startPosition})`); this.started = true; this.networkControllers.forEach((controller) => { controller.startLoad(startPosition); @@ -439,7 +451,7 @@ export default class Hls implements HlsEventEmitter { * Stop loading of any stream data. */ stopLoad() { - logger.log('stopLoad'); + this.logger.log('stopLoad'); this.started = false; this.networkControllers.forEach((controller) => { controller.stopLoad(); @@ -475,7 +487,7 @@ export default class Hls implements HlsEventEmitter { * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1) */ swapAudioCodec() { - logger.log('swapAudioCodec'); + this.logger.log('swapAudioCodec'); this.streamController.swapAudioCodec(); } @@ -486,7 +498,7 @@ export default class Hls implements HlsEventEmitter { * Automatic recovery of media-errors by this process is configurable. */ recoverMediaError() { - logger.log('recoverMediaError'); + this.logger.log('recoverMediaError'); const media = this._media; this.detachMedia(); if (media) { @@ -517,7 +529,7 @@ export default class Hls implements HlsEventEmitter { * 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) { - logger.log(`set currentLevel:${newLevel}`); + this.logger.log(`set currentLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.immediateLevelSwitch(); } @@ -536,7 +548,7 @@ export default class Hls implements HlsEventEmitter { * @param newLevel - Pass -1 for automatic level selection */ set nextLevel(newLevel: number) { - logger.log(`set nextLevel:${newLevel}`); + this.logger.log(`set nextLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.nextLevelSwitch(); } @@ -555,7 +567,7 @@ export default class Hls implements HlsEventEmitter { * @param newLevel - Pass -1 for automatic level selection */ set loadLevel(newLevel: number) { - logger.log(`set loadLevel:${newLevel}`); + this.logger.log(`set loadLevel:${newLevel}`); this.levelController.manualLevel = newLevel; } @@ -586,7 +598,7 @@ export default class Hls implements HlsEventEmitter { * Sets "first-level", see getter. */ set firstLevel(newLevel: number) { - logger.log(`set firstLevel:${newLevel}`); + this.logger.log(`set firstLevel:${newLevel}`); this.levelController.firstLevel = newLevel; } @@ -611,7 +623,7 @@ export default class Hls implements HlsEventEmitter { * (determined from download of first segment) */ set startLevel(newLevel: number) { - logger.log(`set startLevel:${newLevel}`); + 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); @@ -686,7 +698,7 @@ export default class Hls implements HlsEventEmitter { */ set autoLevelCapping(newLevel: number) { if (this._autoLevelCapping !== newLevel) { - logger.log(`set autoLevelCapping:${newLevel}`); + this.logger.log(`set autoLevelCapping:${newLevel}`); this._autoLevelCapping = newLevel; this.levelController.checkMaxAutoUpdated(); } @@ -1032,7 +1044,7 @@ export type { TSDemuxerConfig, } from './config'; export type { MediaKeySessionContext } from './controller/eme-controller'; -export type { ILogger } from './utils/logger'; +export type { ILogger, Logger } from './utils/logger'; export type { PathwayClone, SteeringManifest, diff --git a/src/task-loop.ts b/src/task-loop.ts index 84994cb7a2d..e02904ff641 100644 --- a/src/task-loop.ts +++ b/src/task-loop.ts @@ -1,3 +1,5 @@ +import { type ILogger, Logger } from './utils/logger'; + /** * @ignore * Sub-class specialization of EventHandler base class. @@ -27,13 +29,14 @@ * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo). */ -export default class TaskLoop { +export default class TaskLoop extends Logger { private readonly _boundTick: () => void; private _tickTimer: number | null = null; private _tickInterval: number | null = null; private _tickCallCount = 0; - constructor() { + constructor(label: string, logger: ILogger) { + super(label, logger); this._boundTick = this.tick.bind(this); } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index eb34cd59380..444d6a487de 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -11,6 +11,25 @@ export interface ILogger { error: ILogFunction; } +export class Logger implements ILogger { + trace: ILogFunction; + debug: ILogFunction; + log: ILogFunction; + warn: ILogFunction; + info: ILogFunction; + error: ILogFunction; + + constructor(label: string, logger: ILogger) { + const lb = `[${label}]:`; + this.trace = noop; + this.debug = logger.debug.bind(null, lb); + this.log = logger.log.bind(null, lb); + this.warn = logger.warn.bind(null, lb); + this.info = logger.info.bind(null, lb); + this.error = logger.error.bind(null, lb); + } +} + const noop: ILogFunction = function () {}; const fakeLogger: ILogger = { @@ -22,7 +41,9 @@ const fakeLogger: ILogger = { error: noop, }; -let exportedLogger: ILogger = fakeLogger; +function createLogger() { + return Object.assign({}, fakeLogger); +} // let lastCallTime; // function formatMsgWithTimeInfo(type, msg) { @@ -33,33 +54,37 @@ let exportedLogger: ILogger = fakeLogger; // return msg; // } -function consolePrintFn(type: string): ILogFunction { +function consolePrintFn(type: string, id: string | undefined): ILogFunction { const func: ILogFunction = self.console[type]; - if (func) { - return func.bind(self.console, `[${type}] >`); - } - return noop; + return func + ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) + : noop; } -function exportLoggerFunctions( - debugConfig: boolean | ILogger, - ...functions: string[] -): void { - functions.forEach(function (type) { - exportedLogger[type] = debugConfig[type] - ? debugConfig[type].bind(debugConfig) - : consolePrintFn(type); - }); +function getLoggerFn( + key: string, + debugConfig: boolean | Partial, + id: string | undefined, +): ILogFunction { + return debugConfig[key] + ? debugConfig[key].bind(debugConfig) + : consolePrintFn(key, id); } -export function enableLogs(debugConfig: boolean | ILogger, id: string): void { +let exportedLogger: ILogger = createLogger(); + +export function enableLogs( + debugConfig: boolean | ILogger, + context: string, + id?: string | undefined, +): ILogger { // check that console is available + const newLogger = createLogger(); if ( (typeof console === 'object' && debugConfig === true) || typeof debugConfig === 'object' ) { - exportLoggerFunctions( - debugConfig, + const keys: (keyof ILogger)[] = [ // Remove out from list here to hard-disable a log-level // 'trace', 'debug', @@ -67,19 +92,23 @@ export function enableLogs(debugConfig: boolean | ILogger, id: string): void { 'info', 'warn', 'error', - ); + ]; + keys.forEach((key) => { + newLogger[key] = getLoggerFn(key, debugConfig, id); + }); // Some browsers don't allow to use bind on console object anyway // fallback to default if needed try { - exportedLogger.log( - `Debug logs enabled for "${id}" in hls.js version ${__VERSION__}`, + newLogger.log( + `Debug logs enabled for "${context}" in hls.js version ${__VERSION__}`, ); } catch (e) { - exportedLogger = fakeLogger; + /* log fn threw an exception. All logger methods are no-ops. */ + return createLogger(); } - } else { - exportedLogger = fakeLogger; } + exportedLogger = newLogger; + return newLogger; } export const logger: ILogger = exportedLogger; diff --git a/tests/mocks/hls.mock.ts b/tests/mocks/hls.mock.ts index cd3b3f81082..dcce4ea51a0 100644 --- a/tests/mocks/hls.mock.ts +++ b/tests/mocks/hls.mock.ts @@ -1,5 +1,6 @@ import Hls from '../../src/hls'; import { EventEmitter } from 'eventemitter3'; +import { logger } from '../../src/utils/logger'; import type { HlsEventEmitter, HlsListeners } from '../../src/events'; import type { HlsConfig } from '../../src/config'; import type { Level } from '../../src/types/level'; @@ -26,6 +27,7 @@ export default class HlsMock extends EventEmitter implements HlsEventEmitter { // Mock arguments can at will override the default config // and have to specify things that are not in the default config this.config = Object.assign({}, Hls.DefaultConfig, config); + this.logger = logger; // stub public API with spies publicMethods.forEach((methodName) => { this[methodName] = sinon.stub(); diff --git a/tests/unit/controller/subtitle-stream-controller.js b/tests/unit/controller/subtitle-stream-controller.js index 2cbf03c07c5..406a78f973e 100644 --- a/tests/unit/controller/subtitle-stream-controller.js +++ b/tests/unit/controller/subtitle-stream-controller.js @@ -7,6 +7,7 @@ import { Fragment } from '../../../src/loader/fragment'; import { PlaylistLevelType } from '../../../src/types/loader'; import { AttrList } from '../../../src/utils/attr-list'; import KeyLoader from '../../../src/loader/key-loader'; +import { State } from '../../../src/controller/base-stream-controller'; import { SubtitleStreamController } from '../../../src/controller/subtitle-stream-controller'; const mediaMock = { @@ -48,6 +49,7 @@ describe('SubtitleStreamController', function () { subtitleStreamController.onMediaAttached(Events.MEDIA_ATTACHED, { media: mediaMock, }); + subtitleStreamController.state = State.IDLE; }); afterEach(function () { diff --git a/tests/unit/loader/fragment-loader.ts b/tests/unit/loader/fragment-loader.ts index 4a137fe9fe4..c1e1e4c989c 100644 --- a/tests/unit/loader/fragment-loader.ts +++ b/tests/unit/loader/fragment-loader.ts @@ -6,6 +6,7 @@ import { LoadStats } from '../../../src/loader/load-stats'; import { hlsDefaultConfig, mergeConfig } from '../../../src/config'; import { PlaylistLevelType } from '../../../src/types/loader'; import { MockXhr } from '../../mocks/loader.mock'; +import { logger } from '../../../src/utils/logger'; import sinon from 'sinon'; import chai from 'chai'; @@ -25,7 +26,7 @@ describe('FragmentLoader tests', function () { beforeEach(function () { fragmentLoader = new FragmentLoader( - mergeConfig(hlsDefaultConfig, { loader: MockXhr }), + mergeConfig(hlsDefaultConfig, { loader: MockXhr }, logger), ); frag = new Fragment(PlaylistLevelType.MAIN, ''); frag.url = 'foo';