@livekit/track-processors 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,27 +7,30 @@ export interface VideoTransformerInitOptions extends TrackTransformerInitOptions
7
7
  }
8
8
  export interface AudioTransformerInitOptions extends TrackTransformerInitOptions {
9
9
  }
10
- export interface VideoTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<VideoTransformerInitOptions, VideoFrame> {
10
+ export interface VideoTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<VideoTransformerInitOptions, VideoFrame, TrackTransformerDestroyOptions> {
11
11
  init: (options: VideoTransformerInitOptions) => void;
12
- destroy: () => void;
12
+ destroy: (options?: TrackTransformerDestroyOptions) => void;
13
13
  restart: (options: VideoTransformerInitOptions) => void;
14
14
  transform: (frame: VideoFrame, controller: TransformStreamDefaultController) => void;
15
15
  transformer?: TransformStream;
16
16
  update: (options: Options) => void;
17
17
  }
18
- export interface AudioTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<AudioTransformerInitOptions, AudioData> {
18
+ export interface AudioTrackTransformer<Options extends Record<string, unknown>> extends BaseTrackTransformer<AudioTransformerInitOptions, AudioData, TrackTransformerDestroyOptions> {
19
19
  init: (options: AudioTransformerInitOptions) => void;
20
- destroy: () => void;
20
+ destroy: (options: TrackTransformerDestroyOptions) => void;
21
21
  restart: (options: AudioTransformerInitOptions) => void;
22
22
  transform: (frame: AudioData, controller: TransformStreamDefaultController) => void;
23
23
  transformer?: TransformStream;
24
24
  update: (options: Options) => void;
25
25
  }
26
+ export type TrackTransformerDestroyOptions = {
27
+ willProcessorRestart: boolean;
28
+ };
26
29
  export type TrackTransformer<Options extends Record<string, unknown>> = VideoTrackTransformer<Options> | AudioTrackTransformer<Options>;
27
- export interface BaseTrackTransformer<T extends TrackTransformerInitOptions, DataType extends VideoFrame | AudioData> {
28
- init: (options: T) => void;
29
- destroy: () => void;
30
- restart: (options: T) => void;
30
+ export interface BaseTrackTransformer<InitOpts extends TrackTransformerInitOptions, DataType extends VideoFrame | AudioData, DestroyOpts extends TrackTransformerDestroyOptions = TrackTransformerDestroyOptions> {
31
+ init: (options: InitOpts) => void;
32
+ destroy: (options: DestroyOpts) => void;
33
+ restart: (options: InitOpts) => void;
31
34
  transform: (frame: DataType, controller: TransformStreamDefaultController) => void;
32
35
  transformer?: TransformStream;
33
36
  }
@@ -3,5 +3,6 @@ export declare const setupWebGL: (canvas: OffscreenCanvas | HTMLCanvasElement) =
3
3
  updateMask: (mask: WebGLTexture) => void;
4
4
  setBackgroundImage: (image: ImageBitmap | null) => Promise<void>;
5
5
  setBlurRadius: (radius: number | null) => void;
6
+ setBackgroundDisabled: (disabled: boolean) => void;
6
7
  cleanup: () => void;
7
8
  } | undefined;
@@ -13,6 +13,7 @@ export declare function createCompositeProgram(gl: WebGL2RenderingContext): {
13
13
  mask: WebGLUniformLocation;
14
14
  frame: WebGLUniformLocation;
15
15
  background: WebGLUniformLocation;
16
+ disableBackground: WebGLUniformLocation;
16
17
  stepWidth: WebGLUniformLocation;
17
18
  };
18
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/track-processors",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "LiveKit track processors",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -20,8 +20,8 @@
20
20
  "@mediapipe/tasks-vision": "0.10.14"
21
21
  },
22
22
  "peerDependencies": {
23
- "livekit-client": "^1.12.0 || ^2.1.0",
24
- "@types/dom-mediacapture-transform": "^0.1.9"
23
+ "@types/dom-mediacapture-transform": "^0.1.9",
24
+ "livekit-client": "^1.12.0 || ^2.1.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@changesets/cli": "^2.26.2",
@@ -1,6 +1,9 @@
1
- import type { ProcessorOptions, Track, TrackProcessor } from 'livekit-client';
2
- import { TrackTransformer } from './transformers';
1
+ import { type ProcessorOptions, type Track, type TrackProcessor } from 'livekit-client';
2
+ import { TrackTransformer, TrackTransformerDestroyOptions } from './transformers';
3
3
  import { createCanvas, waitForTrackResolution } from './utils';
4
+ import { LoggerNames, getLogger } from './logger';
5
+
6
+ type ProcessorWrapperLifecycleState = 'idle' | 'initializing' | 'running' | 'media-exhausted' | 'destroying' | 'destroyed';
4
7
 
5
8
  export interface ProcessorWrapperOptions {
6
9
  /**
@@ -10,7 +13,10 @@ export interface ProcessorWrapperOptions {
10
13
  maxFps?: number;
11
14
  }
12
15
 
13
- export default class ProcessorWrapper<TransformerOptions extends Record<string, unknown>>
16
+ export default class ProcessorWrapper<
17
+ TransformerOptions extends Record<string, unknown>,
18
+ Transformer extends TrackTransformer<TransformerOptions> = TrackTransformer<TransformerOptions>,
19
+ >
14
20
  implements TrackProcessor<Track.Kind>
15
21
  {
16
22
  /**
@@ -58,7 +64,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
58
64
 
59
65
  processedTrack?: MediaStreamTrack;
60
66
 
61
- transformer: TrackTransformer<TransformerOptions>;
67
+ transformer: Transformer;
62
68
 
63
69
  // For tracking whether we're using the stream API fallback
64
70
  private useStreamFallback = false;
@@ -77,10 +83,12 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
77
83
  // FPS control for fallback implementation
78
84
  private maxFps: number;
79
85
 
80
- private symbol?: Symbol;
86
+ private log = getLogger(LoggerNames.ProcessorWrapper);
87
+
88
+ private lifecycleState: ProcessorWrapperLifecycleState = 'idle';
81
89
 
82
90
  constructor(
83
- transformer: TrackTransformer<TransformerOptions>,
91
+ transformer: Transformer,
84
92
  name: string,
85
93
  options: ProcessorWrapperOptions = {},
86
94
  ) {
@@ -140,6 +148,8 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
140
148
  }
141
149
 
142
150
  async init(opts: ProcessorOptions<Track.Kind>): Promise<void> {
151
+ this.log.debug('Init called');
152
+ this.lifecycleState = 'initializing';
143
153
  await this.setup(opts);
144
154
 
145
155
  if (!this.canvas) {
@@ -156,6 +166,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
156
166
  } else {
157
167
  this.initStreamProcessorPath();
158
168
  }
169
+ this.lifecycleState = 'running';
159
170
  }
160
171
 
161
172
  private initStreamProcessorPath() {
@@ -168,20 +179,20 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
168
179
  const readableStream = this.processor.readable;
169
180
  const pipedStream = readableStream.pipeThrough(this.transformer!.transformer!);
170
181
 
171
- const symbol = Symbol('stream');
172
- this.symbol = symbol;
173
-
174
182
  pipedStream
175
183
  .pipeTo(this.trackGenerator.writable)
176
- // destroy processor if stream finishes
177
- .then(() => this.destroy(symbol))
184
+ // if stream finishes, the media to process is exhausted
185
+ .then(() => this.handleMediaExhausted())
178
186
  // destroy processor if stream errors - unless it's an abort error
179
187
  .catch((e) => {
180
188
  if (e instanceof DOMException && e.name === 'AbortError') {
181
- console.log('stream processor path aborted');
189
+ this.log.log('stream processor path aborted');
190
+ } else if (e instanceof DOMException && e.name === 'InvalidStateError' && e.message === 'Stream closed') {
191
+ this.log.log('stream processor underlying stream closed');
192
+ this.handleMediaExhausted();
182
193
  } else {
183
- console.error('error when trying to pipe', e);
184
- this.destroy(symbol);
194
+ this.log.error('error when trying to pipe', e);
195
+ this.destroy();
185
196
  }
186
197
  });
187
198
 
@@ -224,7 +235,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
224
235
  // @ts-ignore - The controller expects both VideoFrame & AudioData but we're only using VideoFrame
225
236
  this.transformer.transform(frame, controller);
226
237
  } catch (e) {
227
- console.error('Error in transform:', e);
238
+ this.log.error('Error in transform:', e);
228
239
  frame.close();
229
240
  }
230
241
  };
@@ -235,6 +246,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
235
246
 
236
247
  private startRenderLoop() {
237
248
  if (!this.sourceDummy || !(this.sourceDummy instanceof HTMLVideoElement)) {
249
+ this.handleMediaExhausted();
238
250
  return;
239
251
  }
240
252
 
@@ -257,11 +269,12 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
257
269
  !this.sourceDummy ||
258
270
  !(this.sourceDummy instanceof HTMLVideoElement)
259
271
  ) {
272
+ this.handleMediaExhausted();
260
273
  return;
261
274
  }
262
275
 
263
276
  if (this.sourceDummy.paused) {
264
- console.warn('Video is paused, trying to play');
277
+ this.log.warn('Video is paused, trying to play');
265
278
  this.sourceDummy.play();
266
279
  return;
267
280
  }
@@ -298,7 +311,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
298
311
  window.location.hostname === '127.0.0.1';
299
312
 
300
313
  if (isDevelopment && now - lastFpsLog > 5000) {
301
- console.debug(
314
+ this.log.debug(
302
315
  `[${this.name}] Estimated video FPS: ${estimatedVideoFps.toFixed(
303
316
  1,
304
317
  )}, Processing at: ${(frameCount / 5).toFixed(1)} FPS`,
@@ -333,7 +346,7 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
333
346
  }
334
347
  }
335
348
  } catch (e) {
336
- console.error('Error in render loop:', e);
349
+ this.log.error('Error in render loop:', e);
337
350
  }
338
351
  }
339
352
  this.animationFrameId = requestAnimationFrame(renderLoop);
@@ -343,7 +356,8 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
343
356
  }
344
357
 
345
358
  async restart(opts: ProcessorOptions<Track.Kind>): Promise<void> {
346
- await this.destroy();
359
+ this.log.debug('Restart called');
360
+ await this.destroy({ willProcessorRestart: true });
347
361
  await this.init(opts);
348
362
  }
349
363
 
@@ -356,11 +370,18 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
356
370
  await this.transformer.update(options[0]);
357
371
  }
358
372
 
359
- async destroy(symbol?: Symbol) {
360
- if (symbol && this.symbol !== symbol) {
361
- // If the symbol is provided, we only destroy if it matches the current symbol
373
+ /** Called if the media pipeline no longer can read frames to process from the source media */
374
+ private async handleMediaExhausted() {
375
+ this.log.debug('Media was exhausted from source');
376
+ if (this.lifecycleState !== 'running') {
362
377
  return;
363
378
  }
379
+ this.lifecycleState = 'media-exhausted'
380
+ await this.cleanup();
381
+ }
382
+
383
+ /** Tears down the media stack logic initialized in initStreamProcessorPath / initFallbackPath */
384
+ private async cleanup() {
364
385
  if (this.useStreamFallback) {
365
386
  this.processingEnabled = false;
366
387
  if (this.animationFrameId) {
@@ -372,9 +393,28 @@ export default class ProcessorWrapper<TransformerOptions extends Record<string,
372
393
  }
373
394
  this.capturedStream?.getTracks().forEach((track) => track.stop());
374
395
  } else {
396
+ // NOTE: closing writableControl below terminates the stream in initStreamProcessorPath /
397
+ // calls the .then(...) which calls this.handleMediaExhausted
375
398
  await this.processor?.writableControl?.close();
376
399
  this.trackGenerator?.stop();
377
400
  }
378
- await this.transformer.destroy();
401
+ }
402
+
403
+ async destroy(transformerDestroyOptions: TrackTransformerDestroyOptions = { willProcessorRestart: false }) {
404
+ this.log.debug(`Destroy called - lifecycleState=${this.lifecycleState}, transformerDestroyOptions=${JSON.stringify(transformerDestroyOptions)}`);
405
+ switch (this.lifecycleState) {
406
+ case 'running':
407
+ case 'media-exhausted':
408
+ this.lifecycleState = 'destroying';
409
+
410
+ await this.cleanup();
411
+
412
+ await this.transformer.destroy(transformerDestroyOptions);
413
+ this.lifecycleState = 'destroyed';
414
+ break;
415
+
416
+ default:
417
+ break;
418
+ }
379
419
  }
380
420
  }
package/src/index.ts CHANGED
@@ -14,6 +14,9 @@ export {
14
14
  BackgroundTransformer,
15
15
  type ProcessorWrapperOptions,
16
16
  };
17
+ export * from './logger';
18
+
19
+ const DEFAULT_BLUR_RADIUS = 10;
17
20
 
18
21
  /**
19
22
  * Determines if the current browser supports background processors
@@ -27,16 +30,212 @@ export const supportsBackgroundProcessors = () =>
27
30
  export const supportsModernBackgroundProcessors = () =>
28
31
  BackgroundTransformer.isSupported && ProcessorWrapper.hasModernApiSupport;
29
32
 
30
- export interface BackgroundProcessorOptions extends ProcessorWrapperOptions {
33
+ type SwitchBackgroundProcessorBackgroundBlurOptions = {
34
+ mode: 'background-blur';
35
+ /** If unspecified, defaults to {@link DEFAULT_BLUR_RADIUS} */
31
36
  blurRadius?: number;
32
- imagePath?: string;
37
+ };
38
+
39
+ type SwitchBackgroundProcessorVirtualBackgroundOptions = {
40
+ mode: 'virtual-background';
41
+ imagePath: string;
42
+ };
43
+
44
+ type SwitchBackgroundProcessorDisabledOptions = {
45
+ mode: 'disabled';
46
+ };
47
+
48
+ export type SwitchBackgroundProcessorOptions =
49
+ | SwitchBackgroundProcessorDisabledOptions
50
+ | SwitchBackgroundProcessorBackgroundBlurOptions
51
+ | SwitchBackgroundProcessorVirtualBackgroundOptions
52
+
53
+ type BackgroundProcessorCommonOptions = ProcessorWrapperOptions & {
33
54
  segmenterOptions?: SegmenterOptions;
34
55
  assetPaths?: { tasksVisionFileSet?: string; modelAssetPath?: string };
35
56
  onFrameProcessed?: (stats: FrameProcessingStats) => void;
57
+ };
58
+
59
+ type BackgroundProcessorModeOptions = BackgroundProcessorCommonOptions & SwitchBackgroundProcessorOptions;
60
+ type BackgroundProcessorLegacyOptions = BackgroundProcessorCommonOptions & {
61
+ mode?: never;
62
+ blurRadius?: number;
63
+ imagePath?: string;
64
+ };
65
+
66
+ export type BackgroundProcessorOptions =
67
+ | BackgroundProcessorModeOptions
68
+ | BackgroundProcessorLegacyOptions;
69
+
70
+ class BackgroundProcessorWrapper extends ProcessorWrapper<BackgroundOptions, BackgroundTransformer> {
71
+ get mode(): BackgroundProcessorModeOptions['mode'] | 'legacy' {
72
+ const options = this.transformer.options;
73
+
74
+ if (options.backgroundDisabled) {
75
+ return 'disabled';
76
+ }
77
+
78
+ if (typeof options.imagePath === 'string' && typeof options.blurRadius === 'undefined') {
79
+ return 'virtual-background';
80
+ }
81
+
82
+ if (typeof options.imagePath === 'undefined') {
83
+ return 'background-blur';
84
+ }
85
+
86
+ return 'legacy';
87
+ }
88
+
89
+ async switchTo(options: SwitchBackgroundProcessorOptions) {
90
+ switch (options.mode) {
91
+ case 'background-blur':
92
+ await this.updateTransformerOptions({
93
+ imagePath: undefined,
94
+ blurRadius: options.blurRadius ?? DEFAULT_BLUR_RADIUS,
95
+ backgroundDisabled: false,
96
+ });
97
+ break;
98
+ case 'virtual-background':
99
+ await this.updateTransformerOptions({
100
+ imagePath: options.imagePath,
101
+ blurRadius: undefined,
102
+ backgroundDisabled: false,
103
+ });
104
+ break;
105
+ case 'disabled':
106
+ await this.updateTransformerOptions({ imagePath: undefined, backgroundDisabled: true });
107
+ break;
108
+ }
109
+ }
36
110
  }
37
111
 
112
+ /**
113
+ * Instantiates a background processor that supports blurring the background of a user's local
114
+ * video or replacing the user's background with a virtual background image, and supports switching
115
+ * the active mode later on the fly to avoid visual artifacts.
116
+ *
117
+ * @example
118
+ * const camTrack = currentRoom.localParticipant.getTrackPublication(Track.Source.Camera)!.track as LocalVideoTrack;
119
+ * const processor = BackgroundProcessor({ mode: 'background-blur', blurRadius: 10 });
120
+ * camTrack.setProcessor(processor);
121
+ *
122
+ * // Change to background image:
123
+ * processor.switchToVirtualBackground('path/to/image.png');
124
+ * // Change back to background blur:
125
+ * processor.switchToBackgroundBlur(10);
126
+ */
127
+ export const BackgroundProcessor = (
128
+ options: BackgroundProcessorOptions,
129
+ name = 'background-processor',
130
+ ) => {
131
+ const isTransformerSupported = BackgroundTransformer.isSupported;
132
+ const isProcessorSupported = ProcessorWrapper.isSupported;
133
+
134
+ if (!isTransformerSupported) {
135
+ throw new Error('Background transformer is not supported in this browser');
136
+ }
137
+
138
+ if (!isProcessorSupported) {
139
+ throw new Error(
140
+ 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',
141
+ );
142
+ }
143
+
144
+ // Extract transformer-specific options and processor options
145
+ let transformer, processorOpts;
146
+ switch (options.mode) {
147
+ case 'background-blur': {
148
+ const {
149
+ // eslint-disable-next-line no-unused-vars
150
+ mode,
151
+ blurRadius = DEFAULT_BLUR_RADIUS,
152
+ segmenterOptions,
153
+ assetPaths,
154
+ onFrameProcessed,
155
+ ...rest
156
+ } = options;
157
+
158
+ processorOpts = rest;
159
+ transformer = new BackgroundTransformer({
160
+ blurRadius,
161
+ segmenterOptions,
162
+ assetPaths,
163
+ onFrameProcessed,
164
+ });
165
+ break;
166
+ }
167
+
168
+ case 'virtual-background': {
169
+ const {
170
+ // eslint-disable-next-line no-unused-vars
171
+ mode,
172
+ imagePath,
173
+ segmenterOptions,
174
+ assetPaths,
175
+ onFrameProcessed,
176
+ ...rest
177
+ } = options;
178
+
179
+ processorOpts = rest;
180
+ transformer = new BackgroundTransformer({
181
+ imagePath,
182
+ segmenterOptions,
183
+ assetPaths,
184
+ onFrameProcessed,
185
+ });
186
+ break;
187
+ }
188
+
189
+ case 'disabled': {
190
+ const {
191
+ segmenterOptions,
192
+ assetPaths,
193
+ onFrameProcessed,
194
+ ...rest
195
+ } = options;
196
+
197
+ processorOpts = rest;
198
+ transformer = new BackgroundTransformer({
199
+ segmenterOptions,
200
+ assetPaths,
201
+ onFrameProcessed,
202
+ });
203
+ break;
204
+ }
205
+
206
+ default: {
207
+ const {
208
+ blurRadius,
209
+ imagePath,
210
+ segmenterOptions,
211
+ assetPaths,
212
+ onFrameProcessed,
213
+ ...rest
214
+ } = options;
215
+
216
+ processorOpts = rest;
217
+ transformer = new BackgroundTransformer({
218
+ blurRadius,
219
+ imagePath,
220
+ segmenterOptions,
221
+ assetPaths,
222
+ onFrameProcessed,
223
+ });
224
+ break;
225
+ }
226
+ }
227
+
228
+ const processor = new BackgroundProcessorWrapper(transformer, name, processorOpts);
229
+
230
+ return processor;
231
+ };
232
+
233
+ /**
234
+ * Instantiates a background processor that is configured in blur mode.
235
+ * @deprecated Use `BackgroundProcessor({ mode: 'background-blur', blurRadius: 10, ... })` instead.
236
+ */
38
237
  export const BackgroundBlur = (
39
- blurRadius: number = 10,
238
+ blurRadius: number = DEFAULT_BLUR_RADIUS,
40
239
  segmenterOptions?: SegmenterOptions,
41
240
  onFrameProcessed?: (stats: FrameProcessingStats) => void,
42
241
  processorOptions?: ProcessorWrapperOptions,
@@ -52,6 +251,10 @@ export const BackgroundBlur = (
52
251
  );
53
252
  };
54
253
 
254
+ /**
255
+ * Instantiates a background processor that is configured in virtual background mode.
256
+ * @deprecated Use `BackgroundProcessor({ mode: 'virtual-background', imagePath: '...', ... })` instead.
257
+ */
55
258
  export const VirtualBackground = (
56
259
  imagePath: string,
57
260
  segmenterOptions?: SegmenterOptions,
@@ -69,42 +272,3 @@ export const VirtualBackground = (
69
272
  );
70
273
  };
71
274
 
72
- export const BackgroundProcessor = (
73
- options: BackgroundProcessorOptions,
74
- name = 'background-processor',
75
- ) => {
76
- const isTransformerSupported = BackgroundTransformer.isSupported;
77
- const isProcessorSupported = ProcessorWrapper.isSupported;
78
-
79
- if (!isTransformerSupported) {
80
- throw new Error('Background transformer is not supported in this browser');
81
- }
82
-
83
- if (!isProcessorSupported) {
84
- throw new Error(
85
- 'Neither MediaStreamTrackProcessor nor canvas.captureStream() fallback is supported in this browser',
86
- );
87
- }
88
-
89
- // Extract transformer-specific options and processor options
90
- const {
91
- blurRadius,
92
- imagePath,
93
- segmenterOptions,
94
- assetPaths,
95
- onFrameProcessed,
96
- ...processorOpts
97
- } = options;
98
-
99
- const transformer = new BackgroundTransformer({
100
- blurRadius,
101
- imagePath,
102
- segmenterOptions,
103
- assetPaths,
104
- onFrameProcessed,
105
- });
106
-
107
- const processor = new ProcessorWrapper(transformer, name, processorOpts);
108
-
109
- return processor;
110
- };
package/src/logger.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { getLogger as clientGetLogger } from 'livekit-client';
2
+
3
+ export enum LogLevel {
4
+ trace = 0,
5
+ debug = 1,
6
+ info = 2,
7
+ warn = 3,
8
+ error = 4,
9
+ silent = 5,
10
+ }
11
+
12
+ export enum LoggerNames {
13
+ ProcessorWrapper = 'livekit-processor-wrapper',
14
+ BackgroundProcessor = 'livekit-background-processor',
15
+ WebGl = 'livekit-track-processor-web-gl',
16
+ }
17
+
18
+ type LogLevelString = keyof typeof LogLevel;
19
+
20
+ export type StructuredLogger = ReturnType<typeof clientGetLogger>;
21
+
22
+ let livekitLogger = getLogger('livekit');
23
+ const livekitLoggers = Object.values(LoggerNames).map((name) => getLogger(name));
24
+
25
+ livekitLogger.setDefaultLevel(LogLevel.info);
26
+
27
+ export default livekitLogger as StructuredLogger;
28
+
29
+ /**
30
+ * @internal
31
+ */
32
+ export function getLogger(name: string) {
33
+ return clientGetLogger(name);
34
+ }
35
+
36
+ export function setLogLevel(level: LogLevel | LogLevelString, loggerName?: LoggerNames) {
37
+ if (loggerName) {
38
+ getLogger(loggerName).setLevel(level);
39
+ } else {
40
+ for (const logger of livekitLoggers) {
41
+ logger.setLevel(level);
42
+ }
43
+ }
44
+ }
45
+
46
+ export type LogExtension = (level: LogLevel, msg: string, context?: object) => void;
47
+
48
+ /**
49
+ * use this to hook into the logging function to allow sending internal livekit logs to third party services
50
+ * if set, the browser logs will lose their stacktrace information (see https://github.com/pimterry/loglevel#writing-plugins)
51
+ */
52
+ export function setLogExtension(extension: LogExtension, logger?: StructuredLogger) {
53
+ const loggers = logger ? [logger] : livekitLoggers;
54
+
55
+ loggers.forEach((logR) => {
56
+ const originalFactory = logR.methodFactory;
57
+
58
+ logR.methodFactory = (methodName, configLevel, loggerName) => {
59
+ const rawMethod = originalFactory(methodName, configLevel, loggerName);
60
+
61
+ const logLevel = LogLevel[methodName as LogLevelString];
62
+ const needLog = logLevel >= configLevel && logLevel < LogLevel.silent;
63
+
64
+ return (msg, context?: [msg: string, context: object]) => {
65
+ if (context) rawMethod(msg, context);
66
+ else rawMethod(msg);
67
+ if (needLog) {
68
+ extension(logLevel, msg, context);
69
+ }
70
+ };
71
+ };
72
+ logR.setLevel(logR.getLevel());
73
+ });
74
+ }