@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
package/dist/index.es.js CHANGED
@@ -4,9 +4,11 @@ import { ServiceType, stackIntercept, RpcError } from '@protobuf-ts/runtime-rpc'
4
4
  import axios from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
+ import * as scopedLogger from '@stream-io/logger';
8
+ export { LogLevelEnum } from '@stream-io/logger';
9
+ import { parse, write } from 'sdp-transform';
7
10
  import { ReplaySubject, combineLatest, BehaviorSubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, of } from 'rxjs';
8
11
  import { UAParser } from 'ua-parser-js';
9
- import { parse, write } from 'sdp-transform';
10
12
  import { WorkerTimer } from '@stream-io/worker-timer';
11
13
  import https from 'https';
12
14
 
@@ -3726,7 +3728,7 @@ const withRequestLogger = (logger, level) => {
3726
3728
  return {
3727
3729
  interceptUnary: (next, method, input, options) => {
3728
3730
  const invocation = next(method, input, options);
3729
- logger(level, `Invoked SFU RPC method ${method.name}`, {
3731
+ logger[level](`Invoked SFU RPC method ${method.name}`, {
3730
3732
  request: invocation.request,
3731
3733
  headers: invocation.requestHeaders,
3732
3734
  response: invocation.response,
@@ -3862,16 +3864,6 @@ const isReactNative = () => {
3862
3864
  return navigator.product?.toLowerCase() === 'reactnative';
3863
3865
  };
3864
3866
 
3865
- // log levels, sorted by verbosity
3866
- const logLevels = Object.freeze({
3867
- trace: 0,
3868
- debug: 1,
3869
- info: 2,
3870
- warn: 3,
3871
- error: 4,
3872
- });
3873
- let logger;
3874
- let level = 'info';
3875
3867
  const logToConsole = (logLevel, message, ...args) => {
3876
3868
  let logMethod;
3877
3869
  switch (logLevel) {
@@ -3903,26 +3895,7 @@ const logToConsole = (logLevel, message, ...args) => {
3903
3895
  }
3904
3896
  logMethod(message, ...args);
3905
3897
  };
3906
- const setLogger = (l, lvl) => {
3907
- logger = l;
3908
- if (lvl) {
3909
- setLogLevel(lvl);
3910
- }
3911
- };
3912
- const setLogLevel = (l) => {
3913
- level = l;
3914
- };
3915
- const getLogLevel = () => level;
3916
- const getLogger = (withTags) => {
3917
- const loggerMethod = logger || logToConsole;
3918
- const tags = (withTags || []).filter(Boolean).join(':');
3919
- const result = (logLevel, message, ...args) => {
3920
- if (logLevels[logLevel] >= logLevels[level]) {
3921
- loggerMethod(logLevel, `[${tags}]: ${message}`, ...args);
3922
- }
3923
- };
3924
- return result;
3925
- };
3898
+ const videoLoggerSystem = scopedLogger.createLoggerSystem();
3926
3899
 
3927
3900
  /**
3928
3901
  * Creates a closure which wraps the given RPC call and retries invoking
@@ -3948,26 +3921,167 @@ const retryable = async (rpc, signal) => {
3948
3921
  const isAborted = signal?.aborted ?? false;
3949
3922
  if (isRequestCancelled || isAborted)
3950
3923
  throw err;
3951
- getLogger(['sfu-client', 'rpc'])('debug', `rpc failed (${attempt})`, err);
3924
+ videoLoggerSystem
3925
+ .getLogger('sfu-client', { tags: ['rpc'] })
3926
+ .debug(`rpc failed (${attempt})`, err);
3952
3927
  attempt++;
3953
3928
  }
3954
3929
  } while (!result || result.response.error?.shouldRetry);
3955
3930
  return result;
3956
3931
  };
3957
3932
 
3933
+ /**
3934
+ * Extracts the mid from the transceiver or the SDP.
3935
+ *
3936
+ * @param transceiver the transceiver.
3937
+ * @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
3938
+ * @param sdp the SDP.
3939
+ */
3940
+ const extractMid = (transceiver, transceiverInitIndex, sdp) => {
3941
+ if (transceiver.mid)
3942
+ return transceiver.mid;
3943
+ if (!sdp)
3944
+ return String(transceiverInitIndex);
3945
+ const track = transceiver.sender.track;
3946
+ const parsedSdp = parse(sdp);
3947
+ const media = parsedSdp.media.find((m) => {
3948
+ return (m.type === track.kind &&
3949
+ // if `msid` is not present, we assume that the track is the first one
3950
+ (m.msid?.includes(track.id) ?? true));
3951
+ });
3952
+ if (typeof media?.mid !== 'undefined')
3953
+ return String(media.mid);
3954
+ if (transceiverInitIndex < 0)
3955
+ return '';
3956
+ return String(transceiverInitIndex);
3957
+ };
3958
+ /**
3959
+ * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
3960
+ *
3961
+ * @param offerSdp the offer SDP containing the stereo configuration.
3962
+ * @param answerSdp the answer SDP to be modified.
3963
+ */
3964
+ const enableStereo = (offerSdp, answerSdp) => {
3965
+ const offeredStereoMids = new Set();
3966
+ const parsedOfferSdp = parse(offerSdp);
3967
+ for (const media of parsedOfferSdp.media) {
3968
+ if (media.type !== 'audio')
3969
+ continue;
3970
+ const opus = media.rtp.find((r) => r.codec === 'opus');
3971
+ if (!opus)
3972
+ continue;
3973
+ for (const fmtp of media.fmtp) {
3974
+ if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
3975
+ offeredStereoMids.add(media.mid);
3976
+ }
3977
+ }
3978
+ }
3979
+ // No stereo offered, return the original answerSdp
3980
+ if (offeredStereoMids.size === 0)
3981
+ return answerSdp;
3982
+ const parsedAnswerSdp = parse(answerSdp);
3983
+ for (const media of parsedAnswerSdp.media) {
3984
+ if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
3985
+ continue;
3986
+ const opus = media.rtp.find((r) => r.codec === 'opus');
3987
+ if (!opus)
3988
+ continue;
3989
+ for (const fmtp of media.fmtp) {
3990
+ if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
3991
+ fmtp.config += ';stereo=1';
3992
+ }
3993
+ }
3994
+ }
3995
+ return write(parsedAnswerSdp);
3996
+ };
3997
+ /**
3998
+ * Removes all codecs from the SDP except the specified codec.
3999
+ *
4000
+ * @param sdp the SDP to modify.
4001
+ * @param codecMimeTypeToKeep the codec mime type to keep (video/h264 or audio/opus).
4002
+ * @param fmtpProfileToKeep the fmtp profile to keep (e.g. 'profile-level-id=42e01f' or multiple segments like 'profile-level-id=64001f;packetization-mode=1').
4003
+ */
4004
+ const removeCodecsExcept = (sdp, codecMimeTypeToKeep, fmtpProfileToKeep) => {
4005
+ const [kind, codec] = toMimeType(codecMimeTypeToKeep).split('/');
4006
+ if (!kind || !codec)
4007
+ return sdp;
4008
+ const parsed = parse(sdp);
4009
+ for (const media of parsed.media) {
4010
+ if (media.type !== kind)
4011
+ continue;
4012
+ // Build a set of payloads to KEEP: all payloads whose rtp.codec matches codec
4013
+ let payloadsToKeep = new Set();
4014
+ for (const rtp of media.rtp) {
4015
+ if (rtp.codec.toLowerCase() !== codec)
4016
+ continue;
4017
+ payloadsToKeep.add(rtp.payload);
4018
+ }
4019
+ // If a specific fmtp profile is requested, only keep payloads whose fmtp config matches it
4020
+ if (fmtpProfileToKeep) {
4021
+ const filtered = new Set();
4022
+ const required = new Set(fmtpProfileToKeep.split(';'));
4023
+ for (const fmtp of media.fmtp) {
4024
+ if (payloadsToKeep.has(fmtp.payload) &&
4025
+ required.difference(new Set(fmtp.config.split(';'))).size === 0) {
4026
+ filtered.add(fmtp.payload);
4027
+ }
4028
+ }
4029
+ payloadsToKeep = filtered;
4030
+ }
4031
+ // If no payloads to keep AND no fmtpProfile was specified, skip modifications (preserve SDP as-is)
4032
+ if (payloadsToKeep.size === 0 && !fmtpProfileToKeep)
4033
+ continue;
4034
+ // Keep RTX payloads that are associated with kept primary payloads via apt
4035
+ // RTX mappings look like: a=fmtp:<rtxPayload> apt=<primaryPayload>
4036
+ for (const fmtp of media.fmtp) {
4037
+ const matches = /\s*apt\s*=\s*(\d+)\s*/i.exec(fmtp.config);
4038
+ if (!matches)
4039
+ continue;
4040
+ const primaryPayloadApt = Number(matches[1]);
4041
+ if (!payloadsToKeep.has(primaryPayloadApt))
4042
+ continue;
4043
+ payloadsToKeep.add(fmtp.payload);
4044
+ }
4045
+ // Filter rtp, fmtp and rtcpFb entries
4046
+ media.rtp = media.rtp.filter((rtp) => payloadsToKeep.has(rtp.payload));
4047
+ media.fmtp = media.fmtp.filter((fmtp) => payloadsToKeep.has(fmtp.payload));
4048
+ media.rtcpFb = media.rtcpFb?.filter((fb) => typeof fb.payload === 'number' ? payloadsToKeep.has(fb.payload) : true);
4049
+ // Update the m= line payload list to only the kept payloads, preserving original order
4050
+ const payloads = [];
4051
+ for (const id of (media.payloads || '').split(/\s+/)) {
4052
+ const payload = Number(id);
4053
+ if (!payloadsToKeep.has(payload))
4054
+ continue;
4055
+ payloads.push(payload);
4056
+ }
4057
+ media.payloads = payloads.join(' ');
4058
+ }
4059
+ return write(parsed);
4060
+ };
4061
+ /**
4062
+ * Converts the given codec to a mime-type format when necessary.
4063
+ * e.g.: `vp9` -> `video/vp9`
4064
+ */
4065
+ const toMimeType = (codec, kind = 'video') => codec.includes('/') ? codec : `${kind}/${codec}`;
4066
+
3958
4067
  /**
3959
4068
  * Returns a generic SDP for the given direction.
3960
4069
  * We use this SDP to send it as part of our JoinRequest so that the SFU
3961
4070
  * can use it to determine the client's codec capabilities.
3962
4071
  *
3963
4072
  * @param direction the direction of the transceiver.
4073
+ * @param codecToKeep the codec mime type to keep (video/h264 or audio/opus).
4074
+ * @param fmtpProfileToKeep optional fmtp profile to keep.
3964
4075
  */
3965
- const getGenericSdp = async (direction) => {
4076
+ const getGenericSdp = async (direction, codecToKeep, fmtpProfileToKeep) => {
3966
4077
  const tempPc = new RTCPeerConnection();
3967
4078
  tempPc.addTransceiver('video', { direction });
3968
4079
  tempPc.addTransceiver('audio', { direction });
3969
4080
  const offer = await tempPc.createOffer();
3970
- const sdp = offer.sdp ?? '';
4081
+ const { sdp: baseSdp = '' } = offer;
4082
+ const sdp = codecToKeep
4083
+ ? removeCodecsExcept(baseSdp, codecToKeep, fmtpProfileToKeep)
4084
+ : baseSdp;
3971
4085
  tempPc.getTransceivers().forEach((t) => {
3972
4086
  t.stop?.();
3973
4087
  });
@@ -4019,14 +4133,14 @@ const isSfuEvent = (eventName) => {
4019
4133
  };
4020
4134
  class Dispatcher {
4021
4135
  constructor() {
4022
- this.logger = getLogger(['Dispatcher']);
4136
+ this.logger = videoLoggerSystem.getLogger('Dispatcher');
4023
4137
  this.subscribers = {};
4024
4138
  this.dispatch = (message, tag = '0') => {
4025
4139
  const eventKind = message.eventPayload.oneofKind;
4026
4140
  if (!eventKind)
4027
4141
  return;
4028
4142
  const payload = message.eventPayload[eventKind];
4029
- this.logger('debug', `Dispatching ${eventKind}, tag=${tag}`, payload);
4143
+ this.logger.debug(`Dispatching ${eventKind}, tag=${tag}`, payload);
4030
4144
  const listeners = this.subscribers[eventKind];
4031
4145
  if (!listeners)
4032
4146
  return;
@@ -4035,7 +4149,7 @@ class Dispatcher {
4035
4149
  fn(payload);
4036
4150
  }
4037
4151
  catch (e) {
4038
- this.logger('warn', 'Listener failed with error', e);
4152
+ this.logger.warn('Listener failed with error', e);
4039
4153
  }
4040
4154
  }
4041
4155
  };
@@ -4071,8 +4185,8 @@ class IceTrickleBuffer {
4071
4185
  this.publisherCandidates.next(iceCandidate);
4072
4186
  }
4073
4187
  else {
4074
- const logger = getLogger(['sfu-client']);
4075
- logger('warn', `ICETrickle, Unknown peer type`, iceTrickle);
4188
+ const logger = videoLoggerSystem.getLogger('sfu-client');
4189
+ logger.warn(`ICETrickle, Unknown peer type`, iceTrickle);
4076
4190
  }
4077
4191
  };
4078
4192
  this.dispose = () => {
@@ -4086,8 +4200,8 @@ const toIceCandidate = (iceTrickle) => {
4086
4200
  return JSON.parse(iceTrickle.iceCandidate);
4087
4201
  }
4088
4202
  catch (e) {
4089
- const logger = getLogger(['sfu-client']);
4090
- logger('error', `Failed to parse ICE Trickle`, e, iceTrickle);
4203
+ const logger = videoLoggerSystem.getLogger('sfu-client');
4204
+ logger.error(`Failed to parse ICE Trickle`, e, iceTrickle);
4091
4205
  return undefined;
4092
4206
  }
4093
4207
  };
@@ -4263,7 +4377,9 @@ const updateValue = (subject, update) => {
4263
4377
  * @param handler the handler to call when the observable emits a value.
4264
4378
  * @param onError an optional error handler.
4265
4379
  */
4266
- const createSubscription = (observable, handler, onError = (error) => getLogger(['RxUtils'])('warn', 'An observable emitted an error', error)) => {
4380
+ const createSubscription = (observable, handler, onError = (error) => videoLoggerSystem
4381
+ .getLogger('RxUtils')
4382
+ .warn('An observable emitted an error', error)) => {
4267
4383
  const subscription = observable.subscribe({ next: handler, error: onError });
4268
4384
  return () => {
4269
4385
  subscription.unsubscribe();
@@ -4383,8 +4499,8 @@ class StreamVideoWriteableStateStore {
4383
4499
  * @param call the call to remove
4384
4500
  */
4385
4501
  this.unregisterCall = (call) => {
4386
- const logger = getLogger(['client-state']);
4387
- logger('trace', `Unregistering call: ${call.cid}`);
4502
+ const logger = videoLoggerSystem.getLogger('client-state');
4503
+ logger.trace(`Unregistering call: ${call.cid}`);
4388
4504
  return this.setCalls((calls) => calls.filter((c) => c !== call));
4389
4505
  };
4390
4506
  /**
@@ -4399,15 +4515,15 @@ class StreamVideoWriteableStateStore {
4399
4515
  this.connectedUserSubject.subscribe(async (user) => {
4400
4516
  // leave all calls when the user disconnects.
4401
4517
  if (!user) {
4402
- const logger = getLogger(['client-state']);
4518
+ const logger = videoLoggerSystem.getLogger('client-state');
4403
4519
  for (const call of this.calls) {
4404
4520
  if (call.state.callingState === CallingState.LEFT)
4405
4521
  continue;
4406
- logger('info', `User disconnected, leaving call: ${call.cid}`);
4522
+ logger.info(`User disconnected, leaving call: ${call.cid}`);
4407
4523
  await call
4408
4524
  .leave({ message: 'client.disconnectUser() called' })
4409
4525
  .catch((err) => {
4410
- logger('error', `Error leaving call: ${call.cid}`, err);
4526
+ logger.error(`Error leaving call: ${call.cid}`, err);
4411
4527
  });
4412
4528
  }
4413
4529
  }
@@ -4801,7 +4917,7 @@ class CallState {
4801
4917
  // We keep these tracks around until we can associate them with a participant.
4802
4918
  this.orphanedTracks = [];
4803
4919
  this.callGrantsSubject = new ReplaySubject(1);
4804
- this.logger = getLogger(['CallState']);
4920
+ this.logger = videoLoggerSystem.getLogger('CallState');
4805
4921
  /**
4806
4922
  * A list of comparators that are used to sort the participants.
4807
4923
  */
@@ -4982,7 +5098,7 @@ class CallState {
4982
5098
  this.updateParticipant = (sessionId, patch) => {
4983
5099
  const participant = this.findParticipantBySessionId(sessionId);
4984
5100
  if (!participant) {
4985
- this.logger('warn', `Participant with sessionId ${sessionId} not found`);
5101
+ this.logger.warn(`Participant with sessionId ${sessionId} not found`);
4986
5102
  return;
4987
5103
  }
4988
5104
  const thePatch = typeof patch === 'function' ? patch(participant) : patch;
@@ -5861,7 +5977,7 @@ const getSdkVersion = (sdk) => {
5861
5977
  return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
5862
5978
  };
5863
5979
 
5864
- const version = "1.35.1";
5980
+ const version = "1.36.1";
5865
5981
  const [major, minor, patch] = version.split('.');
5866
5982
  let sdkInfo = {
5867
5983
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -6064,7 +6180,7 @@ var browsers = /*#__PURE__*/Object.freeze({
6064
6180
  * Creates a new StatsReporter instance that collects metrics about the ongoing call and reports them to the state store
6065
6181
  */
6066
6182
  const createStatsReporter = ({ subscriber, publisher, state, datacenter, pollingIntervalInMs = 2000, }) => {
6067
- const logger = getLogger(['stats']);
6183
+ const logger = videoLoggerSystem.getLogger('stats');
6068
6184
  const getRawStatsForTrack = async (kind, selector) => {
6069
6185
  if (kind === 'subscriber' && subscriber) {
6070
6186
  return subscriber.getStats(selector);
@@ -6123,7 +6239,7 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
6123
6239
  participantStats[sessionId] = await getStatsForStream(kind, tracks);
6124
6240
  }
6125
6241
  catch (e) {
6126
- logger('warn', `Failed to collect ${kind} stats for ${userId}`, e);
6242
+ logger.warn(`Failed to collect ${kind} stats for ${userId}`, e);
6127
6243
  }
6128
6244
  }
6129
6245
  }
@@ -6155,7 +6271,7 @@ const createStatsReporter = ({ subscriber, publisher, state, datacenter, polling
6155
6271
  // (they are expensive) if no one is listening to them
6156
6272
  if (state.isCallStatsReportObserved) {
6157
6273
  await run().catch((e) => {
6158
- logger('debug', 'Failed to collect stats', e);
6274
+ logger.debug('Failed to collect stats', e);
6159
6275
  });
6160
6276
  }
6161
6277
  timeoutId = setTimeout(loop, pollingIntervalInMs);
@@ -6301,7 +6417,7 @@ const aggregate = (stats) => {
6301
6417
 
6302
6418
  class SfuStatsReporter {
6303
6419
  constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }) {
6304
- this.logger = getLogger(['SfuStatsReporter']);
6420
+ this.logger = videoLoggerSystem.getLogger('SfuStatsReporter');
6305
6421
  this.inputDevices = new Map();
6306
6422
  this.observeDevice = (device, kind) => {
6307
6423
  const { browserPermissionState$ } = device.state;
@@ -6352,7 +6468,7 @@ class SfuStatsReporter {
6352
6468
  // intentionally not awaiting the promise here
6353
6469
  // to avoid impeding with the ongoing actions.
6354
6470
  this.run(telemetryData).catch((err) => {
6355
- this.logger('warn', 'Failed to send telemetry data', err);
6471
+ this.logger.warn('Failed to send telemetry data', err);
6356
6472
  });
6357
6473
  };
6358
6474
  this.run = async (telemetry) => {
@@ -6411,7 +6527,7 @@ class SfuStatsReporter {
6411
6527
  clearInterval(this.intervalId);
6412
6528
  this.intervalId = setInterval(() => {
6413
6529
  this.run().catch((err) => {
6414
- this.logger('warn', 'Failed to report stats', err);
6530
+ this.logger.warn('Failed to report stats', err);
6415
6531
  });
6416
6532
  }, this.options.reporting_interval_ms);
6417
6533
  };
@@ -6428,14 +6544,14 @@ class SfuStatsReporter {
6428
6544
  };
6429
6545
  this.flush = () => {
6430
6546
  this.run().catch((err) => {
6431
- this.logger('warn', 'Failed to flush report stats', err);
6547
+ this.logger.warn('Failed to flush report stats', err);
6432
6548
  });
6433
6549
  };
6434
6550
  this.scheduleOne = (timeout) => {
6435
6551
  clearTimeout(this.timeoutId);
6436
6552
  this.timeoutId = setTimeout(() => {
6437
6553
  this.run().catch((err) => {
6438
- this.logger('warn', 'Failed to report stats', err);
6554
+ this.logger.warn('Failed to report stats', err);
6439
6555
  });
6440
6556
  }, timeout);
6441
6557
  };
@@ -6782,7 +6898,7 @@ class BasePeerConnection {
6782
6898
  /**
6783
6899
  * Constructs a new `BasePeerConnection` instance.
6784
6900
  */
6785
- constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
6901
+ constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
6786
6902
  this.isIceRestarting = false;
6787
6903
  this.isDisposed = false;
6788
6904
  this.trackIdToTrackType = new Map();
@@ -6806,12 +6922,12 @@ class BasePeerConnection {
6806
6922
  this.tryRestartIce = () => {
6807
6923
  this.restartIce().catch((e) => {
6808
6924
  const reason = 'restartICE() failed, initiating reconnect';
6809
- this.logger('error', reason, e);
6925
+ this.logger.error(reason, e);
6810
6926
  const strategy = e instanceof NegotiationError &&
6811
6927
  e.error.code === ErrorCode.PARTICIPANT_SIGNAL_LOST
6812
6928
  ? WebsocketReconnectStrategy.FAST
6813
6929
  : WebsocketReconnectStrategy.REJOIN;
6814
- this.onReconnectionNeeded?.(strategy, reason);
6930
+ this.onReconnectionNeeded?.(strategy, reason, this.peerType);
6815
6931
  });
6816
6932
  };
6817
6933
  /**
@@ -6824,7 +6940,7 @@ class BasePeerConnection {
6824
6940
  withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
6825
6941
  if (this.isDisposed)
6826
6942
  return;
6827
- this.logger('warn', `Error handling ${event}`, err);
6943
+ this.logger.warn(`Error handling ${event}`, err);
6828
6944
  });
6829
6945
  }));
6830
6946
  };
@@ -6841,7 +6957,7 @@ class BasePeerConnection {
6841
6957
  return this.pc.addIceCandidate(candidate).catch((e) => {
6842
6958
  if (this.isDisposed)
6843
6959
  return;
6844
- this.logger('warn', `ICE candidate error`, e, candidate);
6960
+ this.logger.warn(`ICE candidate error`, e, candidate);
6845
6961
  });
6846
6962
  });
6847
6963
  };
@@ -6885,7 +7001,7 @@ class BasePeerConnection {
6885
7001
  this.onIceCandidate = (e) => {
6886
7002
  const { candidate } = e;
6887
7003
  if (!candidate) {
6888
- this.logger('debug', 'null ice candidate');
7004
+ this.logger.debug('null ice candidate');
6889
7005
  return;
6890
7006
  }
6891
7007
  const iceCandidate = this.asJSON(candidate);
@@ -6894,7 +7010,7 @@ class BasePeerConnection {
6894
7010
  .catch((err) => {
6895
7011
  if (this.isDisposed)
6896
7012
  return;
6897
- this.logger('warn', `ICETrickle failed`, err);
7013
+ this.logger.warn(`ICETrickle failed`, err);
6898
7014
  });
6899
7015
  };
6900
7016
  /**
@@ -6915,7 +7031,7 @@ class BasePeerConnection {
6915
7031
  */
6916
7032
  this.onConnectionStateChange = async () => {
6917
7033
  const state = this.pc.connectionState;
6918
- this.logger('debug', `Connection state changed`, state);
7034
+ this.logger.debug(`Connection state changed`, state);
6919
7035
  if (this.tracer && (state === 'connected' || state === 'failed')) {
6920
7036
  try {
6921
7037
  const stats = await this.stats.get();
@@ -6927,7 +7043,7 @@ class BasePeerConnection {
6927
7043
  }
6928
7044
  // we can't recover from a failed connection state (contrary to ICE)
6929
7045
  if (state === 'failed') {
6930
- this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed');
7046
+ this.onReconnectionNeeded?.(WebsocketReconnectStrategy.REJOIN, 'Connection failed', this.peerType);
6931
7047
  return;
6932
7048
  }
6933
7049
  this.handleConnectionStateUpdate(state);
@@ -6937,7 +7053,7 @@ class BasePeerConnection {
6937
7053
  */
6938
7054
  this.onIceConnectionStateChange = () => {
6939
7055
  const state = this.pc.iceConnectionState;
6940
- this.logger('debug', `ICE connection state changed`, state);
7056
+ this.logger.debug(`ICE connection state changed`, state);
6941
7057
  this.handleConnectionStateUpdate(state);
6942
7058
  };
6943
7059
  this.handleConnectionStateUpdate = (state) => {
@@ -6952,13 +7068,13 @@ class BasePeerConnection {
6952
7068
  switch (state) {
6953
7069
  case 'failed':
6954
7070
  // in the `failed` state, we try to restart ICE immediately
6955
- this.logger('info', 'restartICE due to failed connection');
7071
+ this.logger.info('restartICE due to failed connection');
6956
7072
  this.tryRestartIce();
6957
7073
  break;
6958
7074
  case 'disconnected':
6959
7075
  // in the `disconnected` state, we schedule a restartICE() after a delay
6960
7076
  // as the browser might recover the connection in the meantime
6961
- this.logger('info', 'disconnected connection, scheduling restartICE');
7077
+ this.logger.info('disconnected connection, scheduling restartICE');
6962
7078
  clearTimeout(this.iceRestartTimeout);
6963
7079
  this.iceRestartTimeout = setTimeout(() => {
6964
7080
  const currentState = this.pc.iceConnectionState;
@@ -6970,7 +7086,7 @@ class BasePeerConnection {
6970
7086
  case 'connected':
6971
7087
  // in the `connected` state, we clear the ice restart timeout if it exists
6972
7088
  if (this.iceRestartTimeout) {
6973
- this.logger('info', 'connected connection, canceling restartICE');
7089
+ this.logger.info('connected connection, canceling restartICE');
6974
7090
  clearTimeout(this.iceRestartTimeout);
6975
7091
  this.iceRestartTimeout = undefined;
6976
7092
  }
@@ -6984,30 +7100,28 @@ class BasePeerConnection {
6984
7100
  const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent
6985
7101
  ? `${e.errorCode}: ${e.errorText}`
6986
7102
  : e;
6987
- this.logger('debug', 'ICE Candidate error', errorMessage);
7103
+ this.logger.debug('ICE Candidate error', errorMessage);
6988
7104
  };
6989
7105
  /**
6990
7106
  * Handles the ICE gathering state change event.
6991
7107
  */
6992
7108
  this.onIceGatherChange = () => {
6993
- this.logger('debug', `ICE Gathering State`, this.pc.iceGatheringState);
7109
+ this.logger.debug(`ICE Gathering State`, this.pc.iceGatheringState);
6994
7110
  };
6995
7111
  /**
6996
7112
  * Handles the signaling state change event.
6997
7113
  */
6998
7114
  this.onSignalingChange = () => {
6999
- this.logger('debug', `Signaling state changed`, this.pc.signalingState);
7115
+ this.logger.debug(`Signaling state changed`, this.pc.signalingState);
7000
7116
  };
7001
7117
  this.peerType = peerType;
7002
7118
  this.sfuClient = sfuClient;
7003
7119
  this.state = state;
7004
7120
  this.dispatcher = dispatcher;
7005
7121
  this.iceRestartDelay = iceRestartDelay;
7122
+ this.clientPublishOptions = clientPublishOptions;
7006
7123
  this.onReconnectionNeeded = onReconnectionNeeded;
7007
- this.logger = getLogger([
7008
- peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
7009
- tag,
7010
- ]);
7124
+ this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
7011
7125
  this.pc = this.createPeerConnection(connectionConfig);
7012
7126
  this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
7013
7127
  if (enableTracing) {
@@ -7130,7 +7244,7 @@ class TransceiverCache {
7130
7244
  }
7131
7245
 
7132
7246
  const ensureExhausted = (x, message) => {
7133
- getLogger(['helpers'])('warn', message, x);
7247
+ videoLoggerSystem.getLogger('helpers').warn(message, x);
7134
7248
  };
7135
7249
 
7136
7250
  const trackTypeToParticipantStreamKey = (trackType) => {
@@ -7340,71 +7454,6 @@ const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLa
7340
7454
  }));
7341
7455
  };
7342
7456
 
7343
- /**
7344
- * Extracts the mid from the transceiver or the SDP.
7345
- *
7346
- * @param transceiver the transceiver.
7347
- * @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
7348
- * @param sdp the SDP.
7349
- */
7350
- const extractMid = (transceiver, transceiverInitIndex, sdp) => {
7351
- if (transceiver.mid)
7352
- return transceiver.mid;
7353
- if (!sdp)
7354
- return String(transceiverInitIndex);
7355
- const track = transceiver.sender.track;
7356
- const parsedSdp = parse(sdp);
7357
- const media = parsedSdp.media.find((m) => {
7358
- return (m.type === track.kind &&
7359
- // if `msid` is not present, we assume that the track is the first one
7360
- (m.msid?.includes(track.id) ?? true));
7361
- });
7362
- if (typeof media?.mid !== 'undefined')
7363
- return String(media.mid);
7364
- if (transceiverInitIndex < 0)
7365
- return '';
7366
- return String(transceiverInitIndex);
7367
- };
7368
- /**
7369
- * Enables stereo in the answer SDP based on the offered stereo in the offer SDP.
7370
- *
7371
- * @param offerSdp the offer SDP containing the stereo configuration.
7372
- * @param answerSdp the answer SDP to be modified.
7373
- */
7374
- const enableStereo = (offerSdp, answerSdp) => {
7375
- const offeredStereoMids = new Set();
7376
- const parsedOfferSdp = parse(offerSdp);
7377
- for (const media of parsedOfferSdp.media) {
7378
- if (media.type !== 'audio')
7379
- continue;
7380
- const opus = media.rtp.find((r) => r.codec === 'opus');
7381
- if (!opus)
7382
- continue;
7383
- for (const fmtp of media.fmtp) {
7384
- if (fmtp.payload === opus.payload && fmtp.config.includes('stereo=1')) {
7385
- offeredStereoMids.add(media.mid);
7386
- }
7387
- }
7388
- }
7389
- // No stereo offered, return the original answerSdp
7390
- if (offeredStereoMids.size === 0)
7391
- return answerSdp;
7392
- const parsedAnswerSdp = parse(answerSdp);
7393
- for (const media of parsedAnswerSdp.media) {
7394
- if (media.type !== 'audio' || !offeredStereoMids.has(media.mid))
7395
- continue;
7396
- const opus = media.rtp.find((r) => r.codec === 'opus');
7397
- if (!opus)
7398
- continue;
7399
- for (const fmtp of media.fmtp) {
7400
- if (fmtp.payload === opus.payload && !fmtp.config.includes('stereo=1')) {
7401
- fmtp.config += ';stereo=1';
7402
- }
7403
- }
7404
- }
7405
- return write(parsedAnswerSdp);
7406
- };
7407
-
7408
7457
  /**
7409
7458
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
7410
7459
  *
@@ -7414,7 +7463,7 @@ class Publisher extends BasePeerConnection {
7414
7463
  /**
7415
7464
  * Constructs a new `Publisher` instance.
7416
7465
  */
7417
- constructor({ publishOptions, ...baseOptions }) {
7466
+ constructor(baseOptions, publishOptions) {
7418
7467
  super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
7419
7468
  this.transceiverCache = new TransceiverCache();
7420
7469
  this.clonedTracks = new Set();
@@ -7469,7 +7518,7 @@ class Publisher extends BasePeerConnection {
7469
7518
  params.degradationPreference = 'maintain-framerate';
7470
7519
  await transceiver.sender.setParameters(params);
7471
7520
  const trackType = publishOption.trackType;
7472
- this.logger('debug', `Added ${TrackType[trackType]} transceiver`);
7521
+ this.logger.debug(`Added ${TrackType[trackType]} transceiver`);
7473
7522
  this.transceiverCache.add({ publishOption, transceiver, options });
7474
7523
  this.trackIdToTrackType.set(track.id, trackType);
7475
7524
  await this.negotiate();
@@ -7589,16 +7638,16 @@ class Publisher extends BasePeerConnection {
7589
7638
  const { trackType, layers, publishOptionId } = videoSender;
7590
7639
  const enabledLayers = layers.filter((l) => l.active);
7591
7640
  const tag = 'Update publish quality:';
7592
- this.logger('info', `${tag} requested layers by SFU:`, enabledLayers);
7641
+ this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
7593
7642
  const transceiverId = this.transceiverCache.find((t) => t.publishOption.id === publishOptionId &&
7594
7643
  t.publishOption.trackType === trackType);
7595
7644
  const sender = transceiverId?.transceiver.sender;
7596
7645
  if (!sender) {
7597
- return this.logger('warn', `${tag} no video sender found.`);
7646
+ return this.logger.warn(`${tag} no video sender found.`);
7598
7647
  }
7599
7648
  const params = sender.getParameters();
7600
7649
  if (params.encodings.length === 0) {
7601
- return this.logger('warn', `${tag} there are no encodings set.`);
7650
+ return this.logger.warn(`${tag} there are no encodings set.`);
7602
7651
  }
7603
7652
  const codecInUse = transceiverId?.publishOption.codec?.name;
7604
7653
  const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
@@ -7642,19 +7691,19 @@ class Publisher extends BasePeerConnection {
7642
7691
  }
7643
7692
  const activeEncoders = params.encodings.filter((e) => e.active);
7644
7693
  if (!changed) {
7645
- return this.logger('info', `${tag} no change:`, activeEncoders);
7694
+ return this.logger.info(`${tag} no change:`, activeEncoders);
7646
7695
  }
7647
7696
  await sender.setParameters(params);
7648
- this.logger('info', `${tag} enabled rids:`, activeEncoders);
7697
+ this.logger.info(`${tag} enabled rids:`, activeEncoders);
7649
7698
  };
7650
7699
  /**
7651
7700
  * Restarts the ICE connection and renegotiates with the SFU.
7652
7701
  */
7653
7702
  this.restartIce = async () => {
7654
- this.logger('debug', 'Restarting ICE connection');
7703
+ this.logger.debug('Restarting ICE connection');
7655
7704
  const signalingState = this.pc.signalingState;
7656
7705
  if (this.isIceRestarting || signalingState === 'have-local-offer') {
7657
- this.logger('debug', 'ICE restart is already in progress');
7706
+ this.logger.debug('ICE restart is already in progress');
7658
7707
  return;
7659
7708
  }
7660
7709
  await this.negotiate({ iceRestart: true });
@@ -7673,7 +7722,11 @@ class Publisher extends BasePeerConnection {
7673
7722
  try {
7674
7723
  this.isIceRestarting = options?.iceRestart ?? false;
7675
7724
  await this.pc.setLocalDescription(offer);
7676
- const { sdp = '' } = offer;
7725
+ const { sdp: baseSdp = '' } = offer;
7726
+ const { dangerouslyForceCodec, fmtpLine } = this.clientPublishOptions || {};
7727
+ const sdp = dangerouslyForceCodec
7728
+ ? removeCodecsExcept(baseSdp, dangerouslyForceCodec, fmtpLine)
7729
+ : baseSdp;
7677
7730
  const { response } = await this.sfuClient.setPublisher({ sdp, tracks });
7678
7731
  if (response.error)
7679
7732
  throw new NegotiationError(response.error);
@@ -7816,13 +7869,13 @@ class Subscriber extends BasePeerConnection {
7816
7869
  * Restarts the ICE connection and renegotiates with the SFU.
7817
7870
  */
7818
7871
  this.restartIce = async () => {
7819
- this.logger('debug', 'Restarting ICE connection');
7872
+ this.logger.debug('Restarting ICE connection');
7820
7873
  if (this.pc.signalingState === 'have-remote-offer') {
7821
- this.logger('debug', 'ICE restart is already in progress');
7874
+ this.logger.debug('ICE restart is already in progress');
7822
7875
  return;
7823
7876
  }
7824
7877
  if (this.pc.connectionState === 'new') {
7825
- this.logger('debug', `ICE connection is not yet established, skipping restart.`);
7878
+ this.logger.debug(`ICE connection is not yet established, skipping restart.`);
7826
7879
  return;
7827
7880
  }
7828
7881
  const previousIsIceRestarting = this.isIceRestarting;
@@ -7845,25 +7898,25 @@ class Subscriber extends BasePeerConnection {
7845
7898
  // example: `e3f6aaf8-b03d-4911-be36-83f47d37a76a:TRACK_TYPE_VIDEO`
7846
7899
  const [trackId, rawTrackType] = primaryStream.id.split(':');
7847
7900
  const participantToUpdate = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
7848
- this.logger('debug', `[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, e.track.id, e.track);
7901
+ this.logger.debug(`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`, e.track.id, e.track);
7849
7902
  const trackDebugInfo = `${participantToUpdate?.userId} ${rawTrackType}:${trackId}`;
7850
7903
  e.track.addEventListener('mute', () => {
7851
- this.logger('info', `[onTrack]: Track muted: ${trackDebugInfo}`);
7904
+ this.logger.info(`[onTrack]: Track muted: ${trackDebugInfo}`);
7852
7905
  });
7853
7906
  e.track.addEventListener('unmute', () => {
7854
- this.logger('info', `[onTrack]: Track unmuted: ${trackDebugInfo}`);
7907
+ this.logger.info(`[onTrack]: Track unmuted: ${trackDebugInfo}`);
7855
7908
  });
7856
7909
  e.track.addEventListener('ended', () => {
7857
- this.logger('info', `[onTrack]: Track ended: ${trackDebugInfo}`);
7910
+ this.logger.info(`[onTrack]: Track ended: ${trackDebugInfo}`);
7858
7911
  this.state.removeOrphanedTrack(primaryStream.id);
7859
7912
  });
7860
7913
  const trackType = toTrackType(rawTrackType);
7861
7914
  if (!trackType) {
7862
- return this.logger('error', `Unknown track type: ${rawTrackType}`);
7915
+ return this.logger.error(`Unknown track type: ${rawTrackType}`);
7863
7916
  }
7864
7917
  this.trackIdToTrackType.set(e.track.id, trackType);
7865
7918
  if (!participantToUpdate) {
7866
- this.logger('warn', `[onTrack]: Received track for unknown participant: ${trackId}`, e);
7919
+ this.logger.warn(`[onTrack]: Received track for unknown participant: ${trackId}`, e);
7867
7920
  this.state.registerOrphanedTrack({
7868
7921
  id: primaryStream.id,
7869
7922
  trackLookupPrefix: trackId,
@@ -7874,7 +7927,7 @@ class Subscriber extends BasePeerConnection {
7874
7927
  }
7875
7928
  const streamKindProp = trackTypeToParticipantStreamKey(trackType);
7876
7929
  if (!streamKindProp) {
7877
- this.logger('error', `Unknown track type: ${rawTrackType}`);
7930
+ this.logger.error(`Unknown track type: ${rawTrackType}`);
7878
7931
  return;
7879
7932
  }
7880
7933
  // get the previous stream to dispose it later
@@ -7887,7 +7940,7 @@ class Subscriber extends BasePeerConnection {
7887
7940
  });
7888
7941
  // now, dispose the previous stream if it exists
7889
7942
  if (previousStream) {
7890
- this.logger('info', `[onTrack]: Cleaning up previous remote ${e.track.kind} tracks for userId: ${participantToUpdate.userId}`);
7943
+ this.logger.info(`[onTrack]: Cleaning up previous remote ${e.track.kind} tracks for userId: ${participantToUpdate.userId}`);
7891
7944
  previousStream.getTracks().forEach((t) => {
7892
7945
  t.stop();
7893
7946
  previousStream.removeTrack(t);
@@ -7903,6 +7956,10 @@ class Subscriber extends BasePeerConnection {
7903
7956
  const answer = await this.pc.createAnswer();
7904
7957
  if (answer.sdp) {
7905
7958
  answer.sdp = enableStereo(subscriberOffer.sdp, answer.sdp);
7959
+ const { dangerouslyForceCodec, subscriberFmtpLine } = this.clientPublishOptions || {};
7960
+ if (dangerouslyForceCodec) {
7961
+ answer.sdp = removeCodecsExcept(answer.sdp, dangerouslyForceCodec, subscriberFmtpLine);
7962
+ }
7906
7963
  }
7907
7964
  await this.pc.setLocalDescription(answer);
7908
7965
  await this.sfuClient.sendAnswer({
@@ -7914,7 +7971,7 @@ class Subscriber extends BasePeerConnection {
7914
7971
  this.pc.addEventListener('track', this.handleOnTrack);
7915
7972
  this.on('subscriberOffer', async (subscriberOffer) => {
7916
7973
  return this.negotiate(subscriberOffer).catch((err) => {
7917
- this.logger('error', `Negotiation failed.`, err);
7974
+ this.logger.error(`Negotiation failed.`, err);
7918
7975
  });
7919
7976
  });
7920
7977
  }
@@ -7931,20 +7988,20 @@ class Subscriber extends BasePeerConnection {
7931
7988
 
7932
7989
  const createWebSocketSignalChannel = (opts) => {
7933
7990
  const { endpoint, onMessage, tag, tracer } = opts;
7934
- const logger = getLogger(['SfuClientWS', tag]);
7935
- logger('debug', 'Creating signaling WS channel:', endpoint);
7991
+ const logger = videoLoggerSystem.getLogger('SfuClientWS', { tags: [tag] });
7992
+ logger.debug('Creating signaling WS channel:', endpoint);
7936
7993
  const ws = new WebSocket(endpoint);
7937
7994
  ws.binaryType = 'arraybuffer'; // do we need this?
7938
7995
  ws.addEventListener('error', (e) => {
7939
- logger('error', 'Signaling WS channel error', e);
7996
+ logger.error('Signaling WS channel error', e);
7940
7997
  tracer?.trace('signal.ws.error', e);
7941
7998
  });
7942
7999
  ws.addEventListener('close', (e) => {
7943
- logger('info', 'Signaling WS channel is closed', e);
8000
+ logger.info('Signaling WS channel is closed', e);
7944
8001
  tracer?.trace('signal.ws.close', e);
7945
8002
  });
7946
8003
  ws.addEventListener('open', (e) => {
7947
- logger('info', 'Signaling WS channel is open', e);
8004
+ logger.info('Signaling WS channel is open', e);
7948
8005
  tracer?.trace('signal.ws.open', e);
7949
8006
  });
7950
8007
  ws.addEventListener('message', (e) => {
@@ -7956,7 +8013,7 @@ const createWebSocketSignalChannel = (opts) => {
7956
8013
  }
7957
8014
  catch (err) {
7958
8015
  const message = 'Failed to decode a message. Check whether the Proto models match.';
7959
- logger('error', message, { event: e, error: err });
8016
+ logger.error(message, { event: e, error: err });
7960
8017
  tracer?.trace('signal.ws.message.error', message);
7961
8018
  }
7962
8019
  });
@@ -8157,14 +8214,14 @@ class StreamSfuClient {
8157
8214
  this.close = (code = StreamSfuClient.NORMAL_CLOSURE, reason) => {
8158
8215
  this.isClosingClean = code !== StreamSfuClient.ERROR_CONNECTION_UNHEALTHY;
8159
8216
  if (this.signalWs.readyState === WebSocket.OPEN) {
8160
- this.logger('debug', `Closing SFU WS connection: ${code} - ${reason}`);
8217
+ this.logger.debug(`Closing SFU WS connection: ${code} - ${reason}`);
8161
8218
  this.signalWs.close(code, `js-client: ${reason}`);
8162
8219
  this.signalWs.removeEventListener('close', this.handleWebSocketClose);
8163
8220
  }
8164
8221
  this.dispose();
8165
8222
  };
8166
8223
  this.dispose = () => {
8167
- this.logger('debug', 'Disposing SFU client');
8224
+ this.logger.debug('Disposing SFU client');
8168
8225
  this.unsubscribeIceTrickle();
8169
8226
  this.unsubscribeNetworkChanged();
8170
8227
  clearInterval(this.keepAliveInterval);
@@ -8184,7 +8241,7 @@ class StreamSfuClient {
8184
8241
  await this.notifyLeave(reason);
8185
8242
  }
8186
8243
  catch (err) {
8187
- this.logger('debug', 'Error notifying SFU about leaving call', err);
8244
+ this.logger.debug('Error notifying SFU about leaving call', err);
8188
8245
  }
8189
8246
  this.close(StreamSfuClient.NORMAL_CLOSURE, reason.substring(0, 115));
8190
8247
  };
@@ -8305,10 +8362,10 @@ class StreamSfuClient {
8305
8362
  await this.signalReady(); // wait for the signal ws to be open
8306
8363
  const msgJson = SfuRequest.toJson(message);
8307
8364
  if (this.signalWs.readyState !== WebSocket.OPEN) {
8308
- this.logger('debug', 'Signal WS is not open. Skipping message', msgJson);
8365
+ this.logger.debug('Signal WS is not open. Skipping message', msgJson);
8309
8366
  return;
8310
8367
  }
8311
- this.logger('debug', `Sending message to: ${this.edgeName}`, msgJson);
8368
+ this.logger.debug(`Sending message to: ${this.edgeName}`, msgJson);
8312
8369
  this.signalWs.send(SfuRequest.toBinary(message));
8313
8370
  };
8314
8371
  this.keepAlive = () => {
@@ -8316,7 +8373,7 @@ class StreamSfuClient {
8316
8373
  timers.clearInterval(this.keepAliveInterval);
8317
8374
  this.keepAliveInterval = timers.setInterval(() => {
8318
8375
  this.ping().catch((e) => {
8319
- this.logger('error', 'Error sending healthCheckRequest to SFU', e);
8376
+ this.logger.error('Error sending healthCheckRequest to SFU', e);
8320
8377
  });
8321
8378
  }, this.pingIntervalInMs);
8322
8379
  };
@@ -8339,7 +8396,7 @@ class StreamSfuClient {
8339
8396
  this.edgeName = server.edge_name;
8340
8397
  this.joinResponseTimeout = joinResponseTimeout;
8341
8398
  this.tag = tag;
8342
- this.logger = getLogger(['SfuClient', tag]);
8399
+ this.logger = videoLoggerSystem.getLogger('SfuClient', { tags: [tag] });
8343
8400
  this.tracer = enableTracing
8344
8401
  ? new Tracer(`${tag}-${this.edgeName}`)
8345
8402
  : undefined;
@@ -8348,7 +8405,8 @@ class StreamSfuClient {
8348
8405
  interceptors: [
8349
8406
  withHeaders({ Authorization: `Bearer ${token}` }),
8350
8407
  this.tracer && withRequestTracer(this.tracer.trace),
8351
- getLogLevel() === 'trace' && withRequestLogger(this.logger, 'trace'),
8408
+ this.logger.getLogLevel() === 'trace' &&
8409
+ withRequestLogger(this.logger, 'trace'),
8352
8410
  ].filter((v) => !!v),
8353
8411
  });
8354
8412
  // Special handling for the ICETrickle kind of events.
@@ -8436,13 +8494,13 @@ const watchCallRejected = (call) => {
8436
8494
  const { call: eventCall } = event;
8437
8495
  const { session: callSession } = eventCall;
8438
8496
  if (!callSession) {
8439
- call.logger('warn', 'No call session provided. Ignoring call.rejected event.', event);
8497
+ call.logger.warn('No call session provided. Ignoring call.rejected event.', event);
8440
8498
  return;
8441
8499
  }
8442
8500
  const rejectedBy = callSession.rejected_by;
8443
8501
  const { members, callingState } = call.state;
8444
8502
  if (callingState !== CallingState.RINGING) {
8445
- call.logger('info', 'Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.', event);
8503
+ call.logger.info('Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.', event);
8446
8504
  return;
8447
8505
  }
8448
8506
  if (call.isCreatedByMe) {
@@ -8450,7 +8508,7 @@ const watchCallRejected = (call) => {
8450
8508
  .filter((m) => m.user_id !== call.currentUserId)
8451
8509
  .every((m) => rejectedBy[m.user_id]);
8452
8510
  if (everyoneElseRejected) {
8453
- call.logger('info', 'everyone rejected, leaving the call');
8511
+ call.logger.info('everyone rejected, leaving the call');
8454
8512
  await call.leave({
8455
8513
  reject: true,
8456
8514
  reason: 'cancel',
@@ -8460,7 +8518,7 @@ const watchCallRejected = (call) => {
8460
8518
  }
8461
8519
  else {
8462
8520
  if (rejectedBy[eventCall.created_by.id]) {
8463
- call.logger('info', 'call creator rejected, leaving call');
8521
+ call.logger.info('call creator rejected, leaving call');
8464
8522
  await call.leave({ message: 'ring: creator rejected' });
8465
8523
  }
8466
8524
  }
@@ -8477,7 +8535,7 @@ const watchCallEnded = (call) => {
8477
8535
  call
8478
8536
  .leave({ message: 'call.ended event received', reject: false })
8479
8537
  .catch((err) => {
8480
- call.logger('error', 'Failed to leave call after call.ended ', err);
8538
+ call.logger.error('Failed to leave call after call.ended ', err);
8481
8539
  });
8482
8540
  }
8483
8541
  };
@@ -8505,7 +8563,7 @@ const watchSfuCallEnded = (call) => {
8505
8563
  await call.leave({ message: `callEnded received: ${reason}` });
8506
8564
  }
8507
8565
  catch (err) {
8508
- call.logger('error', 'Failed to leave call after being ended by the SFU', err);
8566
+ call.logger.error('Failed to leave call after being ended by the SFU', err);
8509
8567
  }
8510
8568
  });
8511
8569
  };
@@ -8588,7 +8646,7 @@ const watchLiveEnded = (dispatcher, call) => {
8588
8646
  call.state.setBackstage(true);
8589
8647
  if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
8590
8648
  call.leave({ message: 'live ended' }).catch((err) => {
8591
- call.logger('error', 'Failed to leave call after live ended', err);
8649
+ call.logger.error('Failed to leave call after live ended', err);
8592
8650
  });
8593
8651
  }
8594
8652
  });
@@ -8600,9 +8658,9 @@ const watchSfuErrorReports = (dispatcher) => {
8600
8658
  return dispatcher.on('error', (e) => {
8601
8659
  if (!e.error)
8602
8660
  return;
8603
- const logger = getLogger(['SfuClient']);
8661
+ const logger = videoLoggerSystem.getLogger('SfuClient');
8604
8662
  const { error, reconnectStrategy } = e;
8605
- logger('error', 'SFU reported error', {
8663
+ logger.error('SFU reported error', {
8606
8664
  code: ErrorCode[error.code],
8607
8665
  reconnectStrategy: WebsocketReconnectStrategy[reconnectStrategy],
8608
8666
  message: error.message,
@@ -8656,7 +8714,7 @@ const handleRemoteSoftMute = (call) => {
8656
8714
  if (cause === TrackUnpublishReason.MODERATION &&
8657
8715
  sessionId === localParticipant?.sessionId) {
8658
8716
  const logger = call.logger;
8659
- logger('info', `Local participant's ${TrackType[type]} track is muted remotely`);
8717
+ logger.info(`Local participant's ${TrackType[type]} track is muted remotely`);
8660
8718
  try {
8661
8719
  if (type === TrackType.VIDEO) {
8662
8720
  await call.camera.disable();
@@ -8669,11 +8727,11 @@ const handleRemoteSoftMute = (call) => {
8669
8727
  await call.screenShare.disable();
8670
8728
  }
8671
8729
  else {
8672
- logger('warn', 'Unsupported track type to soft mute', TrackType[type]);
8730
+ logger.warn('Unsupported track type to soft mute', TrackType[type]);
8673
8731
  }
8674
8732
  }
8675
8733
  catch (error) {
8676
- logger('error', 'Failed to stop publishing', error);
8734
+ logger.error('Failed to stop publishing', error);
8677
8735
  }
8678
8736
  }
8679
8737
  });
@@ -8994,7 +9052,7 @@ class DynascaleManager {
8994
9052
  * The viewport tracker instance.
8995
9053
  */
8996
9054
  this.viewportTracker = new ViewportTracker();
8997
- this.logger = getLogger(['DynascaleManager']);
9055
+ this.logger = videoLoggerSystem.getLogger('DynascaleManager');
8998
9056
  this.pendingSubscriptionsUpdate = null;
8999
9057
  this.videoTrackSubscriptionOverridesSubject = new BehaviorSubject({});
9000
9058
  this.videoTrackSubscriptionOverrides$ = this.videoTrackSubscriptionOverridesSubject.asObservable();
@@ -9051,7 +9109,7 @@ class DynascaleManager {
9051
9109
  this.sfuClient
9052
9110
  ?.updateSubscriptions(this.trackSubscriptions)
9053
9111
  .catch((err) => {
9054
- this.logger('debug', `Failed to update track subscriptions`, err);
9112
+ this.logger.debug(`Failed to update track subscriptions`, err);
9055
9113
  });
9056
9114
  };
9057
9115
  if (debounceType) {
@@ -9140,7 +9198,7 @@ class DynascaleManager {
9140
9198
  // is not visible (e.g., has display: none).
9141
9199
  // we treat this as "unsubscription" as we don't want to keep
9142
9200
  // consuming bandwidth for a video that is not visible on the screen.
9143
- this.logger('debug', `Ignoring 0x0 dimension`, boundParticipant);
9201
+ this.logger.debug(`Ignoring 0x0 dimension`, boundParticipant);
9144
9202
  dimension = undefined;
9145
9203
  }
9146
9204
  this.callState.updateParticipantTracks(trackType, {
@@ -9245,7 +9303,7 @@ class DynascaleManager {
9245
9303
  setTimeout(() => {
9246
9304
  videoElement.srcObject = source ?? null;
9247
9305
  videoElement.play().catch((e) => {
9248
- this.logger('warn', `Failed to play stream`, e);
9306
+ this.logger.warn(`Failed to play stream`, e);
9249
9307
  });
9250
9308
  // we add extra delay until we attempt to force-play
9251
9309
  // the participant's media stream in Firefox and Safari,
@@ -9282,13 +9340,13 @@ class DynascaleManager {
9282
9340
  return;
9283
9341
  if ('setSinkId' in audioElement) {
9284
9342
  audioElement.setSinkId(deviceId).catch((e) => {
9285
- this.logger('warn', `Can't to set AudioElement sinkId`, e);
9343
+ this.logger.warn(`Can't to set AudioElement sinkId`, e);
9286
9344
  });
9287
9345
  }
9288
9346
  if (audioContext && 'setSinkId' in audioContext) {
9289
9347
  // @ts-expect-error setSinkId is not available in all browsers
9290
9348
  audioContext.setSinkId(deviceId).catch((e) => {
9291
- this.logger('warn', `Can't to set AudioContext sinkId`, e);
9349
+ this.logger.warn(`Can't to set AudioContext sinkId`, e);
9292
9350
  });
9293
9351
  }
9294
9352
  };
@@ -9330,7 +9388,7 @@ class DynascaleManager {
9330
9388
  // we will play audio directly through the audio element in other browsers
9331
9389
  audioElement.muted = false;
9332
9390
  audioElement.play().catch((e) => {
9333
- this.logger('warn', `Failed to play audio stream`, e);
9391
+ this.logger.warn(`Failed to play audio stream`, e);
9334
9392
  });
9335
9393
  }
9336
9394
  const { selectedDevice } = this.speaker.state;
@@ -9382,7 +9440,7 @@ class DynascaleManager {
9382
9440
  if (this.audioContext?.state === 'suspended') {
9383
9441
  this.audioContext
9384
9442
  .resume()
9385
- .catch((err) => this.logger('warn', `Can't resume audio context`, err))
9443
+ .catch((err) => this.logger.warn(`Can't resume audio context`, err))
9386
9444
  .then(() => {
9387
9445
  document.removeEventListener('click', this.resumeAudioContext);
9388
9446
  });
@@ -9619,7 +9677,7 @@ class BrowserPermission {
9619
9677
  this.disposeController = new AbortController();
9620
9678
  this.wasPrompted = false;
9621
9679
  this.listeners = new Set();
9622
- this.logger = getLogger(['permissions']);
9680
+ this.logger = videoLoggerSystem.getLogger('permissions');
9623
9681
  const signal = this.disposeController.signal;
9624
9682
  this.ready = (async () => {
9625
9683
  const assumeGranted = () => {
@@ -9645,7 +9703,7 @@ class BrowserPermission {
9645
9703
  }
9646
9704
  }
9647
9705
  catch (err) {
9648
- this.logger('debug', 'Failed to query permission status', err);
9706
+ this.logger.debug('Failed to query permission status', err);
9649
9707
  assumeGranted();
9650
9708
  }
9651
9709
  })();
@@ -9684,7 +9742,7 @@ class BrowserPermission {
9684
9742
  typeof e === 'object' &&
9685
9743
  'name' in e &&
9686
9744
  (e.name === 'NotAllowedError' || e.name === 'SecurityError')) {
9687
- this.logger('info', 'Browser permission was not granted', {
9745
+ this.logger.info('Browser permission was not granted', {
9688
9746
  permission: this.permission,
9689
9747
  });
9690
9748
  this.setState('denied');
@@ -9693,7 +9751,7 @@ class BrowserPermission {
9693
9751
  }
9694
9752
  return false;
9695
9753
  }
9696
- this.logger('error', `Failed to getUserMedia`, {
9754
+ this.logger.error(`Failed to getUserMedia`, {
9697
9755
  error: e,
9698
9756
  permission: this.permission,
9699
9757
  });
@@ -9914,10 +9972,12 @@ const getAudioStream = async (trackConstraints, tracer) => {
9914
9972
  if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
9915
9973
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
9916
9974
  const { deviceId, ...relaxedConstraints } = trackConstraints;
9917
- getLogger(['devices'])('warn', 'Failed to get audio stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
9975
+ videoLoggerSystem
9976
+ .getLogger('devices')
9977
+ .warn('Failed to get audio stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
9918
9978
  return getAudioStream(relaxedConstraints);
9919
9979
  }
9920
- getLogger(['devices'])('error', 'Failed to get audio stream', {
9980
+ videoLoggerSystem.getLogger('devices').error('Failed to get audio stream', {
9921
9981
  error,
9922
9982
  constraints,
9923
9983
  });
@@ -9950,10 +10010,12 @@ const getVideoStream = async (trackConstraints, tracer) => {
9950
10010
  if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
9951
10011
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
9952
10012
  const { deviceId, ...relaxedConstraints } = trackConstraints;
9953
- getLogger(['devices'])('warn', 'Failed to get video stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
10013
+ videoLoggerSystem
10014
+ .getLogger('devices')
10015
+ .warn('Failed to get video stream, will try again with relaxed constraints', { error, constraints, relaxedConstraints });
9954
10016
  return getVideoStream(relaxedConstraints);
9955
10017
  }
9956
- getLogger(['devices'])('error', 'Failed to get video stream', {
10018
+ videoLoggerSystem.getLogger('devices').error('Failed to get video stream', {
9957
10019
  error,
9958
10020
  constraints,
9959
10021
  });
@@ -10002,7 +10064,9 @@ const getScreenShareStream = async (options, tracer) => {
10002
10064
  }
10003
10065
  catch (e) {
10004
10066
  tracer?.trace(`${tag}OnFailure`, e.name);
10005
- getLogger(['devices'])('error', 'Failed to get screen share stream', e);
10067
+ videoLoggerSystem
10068
+ .getLogger('devices')
10069
+ .error('Failed to get screen share stream', e);
10006
10070
  throw e;
10007
10071
  }
10008
10072
  };
@@ -10064,7 +10128,7 @@ class DeviceManager {
10064
10128
  this.call = call;
10065
10129
  this.state = state;
10066
10130
  this.trackType = trackType;
10067
- this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
10131
+ this.logger = videoLoggerSystem.getLogger(`${TrackType[trackType].toLowerCase()} manager`);
10068
10132
  this.setup();
10069
10133
  }
10070
10134
  setup() {
@@ -10227,6 +10291,7 @@ class DeviceManager {
10227
10291
  }
10228
10292
  }
10229
10293
  async applySettingsToStream() {
10294
+ console.log('applySettingsToStream ');
10230
10295
  await withCancellation(this.statusChangeConcurrencyTag, async (signal) => {
10231
10296
  if (this.enabled) {
10232
10297
  try {
@@ -10259,7 +10324,7 @@ class DeviceManager {
10259
10324
  const mediaStream = this.state.mediaStream;
10260
10325
  if (!mediaStream)
10261
10326
  return;
10262
- this.logger('debug', `${stopTracks ? 'Stopping' : 'Disabling'} stream`);
10327
+ this.logger.debug(`${stopTracks ? 'Stopping' : 'Disabling'} stream`);
10263
10328
  if (this.call.state.callingState === CallingState.JOINED) {
10264
10329
  await this.stopPublishStream();
10265
10330
  }
@@ -10305,7 +10370,7 @@ class DeviceManager {
10305
10370
  }
10306
10371
  }
10307
10372
  async unmuteStream() {
10308
- this.logger('debug', 'Starting stream');
10373
+ this.logger.debug('Starting stream');
10309
10374
  let stream;
10310
10375
  let rootStream;
10311
10376
  if (this.state.mediaStream &&
@@ -10386,7 +10451,7 @@ class DeviceManager {
10386
10451
  return output;
10387
10452
  })
10388
10453
  .then(chainWith(parent), (error) => {
10389
- this.logger('warn', 'Filter failed to start and will be ignored', error);
10454
+ this.logger.warn('Filter failed to start and will be ignored', error);
10390
10455
  return parent;
10391
10456
  }), rootStream);
10392
10457
  }
@@ -10409,7 +10474,7 @@ class DeviceManager {
10409
10474
  if (!isMobile() || this.trackType !== TrackType.VIDEO)
10410
10475
  return;
10411
10476
  this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
10412
- this.logger('warn', 'Error while notifying track mute state', err);
10477
+ this.logger.warn('Error while notifying track mute state', err);
10413
10478
  });
10414
10479
  };
10415
10480
  stream.getTracks().forEach((track) => {
@@ -10473,7 +10538,7 @@ class DeviceManager {
10473
10538
  }
10474
10539
  }
10475
10540
  catch (err) {
10476
- this.logger('warn', 'Unexpected error while handling disconnected or replaced device', err);
10541
+ this.logger.warn('Unexpected error while handling disconnected or replaced device', err);
10477
10542
  }
10478
10543
  }));
10479
10544
  }
@@ -10680,7 +10745,7 @@ class CameraManager extends DeviceManager {
10680
10745
  */
10681
10746
  async selectDirection(direction) {
10682
10747
  if (!this.isDirectionSupportedByDevice()) {
10683
- this.logger('warn', 'Setting direction is not supported on this device');
10748
+ this.logger.warn('Setting direction is not supported on this device');
10684
10749
  return;
10685
10750
  }
10686
10751
  if (isReactNative()) {
@@ -10731,7 +10796,7 @@ class CameraManager extends DeviceManager {
10731
10796
  }
10732
10797
  catch (error) {
10733
10798
  // couldn't enable device, target resolution will be applied the next time user attempts to start the device
10734
- this.logger('warn', 'could not apply target resolution', error);
10799
+ this.logger.warn('could not apply target resolution', error);
10735
10800
  }
10736
10801
  }
10737
10802
  if (this.enabled && this.state.mediaStream) {
@@ -10742,7 +10807,7 @@ class CameraManager extends DeviceManager {
10742
10807
  if (width !== this.targetResolution.width ||
10743
10808
  height !== this.targetResolution.height) {
10744
10809
  await this.applySettingsToStream();
10745
- this.logger('debug', `${width}x${height} target resolution applied to media stream`);
10810
+ this.logger.debug(`${width}x${height} target resolution applied to media stream`);
10746
10811
  }
10747
10812
  }
10748
10813
  }
@@ -11006,8 +11071,8 @@ class RNSpeechDetector {
11006
11071
  };
11007
11072
  }
11008
11073
  catch (error) {
11009
- const logger = getLogger(['RNSpeechDetector']);
11010
- logger('error', 'error handling permissions: ', error);
11074
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
11075
+ logger.error('error handling permissions: ', error);
11011
11076
  return () => { };
11012
11077
  }
11013
11078
  }
@@ -11091,8 +11156,8 @@ class RNSpeechDetector {
11091
11156
  }
11092
11157
  }
11093
11158
  catch (error) {
11094
- const logger = getLogger(['RNSpeechDetector']);
11095
- logger('error', 'error checking audio level from stats', error);
11159
+ const logger = videoLoggerSystem.getLogger('RNSpeechDetector');
11160
+ logger.error('error checking audio level from stats', error);
11096
11161
  }
11097
11162
  };
11098
11163
  // Call checkAudioLevel periodically (every 100ms)
@@ -11152,7 +11217,7 @@ class MicrophoneManager extends AudioDeviceManager {
11152
11217
  }
11153
11218
  }
11154
11219
  catch (err) {
11155
- this.logger('warn', 'Could not enable speaking while muted', err);
11220
+ this.logger.warn('Could not enable speaking while muted', err);
11156
11221
  }
11157
11222
  }));
11158
11223
  this.subscriptions.push(createSubscription(this.call.state.callingState$, (callingState) => {
@@ -11175,7 +11240,7 @@ class MicrophoneManager extends AudioDeviceManager {
11175
11240
  }
11176
11241
  })
11177
11242
  .catch((err) => {
11178
- this.logger('warn', `Failed to enable noise cancellation`, err);
11243
+ this.logger.warn(`Failed to enable noise cancellation`, err);
11179
11244
  return this.call.notifyNoiseCancellationStopped();
11180
11245
  });
11181
11246
  }
@@ -11183,7 +11248,7 @@ class MicrophoneManager extends AudioDeviceManager {
11183
11248
  this.noiseCancellationRegistration
11184
11249
  .then(() => this.noiseCancellation?.disable())
11185
11250
  .catch((err) => {
11186
- this.logger('warn', `Failed to disable noise cancellation`, err);
11251
+ this.logger.warn(`Failed to disable noise cancellation`, err);
11187
11252
  });
11188
11253
  }
11189
11254
  }));
@@ -11212,12 +11277,12 @@ class MicrophoneManager extends AudioDeviceManager {
11212
11277
  this.call.tracer.trace('noiseCancellation.enabled', enabled);
11213
11278
  if (enabled) {
11214
11279
  this.call.notifyNoiseCancellationStarting().catch((err) => {
11215
- this.logger('warn', `notifyNoiseCancellationStart failed`, err);
11280
+ this.logger.warn(`notifyNoiseCancellationStart failed`, err);
11216
11281
  });
11217
11282
  }
11218
11283
  else {
11219
11284
  this.call.notifyNoiseCancellationStopped().catch((err) => {
11220
- this.logger('warn', `notifyNoiseCancellationStop failed`, err);
11285
+ this.logger.warn(`notifyNoiseCancellationStop failed`, err);
11221
11286
  });
11222
11287
  }
11223
11288
  });
@@ -11246,9 +11311,9 @@ class MicrophoneManager extends AudioDeviceManager {
11246
11311
  }
11247
11312
  }
11248
11313
  catch (e) {
11249
- this.logger('warn', 'Failed to enable noise cancellation', e);
11314
+ this.logger.warn('Failed to enable noise cancellation', e);
11250
11315
  await this.disableNoiseCancellation().catch((err) => {
11251
- this.logger('warn', 'Failed to disable noise cancellation', err);
11316
+ this.logger.warn('Failed to disable noise cancellation', err);
11252
11317
  });
11253
11318
  throw e;
11254
11319
  }
@@ -11261,7 +11326,7 @@ class MicrophoneManager extends AudioDeviceManager {
11261
11326
  .then(() => this.noiseCancellation?.disable())
11262
11327
  .then(() => this.noiseCancellationChangeUnsubscribe?.())
11263
11328
  .catch((err) => {
11264
- this.logger('warn', 'Failed to unregister noise cancellation', err);
11329
+ this.logger.warn('Failed to unregister noise cancellation', err);
11265
11330
  });
11266
11331
  this.call.tracer.trace('noiseCancellation.disabled', true);
11267
11332
  await this.call.notifyNoiseCancellationStopped();
@@ -11769,9 +11834,9 @@ class Call {
11769
11834
  return;
11770
11835
  const currentUserId = this.currentUserId;
11771
11836
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
11772
- this.logger('info', 'Leaving call because of being blocked');
11837
+ this.logger.info('Leaving call because of being blocked');
11773
11838
  await this.leave({ message: 'user blocked' }).catch((err) => {
11774
- this.logger('error', 'Error leaving call after being blocked', err);
11839
+ this.logger.error('Error leaving call after being blocked', err);
11775
11840
  });
11776
11841
  }
11777
11842
  }));
@@ -11807,7 +11872,7 @@ class Call {
11807
11872
  if ((isAcceptedElsewhere || isRejectedByMe) &&
11808
11873
  !hasPending(this.joinLeaveConcurrencyTag)) {
11809
11874
  this.leave().catch(() => {
11810
- this.logger('error', 'Could not leave a call that was accepted or rejected elsewhere');
11875
+ this.logger.error('Could not leave a call that was accepted or rejected elsewhere');
11811
11876
  });
11812
11877
  }
11813
11878
  }));
@@ -11887,7 +11952,7 @@ class Call {
11887
11952
  }
11888
11953
  }
11889
11954
  catch (err) {
11890
- this.logger('error', `Can't disable mic/camera/screenshare after revoked permissions`, err);
11955
+ this.logger.error(`Can't disable mic/camera/screenshare after revoked permissions`, err);
11891
11956
  }
11892
11957
  }
11893
11958
  };
@@ -12148,13 +12213,13 @@ class Call {
12148
12213
  maxJoinRetries = Math.max(maxJoinRetries, 1);
12149
12214
  for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
12150
12215
  try {
12151
- this.logger('trace', `Joining call (${attempt})`, this.cid);
12216
+ this.logger.trace(`Joining call (${attempt})`, this.cid);
12152
12217
  await this.doJoin(data);
12153
12218
  delete joinData.migrating_from;
12154
12219
  break;
12155
12220
  }
12156
12221
  catch (err) {
12157
- this.logger('warn', `Failed to join call (${attempt})`, this.cid);
12222
+ this.logger.warn(`Failed to join call (${attempt})`, this.cid);
12158
12223
  if (err instanceof ErrorFromResponse && err.unrecoverable) {
12159
12224
  // if the error is unrecoverable, we should not retry as that signals
12160
12225
  // that connectivity is good, but the coordinator doesn't allow the user
@@ -12184,7 +12249,7 @@ class Call {
12184
12249
  const connectStartTime = Date.now();
12185
12250
  const callingState = this.state.callingState;
12186
12251
  this.joinCallData = data;
12187
- this.logger('debug', 'Starting join flow');
12252
+ this.logger.debug('Starting join flow');
12188
12253
  this.state.setCallingState(CallingState.JOINING);
12189
12254
  const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
12190
12255
  const performingRejoin = this.reconnectStrategy === WebsocketReconnectStrategy.REJOIN;
@@ -12235,9 +12300,10 @@ class Call {
12235
12300
  // prepare a generic SDP and send it to the SFU.
12236
12301
  // these are throw-away SDPs that the SFU will use to determine
12237
12302
  // the capabilities of the client (codec support, etc.)
12303
+ const { dangerouslyForceCodec, fmtpLine, subscriberFmtpLine } = this.clientPublishOptions || {};
12238
12304
  const [subscriberSdp, publisherSdp] = await Promise.all([
12239
- getGenericSdp('recvonly'),
12240
- getGenericSdp('sendonly'),
12305
+ getGenericSdp('recvonly', dangerouslyForceCodec, subscriberFmtpLine),
12306
+ getGenericSdp('sendonly', dangerouslyForceCodec, fmtpLine),
12241
12307
  ]);
12242
12308
  const isReconnecting = this.reconnectStrategy !== WebsocketReconnectStrategy.UNSPECIFIED;
12243
12309
  const reconnectDetails = isReconnecting
@@ -12269,7 +12335,7 @@ class Call {
12269
12335
  }
12270
12336
  }
12271
12337
  catch (error) {
12272
- this.logger('warn', 'Join SFU request failed', error);
12338
+ this.logger.warn('Join SFU request failed', error);
12273
12339
  sfuClient.close(StreamSfuClient.JOIN_FAILED, 'Join request failed, connection considered unhealthy');
12274
12340
  // restore the previous call state if the join-flow fails
12275
12341
  this.state.setCallingState(callingState);
@@ -12326,7 +12392,7 @@ class Call {
12326
12392
  // reset the reconnect strategy to unspecified after a successful reconnection
12327
12393
  this.reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
12328
12394
  this.reconnectReason = '';
12329
- this.logger('info', `Joined call ${this.cid}`);
12395
+ this.logger.info(`Joined call ${this.cid}`);
12330
12396
  };
12331
12397
  /**
12332
12398
  * Prepares Reconnect Details object.
@@ -12429,20 +12495,22 @@ class Call {
12429
12495
  if (closePreviousInstances && this.subscriber) {
12430
12496
  this.subscriber.dispose();
12431
12497
  }
12432
- this.subscriber = new Subscriber({
12498
+ const basePeerConnectionOptions = {
12433
12499
  sfuClient,
12434
12500
  dispatcher: this.dispatcher,
12435
12501
  state: this.state,
12436
12502
  connectionConfig,
12437
12503
  tag: sfuClient.tag,
12438
12504
  enableTracing,
12439
- onReconnectionNeeded: (kind, reason) => {
12505
+ clientPublishOptions: this.clientPublishOptions,
12506
+ onReconnectionNeeded: (kind, reason, peerType) => {
12440
12507
  this.reconnect(kind, reason).catch((err) => {
12441
- const message = `[Reconnect] Error reconnecting after a subscriber error: ${reason}`;
12442
- this.logger('warn', message, err);
12508
+ const message = `[Reconnect] Error reconnecting, after a ${PeerType[peerType]} error: ${reason}`;
12509
+ this.logger.warn(message, err);
12443
12510
  });
12444
12511
  },
12445
- });
12512
+ };
12513
+ this.subscriber = new Subscriber(basePeerConnectionOptions);
12446
12514
  // anonymous users can't publish anything hence, there is no need
12447
12515
  // to create Publisher Peer Connection for them
12448
12516
  const isAnonymous = this.streamClient.user?.type === 'anonymous';
@@ -12450,21 +12518,7 @@ class Call {
12450
12518
  if (closePreviousInstances && this.publisher) {
12451
12519
  this.publisher.dispose();
12452
12520
  }
12453
- this.publisher = new Publisher({
12454
- sfuClient,
12455
- dispatcher: this.dispatcher,
12456
- state: this.state,
12457
- connectionConfig,
12458
- publishOptions,
12459
- tag: sfuClient.tag,
12460
- enableTracing,
12461
- onReconnectionNeeded: (kind, reason) => {
12462
- this.reconnect(kind, reason).catch((err) => {
12463
- const message = `[Reconnect] Error reconnecting after a publisher error: ${reason}`;
12464
- this.logger('warn', message, err);
12465
- });
12466
- },
12467
- });
12521
+ this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
12468
12522
  }
12469
12523
  this.statsReporter?.stop();
12470
12524
  if (this.statsReportingIntervalInMs > 0) {
@@ -12530,7 +12584,7 @@ class Call {
12530
12584
  * @param reason the reason for the closure.
12531
12585
  */
12532
12586
  this.handleSfuSignalClose = (sfuClient, reason) => {
12533
- this.logger('debug', '[Reconnect] SFU signal connection closed');
12587
+ this.logger.debug('[Reconnect] SFU signal connection closed');
12534
12588
  const { callingState } = this.state;
12535
12589
  if (
12536
12590
  // SFU WS closed before we finished current join,
@@ -12551,7 +12605,7 @@ class Call {
12551
12605
  ? WebsocketReconnectStrategy.FAST
12552
12606
  : WebsocketReconnectStrategy.REJOIN;
12553
12607
  this.reconnect(strategy, reason).catch((err) => {
12554
- this.logger('warn', '[Reconnect] Error reconnecting', err);
12608
+ this.logger.warn('[Reconnect] Error reconnecting', err);
12555
12609
  });
12556
12610
  };
12557
12611
  /**
@@ -12587,7 +12641,7 @@ class Call {
12587
12641
  const shouldGiveUpReconnecting = this.disconnectionTimeoutSeconds > 0 &&
12588
12642
  reconnectingTime / 1000 > this.disconnectionTimeoutSeconds;
12589
12643
  if (shouldGiveUpReconnecting) {
12590
- this.logger('warn', '[Reconnect] Stopping reconnection attempts after reaching disconnection timeout');
12644
+ this.logger.warn('[Reconnect] Stopping reconnection attempts after reaching disconnection timeout');
12591
12645
  await markAsReconnectingFailed();
12592
12646
  return;
12593
12647
  }
@@ -12599,11 +12653,11 @@ class Call {
12599
12653
  try {
12600
12654
  // wait until the network is available
12601
12655
  await this.networkAvailableTask?.promise;
12602
- this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
12656
+ this.logger.info(`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`);
12603
12657
  switch (this.reconnectStrategy) {
12604
12658
  case WebsocketReconnectStrategy.UNSPECIFIED:
12605
12659
  case WebsocketReconnectStrategy.DISCONNECT:
12606
- this.logger('debug', `[Reconnect] No-op strategy ${currentStrategy}`);
12660
+ this.logger.debug(`[Reconnect] No-op strategy ${currentStrategy}`);
12607
12661
  break;
12608
12662
  case WebsocketReconnectStrategy.FAST:
12609
12663
  await this.reconnectFast();
@@ -12622,13 +12676,13 @@ class Call {
12622
12676
  }
12623
12677
  catch (error) {
12624
12678
  if (this.state.callingState === CallingState.OFFLINE) {
12625
- this.logger('debug', `[Reconnect] Can't reconnect while offline, stopping reconnection attempts`);
12679
+ this.logger.debug(`[Reconnect] Can't reconnect while offline, stopping reconnection attempts`);
12626
12680
  break;
12627
12681
  // we don't need to handle the error if the call is offline
12628
12682
  // network change event will trigger the reconnection
12629
12683
  }
12630
12684
  if (error instanceof ErrorFromResponse && error.unrecoverable) {
12631
- this.logger('warn', `[Reconnect] Can't reconnect due to coordinator unrecoverable error`, error);
12685
+ this.logger.warn(`[Reconnect] Can't reconnect due to coordinator unrecoverable error`, error);
12632
12686
  await markAsReconnectingFailed();
12633
12687
  return;
12634
12688
  }
@@ -12649,12 +12703,12 @@ class Call {
12649
12703
  ? WebsocketReconnectStrategy.REJOIN
12650
12704
  : WebsocketReconnectStrategy.FAST;
12651
12705
  this.reconnectStrategy = nextStrategy;
12652
- this.logger('info', `[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`, error);
12706
+ this.logger.info(`[Reconnect] ${currentStrategy} (${this.reconnectAttempts}) failed. Attempting with ${WebsocketReconnectStrategy[nextStrategy]}`, error);
12653
12707
  }
12654
12708
  } while (this.state.callingState !== CallingState.JOINED &&
12655
12709
  this.state.callingState !== CallingState.RECONNECTING_FAILED &&
12656
12710
  this.state.callingState !== CallingState.LEFT);
12657
- this.logger('info', '[Reconnect] Reconnection flow finished');
12711
+ this.logger.info('[Reconnect] Reconnection flow finished');
12658
12712
  });
12659
12713
  };
12660
12714
  /**
@@ -12736,7 +12790,7 @@ class Call {
12736
12790
  this.registerReconnectHandlers = () => {
12737
12791
  // handles the legacy "goAway" event
12738
12792
  const unregisterGoAway = this.on('goAway', () => {
12739
- this.reconnect(WebsocketReconnectStrategy.MIGRATE, 'goAway').catch((err) => this.logger('warn', '[Reconnect] Error reconnecting', err));
12793
+ this.reconnect(WebsocketReconnectStrategy.MIGRATE, 'goAway').catch((err) => this.logger.warn('[Reconnect] Error reconnecting', err));
12740
12794
  });
12741
12795
  // handles the "error" event, through which the SFU can request a reconnect
12742
12796
  const unregisterOnError = this.on('error', (e) => {
@@ -12745,19 +12799,19 @@ class Call {
12745
12799
  return;
12746
12800
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
12747
12801
  this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
12748
- this.logger('warn', `Can't leave call after disconnect request`, err);
12802
+ this.logger.warn(`Can't leave call after disconnect request`, err);
12749
12803
  });
12750
12804
  }
12751
12805
  else {
12752
12806
  this.reconnect(strategy, error?.message || 'SFU Error').catch((err) => {
12753
- this.logger('warn', '[Reconnect] Error reconnecting', err);
12807
+ this.logger.warn('[Reconnect] Error reconnecting', err);
12754
12808
  });
12755
12809
  }
12756
12810
  });
12757
12811
  const unregisterNetworkChanged = this.streamClient.on('network.changed', (e) => {
12758
12812
  this.tracer.trace('network.changed', e);
12759
12813
  if (!e.online) {
12760
- this.logger('debug', '[Reconnect] Going offline');
12814
+ this.logger.debug('[Reconnect] Going offline');
12761
12815
  if (!this.hasJoinedOnce)
12762
12816
  return;
12763
12817
  this.lastOfflineTimestamp = Date.now();
@@ -12774,7 +12828,7 @@ class Call {
12774
12828
  }
12775
12829
  }
12776
12830
  this.reconnect(strategy, 'Going online').catch((err) => {
12777
- this.logger('warn', '[Reconnect] Error reconnecting after going online', err);
12831
+ this.logger.warn('[Reconnect] Error reconnecting after going online', err);
12778
12832
  });
12779
12833
  });
12780
12834
  this.networkAvailableTask = networkAvailableTask;
@@ -12782,7 +12836,7 @@ class Call {
12782
12836
  this.state.setCallingState(CallingState.OFFLINE);
12783
12837
  }
12784
12838
  else {
12785
- this.logger('debug', '[Reconnect] Going online');
12839
+ this.logger.debug('[Reconnect] Going online');
12786
12840
  this.sfuClient?.close(StreamSfuClient.DISPOSE_OLD_SOCKET, 'Closing WS to reconnect after going online');
12787
12841
  // we went online, release the previous waiters and reset the state
12788
12842
  this.networkAvailableTask?.resolve();
@@ -12944,10 +12998,10 @@ class Call {
12944
12998
  * @param options the options to use.
12945
12999
  */
12946
13000
  this.updatePublishOptions = (options) => {
12947
- this.logger('warn', '[call.updatePublishOptions]: You are manually overriding the publish options for this call. ' +
13001
+ this.logger.warn('[call.updatePublishOptions]: You are manually overriding the publish options for this call. ' +
12948
13002
  'This is not recommended, and it can cause call stability/compatibility issues. Use with caution.');
12949
13003
  if (this.state.callingState === CallingState.JOINED) {
12950
- this.logger('warn', 'Updating publish options after joining the call does not have an effect');
13004
+ this.logger.warn('Updating publish options after joining the call does not have an effect');
12951
13005
  }
12952
13006
  this.clientPublishOptions = { ...this.clientPublishOptions, ...options };
12953
13007
  };
@@ -12958,7 +13012,7 @@ class Call {
12958
13012
  */
12959
13013
  this.notifyNoiseCancellationStarting = async () => {
12960
13014
  return this.sfuClient?.startNoiseCancellation().catch((err) => {
12961
- this.logger('warn', 'Failed to notify start of noise cancellation', err);
13015
+ this.logger.warn('Failed to notify start of noise cancellation', err);
12962
13016
  });
12963
13017
  };
12964
13018
  /**
@@ -12968,7 +13022,7 @@ class Call {
12968
13022
  */
12969
13023
  this.notifyNoiseCancellationStopped = async () => {
12970
13024
  return this.sfuClient?.stopNoiseCancellation().catch((err) => {
12971
- this.logger('warn', 'Failed to notify stop of noise cancellation', err);
13025
+ this.logger.warn('Failed to notify stop of noise cancellation', err);
12972
13026
  });
12973
13027
  };
12974
13028
  /**
@@ -13402,7 +13456,7 @@ class Call {
13402
13456
  reason: 'timeout',
13403
13457
  message: `ringing timeout - ${this.isCreatedByMe ? 'no one accepted' : `user didn't interact with incoming call screen`}`,
13404
13458
  }).catch((err) => {
13405
- this.logger('error', 'Failed to drop call', err);
13459
+ this.logger.error('Failed to drop call', err);
13406
13460
  });
13407
13461
  }, timeoutInMs);
13408
13462
  };
@@ -13514,10 +13568,10 @@ class Call {
13514
13568
  */
13515
13569
  this.applyDeviceConfig = async (settings, publish) => {
13516
13570
  await this.camera.apply(settings.video, publish).catch((err) => {
13517
- this.logger('warn', 'Camera init failed', err);
13571
+ this.logger.warn('Camera init failed', err);
13518
13572
  });
13519
13573
  await this.microphone.apply(settings.audio, publish).catch((err) => {
13520
- this.logger('warn', 'Mic init failed', err);
13574
+ this.logger.warn('Mic init failed', err);
13521
13575
  });
13522
13576
  };
13523
13577
  /**
@@ -13669,7 +13723,7 @@ class Call {
13669
13723
  this.streamClient = streamClient;
13670
13724
  this.clientStore = clientStore;
13671
13725
  this.streamClientBasePath = `/call/${this.type}/${this.id}`;
13672
- this.logger = getLogger(['Call']);
13726
+ this.logger = videoLoggerSystem.getLogger('Call');
13673
13727
  const callTypeConfig = CallTypes.get(type);
13674
13728
  const participantSorter = sortParticipantsBy || callTypeConfig.options.sortParticipantsBy;
13675
13729
  if (participantSorter) {
@@ -13756,7 +13810,7 @@ const APIErrorCodes = {
13756
13810
  class StableWSConnection {
13757
13811
  constructor(client) {
13758
13812
  this._log = (msg, extra = {}, level = 'info') => {
13759
- this.client.logger(level, `connection:${msg}`, extra);
13813
+ this.client.logger[level](`connection:${msg}`, extra);
13760
13814
  };
13761
13815
  this.setClient = (client) => {
13762
13816
  this.client = client;
@@ -13802,12 +13856,12 @@ class StableWSConnection {
13802
13856
  return;
13803
13857
  const user = this.client.user;
13804
13858
  if (!user) {
13805
- this.client.logger('error', `User not set, can't connect to WS`);
13859
+ this.client.logger.error(`User not set, can't connect to WS`);
13806
13860
  return;
13807
13861
  }
13808
13862
  const token = this.client._getToken();
13809
13863
  if (!token) {
13810
- this.client.logger('error', `Token not set, can't connect authenticate`);
13864
+ this.client.logger.error(`Token not set, can't connect authenticate`);
13811
13865
  return;
13812
13866
  }
13813
13867
  const authMessage = JSON.stringify({
@@ -14444,7 +14498,7 @@ class TokenManager {
14444
14498
  }
14445
14499
 
14446
14500
  const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, timeout = 2000, maxAttempts = 3) => {
14447
- const logger = getLogger(['location-hint']);
14501
+ const logger = videoLoggerSystem.getLogger('location-hint');
14448
14502
  let attempt = 0;
14449
14503
  let locationHint = 'ERR';
14450
14504
  do {
@@ -14456,11 +14510,11 @@ const getLocationHint = async (hintUrl = `https://hint.stream-io-video.com/`, ti
14456
14510
  signal: abortController.signal,
14457
14511
  });
14458
14512
  const awsPop = response.headers.get('x-amz-cf-pop') || 'ERR';
14459
- logger('debug', `Location header: ${awsPop}`);
14513
+ logger.debug(`Location header: ${awsPop}`);
14460
14514
  locationHint = awsPop.substring(0, 3); // AMS1-P2 -> AMS
14461
14515
  }
14462
14516
  catch (e) {
14463
- logger('warn', `Failed to get location hint from ${hintUrl}`, e);
14517
+ logger.warn(`Failed to get location hint from ${hintUrl}`, e);
14464
14518
  locationHint = 'ERR';
14465
14519
  }
14466
14520
  finally {
@@ -14521,14 +14575,14 @@ class StreamClient {
14521
14575
  * If the user id remains the same we don't throw error
14522
14576
  */
14523
14577
  if (this.userID === user.id && this.connectUserTask) {
14524
- this.logger('warn', 'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
14578
+ this.logger.warn('Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.');
14525
14579
  return this.connectUserTask;
14526
14580
  }
14527
14581
  if (this.userID) {
14528
14582
  throw new Error('Use client.disconnect() before trying to connect as a different user. connectUser was called twice.');
14529
14583
  }
14530
14584
  if ((this.secret || this.node) && !this.options.allowServerSideConnect) {
14531
- this.logger('warn', 'Please do not use connectUser server side. Use our @stream-io/node-sdk instead: https://getstream.io/video/docs/api/');
14585
+ this.logger.warn('Please do not use connectUser server side. Use our @stream-io/node-sdk instead: https://getstream.io/video/docs/api/');
14532
14586
  }
14533
14587
  // we generate the client id client side
14534
14588
  this.userID = user.id;
@@ -14586,11 +14640,11 @@ class StreamClient {
14586
14640
  }
14587
14641
  const wsPromise = this.wsPromiseSafe?.();
14588
14642
  if (this.wsConnection?.isConnecting && wsPromise) {
14589
- this.logger('info', 'client:openConnection() - connection already in progress');
14643
+ this.logger.info('client:openConnection() - connection already in progress');
14590
14644
  return await wsPromise;
14591
14645
  }
14592
14646
  if (this.wsConnection?.isHealthy && this._hasConnectionID()) {
14593
- this.logger('info', 'client:openConnection() - openConnection called twice, healthy connection already exists');
14647
+ this.logger.info('client:openConnection() - openConnection called twice, healthy connection already exists');
14594
14648
  return;
14595
14649
  }
14596
14650
  this._setupConnectionIdPromise();
@@ -14606,7 +14660,7 @@ class StreamClient {
14606
14660
  * https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
14607
14661
  */
14608
14662
  this.disconnectUser = async (timeout) => {
14609
- this.logger('info', 'client:disconnect() - Disconnecting the client');
14663
+ this.logger.info('client:disconnect() - Disconnecting the client');
14610
14664
  // remove the user specific fields
14611
14665
  delete this.user;
14612
14666
  delete this._user;
@@ -14653,7 +14707,7 @@ class StreamClient {
14653
14707
  if (!this.listeners[eventName]) {
14654
14708
  this.listeners[eventName] = [];
14655
14709
  }
14656
- this.logger('debug', `Adding listener for ${eventName} event`);
14710
+ this.logger.debug(`Adding listener for ${eventName} event`);
14657
14711
  this.listeners[eventName]?.push(callback);
14658
14712
  return () => {
14659
14713
  this.off(eventName, callback);
@@ -14666,7 +14720,7 @@ class StreamClient {
14666
14720
  if (!this.listeners[eventName]) {
14667
14721
  this.listeners[eventName] = [];
14668
14722
  }
14669
- this.logger('debug', `Removing listener for ${eventName} event`);
14723
+ this.logger.debug(`Removing listener for ${eventName} event`);
14670
14724
  this.listeners[eventName] = this.listeners[eventName]?.filter((value) => value !== callback);
14671
14725
  };
14672
14726
  /**
@@ -14680,17 +14734,17 @@ class StreamClient {
14680
14734
  }));
14681
14735
  };
14682
14736
  this._logApiRequest = (type, url, data, config) => {
14683
- if (getLogLevel() !== 'trace')
14737
+ if (this.logger.getLogLevel() !== 'trace')
14684
14738
  return;
14685
- this.logger('trace', `client: ${type} - Request - ${url}`, {
14739
+ this.logger.trace(`client: ${type} - Request - ${url}`, {
14686
14740
  payload: data,
14687
14741
  config,
14688
14742
  });
14689
14743
  };
14690
14744
  this._logApiResponse = (type, url, response) => {
14691
- if (getLogLevel() !== 'trace')
14745
+ if (this.logger.getLogLevel() !== 'trace')
14692
14746
  return;
14693
- this.logger('trace', `client:${type} - Response - url: ${url} > status ${response.status}`, {
14747
+ this.logger.trace(`client:${type} - Response - url: ${url} > status ${response.status}`, {
14694
14748
  response,
14695
14749
  });
14696
14750
  };
@@ -14747,13 +14801,13 @@ class StreamClient {
14747
14801
  this.consecutiveFailures += 1;
14748
14802
  const { response } = e;
14749
14803
  if (!response || !isErrorResponse(response)) {
14750
- this.logger('error', `client:${type} url: ${url}`, e);
14804
+ this.logger.error(`client:${type} url: ${url}`, e);
14751
14805
  throw e;
14752
14806
  }
14753
14807
  const { data: responseData, status } = response;
14754
14808
  const isTokenExpired = responseData.code === KnownCodes.TOKEN_EXPIRED;
14755
14809
  if (isTokenExpired && !this.tokenManager.isStatic()) {
14756
- this.logger('warn', `client:${type}: url: ${url}`, response);
14810
+ this.logger.warn(`client:${type}: url: ${url}`, response);
14757
14811
  if (this.consecutiveFailures > 1) {
14758
14812
  await sleep(retryInterval(this.consecutiveFailures));
14759
14813
  }
@@ -14762,7 +14816,7 @@ class StreamClient {
14762
14816
  return await this.doAxiosRequest(type, url, data, options);
14763
14817
  }
14764
14818
  else {
14765
- this.logger('error', `client:${type} url: ${url}`, response);
14819
+ this.logger.error(`client:${type} url: ${url}`, response);
14766
14820
  throw new ErrorFromResponse({
14767
14821
  message: `Stream error code ${responseData.code}: ${responseData.message}`,
14768
14822
  code: responseData.code ?? null,
@@ -14793,7 +14847,7 @@ class StreamClient {
14793
14847
  });
14794
14848
  };
14795
14849
  this.dispatchEvent = (event) => {
14796
- this.logger('debug', `Dispatching event: ${event.type}`, event);
14850
+ this.logger.debug(`Dispatching event: ${event.type}`, event);
14797
14851
  if (!this.listeners)
14798
14852
  return;
14799
14853
  // call generic listeners
@@ -14818,13 +14872,13 @@ class StreamClient {
14818
14872
  throw Error('clientID is not set');
14819
14873
  // The StableWSConnection handles all the reconnection logic.
14820
14874
  this.wsConnection = new StableWSConnection(this);
14821
- this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
14875
+ this.logger.info('StreamClient.connect: this.wsConnection.connect()');
14822
14876
  return await this.wsConnection.connect(this.defaultWSTimeout);
14823
14877
  };
14824
14878
  this.getUserAgent = () => {
14825
14879
  if (!this.cachedUserAgent) {
14826
14880
  const { clientAppIdentifier = {} } = this.options;
14827
- const { sdkName = 'js', sdkVersion = "1.35.1", ...extras } = clientAppIdentifier;
14881
+ const { sdkName = 'js', sdkVersion = "1.36.1", ...extras } = clientAppIdentifier;
14828
14882
  this.cachedUserAgent = [
14829
14883
  `stream-video-${sdkName}-v${sdkVersion}`,
14830
14884
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -14875,11 +14929,11 @@ class StreamClient {
14875
14929
  };
14876
14930
  this.updateNetworkConnectionStatus = (event) => {
14877
14931
  if (event.type === 'offline') {
14878
- this.logger('debug', 'device went offline');
14932
+ this.logger.debug('device went offline');
14879
14933
  this.dispatchEvent({ type: 'network.changed', online: false });
14880
14934
  }
14881
14935
  else if (event.type === 'online') {
14882
- this.logger('debug', 'device went online');
14936
+ this.logger.debug('device went online');
14883
14937
  this.dispatchEvent({ type: 'network.changed', online: true });
14884
14938
  }
14885
14939
  };
@@ -14927,9 +14981,7 @@ class StreamClient {
14927
14981
  this.tokenManager = new TokenManager(this.secret);
14928
14982
  this.consecutiveFailures = 0;
14929
14983
  this.defaultWSTimeout = this.options.defaultWsTimeout ?? 15000;
14930
- this.logger = isFunction(inputOptions.logger)
14931
- ? inputOptions.logger
14932
- : () => null;
14984
+ this.logger = videoLoggerSystem.getLogger('coordinator');
14933
14985
  }
14934
14986
  get connectionIdPromise() {
14935
14987
  return this.connectionIdPromiseSafe?.();
@@ -14974,12 +15026,10 @@ const getClientAppIdentifier = (options) => {
14974
15026
  */
14975
15027
  const createCoordinatorClient = (apiKey, options) => {
14976
15028
  const clientAppIdentifier = getClientAppIdentifier(options);
14977
- const coordinatorLogger = getLogger(['coordinator']);
14978
15029
  return new StreamClient(apiKey, {
14979
15030
  persistUserOnConnectionFailure: true,
14980
15031
  ...options,
14981
15032
  clientAppIdentifier,
14982
- logger: coordinatorLogger,
14983
15033
  });
14984
15034
  };
14985
15035
  /**
@@ -15017,7 +15067,7 @@ class StreamVideoClient {
15017
15067
  this.registerClientInstance = (apiKey, user) => {
15018
15068
  const instanceKey = getInstanceKey(apiKey, user);
15019
15069
  if (StreamVideoClient._instances.has(instanceKey)) {
15020
- this.logger('warn', `A StreamVideoClient already exists for ${user.id}; Prefer using getOrCreateInstance method`);
15070
+ this.logger.warn(`A StreamVideoClient already exists for ${user.id}; Prefer using getOrCreateInstance method`);
15021
15071
  }
15022
15072
  StreamVideoClient._instances.set(instanceKey, this);
15023
15073
  };
@@ -15032,13 +15082,13 @@ class StreamVideoClient {
15032
15082
  .map((call) => call.cid);
15033
15083
  if (callsToReWatch.length <= 0)
15034
15084
  return;
15035
- this.logger('info', `Rewatching calls ${callsToReWatch.join(', ')}`);
15085
+ this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')}`);
15036
15086
  this.queryCalls({
15037
15087
  watch: true,
15038
15088
  filter_conditions: { cid: { $in: callsToReWatch } },
15039
15089
  sort: [{ field: 'cid', direction: 1 }],
15040
15090
  }).catch((err) => {
15041
- this.logger('error', 'Failed to re-watch calls', err);
15091
+ this.logger.error('Failed to re-watch calls', err);
15042
15092
  });
15043
15093
  }));
15044
15094
  this.effectsRegistered = true;
@@ -15049,7 +15099,7 @@ class StreamVideoClient {
15049
15099
  */
15050
15100
  this.initCallFromEvent = async (e) => {
15051
15101
  if (this.state.connectedUser?.id === e.call.created_by.id) {
15052
- this.logger('debug', `Ignoring ${e.type} event sent by the current user`);
15102
+ this.logger.debug(`Ignoring ${e.type} event sent by the current user`);
15053
15103
  return;
15054
15104
  }
15055
15105
  try {
@@ -15060,7 +15110,7 @@ class StreamVideoClient {
15060
15110
  if (call) {
15061
15111
  if (ringing) {
15062
15112
  if (this.shouldRejectCall(call.cid)) {
15063
- this.logger('info', `Leaving call with busy reject reason ${call.cid} because user is busy`);
15113
+ this.logger.info(`Leaving call with busy reject reason ${call.cid} because user is busy`);
15064
15114
  // remove the instance from the state store
15065
15115
  await call.leave();
15066
15116
  // explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
@@ -15085,7 +15135,7 @@ class StreamVideoClient {
15085
15135
  });
15086
15136
  if (ringing) {
15087
15137
  if (this.shouldRejectCall(call.cid)) {
15088
- this.logger('info', `Rejecting call ${call.cid} because user is busy`);
15138
+ this.logger.info(`Rejecting call ${call.cid} because user is busy`);
15089
15139
  // call is not in the state store yet, so just reject api is enough
15090
15140
  await call.reject('busy');
15091
15141
  }
@@ -15097,12 +15147,12 @@ class StreamVideoClient {
15097
15147
  else {
15098
15148
  call.state.updateFromCallResponse(e.call);
15099
15149
  this.writeableStateStore.registerCall(call);
15100
- this.logger('info', `New call created and registered: ${call.cid}`);
15150
+ this.logger.info(`New call created and registered: ${call.cid}`);
15101
15151
  }
15102
15152
  });
15103
15153
  }
15104
15154
  catch (err) {
15105
- this.logger('error', `Failed to init call from event ${e.type}`, err);
15155
+ this.logger.error(`Failed to init call from event ${e.type}`, err);
15106
15156
  }
15107
15157
  };
15108
15158
  /**
@@ -15126,13 +15176,13 @@ class StreamVideoClient {
15126
15176
  const errorQueue = [];
15127
15177
  for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
15128
15178
  try {
15129
- this.logger('trace', `Connecting user (${attempt})`, user);
15179
+ this.logger.trace(`Connecting user (${attempt})`, user);
15130
15180
  return user.type === 'guest'
15131
15181
  ? await client.connectGuestUser(user)
15132
15182
  : await client.connectUser(user, tokenOrProvider);
15133
15183
  }
15134
15184
  catch (err) {
15135
- this.logger('warn', `Failed to connect a user (${attempt})`, err);
15185
+ this.logger.warn(`Failed to connect a user (${attempt})`, err);
15136
15186
  errorQueue.push(err);
15137
15187
  if (attempt === maxConnectUserRetries - 1) {
15138
15188
  onConnectUserError?.(err, errorQueue);
@@ -15377,8 +15427,11 @@ class StreamVideoClient {
15377
15427
  if (clientOptions?.enableTimerWorker)
15378
15428
  enableTimerWorker();
15379
15429
  const rootLogger = clientOptions?.logger || logToConsole;
15380
- setLogger(rootLogger, clientOptions?.logLevel || 'warn');
15381
- this.logger = getLogger(['client']);
15430
+ videoLoggerSystem.configureLoggers({
15431
+ default: { sink: rootLogger, level: clientOptions?.logLevel || 'warn' },
15432
+ ...clientOptions?.logOptions,
15433
+ });
15434
+ this.logger = videoLoggerSystem.getLogger('client');
15382
15435
  this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
15383
15436
  this.streamClient = createCoordinatorClient(apiKey, clientOptions);
15384
15437
  this.writeableStateStore = new StreamVideoWriteableStateStore();
@@ -15391,7 +15444,7 @@ class StreamVideoClient {
15391
15444
  this.registerClientInstance(apiKey, user);
15392
15445
  const tokenOrProvider = createTokenOrProvider(apiKeyOrArgs);
15393
15446
  this.connectUser(user, tokenOrProvider).catch((err) => {
15394
- this.logger('error', 'Failed to connect', err);
15447
+ this.logger.error('Failed to connect', err);
15395
15448
  });
15396
15449
  }
15397
15450
  }
@@ -15421,5 +15474,5 @@ class StreamVideoClient {
15421
15474
  }
15422
15475
  StreamVideoClient._instances = new Map();
15423
15476
 
15424
- export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getLogLevel, getLogger, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, withParticipantSource };
15477
+ export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RNSpeechDetector, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
15425
15478
  //# sourceMappingURL=index.es.js.map