@livedigital/client 3.32.0-createCustomMediaTracks.1 → 3.32.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.
@@ -119,11 +119,6 @@ export declare type CreateScreenMediaOptions = BaseVideoTrackOptions & BaseAudio
119
119
  videoEncoderConfig?: VideoEncoderConfig;
120
120
  audioEncoderConfig?: AudioEncoderConfig;
121
121
  };
122
- export declare type CreateCustomMediaOptions = BaseVideoTrackOptions & BaseAudioTrackOptions & {
123
- videoEncoderConfig?: VideoEncoderConfig;
124
- audioEncoderConfig?: AudioEncoderConfig;
125
- mediaStream: MediaStream;
126
- };
127
122
  export declare enum TrackLabel {
128
123
  Camera = "camera",
129
124
  CameraPreview = "camera-preview",
@@ -68,6 +68,7 @@ export interface TrackWithEffects extends BaseTrack {
68
68
  }
69
69
  export interface AudioTrack extends BaseTrack, TrackWithEncodings, TrackWithEffects {
70
70
  prepareTrackProcessor(trackProcessor?: AudioTrackProcessor): void;
71
+ unsetTrackProcessor(): void;
71
72
  enableNoiseSuppression(): Promise<void>;
72
73
  disableNoiseSuppression(): Promise<void>;
73
74
  pause(): Promise<void>;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@livedigital/client",
3
3
  "author": "vlprojects",
4
4
  "license": "MIT",
5
- "version": "3.32.0-createCustomMediaTracks.1",
5
+ "version": "3.32.0",
6
6
  "private": false,
7
7
  "bugs": {
8
8
  "url": "https://github.com/vlprojects/livedigital-sdk/issues"
@@ -85,6 +85,7 @@
85
85
  "@types/sinon-chai": "^3.2.9",
86
86
  "@typescript-eslint/eslint-plugin": "^4.32.0",
87
87
  "@typescript-eslint/parser": "^4.32.0",
88
+ "audio-effects-sdk": "^2.4.2",
88
89
  "axios-mock-adapter": "^1.21.2",
89
90
  "chai": "^4.3.7",
90
91
  "chai-as-promised": "^7.1.1",
@@ -54,4 +54,8 @@ export default class ConfigService {
54
54
  get<K extends keyof ProcessedParams>(key: K): ProcessedParams[K] {
55
55
  return this.#params[key] as ProcessedParams[K];
56
56
  }
57
+
58
+ set<K extends keyof ProcessedParams, V extends ProcessedParams[K]>(key: K, value: V) {
59
+ this.#params[key] = value;
60
+ }
57
61
  }
@@ -144,7 +144,7 @@ export class IntegrationsService {
144
144
  const context = this.#integrationsStorage.get('rnnoise');
145
145
 
146
146
  if (!context) {
147
- throw Error('IntegrationContext is missing for ASDK');
147
+ throw Error('IntegrationContext is missing for RNNoise');
148
148
  }
149
149
 
150
150
  context.integrated = true;
@@ -17,7 +17,6 @@ import { retryAsync } from '../helpers/retry';
17
17
  import { TOKEN } from '../inversify.tokens';
18
18
  import {
19
19
  CreateCameraVideoTrackOptions,
20
- CreateCustomMediaOptions,
21
20
  CreateMicrophoneAudioTrackOptions,
22
21
  CreateScreenMediaOptions,
23
22
  JoinChannelParams,
@@ -402,65 +401,6 @@ class Engine {
402
401
  }
403
402
  }
404
403
 
405
- async createCustomMediaTracks(options?: CreateCustomMediaOptions): Promise<Track[]> {
406
- const videoTrackParams = Media.getScreenVideoTrackParams(options);
407
-
408
- if (!options?.mediaStream) {
409
- throw new Error('MediaStream is required');
410
- }
411
-
412
- try {
413
- const tracks = await this.media.createCustomMediaTracks(options.mediaStream, {
414
- video: options?.video,
415
- audio: options?.audio,
416
- });
417
-
418
- tracks.forEach((track) => {
419
- track.mediaStreamTrack.addEventListener('ended', () => {
420
- track.unpublish().catch((error: Error) => {
421
- this.#logger.error('Failed to unpublish media track', {
422
- track,
423
- error: serializeError(error),
424
- });
425
- });
426
- }, { once: true });
427
-
428
- track.setStopTrackOnPause(Boolean(options?.stopTrackOnPause));
429
- if (track instanceof VideoTrack) {
430
- track.mediaStreamTrack.contentHint = 'text';
431
- track.setLabel(TrackLabel.ScreenVideo);
432
- track.setEncoderConfig(videoTrackParams.encoderConfig);
433
- const transformParams = Media.getVideoTrackTransformParams(track.mediaStreamTrack);
434
- track.setTransformParams(transformParams);
435
- this.media.setTrack(track);
436
- this.#logger.debug('createScreenMediaTrack()', {
437
- trackParams: videoTrackParams,
438
- transformParams,
439
- label: track.logCtx,
440
- });
441
-
442
- return;
443
- }
444
-
445
- track.setLabel(TrackLabel.ScreenAudio);
446
- if (options?.audioEncoderConfig) {
447
- track.setEncoderConfig(options.audioEncoderConfig);
448
- }
449
-
450
- this.media.setTrack(track);
451
- this.#logger.debug('createCustomMediaTrack()', {
452
- trackParams: options?.audio,
453
- encoderConfig: options?.audioEncoderConfig,
454
- });
455
- });
456
-
457
- return tracks as Track[];
458
- } catch (error) {
459
- this.#logger.error('createCustomMediaTrack()', { error, trackParams: videoTrackParams });
460
- throw new Error(`Can not create custom media tracks: ${error.message}`);
461
- }
462
- }
463
-
464
404
  deleteTrack(tracks: BaseTrack): Promise<void> {
465
405
  return this.media.deleteTrack(tracks);
466
406
  }
@@ -29,6 +29,7 @@ import {
29
29
  } from '../../types/media';
30
30
  import { IntegrationsService } from '../IntegrationsService';
31
31
  import Logger from '../Logger';
32
+ import { ASDKTrackProcessor } from './streamEffects/audio/asdk/ASDKTrackProcessor';
32
33
  import { ProcessorsCache } from './streamEffects/ProcessorsCache';
33
34
  import EffectsSDKTrackProcessor from './streamEffects/video/esdk/TrackProcessor';
34
35
  import DefaultAudioTrack from './tracks/DefaultAudioTrack';
@@ -53,6 +54,8 @@ class Media {
53
54
 
54
55
  #denoiserInitializing = false;
55
56
 
57
+ #denoiserFallbacked = false;
58
+
56
59
  #denoiser: ProcessedParams['denoiser'];
57
60
 
58
61
  constructor(
@@ -149,11 +152,6 @@ class Media {
149
152
  return this.createTracks({ constraints, mediaStreamTracks });
150
153
  }
151
154
 
152
- async createCustomMediaTracks(mediaStream: MediaStream, constraints: MediaStreamConstraints): Promise<BaseTrack[]> {
153
- const mediaStreamTracks = mediaStream.getTracks();
154
- return this.createTracks({ constraints, mediaStreamTracks });
155
- }
156
-
157
155
  async deleteTrack(track: BaseTrack) {
158
156
  const logContext = {
159
157
  trackId: track.id,
@@ -288,6 +286,24 @@ class Media {
288
286
  return;
289
287
  }
290
288
 
289
+ if (this.#denoiser === 'asdk') {
290
+ (trackProcessor as ASDKTrackProcessor).setOnError(() => {
291
+ this.clientEventEmitter.emit(CLIENT_EVENTS.denoiserInitializing, true);
292
+ this.fallbackToRNoise(track, options)
293
+ .catch((error) => {
294
+ this.#logger.error('Failed to fallback to RNNoise through onError', {
295
+ case: 'Denoiser',
296
+ type: this.#denoiser,
297
+ error: serializeError(error),
298
+ label: track.getLabel(),
299
+ });
300
+ })
301
+ .then(() => {
302
+ this.clientEventEmitter.emit(CLIENT_EVENTS.denoiserInitializing, false);
303
+ });
304
+ });
305
+ }
306
+
291
307
  audioTrack.prepareTrackProcessor(trackProcessor);
292
308
 
293
309
  this.#logger.debug('Denoiser trackProcessor set, check if activation is needed', {
@@ -307,12 +323,67 @@ class Media {
307
323
  error: serializeError(error),
308
324
  label: track.getLabel(),
309
325
  });
326
+
327
+ if (this.#denoiser === 'asdk') {
328
+ try {
329
+ this.#logger.warn('Fallback to RNNoise', {
330
+ error: serializeError(error),
331
+ });
332
+ await this.fallbackToRNoise(track, options);
333
+ } catch (error2) {
334
+ this.#logger.error('Failed to fallback to RNNoise through catch', {
335
+ case: 'Denoiser',
336
+ type: this.#denoiser,
337
+ error: serializeError(error2),
338
+ label: track.getLabel(),
339
+ });
340
+ }
341
+ }
310
342
  } finally {
311
343
  this.clientEventEmitter.emit(CLIENT_EVENTS.denoiserInitializing, false);
312
344
  this.#denoiserInitializing = false;
313
345
  }
314
346
  }
315
347
 
348
+ async fallbackToRNoise(track: AudioTrack, options?: CreateMicrophoneAudioTrackOptions) {
349
+ this.#logger.debug('Fallback to RNNoise denoiser', {
350
+ case: 'Denoiser',
351
+ });
352
+
353
+ const audioTrack = track as AudioTrack;
354
+
355
+ await audioTrack.disableNoiseSuppression();
356
+ audioTrack.unsetTrackProcessor();
357
+
358
+ this.#denoiser = 'rnnoise';
359
+ this.config.set('denoiser', this.#denoiser);
360
+ this.#logger.debug('Set config to rnnoise denoiser', {
361
+ case: 'Denoiser',
362
+ type: this.config.get('denoiser'),
363
+ });
364
+
365
+ this.integrationsService.init();
366
+ await this.integrationsService.audio.waitForInitialization;
367
+
368
+ const trackProcessor = this.processorsCache.get(this.#denoiser, TrackLabel.Microphone);
369
+
370
+ if (!trackProcessor) {
371
+ this.#logger.error('No cached trackProcessor found', {
372
+ case: 'Denoiser',
373
+ trackLabel: track.getLabel(),
374
+ type: this.integrationsService.audio.type,
375
+ });
376
+
377
+ return;
378
+ }
379
+
380
+ audioTrack.prepareTrackProcessor(trackProcessor);
381
+
382
+ if (options?.noiseSuppression) {
383
+ await audioTrack.enableNoiseSuppression();
384
+ }
385
+ }
386
+
316
387
  async createEffectsSDKTrackProcessor(trackLabel?: TrackLabel) {
317
388
  if (trackLabel) {
318
389
  const processor = this.#effectsProcessors.get(trackLabel);
@@ -1,9 +1,8 @@
1
1
  import { serializeError } from 'serialize-error';
2
+ import type { atsvb, Config } from 'audio-effects-sdk';
2
3
  import { LoggerFactory } from '../../../../../types/container';
3
4
  import Logger from '../../../../Logger';
4
5
  import { AudioTrackProcessor } from '../AudioTrackProcessor';
5
- import type { atsvb, Config } from './atsrb';
6
- import { ErrorObject } from './errorBus';
7
6
 
8
7
  declare global {
9
8
  interface Window {
@@ -11,10 +10,13 @@ declare global {
11
10
  }
12
11
  }
13
12
 
13
+ const noop = () => { };
14
+
14
15
  export type AudioTrackProcessorParams = {
15
16
  asdkCustomerId: string,
16
17
  loggerFactory: LoggerFactory,
17
18
  onModelsLoaded?: () => void,
19
+ onError?: () => void,
18
20
  };
19
21
 
20
22
  export class ASDKTrackProcessor implements AudioTrackProcessor {
@@ -32,21 +34,64 @@ export class ASDKTrackProcessor implements AudioTrackProcessor {
32
34
 
33
35
  readonly type = 'asdk';
34
36
 
37
+ #onError: () => void;
38
+
35
39
  constructor(params: AudioTrackProcessorParams) {
36
40
  const { loggerFactory } = params;
37
41
  this.#logger = loggerFactory('ASDKTrackProcessor');
42
+ this.#onError = typeof params.onError === 'function' ? params.onError : noop;
38
43
 
39
44
  // eslint-disable-next-line new-cap
40
45
  this.#atsvb = new window.atsvb(params.asdkCustomerId);
41
46
 
42
- const config: Config = {
47
+ this.#atsvb.config({
43
48
  preset: this.#preset,
44
- sampleRate: this.#sampleRate,
45
- };
46
-
47
- this.#atsvb.config(config);
49
+ sample_rate: this.#sampleRate,
50
+ sdk_url: 'https://effectssdk.ai/sdk/audio/',
51
+ // path to wasm files
52
+ wasmPaths: {
53
+ 'ort-wasm.wasm': 'https://effectssdk.ai/sdk/audio/dev/2.4.2/ort-wasm.wasm',
54
+ 'ort-wasm-simd.wasm': 'https://effectssdk.ai/sdk/audio/dev/2.4.2/ort-wasm-simd.wasm',
55
+ },
56
+ });
57
+
58
+ this.#atsvb.onError((error) => {
59
+ const {
60
+ message, type, cause, ...rest
61
+ } = error;
62
+
63
+ if (type === 'error') {
64
+ this.#logger.error(message, {
65
+ error: serializeError(cause),
66
+ cause,
67
+ ...rest,
68
+ type: this.type,
69
+ case: 'Denoiser',
70
+ });
71
+ this.#onError();
72
+ this.#onError = noop;
73
+ }
74
+
75
+ if (type === 'warning') {
76
+ this.#logger.warn(message, {
77
+ ...rest,
78
+ type: this.type,
79
+ case: 'Denoiser',
80
+ });
81
+ }
82
+
83
+ if (type === 'info') {
84
+ this.#logger.info(message, {
85
+ ...rest,
86
+ type: this.type,
87
+ case: 'Denoiser',
88
+ });
89
+ }
90
+ });
91
+ }
48
92
 
49
- this.#atsvb.onError(this.atsvbErrorHandler.bind(this));
93
+ setOnError(onError: () => void): void {
94
+ this.#onError = onError;
50
95
  }
51
96
 
52
97
  async init() {
@@ -102,13 +147,15 @@ export class ASDKTrackProcessor implements AudioTrackProcessor {
102
147
  track,
103
148
  });
104
149
 
150
+ this.#onError();
151
+ this.#onError = noop;
152
+
105
153
  throw new Error('Can not process this track');
106
154
  }
107
155
  }
108
156
 
109
157
  async stopProcessing() {
110
158
  try {
111
- this.#atsvb.clear();
112
159
  this.#atsvb.stop();
113
160
  } catch (error) {
114
161
  this.#logger.error('stopProcessing()', {
@@ -120,36 +167,4 @@ export class ASDKTrackProcessor implements AudioTrackProcessor {
120
167
  this.#originalTrack = null;
121
168
  }
122
169
  }
123
-
124
- private atsvbErrorHandler(error: ErrorObject) {
125
- const {
126
- message, type, cause, ...rest
127
- } = error;
128
-
129
- if (type === 'error') {
130
- this.#logger.error(message, {
131
- error: serializeError(cause),
132
- cause,
133
- ...rest,
134
- type: this.type,
135
- case: 'Denoiser',
136
- });
137
- }
138
-
139
- if (type === 'warning') {
140
- this.#logger.warn(message, {
141
- ...rest,
142
- type: this.type,
143
- case: 'Denoiser',
144
- });
145
- }
146
-
147
- if (type === 'info') {
148
- this.#logger.info(message, {
149
- ...rest,
150
- type: this.type,
151
- case: 'Denoiser',
152
- });
153
- }
154
- }
155
170
  }
@@ -37,6 +37,10 @@ class DefaultAudioTrack extends DefaultBaseTrack implements AudioTrack {
37
37
  this.#trackProcessor = trackProcessor;
38
38
  }
39
39
 
40
+ unsetTrackProcessor() {
41
+ this.#trackProcessor = undefined;
42
+ }
43
+
40
44
  get isEffectsProcessing(): boolean {
41
45
  return Boolean(this.#trackProcessor?.isProcessing);
42
46
  }
package/src/index.ts CHANGED
@@ -10,7 +10,6 @@ import { ClientParams } from './types/client';
10
10
  import {
11
11
  AvailableMediaDevices,
12
12
  CreateCameraVideoTrackOptions,
13
- CreateCustomMediaOptions,
14
13
  CreateMicrophoneAudioTrackOptions,
15
14
  CreateScreenMediaOptions,
16
15
  JoinChannelParams, Role,
@@ -122,10 +121,6 @@ class Client {
122
121
  return this.#engine.createScreenMediaTracks(options);
123
122
  }
124
123
 
125
- createCustomMediaTracks(options?: CreateCustomMediaOptions): Promise<Track[]> {
126
- return this.#engine.createCustomMediaTracks(options);
127
- }
128
-
129
124
  deleteTrack(track: Track): Promise<void> {
130
125
  return this.#engine.deleteTrack(track);
131
126
  }
@@ -155,10 +155,11 @@ export function audioTrackProcessorFactory(context: interfaces.Context): AudioTr
155
155
  const loggerFactory = context.container.get<LoggerFactory>(TOKEN.LoggerFactory);
156
156
  const config = context.container.get<ConfigService>(TOKEN.Config);
157
157
  const processorsCache = context.container.get<ProcessorsCache>(TOKEN.ProcessorsCache);
158
- const denoiser = config.get('denoiser');
159
158
  const asdk = config.get('asdk');
160
159
 
161
160
  return (params: CreateAudioTrackProcessorParams): AudioTrackProcessor => {
161
+ const denoiser = config.get('denoiser');
162
+
162
163
  if (processorsCache.has(denoiser, params.trackLabel)) {
163
164
  throw new Error(`Track processor for type:${denoiser}, label:${params.trackLabel} already created`);
164
165
  }
@@ -154,12 +154,6 @@ export type CreateScreenMediaOptions = BaseVideoTrackOptions & BaseAudioTrackOpt
154
154
  audioEncoderConfig?: AudioEncoderConfig,
155
155
  };
156
156
 
157
- export type CreateCustomMediaOptions = BaseVideoTrackOptions & BaseAudioTrackOptions & {
158
- videoEncoderConfig?: VideoEncoderConfig,
159
- audioEncoderConfig?: AudioEncoderConfig,
160
- mediaStream: MediaStream,
161
- };
162
-
163
157
  export enum TrackLabel {
164
158
  Camera = 'camera',
165
159
  CameraPreview = 'camera-preview',
@@ -87,6 +87,7 @@ export interface TrackWithEffects extends BaseTrack {
87
87
 
88
88
  export interface AudioTrack extends BaseTrack, TrackWithEncodings, TrackWithEffects {
89
89
  prepareTrackProcessor(trackProcessor?: AudioTrackProcessor): void;
90
+ unsetTrackProcessor(): void;
90
91
  enableNoiseSuppression(): Promise<void>;
91
92
  disableNoiseSuppression(): Promise<void>;
92
93
  pause(): Promise<void>;