@stream-io/video-client 1.35.1 → 1.36.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/index.browser.es.js +382 -329
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +404 -333
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +382 -329
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +3 -2
  9. package/dist/src/StreamVideoClient.d.ts +3 -2
  10. package/dist/src/coordinator/connection/client.d.ts +3 -2
  11. package/dist/src/coordinator/connection/connection.d.ts +2 -1
  12. package/dist/src/coordinator/connection/types.d.ts +17 -1
  13. package/dist/src/devices/DeviceManager.d.ts +2 -2
  14. package/dist/src/logger.d.ts +9 -6
  15. package/dist/src/rpc/createClient.d.ts +3 -2
  16. package/dist/src/rtc/BasePeerConnection.d.ts +7 -4
  17. package/dist/src/rtc/Publisher.d.ts +3 -3
  18. package/dist/src/rtc/codecs.d.ts +3 -1
  19. package/dist/src/rtc/helpers/sdp.d.ts +8 -0
  20. package/dist/src/rtc/types.d.ts +4 -5
  21. package/dist/src/store/CallState.d.ts +1 -1
  22. package/dist/src/types.d.ts +6 -0
  23. package/package.json +3 -2
  24. package/src/Call.ts +49 -68
  25. package/src/StreamSfuClient.ts +11 -11
  26. package/src/StreamVideoClient.ts +19 -21
  27. package/src/coordinator/connection/client.ts +21 -30
  28. package/src/coordinator/connection/connection.ts +5 -4
  29. package/src/coordinator/connection/location.ts +4 -4
  30. package/src/coordinator/connection/types.ts +21 -2
  31. package/src/devices/BrowserPermission.ts +5 -5
  32. package/src/devices/CameraManager.ts +3 -4
  33. package/src/devices/DeviceManager.ts +11 -11
  34. package/src/devices/MicrophoneManager.ts +8 -8
  35. package/src/devices/devices.ts +18 -14
  36. package/src/events/call.ts +6 -9
  37. package/src/events/internal.ts +4 -4
  38. package/src/events/mutes.ts +3 -8
  39. package/src/helpers/DynascaleManager.ts +9 -9
  40. package/src/helpers/RNSpeechDetector.ts +5 -5
  41. package/src/helpers/clientUtils.ts +1 -3
  42. package/src/helpers/ensureExhausted.ts +2 -2
  43. package/src/logger.ts +9 -34
  44. package/src/rpc/__tests__/createClient.test.ts +5 -1
  45. package/src/rpc/createClient.ts +4 -3
  46. package/src/rpc/retryable.ts +4 -2
  47. package/src/rtc/BasePeerConnection.ts +26 -24
  48. package/src/rtc/Dispatcher.ts +4 -4
  49. package/src/rtc/IceTrickleBuffer.ts +5 -5
  50. package/src/rtc/Publisher.ts +21 -13
  51. package/src/rtc/Subscriber.ts +22 -17
  52. package/src/rtc/__tests__/Publisher.test.ts +12 -8
  53. package/src/rtc/codecs.ts +13 -2
  54. package/src/rtc/helpers/__tests__/sdp.codecs.test.ts +628 -0
  55. package/src/rtc/helpers/sdp.ts +82 -0
  56. package/src/rtc/signal.ts +7 -7
  57. package/src/rtc/types.ts +4 -4
  58. package/src/stats/CallStateStatsReporter.ts +4 -4
  59. package/src/stats/SfuStatsReporter.ts +6 -6
  60. package/src/store/CallState.ts +3 -3
  61. package/src/store/rxUtils.ts +4 -2
  62. package/src/store/stateStore.ts +6 -6
  63. package/src/types.ts +6 -0
@@ -18,8 +18,7 @@ export const handleRemoteSoftMute = (call: Call) => {
18
18
  sessionId === localParticipant?.sessionId
19
19
  ) {
20
20
  const logger = call.logger;
21
- logger(
22
- 'info',
21
+ logger.info(
23
22
  `Local participant's ${TrackType[type]} track is muted remotely`,
24
23
  );
25
24
  try {
@@ -33,14 +32,10 @@ export const handleRemoteSoftMute = (call: Call) => {
33
32
  ) {
34
33
  await call.screenShare.disable();
35
34
  } else {
36
- logger(
37
- 'warn',
38
- 'Unsupported track type to soft mute',
39
- TrackType[type],
40
- );
35
+ logger.warn('Unsupported track type to soft mute', TrackType[type]);
41
36
  }
42
37
  } catch (error) {
43
- logger('error', 'Failed to stop publishing', error);
38
+ logger.error('Failed to stop publishing', error);
44
39
  }
45
40
  }
46
41
  });
@@ -15,7 +15,6 @@ import {
15
15
  takeWhile,
16
16
  } from 'rxjs';
17
17
  import { ViewportTracker } from './ViewportTracker';
18
- import { getLogger } from '../logger';
19
18
  import { isFirefox, isSafari } from './browsers';
20
19
  import {
21
20
  hasScreenShare,
@@ -27,6 +26,7 @@ import type { CallState } from '../store';
27
26
  import type { StreamSfuClient } from '../StreamSfuClient';
28
27
  import { SpeakerManager } from '../devices';
29
28
  import { getCurrentValue, setCurrentValue } from '../store/rxUtils';
29
+ import { videoLoggerSystem } from '../logger';
30
30
 
31
31
  const DEFAULT_VIEWPORT_VISIBILITY_STATE: Record<
32
32
  VideoTrackType,
@@ -66,7 +66,7 @@ export class DynascaleManager {
66
66
  */
67
67
  readonly viewportTracker = new ViewportTracker();
68
68
 
69
- private logger = getLogger(['DynascaleManager']);
69
+ private logger = videoLoggerSystem.getLogger('DynascaleManager');
70
70
  private callState: CallState;
71
71
  private speaker: SpeakerManager;
72
72
  private audioContext: AudioContext | undefined;
@@ -212,7 +212,7 @@ export class DynascaleManager {
212
212
  this.sfuClient
213
213
  ?.updateSubscriptions(this.trackSubscriptions)
214
214
  .catch((err: unknown) => {
215
- this.logger('debug', `Failed to update track subscriptions`, err);
215
+ this.logger.debug(`Failed to update track subscriptions`, err);
216
216
  });
217
217
  };
218
218
 
@@ -324,7 +324,7 @@ export class DynascaleManager {
324
324
  // is not visible (e.g., has display: none).
325
325
  // we treat this as "unsubscription" as we don't want to keep
326
326
  // consuming bandwidth for a video that is not visible on the screen.
327
- this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
327
+ this.logger.debug(`Ignoring 0x0 dimension`, boundParticipant);
328
328
  dimension = undefined;
329
329
  }
330
330
  this.callState.updateParticipantTracks(trackType, {
@@ -468,7 +468,7 @@ export class DynascaleManager {
468
468
  setTimeout(() => {
469
469
  videoElement.srcObject = source ?? null;
470
470
  videoElement.play().catch((e) => {
471
- this.logger('warn', `Failed to play stream`, e);
471
+ this.logger.warn(`Failed to play stream`, e);
472
472
  });
473
473
  // we add extra delay until we attempt to force-play
474
474
  // the participant's media stream in Firefox and Safari,
@@ -519,14 +519,14 @@ export class DynascaleManager {
519
519
  if (!deviceId) return;
520
520
  if ('setSinkId' in audioElement) {
521
521
  audioElement.setSinkId(deviceId).catch((e) => {
522
- this.logger('warn', `Can't to set AudioElement sinkId`, e);
522
+ this.logger.warn(`Can't to set AudioElement sinkId`, e);
523
523
  });
524
524
  }
525
525
 
526
526
  if (audioContext && 'setSinkId' in audioContext) {
527
527
  // @ts-expect-error setSinkId is not available in all browsers
528
528
  audioContext.setSinkId(deviceId).catch((e) => {
529
- this.logger('warn', `Can't to set AudioContext sinkId`, e);
529
+ this.logger.warn(`Can't to set AudioContext sinkId`, e);
530
530
  });
531
531
  }
532
532
  };
@@ -574,7 +574,7 @@ export class DynascaleManager {
574
574
  // we will play audio directly through the audio element in other browsers
575
575
  audioElement.muted = false;
576
576
  audioElement.play().catch((e) => {
577
- this.logger('warn', `Failed to play audio stream`, e);
577
+ this.logger.warn(`Failed to play audio stream`, e);
578
578
  });
579
579
  }
580
580
 
@@ -630,7 +630,7 @@ export class DynascaleManager {
630
630
  if (this.audioContext?.state === 'suspended') {
631
631
  this.audioContext
632
632
  .resume()
633
- .catch((err) => this.logger('warn', `Can't resume audio context`, err))
633
+ .catch((err) => this.logger.warn(`Can't resume audio context`, err))
634
634
  .then(() => {
635
635
  document.removeEventListener('click', this.resumeAudioContext);
636
636
  });
@@ -1,7 +1,7 @@
1
1
  import { BaseStats } from '../stats';
2
2
  import { SoundStateChangeHandler } from './sound-detector';
3
3
  import { flatten } from '../stats/utils';
4
- import { getLogger } from '../logger';
4
+ import { videoLoggerSystem } from '../logger';
5
5
 
6
6
  export class RNSpeechDetector {
7
7
  private pc1 = new RTCPeerConnection({});
@@ -64,8 +64,8 @@ export class RNSpeechDetector {
64
64
  this.stop();
65
65
  };
66
66
  } catch (error) {
67
- const logger = getLogger(['RNSpeechDetector']);
68
- logger('error', 'error handling permissions: ', error);
67
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
68
+ logger.error('error handling permissions: ', error);
69
69
  return () => {};
70
70
  }
71
71
  }
@@ -164,8 +164,8 @@ export class RNSpeechDetector {
164
164
  }
165
165
  }
166
166
  } catch (error) {
167
- const logger = getLogger(['RNSpeechDetector']);
168
- logger('error', 'error checking audio level from stats', error);
167
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
168
+ logger.error('error checking audio level from stats', error);
169
169
  }
170
170
  };
171
171
 
@@ -4,7 +4,6 @@ import type {
4
4
  TokenOrProvider,
5
5
  User,
6
6
  } from '../coordinator/connection/types';
7
- import { getLogger } from '../logger';
8
7
  import { StreamClient } from '../coordinator/connection/client';
9
8
  import { getSdkInfo } from './client-details';
10
9
  import { SdkType } from '../gen/video/sfu/models/models';
@@ -50,12 +49,11 @@ export const createCoordinatorClient = (
50
49
  options: StreamClientOptions | undefined,
51
50
  ) => {
52
51
  const clientAppIdentifier = getClientAppIdentifier(options);
53
- const coordinatorLogger = getLogger(['coordinator']);
52
+
54
53
  return new StreamClient(apiKey, {
55
54
  persistUserOnConnectionFailure: true,
56
55
  ...options,
57
56
  clientAppIdentifier,
58
- logger: coordinatorLogger,
59
57
  });
60
58
  };
61
59
 
@@ -1,5 +1,5 @@
1
- import { getLogger } from '../logger';
1
+ import { videoLoggerSystem } from '../logger';
2
2
 
3
3
  export const ensureExhausted = (x: never, message: string) => {
4
- getLogger(['helpers'])('warn', message, x);
4
+ videoLoggerSystem.getLogger('helpers').warn(message, x);
5
5
  };
package/src/logger.ts CHANGED
@@ -1,17 +1,6 @@
1
- import { Logger, LogLevel } from './coordinator/connection/types';
1
+ import * as scopedLogger from '@stream-io/logger';
2
2
  import { isReactNative } from './helpers/platforms';
3
-
4
- // log levels, sorted by verbosity
5
- export const logLevels: Record<LogLevel, number> = Object.freeze({
6
- trace: 0,
7
- debug: 1,
8
- info: 2,
9
- warn: 3,
10
- error: 4,
11
- });
12
-
13
- let logger: Logger | undefined;
14
- let level: LogLevel = 'info';
3
+ import type { Logger } from './coordinator/connection/types';
15
4
 
16
5
  export const logToConsole: Logger = (logLevel, message, ...args) => {
17
6
  let logMethod;
@@ -46,26 +35,12 @@ export const logToConsole: Logger = (logLevel, message, ...args) => {
46
35
  logMethod(message, ...args);
47
36
  };
48
37
 
49
- export const setLogger = (l: Logger, lvl?: LogLevel) => {
50
- logger = l;
51
- if (lvl) {
52
- setLogLevel(lvl);
53
- }
54
- };
38
+ /**
39
+ * @internal
40
+ */
41
+ export type ScopedLogger = scopedLogger.Logger;
55
42
 
56
- export const setLogLevel = (l: LogLevel) => {
57
- level = l;
58
- };
59
-
60
- export const getLogLevel = (): LogLevel => level;
43
+ export { LogLevelEnum } from '@stream-io/logger';
44
+ export type { LogLevel, Sink } from '@stream-io/logger';
61
45
 
62
- export const getLogger = (withTags?: string[]) => {
63
- const loggerMethod = logger || logToConsole;
64
- const tags = (withTags || []).filter(Boolean).join(':');
65
- const result: Logger = (logLevel, message, ...args) => {
66
- if (logLevels[logLevel] >= logLevels[level]) {
67
- loggerMethod(logLevel, `[${tags}]: ${message}`, ...args);
68
- }
69
- };
70
- return result;
71
- };
46
+ export const videoLoggerSystem = scopedLogger.createLoggerSystem();
@@ -8,6 +8,7 @@ import {
8
8
  import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
9
9
  import { NextUnaryFn, UnaryCall } from '@protobuf-ts/runtime-rpc';
10
10
  import { promiseWithResolvers } from '../../helpers/promise';
11
+ import { ScopedLogger } from '../../logger';
11
12
 
12
13
  describe('createClient', () => {
13
14
  it('should create a client with TwirpFetchTransport', () => {
@@ -30,7 +31,10 @@ describe('createClient', () => {
30
31
 
31
32
  it('withRequestLogger should log the request', () => {
32
33
  const logger = vi.fn();
33
- const interceptor = withRequestLogger(logger, 'debug');
34
+ const interceptor = withRequestLogger(
35
+ { debug: logger } as unknown as ScopedLogger,
36
+ 'debug',
37
+ );
34
38
  const next = vi.fn().mockReturnValue({});
35
39
  // @ts-expect-error - private field
36
40
  interceptor.interceptUnary(next, { name: 'test' }, null, null);
@@ -10,9 +10,10 @@ import {
10
10
  TwirpOptions,
11
11
  } from '@protobuf-ts/twirp-transport';
12
12
  import { SignalServerClient } from '../gen/video/sfu/signal_rpc/signal.client';
13
- import { Logger, LogLevel } from '../coordinator/connection/types';
14
13
  import type { Trace } from '../stats';
15
14
  import type { SfuResponseWithError } from './retryable';
15
+ import type { ScopedLogger } from '../logger';
16
+ import type { LogLevel } from '@stream-io/logger';
16
17
 
17
18
  const defaultOptions: TwirpOptions = {
18
19
  baseUrl: '',
@@ -40,7 +41,7 @@ export const withHeaders = (
40
41
  };
41
42
 
42
43
  export const withRequestLogger = (
43
- logger: Logger,
44
+ logger: ScopedLogger,
44
45
  level: LogLevel,
45
46
  ): RpcInterceptor => {
46
47
  return {
@@ -51,7 +52,7 @@ export const withRequestLogger = (
51
52
  options: RpcOptions,
52
53
  ): UnaryCall => {
53
54
  const invocation = next(method, input, options);
54
- logger(level, `Invoked SFU RPC method ${method.name}`, {
55
+ logger[level](`Invoked SFU RPC method ${method.name}`, {
55
56
  request: invocation.request,
56
57
  headers: invocation.requestHeaders,
57
58
  response: invocation.response,
@@ -6,7 +6,7 @@ import {
6
6
  import { TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
7
  import { retryInterval, sleep } from '../coordinator/connection/utils';
8
8
  import { Error as SfuError } from '../gen/video/sfu/models/models';
9
- import { getLogger } from '../logger';
9
+ import { videoLoggerSystem } from '../logger';
10
10
 
11
11
  /**
12
12
  * An internal interface which asserts that "retryable" SFU responses
@@ -48,7 +48,9 @@ export const retryable = async <
48
48
  err.code === TwirpErrorCode[TwirpErrorCode.cancelled];
49
49
  const isAborted = signal?.aborted ?? false;
50
50
  if (isRequestCancelled || isAborted) throw err;
51
- getLogger(['sfu-client', 'rpc'])('debug', `rpc failed (${attempt})`, err);
51
+ videoLoggerSystem
52
+ .getLogger('sfu-client', { tags: ['rpc'] })
53
+ .debug(`rpc failed (${attempt})`, err);
52
54
  attempt++;
53
55
  }
54
56
  } while (!result || result.response.error?.shouldRetry);
@@ -1,8 +1,5 @@
1
- import { getLogger } from '../logger';
2
- import type {
3
- CallEventListener,
4
- Logger,
5
- } from '../coordinator/connection/types';
1
+ import { ScopedLogger, videoLoggerSystem } from '../logger';
2
+ import type { CallEventListener } from '../coordinator/connection/types';
6
3
  import { CallingState, CallState } from '../store';
7
4
  import { createSafeAsyncSubscription } from '../store/rxUtils';
8
5
  import {
@@ -16,18 +13,20 @@ import { StreamSfuClient } from '../StreamSfuClient';
16
13
  import { AllSfuEvents, Dispatcher } from './Dispatcher';
17
14
  import { withoutConcurrency } from '../helpers/concurrency';
18
15
  import { StatsTracer, Tracer, traceRTCPeerConnection } from '../stats';
19
- import { BasePeerConnectionOpts, OnReconnectionNeeded } from './types';
16
+ import type { BasePeerConnectionOpts, OnReconnectionNeeded } from './types';
17
+ import type { ClientPublishOptions } from '../types';
20
18
 
21
19
  /**
22
20
  * A base class for the `Publisher` and `Subscriber` classes.
23
21
  * @internal
24
22
  */
25
23
  export abstract class BasePeerConnection {
26
- protected readonly logger: Logger;
24
+ protected readonly logger: ScopedLogger;
27
25
  protected readonly peerType: PeerType;
28
26
  protected readonly pc: RTCPeerConnection;
29
27
  protected readonly state: CallState;
30
28
  protected readonly dispatcher: Dispatcher;
29
+ protected readonly clientPublishOptions?: ClientPublishOptions;
31
30
  protected sfuClient: StreamSfuClient;
32
31
 
33
32
  private onReconnectionNeeded?: OnReconnectionNeeded;
@@ -58,6 +57,7 @@ export abstract class BasePeerConnection {
58
57
  onReconnectionNeeded,
59
58
  tag,
60
59
  enableTracing,
60
+ clientPublishOptions,
61
61
  iceRestartDelay = 2500,
62
62
  }: BasePeerConnectionOpts,
63
63
  ) {
@@ -66,11 +66,12 @@ export abstract class BasePeerConnection {
66
66
  this.state = state;
67
67
  this.dispatcher = dispatcher;
68
68
  this.iceRestartDelay = iceRestartDelay;
69
+ this.clientPublishOptions = clientPublishOptions;
69
70
  this.onReconnectionNeeded = onReconnectionNeeded;
70
- this.logger = getLogger([
71
+ this.logger = videoLoggerSystem.getLogger(
71
72
  peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
72
- tag,
73
- ]);
73
+ { tags: [tag] },
74
+ );
74
75
  this.pc = this.createPeerConnection(connectionConfig);
75
76
  this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
76
77
  if (enableTracing) {
@@ -142,13 +143,13 @@ export abstract class BasePeerConnection {
142
143
  protected tryRestartIce = () => {
143
144
  this.restartIce().catch((e) => {
144
145
  const reason = 'restartICE() failed, initiating reconnect';
145
- this.logger('error', reason, e);
146
+ this.logger.error(reason, e);
146
147
  const strategy =
147
148
  e instanceof NegotiationError &&
148
149
  e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
149
150
  ? WebsocketReconnectStrategy.FAST
150
151
  : WebsocketReconnectStrategy.REJOIN;
151
- this.onReconnectionNeeded?.(strategy, reason);
152
+ this.onReconnectionNeeded?.(strategy, reason, this.peerType);
152
153
  });
153
154
  };
154
155
 
@@ -165,7 +166,7 @@ export abstract class BasePeerConnection {
165
166
  const lockKey = `pc.${this.lock}.${event}`;
166
167
  withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
167
168
  if (this.isDisposed) return;
168
- this.logger('warn', `Error handling ${event}`, err);
169
+ this.logger.warn(`Error handling ${event}`, err);
169
170
  });
170
171
  }),
171
172
  );
@@ -187,7 +188,7 @@ export abstract class BasePeerConnection {
187
188
  async (candidate) => {
188
189
  return this.pc.addIceCandidate(candidate).catch((e) => {
189
190
  if (this.isDisposed) return;
190
- this.logger('warn', `ICE candidate error`, e, candidate);
191
+ this.logger.warn(`ICE candidate error`, e, candidate);
191
192
  });
192
193
  },
193
194
  );
@@ -240,7 +241,7 @@ export abstract class BasePeerConnection {
240
241
  private onIceCandidate = (e: RTCPeerConnectionIceEvent) => {
241
242
  const { candidate } = e;
242
243
  if (!candidate) {
243
- this.logger('debug', 'null ice candidate');
244
+ this.logger.debug('null ice candidate');
244
245
  return;
245
246
  }
246
247
 
@@ -249,7 +250,7 @@ export abstract class BasePeerConnection {
249
250
  .iceTrickle({ peerType: this.peerType, iceCandidate })
250
251
  .catch((err) => {
251
252
  if (this.isDisposed) return;
252
- this.logger('warn', `ICETrickle failed`, err);
253
+ this.logger.warn(`ICETrickle failed`, err);
253
254
  });
254
255
  };
255
256
 
@@ -272,7 +273,7 @@ export abstract class BasePeerConnection {
272
273
  */
273
274
  private onConnectionStateChange = async () => {
274
275
  const state = this.pc.connectionState;
275
- this.logger('debug', `Connection state changed`, state);
276
+ this.logger.debug(`Connection state changed`, state);
276
277
  if (this.tracer && (state === 'connected' || state === 'failed')) {
277
278
  try {
278
279
  const stats = await this.stats.get();
@@ -287,6 +288,7 @@ export abstract class BasePeerConnection {
287
288
  this.onReconnectionNeeded?.(
288
289
  WebsocketReconnectStrategy.REJOIN,
289
290
  'Connection failed',
291
+ this.peerType,
290
292
  );
291
293
  return;
292
294
  }
@@ -299,7 +301,7 @@ export abstract class BasePeerConnection {
299
301
  */
300
302
  private onIceConnectionStateChange = () => {
301
303
  const state = this.pc.iceConnectionState;
302
- this.logger('debug', `ICE connection state changed`, state);
304
+ this.logger.debug(`ICE connection state changed`, state);
303
305
  this.handleConnectionStateUpdate(state);
304
306
  };
305
307
 
@@ -316,14 +318,14 @@ export abstract class BasePeerConnection {
316
318
  switch (state) {
317
319
  case 'failed':
318
320
  // in the `failed` state, we try to restart ICE immediately
319
- this.logger('info', 'restartICE due to failed connection');
321
+ this.logger.info('restartICE due to failed connection');
320
322
  this.tryRestartIce();
321
323
  break;
322
324
 
323
325
  case 'disconnected':
324
326
  // in the `disconnected` state, we schedule a restartICE() after a delay
325
327
  // as the browser might recover the connection in the meantime
326
- this.logger('info', 'disconnected connection, scheduling restartICE');
328
+ this.logger.info('disconnected connection, scheduling restartICE');
327
329
  clearTimeout(this.iceRestartTimeout);
328
330
  this.iceRestartTimeout = setTimeout(() => {
329
331
  const currentState = this.pc.iceConnectionState;
@@ -336,7 +338,7 @@ export abstract class BasePeerConnection {
336
338
  case 'connected':
337
339
  // in the `connected` state, we clear the ice restart timeout if it exists
338
340
  if (this.iceRestartTimeout) {
339
- this.logger('info', 'connected connection, canceling restartICE');
341
+ this.logger.info('connected connection, canceling restartICE');
340
342
  clearTimeout(this.iceRestartTimeout);
341
343
  this.iceRestartTimeout = undefined;
342
344
  }
@@ -352,20 +354,20 @@ export abstract class BasePeerConnection {
352
354
  e instanceof RTCPeerConnectionIceErrorEvent
353
355
  ? `${e.errorCode}: ${e.errorText}`
354
356
  : e;
355
- this.logger('debug', 'ICE Candidate error', errorMessage);
357
+ this.logger.debug('ICE Candidate error', errorMessage);
356
358
  };
357
359
 
358
360
  /**
359
361
  * Handles the ICE gathering state change event.
360
362
  */
361
363
  private onIceGatherChange = () => {
362
- this.logger('debug', `ICE Gathering State`, this.pc.iceGatheringState);
364
+ this.logger.debug(`ICE Gathering State`, this.pc.iceGatheringState);
363
365
  };
364
366
 
365
367
  /**
366
368
  * Handles the signaling state change event.
367
369
  */
368
370
  private onSignalingChange = () => {
369
- this.logger('debug', `Signaling state changed`, this.pc.signalingState);
371
+ this.logger.debug(`Signaling state changed`, this.pc.signalingState);
370
372
  };
371
373
  }
@@ -1,6 +1,6 @@
1
1
  import { CallEventListener, EventTypes } from '../coordinator/connection/types';
2
2
  import type { SfuEvent } from '../gen/video/sfu/event/events';
3
- import { getLogger } from '../logger';
3
+ import { videoLoggerSystem } from '../logger';
4
4
 
5
5
  export type SfuEventKinds = NonNullable<SfuEvent['eventPayload']['oneofKind']>;
6
6
  export type AllSfuEvents = {
@@ -53,7 +53,7 @@ export const isSfuEvent = (
53
53
  };
54
54
 
55
55
  export class Dispatcher {
56
- private readonly logger = getLogger(['Dispatcher']);
56
+ private readonly logger = videoLoggerSystem.getLogger('Dispatcher');
57
57
  private subscribers: Partial<
58
58
  Record<SfuEventKinds, CallEventListener<any>[] | undefined>
59
59
  > = {};
@@ -65,14 +65,14 @@ export class Dispatcher {
65
65
  const eventKind = message.eventPayload.oneofKind;
66
66
  if (!eventKind) return;
67
67
  const payload = message.eventPayload[eventKind];
68
- this.logger('debug', `Dispatching ${eventKind}, tag=${tag}`, payload);
68
+ this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
69
69
  const listeners = this.subscribers[eventKind];
70
70
  if (!listeners) return;
71
71
  for (const fn of listeners) {
72
72
  try {
73
73
  fn(payload);
74
74
  } catch (e) {
75
- this.logger('warn', 'Listener failed with error', e);
75
+ this.logger.warn('Listener failed with error', e);
76
76
  }
77
77
  }
78
78
  };
@@ -1,7 +1,7 @@
1
1
  import { ReplaySubject } from 'rxjs';
2
2
  import { ICETrickle } from '../gen/video/sfu/event/events';
3
3
  import { PeerType } from '../gen/video/sfu/models/models';
4
- import { getLogger } from '../logger';
4
+ import { videoLoggerSystem } from '../logger';
5
5
 
6
6
  /**
7
7
  * A buffer for ICE Candidates. Used for ICE Trickle:
@@ -20,8 +20,8 @@ export class IceTrickleBuffer {
20
20
  } else if (iceTrickle.peerType === PeerType.PUBLISHER_UNSPECIFIED) {
21
21
  this.publisherCandidates.next(iceCandidate);
22
22
  } else {
23
- const logger = getLogger(['sfu-client']);
24
- logger('warn', `ICETrickle, Unknown peer type`, iceTrickle);
23
+ const logger = videoLoggerSystem.getLogger('sfu-client');
24
+ logger.warn(`ICETrickle, Unknown peer type`, iceTrickle);
25
25
  }
26
26
  };
27
27
 
@@ -37,8 +37,8 @@ const toIceCandidate = (
37
37
  try {
38
38
  return JSON.parse(iceTrickle.iceCandidate);
39
39
  } catch (e) {
40
- const logger = getLogger(['sfu-client']);
41
- logger('error', `Failed to parse ICE Trickle`, e, iceTrickle);
40
+ const logger = videoLoggerSystem.getLogger('sfu-client');
41
+ logger.error(`Failed to parse ICE Trickle`, e, iceTrickle);
42
42
  return undefined;
43
43
  }
44
44
  };
@@ -1,7 +1,7 @@
1
1
  import { BasePeerConnection } from './BasePeerConnection';
2
- import {
2
+ import type {
3
+ BasePeerConnectionOpts,
3
4
  PublishBundle,
4
- PublisherConstructorOpts,
5
5
  TrackPublishOptions,
6
6
  } from './types';
7
7
  import { NegotiationError } from './NegotiationError';
@@ -21,7 +21,7 @@ import {
21
21
  } from './layers';
22
22
  import { isSvcCodec } from './codecs';
23
23
  import { isAudioTrackType } from './helpers/tracks';
24
- import { extractMid } from './helpers/sdp';
24
+ import { extractMid, removeCodecsExcept } from './helpers/sdp';
25
25
  import { withoutConcurrency } from '../helpers/concurrency';
26
26
  import { isReactNative } from '../helpers/platforms';
27
27
 
@@ -38,7 +38,10 @@ export class Publisher extends BasePeerConnection {
38
38
  /**
39
39
  * Constructs a new `Publisher` instance.
40
40
  */
41
- constructor({ publishOptions, ...baseOptions }: PublisherConstructorOpts) {
41
+ constructor(
42
+ baseOptions: BasePeerConnectionOpts,
43
+ publishOptions: PublishOption[],
44
+ ) {
42
45
  super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
43
46
  this.publishOptions = publishOptions;
44
47
 
@@ -136,7 +139,7 @@ export class Publisher extends BasePeerConnection {
136
139
  await transceiver.sender.setParameters(params);
137
140
 
138
141
  const trackType = publishOption.trackType;
139
- this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
142
+ this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
140
143
  this.transceiverCache.add({ publishOption, transceiver, options });
141
144
  this.trackIdToTrackType.set(track.id, trackType);
142
145
 
@@ -273,7 +276,7 @@ export class Publisher extends BasePeerConnection {
273
276
  const enabledLayers = layers.filter((l) => l.active);
274
277
 
275
278
  const tag = 'Update publish quality:';
276
- this.logger('info', `${tag} requested layers by SFU:`, enabledLayers);
279
+ this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
277
280
 
278
281
  const transceiverId = this.transceiverCache.find(
279
282
  (t) =>
@@ -282,12 +285,12 @@ export class Publisher extends BasePeerConnection {
282
285
  );
283
286
  const sender = transceiverId?.transceiver.sender;
284
287
  if (!sender) {
285
- return this.logger('warn', `${tag} no video sender found.`);
288
+ return this.logger.warn(`${tag} no video sender found.`);
286
289
  }
287
290
 
288
291
  const params = sender.getParameters();
289
292
  if (params.encodings.length === 0) {
290
- return this.logger('warn', `${tag} there are no encodings set.`);
293
+ return this.logger.warn(`${tag} there are no encodings set.`);
291
294
  }
292
295
 
293
296
  const codecInUse = transceiverId?.publishOption.codec?.name;
@@ -343,21 +346,21 @@ export class Publisher extends BasePeerConnection {
343
346
 
344
347
  const activeEncoders = params.encodings.filter((e) => e.active);
345
348
  if (!changed) {
346
- return this.logger('info', `${tag} no change:`, activeEncoders);
349
+ return this.logger.info(`${tag} no change:`, activeEncoders);
347
350
  }
348
351
 
349
352
  await sender.setParameters(params);
350
- this.logger('info', `${tag} enabled rids:`, activeEncoders);
353
+ this.logger.info(`${tag} enabled rids:`, activeEncoders);
351
354
  };
352
355
 
353
356
  /**
354
357
  * Restarts the ICE connection and renegotiates with the SFU.
355
358
  */
356
359
  restartIce = async (): Promise<void> => {
357
- this.logger('debug', 'Restarting ICE connection');
360
+ this.logger.debug('Restarting ICE connection');
358
361
  const signalingState = this.pc.signalingState;
359
362
  if (this.isIceRestarting || signalingState === 'have-local-offer') {
360
- this.logger('debug', 'ICE restart is already in progress');
363
+ this.logger.debug('ICE restart is already in progress');
361
364
  return;
362
365
  }
363
366
  await this.negotiate({ iceRestart: true });
@@ -378,7 +381,12 @@ export class Publisher extends BasePeerConnection {
378
381
  this.isIceRestarting = options?.iceRestart ?? false;
379
382
  await this.pc.setLocalDescription(offer);
380
383
 
381
- const { sdp = '' } = offer;
384
+ const { sdp: baseSdp = '' } = offer;
385
+ const { dangerouslyForceCodec, fmtpLine } =
386
+ this.clientPublishOptions || {};
387
+ const sdp = dangerouslyForceCodec
388
+ ? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
389
+ : baseSdp;
382
390
  const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
383
391
  if (response.error) throw new NegotiationError(response.error);
384
392