UNPKG

rx-player

Version:
310 lines (290 loc) 10.1 kB
/** * Copyright 2015 CANAL+ Group * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import EventEmitter from "./event_emitter"; import getMonotonicTimeStamp from "./monotonic_timestamp"; import noop from "./noop"; export type ILoggerLevel = "NONE" | "ERROR" | "WARNING" | "INFO" | "DEBUG"; export type ILogFormat = "standard" | "full"; type IAcceptedLogValueBase = boolean | string | number | null | undefined; export type IAcceptedLogValue = | IAcceptedLogValueBase | Error | Partial<Record<string, IAcceptedLogValueBase>>; /** * Area of the code the log is linked to. * * Do not forget to update the corresponding API documentation page. When adding * or removing namespaces. */ export type ILogNamespace = /** Linked to adaptive bitrate quality selection */ | "ABR" /** Linked to the RxPlayer public API. */ | "API" /** Linked to the part of the RxPlayer handling audio/video/text track selection. */ | "Track" /** * Linked to the "Init" logic of the RxPlayer, which between other things set up a * new content. */ | "Init" /** * Global handling logic in the "Core" part of the player. * The main orchestrator in the RxPlayer's worker-side. */ | "Core" /** Linked to logic implementing Manifest Fetching. */ | "MF" /** Linked to logic implementing Segment Fetching. */ | "SF" /** Linked to a generic utils function. */ | "utils" /** Linked to CMCD logic. */ | "CMCD" /** Heuristics involved in freeze-detection (when the media is stalled unexpectedly). */ | "Freeze" /** Linked to the DASH protocol implementation and manifest parsing. */ | "dash" /** Linked to the Smooth protocol implementation and manifest parsing. */ | "smooth" /** Linked to the MetaPlaylist protocol implementation and manifest parsing. */ | "metaplaylist" /** Linked to the exploitation of the `HTMLMediaElement` linked to the RxPlayer. */ | "media" /** Linked to the code directly interacting with MSE API, e.g. to append media data. */ | "mse" /** Linked to the code handling content decryption, including handling the EME API. */ | "DRM" /** "Segment Inventory". Estimates the list of segment currently present in buffers, */ | "SI" /** Core RxPlayer logic orchestrating segment fetching and pushing. */ | "Stream" /** Linked to the code handling the "Manifest" structure, which describes a content. */ | "manifest" /** Linked to the logic parsing ISOBMFF-compatible container files. */ | "isobmff" /** Linked to the logic parsing MKV-compatible container files. */ | "webm" /** Linked to the logic parsing TTML data, for text tracks. */ | "ttml" /** Linked to the logic parsing VTT data, for text tracks. */ | "vtt" /** Linked to the logic parsing SRT data, for text tracks. */ | "srt" /** Linked to the logic parsing SAMI data, for text tracks. */ | "sami" /** Linked to in-stream image thumbnails. */ | "Thumbnails" /** Linked to subtitles / text management */ | "text" /** * Message sent from the RxPlayer's "main" part logic (e.g. the API) to the "Core" * internal logic. * Both may run in parallel (in "multithreading" mode). */ | "M-->C" /** * Message sent from the RxPlayer's "Core" logic to the "main" logic. * Both may run in parallel (in "multithreading" mode). */ | "M<--C" /** Log from the `MediaCapabilitiesProber` tool. */ | "MediaCapabilitiesProber" /** Log from the `VideoThumbnailLoader` tool. */ | "VideoThumbnailLoader"; type IConsoleFn = (namespace: ILogNamespace, ...args: IAcceptedLogValue[]) => void; const DEFAULT_LOG_LEVEL: ILoggerLevel = "NONE"; /** * Events sent by `Logger` where the keys are the events' name and the values * are the corresponding payloads. */ interface ILoggerEvents { onLogLevelChange: { level: ILoggerLevel; format: ILogFormat; }; } /** * Logger implementation. * @class Logger */ export default class Logger extends EventEmitter<ILoggerEvents> { public error: IConsoleFn; public warn: IConsoleFn; public info: IConsoleFn; public debug: IConsoleFn; private _currentLevel: ILoggerLevel; private _currentFormat: ILogFormat; private readonly _levels: Record<ILoggerLevel, number>; constructor() { super(); this.error = noop; this.warn = noop; this.info = noop; this.debug = noop; this._levels = { NONE: 0, ERROR: 1, WARNING: 2, INFO: 3, DEBUG: 4 }; this._currentFormat = "standard"; this._currentLevel = DEFAULT_LOG_LEVEL; } /** * Update the logger's level to increase or decrease its verbosity, to change * its format with a newly set one, or to update its logging function. * @param {string} levelStr - One of the [upper-case] logger level. If the * given level is not valid, it will default to `"NONE"`. * @param {function|undefined} [logFn] - Optional logger function which will * be called with logs (with the corresponding upper-case logger level as * first argument). * Can be omited to just rely on regular logging functions. */ public setLevel( levelStr: string, format: string, logFn?: ( levelStr: ILoggerLevel, namespace: string, logs: IAcceptedLogValue[], ) => void, ): void { let level: number; const foundLevel = this._levels[levelStr as ILoggerLevel]; if (typeof foundLevel === "number") { level = foundLevel; this._currentLevel = levelStr as ILoggerLevel; } else { // not found level = 0; this._currentLevel = "NONE"; } let actualFormat: ILogFormat; if (format === "standard" || format === "full") { actualFormat = format; } else { actualFormat = "standard"; } if (actualFormat === "full" && actualFormat !== this._currentFormat) { // Add the current Date so we can see at which time logs are displayed const now = getMonotonicTimeStamp(); // eslint-disable-next-line no-console console.log(String(now.toFixed(2)), "[Init]", `Local-Date: ${Date.now()}`); } this._currentFormat = actualFormat; const generateLogFn = this._currentFormat === "full" ? (logMethod: string, consoleFn: (...vals: unknown[]) => void): IConsoleFn => { return (namespace: ILogNamespace, ...args: IAcceptedLogValue[]) => { const now = getMonotonicTimeStamp(); return consoleFn( String(now.toFixed(2)), `[${logMethod}]`, namespace + ":", ...args.map((a) => typeof a === "object" && a !== null && !(a instanceof Error) ? formatContextObject(a) : a, ), ); }; } : (_logMethod: string, consoleFn: (...vals: unknown[]) => void): IConsoleFn => { return (namespace: ILogNamespace, ...args: IAcceptedLogValue[]) => { return consoleFn( namespace + ":", ...args.map((a) => typeof a === "object" && a !== null && !(a instanceof Error) ? formatContextObject(a) : a, ), ); }; }; if (logFn === undefined) { /* eslint-disable no-console */ this.error = level >= this._levels.ERROR ? generateLogFn("error", console.error.bind(console)) : noop; this.warn = level >= this._levels.WARNING ? generateLogFn("warn", console.warn.bind(console)) : noop; this.info = level >= this._levels.INFO ? generateLogFn("info", console.info.bind(console)) : noop; this.debug = level >= this._levels.DEBUG ? generateLogFn("log", console.log.bind(console)) : noop; /* eslint-enable no-console */ } else { const produceLogFn = (logLevel: ILoggerLevel): IConsoleFn => { return level >= this._levels[logLevel] ? (namespace: ILogNamespace, ...args: IAcceptedLogValue[]) => { return logFn(logLevel, namespace, args); } : noop; }; this.error = produceLogFn("ERROR"); this.warn = produceLogFn("WARNING"); this.info = produceLogFn("INFO"); this.debug = produceLogFn("DEBUG"); } this.trigger("onLogLevelChange", { level: this._currentLevel, format: this._currentFormat, }); } /** * Get the last set logger level, as an upper-case string value. * @returns {string} */ public getLevel(): ILoggerLevel { return this._currentLevel; } /** * Get the last set logger's log format. * @returns {string} */ public getFormat(): ILogFormat { return this._currentFormat; } /** * Returns `true` if the currently set level includes logs of the level given * in argument. * @param {string} logLevel * @returns {boolean} */ public hasLevel(logLevel: ILoggerLevel): boolean { return this._levels[logLevel] >= this._levels[this._currentLevel]; } } function formatContextObject(obj: Partial<Record<string, IAcceptedLogValue>>) { let ret = ""; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (ret.length > 0) { ret += " "; } const val = obj[key]; if (val instanceof Error) { ret += `${key}="${JSON.stringify(val?.toString())}"`; } else { ret += `${key}=${typeof val === "string" ? `${JSON.stringify(val)}` : String(val)}`; } } } return ret; }