@stream-io/video-client 1.6.0-rc.0 → 1.6.0-rc2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +219 -6
  2. package/dist/index.browser.es.js +286 -318
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +286 -318
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +286 -318
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +1 -2
  9. package/dist/src/StreamSfuClient.d.ts +2 -2
  10. package/dist/src/StreamVideoClient.d.ts +6 -1
  11. package/dist/src/coordinator/connection/client.d.ts +0 -2
  12. package/dist/src/coordinator/connection/types.d.ts +2 -0
  13. package/dist/src/gen/coordinator/index.d.ts +280 -62
  14. package/dist/src/helpers/sdp-munging.d.ts +0 -21
  15. package/dist/src/rtc/Publisher.d.ts +0 -7
  16. package/dist/src/rtc/Subscriber.d.ts +0 -7
  17. package/dist/src/store/CallState.d.ts +3 -46
  18. package/dist/src/store/CallingState.d.ts +46 -0
  19. package/dist/src/store/index.d.ts +1 -0
  20. package/dist/src/store/stateStore.d.ts +0 -17
  21. package/package.json +1 -1
  22. package/src/Call.ts +56 -62
  23. package/src/StreamSfuClient.ts +16 -12
  24. package/src/StreamVideoClient.ts +7 -2
  25. package/src/coordinator/connection/client.ts +6 -30
  26. package/src/coordinator/connection/types.ts +2 -0
  27. package/src/devices/InputMediaDeviceManager.ts +0 -2
  28. package/src/gen/coordinator/index.ts +283 -63
  29. package/src/helpers/__tests__/sdp-munging.test.ts +1 -267
  30. package/src/helpers/sdp-munging.ts +0 -95
  31. package/src/rtc/Publisher.ts +6 -18
  32. package/src/rtc/Subscriber.ts +0 -14
  33. package/src/store/CallState.ts +40 -74
  34. package/src/store/CallingState.ts +55 -0
  35. package/src/store/__tests__/CallState.test.ts +82 -7
  36. package/src/store/index.ts +1 -0
  37. package/src/store/stateStore.ts +5 -24
@@ -7313,6 +7313,169 @@ var rxUtils = /*#__PURE__*/Object.freeze({
7313
7313
  setCurrentValue: setCurrentValue
7314
7314
  });
7315
7315
 
7316
+ /**
7317
+ * Represents the state of the current call.
7318
+ */
7319
+ var CallingState;
7320
+ (function (CallingState) {
7321
+ /**
7322
+ * The call is in an unknown state.
7323
+ */
7324
+ CallingState["UNKNOWN"] = "unknown";
7325
+ /**
7326
+ * The call is in an idle state.
7327
+ */
7328
+ CallingState["IDLE"] = "idle";
7329
+ /**
7330
+ * The call is in the process of ringing.
7331
+ * (User hasn't accepted nor rejected the call yet.)
7332
+ */
7333
+ CallingState["RINGING"] = "ringing";
7334
+ /**
7335
+ * The call is in the process of joining.
7336
+ */
7337
+ CallingState["JOINING"] = "joining";
7338
+ /**
7339
+ * The call is currently active.
7340
+ */
7341
+ CallingState["JOINED"] = "joined";
7342
+ /**
7343
+ * The call has been left.
7344
+ */
7345
+ CallingState["LEFT"] = "left";
7346
+ /**
7347
+ * The call is in the process of reconnecting.
7348
+ */
7349
+ CallingState["RECONNECTING"] = "reconnecting";
7350
+ /**
7351
+ * The call is in the process of migrating from one node to another.
7352
+ */
7353
+ CallingState["MIGRATING"] = "migrating";
7354
+ /**
7355
+ * The call has failed to reconnect.
7356
+ */
7357
+ CallingState["RECONNECTING_FAILED"] = "reconnecting-failed";
7358
+ /**
7359
+ * The call is in offline mode.
7360
+ */
7361
+ CallingState["OFFLINE"] = "offline";
7362
+ })(CallingState || (CallingState = {}));
7363
+
7364
+ class StreamVideoWriteableStateStore {
7365
+ constructor() {
7366
+ /**
7367
+ * A store keeping data of a successfully connected user over WS to the coordinator server.
7368
+ */
7369
+ this.connectedUserSubject = new BehaviorSubject(undefined);
7370
+ /**
7371
+ * A list of {@link Call} objects created/tracked by this client.
7372
+ */
7373
+ this.callsSubject = new BehaviorSubject([]);
7374
+ /**
7375
+ * Sets the currently connected user.
7376
+ *
7377
+ * @internal
7378
+ * @param user the user to set as connected.
7379
+ */
7380
+ this.setConnectedUser = (user) => {
7381
+ return setCurrentValue(this.connectedUserSubject, user);
7382
+ };
7383
+ /**
7384
+ * Sets the list of {@link Call} objects created/tracked by this client.
7385
+ * @param calls
7386
+ */
7387
+ this.setCalls = (calls) => {
7388
+ return setCurrentValue(this.callsSubject, calls);
7389
+ };
7390
+ /**
7391
+ * Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
7392
+ *
7393
+ * @param call the call to add.
7394
+ */
7395
+ this.registerCall = (call) => {
7396
+ if (!this.calls.find((c) => c.cid === call.cid)) {
7397
+ this.setCalls((calls) => [...calls, call]);
7398
+ }
7399
+ };
7400
+ /**
7401
+ * Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
7402
+ *
7403
+ * @param call the call to remove
7404
+ */
7405
+ this.unregisterCall = (call) => {
7406
+ return this.setCalls((calls) => calls.filter((c) => c !== call));
7407
+ };
7408
+ /**
7409
+ * Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
7410
+ *
7411
+ * @param type the type of call to find.
7412
+ * @param id the id of the call to find.
7413
+ */
7414
+ this.findCall = (type, id) => {
7415
+ return this.calls.find((c) => c.type === type && c.id === id);
7416
+ };
7417
+ this.connectedUserSubject.subscribe(async (user) => {
7418
+ // leave all calls when the user disconnects.
7419
+ if (!user) {
7420
+ const logger = getLogger(['client-state']);
7421
+ for (const call of this.calls) {
7422
+ if (call.state.callingState === CallingState.LEFT)
7423
+ continue;
7424
+ logger('info', `User disconnected, leaving call: ${call.cid}`);
7425
+ await call
7426
+ .leave({ reason: 'client.disconnectUser() called' })
7427
+ .catch((err) => {
7428
+ logger('error', `Error leaving call: ${call.cid}`, err);
7429
+ });
7430
+ }
7431
+ }
7432
+ });
7433
+ }
7434
+ /**
7435
+ * The currently connected user.
7436
+ */
7437
+ get connectedUser() {
7438
+ return getCurrentValue(this.connectedUserSubject);
7439
+ }
7440
+ /**
7441
+ * A list of {@link Call} objects created/tracked by this client.
7442
+ */
7443
+ get calls() {
7444
+ return getCurrentValue(this.callsSubject);
7445
+ }
7446
+ }
7447
+ /**
7448
+ * A reactive store that exposes state variables in a reactive manner.
7449
+ * You can subscribe to changes of the different state variables.
7450
+ * This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
7451
+ */
7452
+ class StreamVideoReadOnlyStateStore {
7453
+ constructor(store) {
7454
+ /**
7455
+ * This method allows you the get the current value of a state variable.
7456
+ *
7457
+ * @param observable the observable to get the current value of.
7458
+ * @returns the current value of the observable.
7459
+ */
7460
+ this.getCurrentValue = getCurrentValue;
7461
+ // convert and expose subjects as observables
7462
+ this.connectedUser$ = store.connectedUserSubject.asObservable();
7463
+ this.calls$ = store.callsSubject.asObservable();
7464
+ }
7465
+ /**
7466
+ * The current user connected over WS to the backend.
7467
+ */
7468
+ get connectedUser() {
7469
+ return getCurrentValue(this.connectedUser$);
7470
+ }
7471
+ /**
7472
+ * A list of {@link Call} objects created/tracked by this client.
7473
+ */
7474
+ get calls() {
7475
+ return getCurrentValue(this.calls$);
7476
+ }
7477
+ }
7478
+
7316
7479
  /**
7317
7480
  * Creates a new combined {@link Comparator<T>} which sorts items by the given comparators.
7318
7481
  * The comparators are applied in the order they are given (left -> right).
@@ -7562,53 +7725,6 @@ const paginatedLayoutSortPreset = combineComparators(pinned, ifInvisibleOrUnknow
7562
7725
  */
7563
7726
  const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)), role('admin', 'host', 'speaker'));
7564
7727
 
7565
- /**
7566
- * Represents the state of the current call.
7567
- */
7568
- var CallingState;
7569
- (function (CallingState) {
7570
- /**
7571
- * The call is in an unknown state.
7572
- */
7573
- CallingState["UNKNOWN"] = "unknown";
7574
- /**
7575
- * The call is in an idle state.
7576
- */
7577
- CallingState["IDLE"] = "idle";
7578
- /**
7579
- * The call is in the process of ringing.
7580
- * (User hasn't accepted nor rejected the call yet.)
7581
- */
7582
- CallingState["RINGING"] = "ringing";
7583
- /**
7584
- * The call is in the process of joining.
7585
- */
7586
- CallingState["JOINING"] = "joining";
7587
- /**
7588
- * The call is currently active.
7589
- */
7590
- CallingState["JOINED"] = "joined";
7591
- /**
7592
- * The call has been left.
7593
- */
7594
- CallingState["LEFT"] = "left";
7595
- /**
7596
- * The call is in the process of reconnecting.
7597
- */
7598
- CallingState["RECONNECTING"] = "reconnecting";
7599
- /**
7600
- * The call is in the process of migrating from one node to another.
7601
- */
7602
- CallingState["MIGRATING"] = "migrating";
7603
- /**
7604
- * The call has failed to reconnect.
7605
- */
7606
- CallingState["RECONNECTING_FAILED"] = "reconnecting-failed";
7607
- /**
7608
- * The call is in offline mode.
7609
- */
7610
- CallingState["OFFLINE"] = "offline";
7611
- })(CallingState || (CallingState = {}));
7612
7728
  /**
7613
7729
  * Returns the default egress object - when no egress data is available.
7614
7730
  */
@@ -7958,7 +8074,8 @@ class CallState {
7958
8074
  this.setCurrentValue(this.egressSubject, call.egress);
7959
8075
  this.setCurrentValue(this.ingressSubject, call.ingress);
7960
8076
  this.setCurrentValue(this.recordingSubject, call.recording);
7961
- this.setCurrentValue(this.sessionSubject, call.session);
8077
+ const s = this.setCurrentValue(this.sessionSubject, call.session);
8078
+ this.updateParticipantCountFromSession(s);
7962
8079
  this.setCurrentValue(this.settingsSubject, call.settings);
7963
8080
  this.setCurrentValue(this.transcribingSubject, call.transcribing);
7964
8081
  this.setCurrentValue(this.thumbnailsSubject, call.thumbnails);
@@ -8033,12 +8150,32 @@ class CallState {
8033
8150
  },
8034
8151
  }));
8035
8152
  };
8153
+ this.updateParticipantCountFromSession = (session) => {
8154
+ // when in JOINED state, we should use the participant count coming through
8155
+ // the SFU healthcheck event, as it's more accurate.
8156
+ if (!session || this.callingState === CallingState.JOINED)
8157
+ return;
8158
+ const byRoleCount = Object.values(session.participants_count_by_role).reduce((total, countByRole) => total + countByRole, 0);
8159
+ const participantCount = Math.max(byRoleCount, session.participants.length);
8160
+ this.setParticipantCount(participantCount);
8161
+ this.setAnonymousParticipantCount(session.anonymous_participant_count || 0);
8162
+ };
8163
+ this.updateFromSessionParticipantCountUpdate = (event) => {
8164
+ const s = this.setCurrentValue(this.sessionSubject, (session) => {
8165
+ if (!session)
8166
+ return session;
8167
+ return {
8168
+ ...session,
8169
+ anonymous_participant_count: event.anonymous_participant_count,
8170
+ participants_count_by_role: event.participants_count_by_role,
8171
+ };
8172
+ });
8173
+ this.updateParticipantCountFromSession(s);
8174
+ };
8036
8175
  this.updateFromSessionParticipantLeft = (event) => {
8037
- this.setCurrentValue(this.sessionSubject, (session) => {
8038
- if (!session) {
8039
- this.logger('warn', `Received call.session_participant_left event but no session is available.`, event);
8176
+ const s = this.setCurrentValue(this.sessionSubject, (session) => {
8177
+ if (!session)
8040
8178
  return session;
8041
- }
8042
8179
  const { participants, participants_count_by_role } = session;
8043
8180
  const { user, user_session_id } = event.participant;
8044
8181
  return {
@@ -8050,13 +8187,12 @@ class CallState {
8050
8187
  },
8051
8188
  };
8052
8189
  });
8190
+ this.updateParticipantCountFromSession(s);
8053
8191
  };
8054
8192
  this.updateFromSessionParticipantJoined = (event) => {
8055
- this.setCurrentValue(this.sessionSubject, (session) => {
8056
- if (!session) {
8057
- this.logger('warn', `Received call.session_participant_joined event but no session is available.`, event);
8193
+ const s = this.setCurrentValue(this.sessionSubject, (session) => {
8194
+ if (!session)
8058
8195
  return session;
8059
- }
8060
8196
  const { participants, participants_count_by_role } = session;
8061
8197
  const { user, user_session_id } = event.participant;
8062
8198
  // It could happen that the backend delivers the same participant more than once.
@@ -8087,6 +8223,7 @@ class CallState {
8087
8223
  },
8088
8224
  };
8089
8225
  });
8226
+ this.updateParticipantCountFromSession(s);
8090
8227
  };
8091
8228
  this.updateMembers = (event) => {
8092
8229
  this.updateFromCallResponse(event.call);
@@ -8236,6 +8373,7 @@ class CallState {
8236
8373
  'call.ring': (e) => this.updateFromCallResponse(e.call),
8237
8374
  'call.missed': (e) => this.updateFromCallResponse(e.call),
8238
8375
  'call.session_ended': (e) => this.updateFromCallResponse(e.call),
8376
+ 'call.session_participant_count_updated': this.updateFromSessionParticipantCountUpdate,
8239
8377
  'call.session_participant_joined': this.updateFromSessionParticipantJoined,
8240
8378
  'call.session_participant_left': this.updateFromSessionParticipantLeft,
8241
8379
  'call.session_started': (e) => this.updateFromCallResponse(e.call),
@@ -8431,138 +8569,6 @@ class CallState {
8431
8569
  }
8432
8570
  }
8433
8571
 
8434
- class StreamVideoWriteableStateStore {
8435
- constructor() {
8436
- /**
8437
- * A store keeping data of a successfully connected user over WS to the coordinator server.
8438
- */
8439
- this.connectedUserSubject = new BehaviorSubject(undefined);
8440
- /**
8441
- * A list of {@link Call} objects created/tracked by this client.
8442
- */
8443
- this.callsSubject = new BehaviorSubject([]);
8444
- /**
8445
- * Gets the current value of an observable, or undefined if the observable has
8446
- * not emitted a value yet.
8447
- *
8448
- * @param observable$ the observable to get the value from.
8449
- */
8450
- this.getCurrentValue = getCurrentValue;
8451
- /**
8452
- * Updates the value of the provided Subject.
8453
- * An `update` can either be a new value or a function which takes
8454
- * the current value and returns a new value.
8455
- *
8456
- * @param subject the subject to update.
8457
- * @param update the update to apply to the subject.
8458
- * @return the updated value.
8459
- */
8460
- this.setCurrentValue = setCurrentValue;
8461
- /**
8462
- * Sets the currently connected user.
8463
- *
8464
- * @internal
8465
- * @param user the user to set as connected.
8466
- */
8467
- this.setConnectedUser = (user) => {
8468
- return this.setCurrentValue(this.connectedUserSubject, user);
8469
- };
8470
- /**
8471
- * Sets the list of {@link Call} objects created/tracked by this client.
8472
- * @param calls
8473
- */
8474
- this.setCalls = (calls) => {
8475
- return this.setCurrentValue(this.callsSubject, calls);
8476
- };
8477
- /**
8478
- * Adds a {@link Call} object to the list of {@link Call} objects created/tracked by this client.
8479
- *
8480
- * @param call the call to add.
8481
- */
8482
- this.registerCall = (call) => {
8483
- if (!this.calls.find((c) => c.cid === call.cid)) {
8484
- this.setCalls((calls) => [...calls, call]);
8485
- }
8486
- };
8487
- /**
8488
- * Removes a {@link Call} object from the list of {@link Call} objects created/tracked by this client.
8489
- *
8490
- * @param call the call to remove
8491
- */
8492
- this.unregisterCall = (call) => {
8493
- return this.setCalls((calls) => calls.filter((c) => c !== call));
8494
- };
8495
- /**
8496
- * Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
8497
- *
8498
- * @param type the type of call to find.
8499
- * @param id the id of the call to find.
8500
- */
8501
- this.findCall = (type, id) => {
8502
- return this.calls.find((c) => c.type === type && c.id === id);
8503
- };
8504
- this.connectedUserSubject.subscribe(async (user) => {
8505
- // leave all calls when the user disconnects.
8506
- if (!user) {
8507
- const logger = getLogger(['client-state']);
8508
- for (const call of this.calls) {
8509
- if (call.state.callingState === CallingState.LEFT)
8510
- continue;
8511
- logger('info', `User disconnected, leaving call: ${call.cid}`);
8512
- await call
8513
- .leave({ reason: 'client.disconnectUser() called' })
8514
- .catch((err) => {
8515
- logger('error', `Error leaving call: ${call.cid}`, err);
8516
- });
8517
- }
8518
- }
8519
- });
8520
- }
8521
- /**
8522
- * The currently connected user.
8523
- */
8524
- get connectedUser() {
8525
- return this.getCurrentValue(this.connectedUserSubject);
8526
- }
8527
- /**
8528
- * A list of {@link Call} objects created/tracked by this client.
8529
- */
8530
- get calls() {
8531
- return this.getCurrentValue(this.callsSubject);
8532
- }
8533
- }
8534
- /**
8535
- * A reactive store that exposes state variables in a reactive manner.
8536
- * You can subscribe to changes of the different state variables.
8537
- * This central store contains all the state variables related to [`StreamVideoClient`](./StreamVideClient.md) and [`Call`](./Call.md).
8538
- */
8539
- class StreamVideoReadOnlyStateStore {
8540
- constructor(store) {
8541
- /**
8542
- * This method allows you the get the current value of a state variable.
8543
- *
8544
- * @param observable the observable to get the current value of.
8545
- * @returns the current value of the observable.
8546
- */
8547
- this.getCurrentValue = getCurrentValue;
8548
- // convert and expose subjects as observables
8549
- this.connectedUser$ = store.connectedUserSubject.asObservable();
8550
- this.calls$ = store.callsSubject.asObservable();
8551
- }
8552
- /**
8553
- * The current user connected over WS to the backend.
8554
- */
8555
- get connectedUser() {
8556
- return getCurrentValue(this.connectedUser$);
8557
- }
8558
- /**
8559
- * A list of {@link Call} objects created/tracked by this client.
8560
- */
8561
- get calls() {
8562
- return getCurrentValue(this.calls$);
8563
- }
8564
- }
8565
-
8566
8572
  const getRtpMap = (line) => {
8567
8573
  // Example: a=rtpmap:110 opus/48000/2
8568
8574
  const rtpRegex = /^a=rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/;
@@ -8711,7 +8717,7 @@ const enableHighQualityAudio = (sdp, trackMid, maxBitrate = 510000) => {
8711
8717
  return SDP.write(parsedSdp);
8712
8718
  };
8713
8719
 
8714
- const version = "1.6.0-rc.0" ;
8720
+ const version = "1.6.0-rc2.0" ;
8715
8721
  const [major, minor, patch] = version.split('.');
8716
8722
  let sdkInfo = {
8717
8723
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -8783,16 +8789,6 @@ const getClientDetails = () => {
8783
8789
  * @internal
8784
8790
  */
8785
8791
  class Publisher {
8786
- /**
8787
- * Returns the current connection configuration.
8788
- *
8789
- * @internal
8790
- */
8791
- get connectionConfiguration() {
8792
- if (this.pc.getConfiguration)
8793
- return this.pc.getConfiguration();
8794
- return this._connectionConfiguration;
8795
- }
8796
8792
  /**
8797
8793
  * Constructs a new `Publisher` instance.
8798
8794
  *
@@ -8840,7 +8836,6 @@ class Publisher {
8840
8836
  this.isIceRestarting = false;
8841
8837
  this.createPeerConnection = (connectionConfig) => {
8842
8838
  const pc = new RTCPeerConnection(connectionConfig);
8843
- this._connectionConfiguration = connectionConfig;
8844
8839
  pc.addEventListener('icecandidate', this.onIceCandidate);
8845
8840
  pc.addEventListener('negotiationneeded', this.onNegotiationNeeded);
8846
8841
  pc.addEventListener('icecandidateerror', this.onIceCandidateError);
@@ -8905,11 +8900,11 @@ class Publisher {
8905
8900
  * An event handler which listens for the 'ended' event on the track.
8906
8901
  * Once the track has ended, it will notify the SFU and update the state.
8907
8902
  */
8908
- const handleTrackEnded = async () => {
8909
- this.logger('info', `Track ${TrackType[trackType]} has ended, notifying the SFU`);
8910
- await this.notifyTrackMuteStateChanged(mediaStream, trackType, true);
8911
- // clean-up, this event listener needs to run only once.
8903
+ const handleTrackEnded = () => {
8904
+ this.logger('info', `Track ${TrackType[trackType]} has ended abruptly, notifying the SFU`);
8905
+ // cleanup, this event listener needs to run only once.
8912
8906
  track.removeEventListener('ended', handleTrackEnded);
8907
+ this.notifyTrackMuteStateChanged(mediaStream, trackType, true).catch((err) => this.logger('warn', `Couldn't notify track mute state`, err));
8913
8908
  };
8914
8909
  if (!transceiver) {
8915
8910
  const { settings } = this.state;
@@ -9367,16 +9362,6 @@ class Publisher {
9367
9362
  * @internal
9368
9363
  */
9369
9364
  class Subscriber {
9370
- /**
9371
- * Returns the current connection configuration.
9372
- *
9373
- * @internal
9374
- */
9375
- get connectionConfiguration() {
9376
- if (this.pc.getConfiguration)
9377
- return this.pc.getConfiguration();
9378
- return this._connectionConfiguration;
9379
- }
9380
9365
  /**
9381
9366
  * Constructs a new `Subscriber` instance.
9382
9367
  *
@@ -9397,7 +9382,6 @@ class Subscriber {
9397
9382
  */
9398
9383
  this.createPeerConnection = (connectionConfig) => {
9399
9384
  const pc = new RTCPeerConnection(connectionConfig);
9400
- this._connectionConfiguration = connectionConfig;
9401
9385
  pc.addEventListener('icecandidate', this.onIceCandidate);
9402
9386
  pc.addEventListener('track', this.handleOnTrack);
9403
9387
  pc.addEventListener('icecandidateerror', this.onIceCandidateError);
@@ -9755,7 +9739,7 @@ class StreamSfuClient {
9755
9739
  this.migrationTask?.resolve();
9756
9740
  };
9757
9741
  this.leaveAndClose = async (reason) => {
9758
- await this.joinResponseTask.promise;
9742
+ await this.joinTask;
9759
9743
  try {
9760
9744
  this.isLeaving = true;
9761
9745
  await this.notifyLeave(reason);
@@ -9766,43 +9750,43 @@ class StreamSfuClient {
9766
9750
  this.close(StreamSfuClient.NORMAL_CLOSURE, reason.substring(0, 115));
9767
9751
  };
9768
9752
  this.updateSubscriptions = async (tracks) => {
9769
- await this.joinResponseTask.promise;
9753
+ await this.joinTask;
9770
9754
  return retryable(() => this.rpc.updateSubscriptions({ sessionId: this.sessionId, tracks }), this.abortController.signal);
9771
9755
  };
9772
9756
  this.setPublisher = async (data) => {
9773
- await this.joinResponseTask.promise;
9757
+ await this.joinTask;
9774
9758
  return retryable(() => this.rpc.setPublisher({ ...data, sessionId: this.sessionId }), this.abortController.signal);
9775
9759
  };
9776
9760
  this.sendAnswer = async (data) => {
9777
- await this.joinResponseTask.promise;
9761
+ await this.joinTask;
9778
9762
  return retryable(() => this.rpc.sendAnswer({ ...data, sessionId: this.sessionId }), this.abortController.signal);
9779
9763
  };
9780
9764
  this.iceTrickle = async (data) => {
9781
- await this.joinResponseTask.promise;
9765
+ await this.joinTask;
9782
9766
  return retryable(() => this.rpc.iceTrickle({ ...data, sessionId: this.sessionId }), this.abortController.signal);
9783
9767
  };
9784
9768
  this.iceRestart = async (data) => {
9785
- await this.joinResponseTask.promise;
9769
+ await this.joinTask;
9786
9770
  return retryable(() => this.rpc.iceRestart({ ...data, sessionId: this.sessionId }), this.abortController.signal);
9787
9771
  };
9788
9772
  this.updateMuteState = async (trackType, muted) => {
9789
- await this.joinResponseTask.promise;
9773
+ await this.joinTask;
9790
9774
  return this.updateMuteStates({ muteStates: [{ trackType, muted }] });
9791
9775
  };
9792
9776
  this.updateMuteStates = async (data) => {
9793
- await this.joinResponseTask.promise;
9777
+ await this.joinTask;
9794
9778
  return retryable(() => this.rpc.updateMuteStates({ ...data, sessionId: this.sessionId }), this.abortController.signal);
9795
9779
  };
9796
9780
  this.sendStats = async (stats) => {
9797
- await this.joinResponseTask.promise;
9781
+ await this.joinTask;
9798
9782
  return retryable(() => this.rpc.sendStats({ ...stats, sessionId: this.sessionId }), this.abortController.signal);
9799
9783
  };
9800
9784
  this.startNoiseCancellation = async () => {
9801
- await this.joinResponseTask.promise;
9785
+ await this.joinTask;
9802
9786
  return retryable(() => this.rpc.startNoiseCancellation({ sessionId: this.sessionId }), this.abortController.signal);
9803
9787
  };
9804
9788
  this.stopNoiseCancellation = async () => {
9805
- await this.joinResponseTask.promise;
9789
+ await this.joinTask;
9806
9790
  return retryable(() => this.rpc.stopNoiseCancellation({ sessionId: this.sessionId }), this.abortController.signal);
9807
9791
  };
9808
9792
  this.enterMigration = async (opts = {}) => {
@@ -9938,6 +9922,9 @@ class StreamSfuClient {
9938
9922
  get isHealthy() {
9939
9923
  return this.signalWs.readyState === WebSocket.OPEN;
9940
9924
  }
9925
+ get joinTask() {
9926
+ return this.joinResponseTask.promise;
9927
+ }
9941
9928
  }
9942
9929
  /**
9943
9930
  * The normal closure code. Used for controlled shutdowns.
@@ -11874,9 +11861,6 @@ class InputMediaDeviceManager {
11874
11861
  if (this.call.state.callingState === CallingState.JOINED) {
11875
11862
  await this.publishStream(stream);
11876
11863
  }
11877
- else {
11878
- this.logger('debug', 'Stream is not published as the call is not joined');
11879
- }
11880
11864
  if (this.state.mediaStream !== stream) {
11881
11865
  this.state.setMediaStream(stream, await rootStream);
11882
11866
  this.getTracks().forEach((track) => {
@@ -12993,7 +12977,14 @@ class Call {
12993
12977
  throw new Error('Cannot leave call that has already been left.');
12994
12978
  }
12995
12979
  if (callingState === CallingState.JOINING) {
12996
- await this.waitUntilCallJoined();
12980
+ const waitUntilCallJoined = () => {
12981
+ return new Promise((resolve) => {
12982
+ this.state.callingState$
12983
+ .pipe(takeWhile((state) => state !== CallingState.JOINED, true))
12984
+ .subscribe(() => resolve());
12985
+ });
12986
+ };
12987
+ await waitUntilCallJoined();
12997
12988
  }
12998
12989
  if (this.ringing) {
12999
12990
  // I'm the one who started the call, so I should cancel it.
@@ -13237,6 +13228,11 @@ class Call {
13237
13228
  await this.applyDeviceConfig(true);
13238
13229
  this.deviceSettingsAppliedOnce = true;
13239
13230
  }
13231
+ // We shouldn't persist the `ring` state after joining the call as it's a one-time event
13232
+ // and clashes with the potential reconnection attempts. When reconnecting,
13233
+ // if provided with `ring: true`, we will spam the other participants with
13234
+ // push notifications and `call.ring` events.
13235
+ delete this.joinCallData?.ring;
13240
13236
  this.logger('info', `Joined call ${this.cid}`);
13241
13237
  };
13242
13238
  /**
@@ -13296,7 +13292,7 @@ class Call {
13296
13292
  dispatcher: this.dispatcher,
13297
13293
  state: this.state,
13298
13294
  connectionConfig,
13299
- logTag: String(this.reconnectAttempts),
13295
+ logTag: String(this.sfuClientTag),
13300
13296
  onUnrecoverableError: () => {
13301
13297
  this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
13302
13298
  this.logger('warn', '[Reconnect] Error reconnecting after a subscriber error', err);
@@ -13320,7 +13316,7 @@ class Call {
13320
13316
  connectionConfig,
13321
13317
  isDtxEnabled,
13322
13318
  isRedEnabled,
13323
- logTag: String(this.reconnectAttempts),
13319
+ logTag: String(this.sfuClientTag),
13324
13320
  onUnrecoverableError: () => {
13325
13321
  this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
13326
13322
  this.logger('warn', '[Reconnect] Error reconnecting after a publisher error', err);
@@ -13373,6 +13369,21 @@ class Call {
13373
13369
  }
13374
13370
  return joinResponse;
13375
13371
  };
13372
+ /**
13373
+ * Handles the closing of the SFU signal connection.
13374
+ *
13375
+ * @internal
13376
+ * @param sfuClient the SFU client instance that was closed.
13377
+ */
13378
+ this.handleSfuSignalClose = (sfuClient) => {
13379
+ this.logger('debug', '[Reconnect] SFU signal connection closed');
13380
+ // normal close, no need to reconnect
13381
+ if (sfuClient.isLeaving)
13382
+ return;
13383
+ this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
13384
+ this.logger('warn', '[Reconnect] Error reconnecting', err);
13385
+ });
13386
+ };
13376
13387
  /**
13377
13388
  * Handles the reconnection flow.
13378
13389
  *
@@ -13385,7 +13396,10 @@ class Call {
13385
13396
  this.logger('info', `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`);
13386
13397
  this.reconnectStrategy = strategy;
13387
13398
  do {
13388
- this.reconnectAttempts++;
13399
+ // we don't increment reconnect attempts for the FAST strategy.
13400
+ if (this.reconnectStrategy !== WebsocketReconnectStrategy.FAST) {
13401
+ this.reconnectAttempts++;
13402
+ }
13389
13403
  const current = WebsocketReconnectStrategy[this.reconnectStrategy];
13390
13404
  try {
13391
13405
  // wait until the network is available
@@ -13411,11 +13425,17 @@ class Call {
13411
13425
  break; // do-while loop, reconnection worked, exit the loop
13412
13426
  }
13413
13427
  catch (error) {
13414
- this.logger('warn', `[Reconnect] ${current}(${this.reconnectAttempts}) failed. Attempting with REJOIN`, error);
13428
+ if (error instanceof ErrorFromResponse && error.unrecoverable) {
13429
+ this.logger('warn', `[Reconnect] Can't reconnect due to coordinator unrecoverable error`, error);
13430
+ this.state.setCallingState(CallingState.RECONNECTING_FAILED);
13431
+ return;
13432
+ }
13433
+ this.logger('warn', `[Reconnect] ${current} (${this.reconnectAttempts}) failed. Attempting with REJOIN`, error);
13415
13434
  await sleep(500);
13416
13435
  this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
13417
13436
  }
13418
13437
  } while (this.state.callingState !== CallingState.JOINED &&
13438
+ this.state.callingState !== CallingState.RECONNECTING_FAILED &&
13419
13439
  this.state.callingState !== CallingState.LEFT);
13420
13440
  });
13421
13441
  };
@@ -13530,7 +13550,7 @@ class Call {
13530
13550
  }
13531
13551
  }
13532
13552
  this.reconnect(strategy).catch((err) => {
13533
- this.logger('warn', '[Reconnect] Error restoring connection after going online', err);
13553
+ this.logger('warn', '[Reconnect] Error reconnecting after going online', err);
13534
13554
  });
13535
13555
  });
13536
13556
  this.networkAvailableTask = networkAvailableTask;
@@ -13613,18 +13633,15 @@ class Call {
13613
13633
  * @param opts the options to use when publishing the stream.
13614
13634
  */
13615
13635
  this.publishVideoStream = async (videoStream, opts = {}) => {
13616
- // we should wait until we get a JoinResponse from the SFU,
13617
- // otherwise we risk breaking the ICETrickle flow.
13618
- await this.waitUntilCallJoined();
13619
- if (!this.publisher) {
13620
- this.logger('error', 'Trying to publish video before join is completed');
13636
+ if (!this.sfuClient)
13621
13637
  throw new Error(`Call not joined yet.`);
13622
- }
13638
+ // joining is in progress, and we should wait until the client is ready
13639
+ await this.sfuClient.joinTask;
13640
+ if (!this.publisher)
13641
+ throw new Error('Publisher is not initialized');
13623
13642
  const [videoTrack] = videoStream.getVideoTracks();
13624
- if (!videoTrack) {
13625
- this.logger('error', `There is no video track to publish in the stream.`);
13626
- return;
13627
- }
13643
+ if (!videoTrack)
13644
+ throw new Error('There is no video track in the stream');
13628
13645
  if (!this.trackPublishOrder.includes(TrackType.VIDEO)) {
13629
13646
  this.trackPublishOrder.push(TrackType.VIDEO);
13630
13647
  }
@@ -13640,18 +13657,15 @@ class Call {
13640
13657
  * @param audioStream the audio stream to publish.
13641
13658
  */
13642
13659
  this.publishAudioStream = async (audioStream) => {
13643
- // we should wait until we get a JoinResponse from the SFU,
13644
- // otherwise we risk breaking the ICETrickle flow.
13645
- await this.waitUntilCallJoined();
13646
- if (!this.publisher) {
13647
- this.logger('error', 'Trying to publish audio before join is completed');
13660
+ if (!this.sfuClient)
13648
13661
  throw new Error(`Call not joined yet.`);
13649
- }
13662
+ // joining is in progress, and we should wait until the client is ready
13663
+ await this.sfuClient.joinTask;
13664
+ if (!this.publisher)
13665
+ throw new Error('Publisher is not initialized');
13650
13666
  const [audioTrack] = audioStream.getAudioTracks();
13651
- if (!audioTrack) {
13652
- this.logger('error', `There is no audio track in the stream to publish`);
13653
- return;
13654
- }
13667
+ if (!audioTrack)
13668
+ throw new Error('There is no audio track in the stream');
13655
13669
  if (!this.trackPublishOrder.includes(TrackType.AUDIO)) {
13656
13670
  this.trackPublishOrder.push(TrackType.AUDIO);
13657
13671
  }
@@ -13667,17 +13681,15 @@ class Call {
13667
13681
  * @param opts the options to use when publishing the stream.
13668
13682
  */
13669
13683
  this.publishScreenShareStream = async (screenShareStream, opts = {}) => {
13670
- // we should wait until we get a JoinResponse from the SFU,
13671
- // otherwise we risk breaking the ICETrickle flow.
13672
- await this.waitUntilCallJoined();
13673
- if (!this.publisher) {
13674
- this.logger('error', 'Trying to publish screen share before join is completed');
13684
+ if (!this.sfuClient)
13675
13685
  throw new Error(`Call not joined yet.`);
13676
- }
13686
+ // joining is in progress, and we should wait until the client is ready
13687
+ await this.sfuClient.joinTask;
13688
+ if (!this.publisher)
13689
+ throw new Error('Publisher is not initialized');
13677
13690
  const [screenShareTrack] = screenShareStream.getVideoTracks();
13678
13691
  if (!screenShareTrack) {
13679
- this.logger('error', `There is no video track in the screen share stream to publish`);
13680
- return;
13692
+ throw new Error('There is no screen share track in the stream');
13681
13693
  }
13682
13694
  if (!this.trackPublishOrder.includes(TrackType.SCREEN_SHARE)) {
13683
13695
  this.trackPublishOrder.push(TrackType.SCREEN_SHARE);
@@ -13832,18 +13844,6 @@ class Call {
13832
13844
  this.updatePublishQuality = async (enabledLayers) => {
13833
13845
  return this.publisher?.updateVideoPublishQuality(enabledLayers);
13834
13846
  };
13835
- this.waitUntilCallJoined = () => {
13836
- if (this.sfuClient) {
13837
- // if we have an SFU client, we can wait for the join response
13838
- return this.sfuClient.joinResponseTask.promise;
13839
- }
13840
- // otherwise, fall back to the calling state
13841
- return new Promise((resolve) => {
13842
- this.state.callingState$
13843
- .pipe(takeWhile((state) => state !== CallingState.JOINED, true))
13844
- .subscribe(() => resolve());
13845
- });
13846
- };
13847
13847
  /**
13848
13848
  * Sends a reaction to the other call participants.
13849
13849
  *
@@ -14465,12 +14465,12 @@ class Call {
14465
14465
  return;
14466
14466
  const callSession = this.state.session;
14467
14467
  const receiver_id = this.clientStore.connectedUser?.id;
14468
- const endedAt = this.state.endedAt;
14468
+ const ended_at = callSession?.ended_at;
14469
14469
  const created_by_id = this.state.createdBy?.id;
14470
14470
  const rejected_by = callSession?.rejected_by;
14471
14471
  const accepted_by = callSession?.accepted_by;
14472
14472
  let leaveCallIdle = false;
14473
- if (endedAt) {
14473
+ if (ended_at) {
14474
14474
  // call was ended before it was accepted or rejected so we should leave it to idle
14475
14475
  leaveCallIdle = true;
14476
14476
  }
@@ -14498,10 +14498,10 @@ class Call {
14498
14498
  }
14499
14499
  }
14500
14500
  else {
14501
- this.scheduleAutoDrop();
14502
14501
  if (this.state.callingState === CallingState.IDLE) {
14503
14502
  this.state.setCallingState(CallingState.RINGING);
14504
14503
  }
14504
+ this.scheduleAutoDrop();
14505
14505
  this.leaveCallHooks.add(registerRingingCallEventHandlers(this));
14506
14506
  }
14507
14507
  }));
@@ -14524,21 +14524,6 @@ class Call {
14524
14524
  get isCreatedByMe() {
14525
14525
  return this.state.createdBy?.id === this.currentUserId;
14526
14526
  }
14527
- /**
14528
- * Handles the closing of the SFU signal connection.
14529
- *
14530
- * @internal
14531
- * @param sfuClient the SFU client instance that was closed.
14532
- */
14533
- handleSfuSignalClose(sfuClient) {
14534
- this.logger('debug', '[Reconnect] SFU signal connection closed');
14535
- // normal close, no need to reconnect
14536
- if (sfuClient.isLeaving)
14537
- return;
14538
- this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
14539
- this.logger('warn', '[Reconnect] Error reconnecting', err);
14540
- });
14541
- }
14542
14527
  }
14543
14528
 
14544
14529
  var https = null;
@@ -15641,7 +15626,6 @@ class StreamClient {
15641
15626
  */
15642
15627
  constructor(key, options) {
15643
15628
  this.listeners = {};
15644
- this.nextRequestAbortController = null;
15645
15629
  this.devToken = (userID) => {
15646
15630
  return DevToken(userID);
15647
15631
  };
@@ -15772,15 +15756,6 @@ class StreamClient {
15772
15756
  this.wsPromise = this.connect();
15773
15757
  return this.wsPromise;
15774
15758
  };
15775
- this._normalizeDate = (before) => {
15776
- if (before instanceof Date) {
15777
- before = before.toISOString();
15778
- }
15779
- if (before === '') {
15780
- throw new Error("Don't pass blank string for since, use null instead if resetting the token revoke");
15781
- }
15782
- return before;
15783
- };
15784
15759
  /**
15785
15760
  * Disconnects the websocket and removes the user from client.
15786
15761
  *
@@ -15957,14 +15932,13 @@ class StreamClient {
15957
15932
  });
15958
15933
  };
15959
15934
  this.errorFromResponse = (response) => {
15960
- let err;
15961
- err = new ErrorFromResponse(`Stream error HTTP code: ${response.status}`);
15962
- if (response.data && response.data.code) {
15963
- err = new Error(`Stream error code ${response.data.code}: ${response.data.message}`);
15964
- err.code = response.data.code;
15965
- }
15935
+ const { data, status } = response;
15936
+ const err = new ErrorFromResponse();
15937
+ err.message = `Stream error code ${data.code}: ${data.message}`;
15938
+ err.code = data.code;
15939
+ err.unrecoverable = data.unrecoverable;
15966
15940
  err.response = response;
15967
- err.status = response.status;
15941
+ err.status = status;
15968
15942
  return err;
15969
15943
  };
15970
15944
  this.handleResponse = (response) => {
@@ -16065,7 +16039,7 @@ class StreamClient {
16065
16039
  });
16066
16040
  };
16067
16041
  this.getUserAgent = () => {
16068
- const version = "1.6.0-rc.0" ;
16042
+ const version = "1.6.0-rc2.0" ;
16069
16043
  return (this.userAgent ||
16070
16044
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
16071
16045
  };
@@ -16083,11 +16057,6 @@ class StreamClient {
16083
16057
  }) => {
16084
16058
  const token = options.publicEndpoint && !this.user ? undefined : this._getToken();
16085
16059
  const authorization = token ? { Authorization: token } : undefined;
16086
- let signal = null;
16087
- if (this.nextRequestAbortController !== null) {
16088
- signal = this.nextRequestAbortController.signal;
16089
- this.nextRequestAbortController = null;
16090
- }
16091
16060
  if (!options.headers?.['x-client-request-id']) {
16092
16061
  options.headers = {
16093
16062
  ...options.headers,
@@ -16109,7 +16078,6 @@ class StreamClient {
16109
16078
  'X-Stream-Client': this.getUserAgent(),
16110
16079
  ...options.headers,
16111
16080
  },
16112
- ...(signal ? { signal } : {}),
16113
16081
  ...options.config,
16114
16082
  ...this.options.axiosRequestConfig,
16115
16083
  };
@@ -16551,7 +16519,7 @@ class StreamVideoClient {
16551
16519
  }
16552
16520
  if (id) {
16553
16521
  if (StreamVideoClient._instanceMap.has(apiKeyOrArgs.apiKey + id)) {
16554
- this.logger('warn', `A StreamVideoClient already exists for ${user.type === 'anonymous' ? 'an anyonymous user' : id}; Prefer using getOrCreateInstance method`);
16522
+ this.logger('warn', `A StreamVideoClient already exists for ${user.type === 'anonymous' ? 'an anonymous user' : id}; Prefer using getOrCreateInstance method`);
16555
16523
  }
16556
16524
  user.id = id;
16557
16525
  StreamVideoClient._instanceMap.set(apiKeyOrArgs.apiKey + id, this);