@stream-io/video-client 0.0.1-alpha.82 → 0.0.1-alpha.83

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.
@@ -3,7 +3,7 @@ import { MessageType, isJsonObject, typeofJsonValue, reflectionMergePartial, Unk
3
3
  import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
4
4
  import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
5
5
  import { v4 } from 'uuid';
6
- import { ReplaySubject, BehaviorSubject, Subject, takeWhile, filter, debounceTime, startWith, pairwise, Observable, concatMap, from, shareReplay, merge, map as map$2, combineLatest, firstValueFrom } from 'rxjs';
6
+ import { ReplaySubject, BehaviorSubject, Subject, takeWhile, debounceTime, startWith, pairwise, Observable, concatMap, from, shareReplay, merge, map as map$2, combineLatest, filter, firstValueFrom } from 'rxjs';
7
7
  import { take, combineLatestWith, map as map$1, distinctUntilChanged } from 'rxjs/operators';
8
8
  import axios, { AxiosHeaders } from 'axios';
9
9
  import WebSocket$1 from 'isomorphic-ws';
@@ -3821,28 +3821,27 @@ const createSignalClient = (options) => {
3821
3821
  };
3822
3822
 
3823
3823
  const createWebSocketSignalChannel = (opts) => {
3824
- return new Promise((resolve) => {
3825
- const { endpoint, onMessage } = opts;
3826
- const ws = new WebSocket(endpoint);
3827
- ws.binaryType = 'arraybuffer'; // do we need this?
3828
- ws.addEventListener('open', () => {
3829
- return resolve(ws);
3830
- });
3831
- ws.addEventListener('error', (e) => {
3832
- console.error('Error', e);
3833
- });
3834
- ws.addEventListener('close', (e) => {
3835
- console.warn('Signalling channel is closed', e);
3836
- });
3837
- if (onMessage) {
3838
- ws.addEventListener('message', (e) => {
3839
- const message = e.data instanceof ArrayBuffer
3840
- ? SfuEvent.fromBinary(new Uint8Array(e.data))
3841
- : SfuEvent.fromJsonString(e.data);
3842
- onMessage(message);
3843
- });
3844
- }
3824
+ const { endpoint, onMessage } = opts;
3825
+ const ws = new WebSocket(endpoint);
3826
+ ws.binaryType = 'arraybuffer'; // do we need this?
3827
+ ws.addEventListener('error', (e) => {
3828
+ console.error('Error', e);
3829
+ });
3830
+ ws.addEventListener('close', (e) => {
3831
+ console.warn('Signalling channel is closed', e);
3845
3832
  });
3833
+ ws.addEventListener('open', (e) => {
3834
+ console.log('Signalling channel is open', e);
3835
+ });
3836
+ if (onMessage) {
3837
+ ws.addEventListener('message', (e) => {
3838
+ const message = e.data instanceof ArrayBuffer
3839
+ ? SfuEvent.fromBinary(new Uint8Array(e.data))
3840
+ : SfuEvent.fromJsonString(e.data);
3841
+ onMessage(message);
3842
+ });
3843
+ }
3844
+ return ws;
3846
3845
  };
3847
3846
 
3848
3847
  /**
@@ -3891,17 +3890,23 @@ const toURL = (url) => {
3891
3890
  return null;
3892
3891
  }
3893
3892
  };
3893
+ /**
3894
+ * The client used for exchanging information with the SFU.
3895
+ */
3894
3896
  class StreamSfuClient {
3895
3897
  constructor(dispatcher, url, token) {
3898
+ /**
3899
+ * A buffer for ICE Candidates that are received before
3900
+ * the PeerConnections are ready to handle them.
3901
+ */
3896
3902
  this.iceTrickleBuffer = new IceTrickleBuffer();
3897
- this.close = () => {
3898
- this.signalReady.then((ws) => {
3899
- this.signalReady = Promise.reject('Connection closed');
3900
- // TODO OL: re-connect flow
3901
- ws.close(1000, 'Requested signal connection close');
3902
- this.dispatcher.offAll();
3903
- clearInterval(this.keepAliveInterval);
3904
- });
3903
+ this.pingIntervalInMs = 25 * 1000;
3904
+ this.unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
3905
+ this.close = (code = 1000) => {
3906
+ this.signalWs.close(code, 'Requested signal connection close');
3907
+ this.unsubscribeIceTrickle();
3908
+ clearInterval(this.keepAliveInterval);
3909
+ clearTimeout(this.connectionCheckTimeout);
3905
3910
  };
3906
3911
  this.updateSubscriptions = (subscriptions) => __awaiter(this, void 0, void 0, function* () {
3907
3912
  return this.rpc.updateSubscriptions({
@@ -3945,7 +3950,35 @@ class StreamSfuClient {
3945
3950
  signal.send(SfuRequest.toBinary(message));
3946
3951
  });
3947
3952
  };
3948
- this.dispatcher = dispatcher;
3953
+ this.keepAlive = () => {
3954
+ if (this.keepAliveInterval) {
3955
+ clearInterval(this.keepAliveInterval);
3956
+ }
3957
+ this.keepAliveInterval = setInterval(() => {
3958
+ console.log('Sending healthCheckRequest to SFU');
3959
+ const message = SfuRequest.create({
3960
+ requestPayload: {
3961
+ oneofKind: 'healthCheckRequest',
3962
+ healthCheckRequest: {},
3963
+ },
3964
+ });
3965
+ void this.send(message);
3966
+ }, this.pingIntervalInMs);
3967
+ };
3968
+ this.scheduleConnectionCheck = () => {
3969
+ if (this.connectionCheckTimeout) {
3970
+ clearTimeout(this.connectionCheckTimeout);
3971
+ }
3972
+ this.connectionCheckTimeout = setTimeout(() => {
3973
+ if (this.lastMessageTimestamp) {
3974
+ const timeSinceLastMessage = new Date().getTime() - this.lastMessageTimestamp.getTime();
3975
+ if (timeSinceLastMessage > this.unhealthyTimeoutInMs) {
3976
+ console.log('SFU connection unhealthy, closing');
3977
+ this.close(4001);
3978
+ }
3979
+ }
3980
+ }, this.unhealthyTimeoutInMs);
3981
+ };
3949
3982
  this.sessionId = v4();
3950
3983
  this.token = token;
3951
3984
  this.rpc = createSignalClient({
@@ -3972,36 +4005,25 @@ class StreamSfuClient {
3972
4005
  // connection is established. In that case, those events (ICE candidates)
3973
4006
  // need to be buffered and later added to the appropriate PeerConnection
3974
4007
  // once the remoteDescription is known and set.
3975
- this.dispatcher.on('iceTrickle', (e) => {
4008
+ this.unsubscribeIceTrickle = dispatcher.on('iceTrickle', (e) => {
3976
4009
  if (e.eventPayload.oneofKind !== 'iceTrickle')
3977
4010
  return;
3978
4011
  const { iceTrickle } = e.eventPayload;
3979
4012
  this.iceTrickleBuffer.push(iceTrickle);
3980
4013
  });
3981
- this.signalReady = createWebSocketSignalChannel({
4014
+ this.signalWs = createWebSocketSignalChannel({
3982
4015
  endpoint: wsEndpoint,
3983
4016
  onMessage: (message) => {
3984
- this.dispatcher.dispatch(message);
4017
+ this.lastMessageTimestamp = new Date();
4018
+ this.scheduleConnectionCheck();
4019
+ dispatcher.dispatch(message);
3985
4020
  },
3986
4021
  });
3987
- }
3988
- // FIXME: make this private
3989
- keepAlive() {
3990
- return __awaiter(this, void 0, void 0, function* () {
3991
- yield this.signalReady;
3992
- console.log('Registering healthcheck for SFU');
3993
- this.keepAliveInterval = setInterval(() => {
3994
- const message = SfuRequest.create({
3995
- requestPayload: {
3996
- oneofKind: 'healthCheckRequest',
3997
- healthCheckRequest: {
3998
- sessionId: this.sessionId,
3999
- },
4000
- },
4001
- });
4002
- console.log('Sending healthCheckRequest to SFU', message);
4003
- this.send(message);
4004
- }, 27000);
4022
+ this.signalReady = new Promise((resolve) => {
4023
+ this.signalWs.addEventListener('open', () => {
4024
+ this.keepAlive();
4025
+ resolve(this.signalWs);
4026
+ });
4005
4027
  });
4006
4028
  }
4007
4029
  }
@@ -4019,8 +4041,9 @@ function getIceCandidate(candidate) {
4019
4041
  }
4020
4042
  }
4021
4043
 
4022
- const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4044
+ const createSubscriber = ({ rpcClient, dispatcher, connectionConfig, onTrack, }) => {
4023
4045
  const subscriber = new RTCPeerConnection(connectionConfig);
4046
+ attachDebugEventListeners(subscriber);
4024
4047
  subscriber.addEventListener('icecandidate', (e) => __awaiter(void 0, void 0, void 0, function* () {
4025
4048
  const { candidate } = e;
4026
4049
  if (!candidate) {
@@ -4032,22 +4055,11 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4032
4055
  peerType: PeerType.SUBSCRIBER,
4033
4056
  });
4034
4057
  }));
4035
- subscriber.addEventListener('icecandidateerror', (e) => {
4036
- const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4037
- `${e.errorCode}: ${e.errorText}`;
4038
- console.error(`Subscriber: ICE Candidate error`, errorMessage, e);
4039
- });
4040
- subscriber.addEventListener('iceconnectionstatechange', (e) => {
4041
- console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState, e);
4042
- });
4043
- subscriber.addEventListener('icegatheringstatechange', (e) => {
4044
- console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState, e);
4045
- });
4046
4058
  if (onTrack) {
4047
4059
  subscriber.addEventListener('track', onTrack);
4048
4060
  }
4049
- const { dispatcher, iceTrickleBuffer } = rpcClient;
4050
- dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
4061
+ const { iceTrickleBuffer } = rpcClient;
4062
+ const unsubscribe = dispatcher.on('subscriberOffer', (message) => __awaiter(void 0, void 0, void 0, function* () {
4051
4063
  if (message.eventPayload.oneofKind !== 'subscriberOffer')
4052
4064
  return;
4053
4065
  const { subscriberOffer } = message.eventPayload;
@@ -4062,7 +4074,7 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4062
4074
  yield subscriber.addIceCandidate(iceCandidate);
4063
4075
  }
4064
4076
  catch (e) {
4065
- console.error(`[Subscriber] ICE candidate error`, e, candidate);
4077
+ console.error(`Subscriber: ICE candidate error`, e, candidate);
4066
4078
  }
4067
4079
  }));
4068
4080
  // apply ice candidates
@@ -4073,8 +4085,30 @@ const createSubscriber = ({ rpcClient, connectionConfig, onTrack, }) => {
4073
4085
  sdp: answer.sdp || '',
4074
4086
  });
4075
4087
  }));
4088
+ // we replace the close method of the subscriber PeerConnection
4089
+ // so that we can preform some cleanups before closing the connection.
4090
+ // We are doing this as currently there is no event that is fired
4091
+ // when the subscriber PeerConnection is closed.
4092
+ const originalClose = subscriber.close;
4093
+ subscriber.close = () => {
4094
+ unsubscribe();
4095
+ originalClose.call(subscriber);
4096
+ };
4076
4097
  return subscriber;
4077
4098
  };
4099
+ const attachDebugEventListeners = (subscriber) => {
4100
+ subscriber.addEventListener('icecandidateerror', (e) => {
4101
+ const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4102
+ `${e.errorCode}: ${e.errorText}`;
4103
+ console.error(`Subscriber: ICE Candidate error`, errorMessage);
4104
+ });
4105
+ subscriber.addEventListener('iceconnectionstatechange', () => {
4106
+ console.log(`Subscriber: ICE Connection state changed`, subscriber.iceConnectionState);
4107
+ });
4108
+ subscriber.addEventListener('icegatheringstatechange', () => {
4109
+ console.log(`Subscriber: ICE Gathering State`, subscriber.iceGatheringState);
4110
+ });
4111
+ };
4078
4112
 
4079
4113
  const findOptimalVideoLayers = (videoTrack) => {
4080
4114
  const steps = [
@@ -4221,8 +4255,8 @@ const getGenericSdp = (direction, preferredCodec) => __awaiter(void 0, void 0, v
4221
4255
  });
4222
4256
 
4223
4257
  /**
4224
- * @internal
4225
4258
  * The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
4259
+ * @internal
4226
4260
  */
4227
4261
  class Publisher {
4228
4262
  constructor({ connectionConfig, rpcClient, isDtxEnabled }) {
@@ -4276,7 +4310,7 @@ class Publisher {
4276
4310
  if (trackType === TrackType.VIDEO) {
4277
4311
  const codecPreferences = getPreferredCodecs('video', opts.preferredCodec || 'vp8');
4278
4312
  if ('setCodecPreferences' in transceiver && codecPreferences) {
4279
- console.log(`set codec preferences`, codecPreferences);
4313
+ console.log(`set video codec preferences`, codecPreferences);
4280
4314
  // @ts-ignore
4281
4315
  transceiver.setCodecPreferences(codecPreferences);
4282
4316
  }
@@ -4286,14 +4320,17 @@ class Publisher {
4286
4320
  const codecPreferences = getPreferredCodecs('audio', opts.preferredCodec, returnOnlyMatched);
4287
4321
  console.log('Preferred codec', opts.preferredCodec);
4288
4322
  if ('setCodecPreferences' in transceiver && codecPreferences) {
4289
- console.log(`set codec preferences`, codecPreferences);
4323
+ console.log(`set audio codec preferences`, codecPreferences);
4290
4324
  // @ts-ignore
4291
4325
  transceiver.setCodecPreferences(codecPreferences);
4292
4326
  }
4293
4327
  }
4294
4328
  }
4295
4329
  else {
4296
- (_a = transceiver.sender.track) === null || _a === void 0 ? void 0 : _a.stop();
4330
+ // don't stop the track if we are re-publishing the same track
4331
+ if (transceiver.sender.track !== track) {
4332
+ (_a = transceiver.sender.track) === null || _a === void 0 ? void 0 : _a.stop();
4333
+ }
4297
4334
  yield transceiver.sender.replaceTrack(track);
4298
4335
  }
4299
4336
  });
@@ -4317,16 +4354,21 @@ class Publisher {
4317
4354
  };
4318
4355
  /**
4319
4356
  * Stops publishing all tracks and stop all tracks.
4357
+ *
4358
+ * @param options - Options
4359
+ * @param options.stopTracks - If `true` (default), all tracks will be stopped.
4320
4360
  */
4321
- this.stopPublishing = () => {
4322
- this.publisher.getSenders().forEach((s) => {
4323
- if (s.track) {
4324
- s.track.stop();
4325
- }
4326
- if (this.publisher.signalingState !== 'closed') {
4327
- this.publisher.removeTrack(s);
4328
- }
4329
- });
4361
+ this.stopPublishing = (options = {}) => {
4362
+ const { stopTracks = true } = options;
4363
+ if (stopTracks) {
4364
+ this.publisher.getSenders().forEach((s) => {
4365
+ var _a;
4366
+ (_a = s.track) === null || _a === void 0 ? void 0 : _a.stop();
4367
+ if (this.publisher.signalingState !== 'closed') {
4368
+ this.publisher.removeTrack(s);
4369
+ }
4370
+ });
4371
+ }
4330
4372
  this.publisher.close();
4331
4373
  };
4332
4374
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
@@ -4400,34 +4442,39 @@ class Publisher {
4400
4442
  };
4401
4443
  });
4402
4444
  // TODO debounce for 250ms
4403
- const response = yield this.rpcClient.setPublisher({
4445
+ const { response } = yield this.rpcClient.setPublisher({
4404
4446
  sdp: offer.sdp || '',
4405
4447
  tracks: trackInfos,
4406
4448
  });
4407
- yield this.publisher.setRemoteDescription({
4408
- type: 'answer',
4409
- sdp: response.response.sdp,
4410
- });
4449
+ try {
4450
+ yield this.publisher.setRemoteDescription({
4451
+ type: 'answer',
4452
+ sdp: response.sdp,
4453
+ });
4454
+ }
4455
+ catch (e) {
4456
+ console.error(`Publisher: setRemoteDescription error`, response.sdp, e);
4457
+ }
4411
4458
  this.rpcClient.iceTrickleBuffer.publisherCandidates.subscribe((candidate) => __awaiter(this, void 0, void 0, function* () {
4412
4459
  try {
4413
4460
  const iceCandidate = JSON.parse(candidate.iceCandidate);
4414
4461
  yield this.publisher.addIceCandidate(iceCandidate);
4415
4462
  }
4416
4463
  catch (e) {
4417
- console.error(`[Publisher] ICE candidate error`, e, candidate);
4464
+ console.error(`Publisher: ICE candidate error`, e, candidate);
4418
4465
  }
4419
4466
  }));
4420
4467
  });
4421
4468
  this.onIceCandidateError = (e) => {
4422
4469
  const errorMessage = e instanceof RTCPeerConnectionIceErrorEvent &&
4423
4470
  `${e.errorCode}: ${e.errorText}`;
4424
- console.error(`Publisher: ICE Candidate error`, errorMessage, e);
4471
+ console.error(`Publisher: ICE Candidate error`, errorMessage);
4425
4472
  };
4426
- this.onIceConnectionStateChange = (e) => {
4427
- console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState, e);
4473
+ this.onIceConnectionStateChange = () => {
4474
+ console.log(`Publisher: ICE Connection state changed`, this.publisher.iceConnectionState);
4428
4475
  };
4429
- this.onIceGatheringStateChange = (e) => {
4430
- console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState, e);
4476
+ this.onIceGatheringStateChange = () => {
4477
+ console.log(`Publisher: ICE Gathering State`, this.publisher.iceGatheringState);
4431
4478
  };
4432
4479
  this.ridToVideoQuality = (rid) => {
4433
4480
  return rid === 'q'
@@ -4978,6 +5025,40 @@ const speakerLayoutSortPreset = combineComparators(pinned, screenSharing, domina
4978
5025
  */
4979
5026
  const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleBy(dominantSpeaker), ifInvisibleBy(speaking), ifInvisibleBy(reactionType('raised-hand')), ifInvisibleBy(publishingVideo), ifInvisibleBy(publishingAudio), role('admin', 'host', 'speaker'));
4980
5027
 
5028
+ /**
5029
+ * Represents the state of the current call.
5030
+ */
5031
+ var CallingState;
5032
+ (function (CallingState) {
5033
+ /**
5034
+ * The call is in an idle state.
5035
+ */
5036
+ CallingState["IDLE"] = "idle";
5037
+ /**
5038
+ * The call is in the process of joining.
5039
+ */
5040
+ CallingState["JOINING"] = "joining";
5041
+ /**
5042
+ * The call is currently active.
5043
+ */
5044
+ CallingState["JOINED"] = "joined";
5045
+ /**
5046
+ * The call has been left.
5047
+ */
5048
+ CallingState["LEFT"] = "left";
5049
+ /**
5050
+ * The call is in the process of reconnecting.
5051
+ */
5052
+ CallingState["RECONNECTING"] = "reconnecting";
5053
+ /**
5054
+ * The call has failed to reconnect.
5055
+ */
5056
+ CallingState["RECONNECTING_FAILED"] = "reconnecting-failed";
5057
+ /**
5058
+ * The call is in offline mode.
5059
+ */
5060
+ CallingState["OFFLINE"] = "offline";
5061
+ })(CallingState || (CallingState = {}));
4981
5062
  /**
4982
5063
  * Holds the state of the current call.
4983
5064
  * @react You don't have to use this class directly, as we are exposing the state through Hooks.
@@ -5001,6 +5082,12 @@ class CallState {
5001
5082
  * @internal
5002
5083
  */
5003
5084
  this.membersSubject = new BehaviorSubject([]);
5085
+ /**
5086
+ * The calling state.
5087
+ *
5088
+ * @internal
5089
+ */
5090
+ this.callingStateSubject = new BehaviorSubject(CallingState.IDLE);
5004
5091
  /**
5005
5092
  * All participants of the current call (including the logged-in user).
5006
5093
  *
@@ -5142,6 +5229,7 @@ class CallState {
5142
5229
  return acc;
5143
5230
  }, {});
5144
5231
  }));
5232
+ this.callingState$ = this.callingStateSubject.asObservable();
5145
5233
  }
5146
5234
  }
5147
5235
 
@@ -5639,6 +5727,129 @@ const CallTypes = new CallTypesRegistry([
5639
5727
  }),
5640
5728
  ]);
5641
5729
 
5730
+ const sleep = (m) => new Promise((r) => setTimeout(r, m));
5731
+ function isFunction(value) {
5732
+ return (value &&
5733
+ (Object.prototype.toString.call(value) === '[object Function]' ||
5734
+ 'function' === typeof value ||
5735
+ value instanceof Function));
5736
+ }
5737
+ // todo: rename so that it does not contain word "chat"
5738
+ const chatCodes = {
5739
+ TOKEN_EXPIRED: 40,
5740
+ WS_CLOSED_SUCCESS: 1000,
5741
+ };
5742
+ /**
5743
+ * retryInterval - A retry interval which increases acc to number of failures
5744
+ *
5745
+ * @return {number} Duration to wait in milliseconds
5746
+ */
5747
+ function retryInterval(numberOfFailures) {
5748
+ // try to reconnect in 0.25-5 seconds (random to spread out the load from failures)
5749
+ const max = Math.min(500 + numberOfFailures * 2000, 5000);
5750
+ const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 5000);
5751
+ return Math.floor(Math.random() * (max - min) + min);
5752
+ }
5753
+ function randomId() {
5754
+ return generateUUIDv4();
5755
+ }
5756
+ function hex(bytes) {
5757
+ let s = '';
5758
+ for (let i = 0; i < bytes.length; i++) {
5759
+ s += bytes[i].toString(16).padStart(2, '0');
5760
+ }
5761
+ return s;
5762
+ }
5763
+ // https://tools.ietf.org/html/rfc4122
5764
+ function generateUUIDv4() {
5765
+ const bytes = getRandomBytes(16);
5766
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
5767
+ bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
5768
+ return (hex(bytes.subarray(0, 4)) +
5769
+ '-' +
5770
+ hex(bytes.subarray(4, 6)) +
5771
+ '-' +
5772
+ hex(bytes.subarray(6, 8)) +
5773
+ '-' +
5774
+ hex(bytes.subarray(8, 10)) +
5775
+ '-' +
5776
+ hex(bytes.subarray(10, 16)));
5777
+ }
5778
+ function getRandomValuesWithMathRandom(bytes) {
5779
+ const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
5780
+ for (let i = 0; i < bytes.length; i++) {
5781
+ bytes[i] = Math.random() * max;
5782
+ }
5783
+ }
5784
+ const getRandomValues = (() => {
5785
+ if (typeof crypto !== 'undefined' &&
5786
+ typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
5787
+ return crypto.getRandomValues.bind(crypto);
5788
+ }
5789
+ else if (typeof msCrypto !== 'undefined') {
5790
+ return msCrypto.getRandomValues.bind(msCrypto);
5791
+ }
5792
+ else {
5793
+ return getRandomValuesWithMathRandom;
5794
+ }
5795
+ })();
5796
+ function getRandomBytes(length) {
5797
+ const bytes = new Uint8Array(length);
5798
+ getRandomValues(bytes);
5799
+ return bytes;
5800
+ }
5801
+ function convertErrorToJson(err) {
5802
+ const jsonObj = {};
5803
+ if (!err)
5804
+ return jsonObj;
5805
+ try {
5806
+ Object.getOwnPropertyNames(err).forEach((key) => {
5807
+ jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
5808
+ });
5809
+ }
5810
+ catch (_) {
5811
+ return {
5812
+ error: 'failed to serialize the error',
5813
+ };
5814
+ }
5815
+ return jsonObj;
5816
+ }
5817
+ /**
5818
+ * isOnline safely return the navigator.online value for browser env
5819
+ * if navigator is not in global object, it always return true
5820
+ */
5821
+ function isOnline() {
5822
+ const nav = typeof navigator !== 'undefined'
5823
+ ? navigator
5824
+ : typeof window !== 'undefined' && window.navigator
5825
+ ? window.navigator
5826
+ : undefined;
5827
+ if (!nav) {
5828
+ console.warn('isOnline failed to access window.navigator and assume browser is online');
5829
+ return true;
5830
+ }
5831
+ // RN navigator has undefined for onLine
5832
+ if (typeof nav.onLine !== 'boolean') {
5833
+ return true;
5834
+ }
5835
+ return nav.onLine;
5836
+ }
5837
+ /**
5838
+ * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
5839
+ */
5840
+ function addConnectionEventListeners(cb) {
5841
+ if (typeof window !== 'undefined' && window.addEventListener) {
5842
+ window.addEventListener('offline', cb);
5843
+ window.addEventListener('online', cb);
5844
+ }
5845
+ }
5846
+ function removeConnectionEventListeners(cb) {
5847
+ if (typeof window !== 'undefined' && window.removeEventListener) {
5848
+ window.removeEventListener('offline', cb);
5849
+ window.removeEventListener('online', cb);
5850
+ }
5851
+ }
5852
+
5642
5853
  const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
5643
5854
  /**
5644
5855
  * A `Call` object represents the active call the user is part of.
@@ -5647,7 +5858,7 @@ const UPDATE_SUBSCRIPTIONS_DEBOUNCE_DURATION = 600;
5647
5858
  class Call {
5648
5859
  get preferredAudioCodec() {
5649
5860
  var _a;
5650
- const audioSettings = (_a = this.state.getCurrentValue(this.state.metadata$)) === null || _a === void 0 ? void 0 : _a.settings.audio;
5861
+ const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
5651
5862
  let preferredCodec = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.redundant_coding_enabled) === undefined
5652
5863
  ? 'opus'
5653
5864
  : audioSettings.redundant_coding_enabled
@@ -5666,7 +5877,7 @@ class Call {
5666
5877
  */
5667
5878
  constructor({ type, id, streamClient, metadata, members, sortParticipantsBy, clientStore, }) {
5668
5879
  /**
5669
- * ViewporTracker instance
5880
+ * ViewportTracker instance
5670
5881
  */
5671
5882
  this.viewportTracker = new ViewportTracker();
5672
5883
  /**
@@ -5675,7 +5886,7 @@ class Call {
5675
5886
  */
5676
5887
  this.dispatcher = new Dispatcher();
5677
5888
  this.trackSubscriptionsSubject = new Subject();
5678
- this.joined$ = new BehaviorSubject(false);
5889
+ this.reconnectAttempts = 0;
5679
5890
  /**
5680
5891
  * Remove subscription for WebSocket events that were created by the `on` method.
5681
5892
  * @param eventName
@@ -5690,10 +5901,9 @@ class Call {
5690
5901
  */
5691
5902
  this.leave = () => {
5692
5903
  var _a, _b, _c, _d;
5693
- if (!this.joined$.getValue()) {
5904
+ if (this.state.getCurrentValue(this.state.callingState$) === CallingState.LEFT) {
5694
5905
  throw new Error('Cannot leave call that has already been left.');
5695
5906
  }
5696
- this.joined$.next(false);
5697
5907
  (_a = this.statsReporter) === null || _a === void 0 ? void 0 : _a.stop();
5698
5908
  this.statsReporter = undefined;
5699
5909
  (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
@@ -5702,13 +5912,19 @@ class Call {
5702
5912
  this.publisher = undefined;
5703
5913
  (_d = this.sfuClient) === null || _d === void 0 ? void 0 : _d.close();
5704
5914
  this.sfuClient = undefined;
5915
+ this.dispatcher.offAll();
5705
5916
  this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, undefined);
5917
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.LEFT);
5706
5918
  };
5707
- this.waitForJoinResponse = (timeout = 10000) => new Promise((resolve, reject) => {
5919
+ this.waitForJoinResponse = (timeout = 5000) => new Promise((resolve, reject) => {
5708
5920
  const unsubscribe = this.on('joinResponse', (event) => {
5709
- resolve(event);
5921
+ if (event.eventPayload.oneofKind !== 'joinResponse')
5922
+ return;
5923
+ clearTimeout(timeoutId);
5924
+ unsubscribe();
5925
+ resolve(event.eventPayload.joinResponse);
5710
5926
  });
5711
- setTimeout(() => {
5927
+ const timeoutId = setTimeout(() => {
5712
5928
  unsubscribe();
5713
5929
  reject(new Error('Waiting for "joinResponse" has timed out'));
5714
5930
  }, timeout);
@@ -5731,9 +5947,10 @@ class Call {
5731
5947
  */
5732
5948
  this.join = (data) => __awaiter(this, void 0, void 0, function* () {
5733
5949
  var _a;
5734
- if (this.joined$.getValue()) {
5950
+ if ([CallingState.JOINED, CallingState.JOINING].includes(this.state.getCurrentValue(this.state.callingState$))) {
5735
5951
  throw new Error(`Illegal State: Already joined.`);
5736
5952
  }
5953
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINING);
5737
5954
  const call = yield join(this.streamClient, this.type, this.id, data);
5738
5955
  this.state.setCurrentValue(this.state.metadataSubject, call.metadata);
5739
5956
  this.state.setCurrentValue(this.state.membersSubject, call.members);
@@ -5749,12 +5966,79 @@ class Call {
5749
5966
  sfuUrl = sfuUrlParam || call.sfuServer.url;
5750
5967
  }
5751
5968
  const sfuClient = (this.sfuClient = new StreamSfuClient(this.dispatcher, sfuUrl, call.token));
5969
+ /**
5970
+ * A closure which hides away the re-connection logic.
5971
+ */
5972
+ const rejoin = () => __awaiter(this, void 0, void 0, function* () {
5973
+ var _b, _c, _d;
5974
+ console.log(`Rejoining call ${this.cid} (${this.reconnectAttempts})...`);
5975
+ this.reconnectAttempts++;
5976
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING);
5977
+ // take a snapshot of the current "local participant" state
5978
+ // we'll need it for restoring the previous publishing state later
5979
+ const localParticipant = this.state.getCurrentValue(this.state.localParticipant$);
5980
+ (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
5981
+ (_c = this.publisher) === null || _c === void 0 ? void 0 : _c.stopPublishing({ stopTracks: false });
5982
+ (_d = this.statsReporter) === null || _d === void 0 ? void 0 : _d.stop();
5983
+ sfuClient.close(); // clean up previous connection
5984
+ yield sleep(retryInterval(this.reconnectAttempts));
5985
+ yield this.join(data);
5986
+ console.log(`Rejoin: ${this.reconnectAttempts} successful!`);
5987
+ if (localParticipant) {
5988
+ const { audioStream, videoStream, screenShareStream: screenShare, } = localParticipant;
5989
+ // restore previous publishing state
5990
+ if (audioStream)
5991
+ yield this.publishAudioStream(audioStream);
5992
+ if (videoStream)
5993
+ yield this.publishVideoStream(videoStream);
5994
+ if (screenShare)
5995
+ yield this.publishScreenShareStream(screenShare);
5996
+ }
5997
+ console.log(`Rejoin: state restored ${this.reconnectAttempts}`);
5998
+ });
5999
+ // reconnect if the connection was closed unexpectedly. example:
6000
+ // - SFU crash or restart
6001
+ // - network change
6002
+ sfuClient.signalReady.then(() => {
6003
+ sfuClient.signalWs.addEventListener('close', (e) => {
6004
+ // do nothing if the connection was closed on purpose
6005
+ if (e.code === 1000)
6006
+ return;
6007
+ if (this.reconnectAttempts >= 10) {
6008
+ console.log('Reconnect attempts exceeded. Giving up...');
6009
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
6010
+ }
6011
+ else {
6012
+ void rejoin();
6013
+ }
6014
+ });
6015
+ });
6016
+ // handlers for connection online/offline events
6017
+ // Note: window.addEventListener is not available in React Native, hence the check
6018
+ if (typeof window !== 'undefined' && window.addEventListener) {
6019
+ const handleOnOffline = () => {
6020
+ window.removeEventListener('offline', handleOnOffline);
6021
+ console.log('Join: Going offline...');
6022
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.OFFLINE);
6023
+ };
6024
+ const handleOnOnline = () => {
6025
+ window.removeEventListener('online', handleOnOnline);
6026
+ if (this.state.getCurrentValue(this.state.callingState$) ===
6027
+ CallingState.OFFLINE) {
6028
+ console.log('Join: Going online...');
6029
+ rejoin();
6030
+ }
6031
+ };
6032
+ window.addEventListener('offline', handleOnOffline);
6033
+ window.addEventListener('online', handleOnOnline);
6034
+ }
5752
6035
  this.subscriber = createSubscriber({
5753
6036
  rpcClient: sfuClient,
6037
+ dispatcher: this.dispatcher,
5754
6038
  connectionConfig: call.connectionConfig,
5755
6039
  onTrack: this.handleOnTrack,
5756
6040
  });
5757
- const audioSettings = (_a = this.state.getCurrentValue(this.state.metadata$)) === null || _a === void 0 ? void 0 : _a.settings.audio;
6041
+ const audioSettings = (_a = this.data) === null || _a === void 0 ? void 0 : _a.settings.audio;
5758
6042
  let isDtxEnabled = (audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled) === undefined
5759
6043
  ? false
5760
6044
  : audioSettings === null || audioSettings === void 0 ? void 0 : audioSettings.opus_dtx_enabled;
@@ -5777,8 +6061,19 @@ class Call {
5777
6061
  store: this.state,
5778
6062
  edgeName: call.sfuServer.edge_name,
5779
6063
  });
5780
- const joinResponsePromise = this.waitForJoinResponse().then((event) => {
5781
- const { callState } = event.eventPayload.joinResponse;
6064
+ try {
6065
+ // 1. wait for the signal server to be ready before sending "joinRequest"
6066
+ sfuClient.signalReady
6067
+ .catch((err) => console.warn('Signal ready failed', err))
6068
+ // prepare a generic SDP and send it to the SFU.
6069
+ // this is a throw-away SDP that the SFU will use to determine
6070
+ // the capabilities of the client (codec support, etc.)
6071
+ .then(() => getGenericSdp('recvonly', this.preferredAudioCodec))
6072
+ .then((sdp) => sfuClient.join({ subscriberSdp: sdp || '' }));
6073
+ // 2. in parallel, wait for the SFU to send us the "joinResponse"
6074
+ // this will throw an error if the SFU rejects the join request or
6075
+ // fails to respond in time
6076
+ const { callState } = yield this.waitForJoinResponse();
5782
6077
  const currentParticipants = (callState === null || callState === void 0 ? void 0 : callState.participants) || [];
5783
6078
  const ownCapabilities = {
5784
6079
  ownCapabilities: call.metadata.own_capabilities,
@@ -5787,14 +6082,22 @@ class Call {
5787
6082
  ? ownCapabilities
5788
6083
  : {})))));
5789
6084
  this.clientStore.setCurrentValue(this.clientStore.activeCallSubject, this);
5790
- sfuClient.keepAlive();
5791
- this.joined$.next(true);
5792
- });
5793
- const genericSdp = yield getGenericSdp('recvonly', this.preferredAudioCodec);
5794
- yield sfuClient.join({
5795
- subscriberSdp: genericSdp || '',
5796
- });
5797
- return joinResponsePromise;
6085
+ this.reconnectAttempts = 0; // reset the reconnect attempts counter
6086
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.JOINED);
6087
+ console.log(`Joined call ${this.cid}`);
6088
+ }
6089
+ catch (err) {
6090
+ // join failed, try to rejoin
6091
+ if (this.reconnectAttempts < 10) {
6092
+ yield rejoin();
6093
+ console.log(`Rejoin ${this.reconnectAttempts} successful!`);
6094
+ }
6095
+ else {
6096
+ console.log(`Rejoin failed for ${this.reconnectAttempts} times. Giving up.`);
6097
+ this.state.setCurrentValue(this.state.callingStateSubject, CallingState.RECONNECTING_FAILED);
6098
+ throw new Error('Join failed');
6099
+ }
6100
+ }
5798
6101
  });
5799
6102
  this.updateCallMembers = (data) => __awaiter(this, void 0, void 0, function* () {
5800
6103
  return yield this.streamClient.post(`${this.streamClientBasePath}/members`, data);
@@ -5814,7 +6117,7 @@ class Call {
5814
6117
  * @param opts the options to use when publishing the stream.
5815
6118
  */
5816
6119
  this.publishVideoStream = (videoStream, opts = {}) => __awaiter(this, void 0, void 0, function* () {
5817
- var _b;
6120
+ var _e;
5818
6121
  // we should wait until we get a JoinResponse from the SFU,
5819
6122
  // otherwise we risk breaking the ICETrickle flow.
5820
6123
  yield this.assertCallJoined();
@@ -5828,7 +6131,7 @@ class Call {
5828
6131
  const trackType = TrackType.VIDEO;
5829
6132
  try {
5830
6133
  yield this.publisher.publishStream(videoStream, videoTrack, trackType, opts);
5831
- yield ((_b = this.sfuClient) === null || _b === void 0 ? void 0 : _b.updateMuteState(trackType, false));
6134
+ yield ((_e = this.sfuClient) === null || _e === void 0 ? void 0 : _e.updateMuteState(trackType, false));
5832
6135
  }
5833
6136
  catch (e) {
5834
6137
  throw e;
@@ -6013,8 +6316,8 @@ class Call {
6013
6316
  * @returns
6014
6317
  */
6015
6318
  this.getStats = (kind, selector) => __awaiter(this, void 0, void 0, function* () {
6016
- var _c;
6017
- return (_c = this.statsReporter) === null || _c === void 0 ? void 0 : _c.getRawStatsForTrack(kind, selector);
6319
+ var _f;
6320
+ return (_f = this.statsReporter) === null || _f === void 0 ? void 0 : _f.getRawStatsForTrack(kind, selector);
6018
6321
  });
6019
6322
  /**
6020
6323
  * Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
@@ -6102,8 +6405,8 @@ class Call {
6102
6405
  * @returns
6103
6406
  */
6104
6407
  this.updatePublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6105
- var _d;
6106
- return (_d = this.publisher) === null || _d === void 0 ? void 0 : _d.updateVideoPublishQuality(enabledRids);
6408
+ var _g;
6409
+ return (_g = this.publisher) === null || _g === void 0 ? void 0 : _g.updateVideoPublishQuality(enabledRids);
6107
6410
  });
6108
6411
  this.handleOnTrack = (e) => {
6109
6412
  const [primaryStream] = e.streams;
@@ -6149,11 +6452,9 @@ class Call {
6149
6452
  };
6150
6453
  this.assertCallJoined = () => {
6151
6454
  return new Promise((resolve) => {
6152
- this.joined$
6153
- .pipe(takeWhile((isJoined) => !isJoined, true), filter((isJoined) => isJoined))
6154
- .subscribe(() => {
6155
- resolve();
6156
- });
6455
+ this.state.callingState$
6456
+ .pipe(takeWhile((state) => state !== CallingState.JOINED, true))
6457
+ .subscribe(() => resolve());
6157
6458
  });
6158
6459
  };
6159
6460
  this.sendReaction = (reaction) => __awaiter(this, void 0, void 0, function* () {
@@ -6817,129 +7118,6 @@ CallDropScheduler.getLatestCall = (from, compareTo) => {
6817
7118
 
6818
7119
  var https = null;
6819
7120
 
6820
- const sleep = (m) => new Promise((r) => setTimeout(r, m));
6821
- function isFunction(value) {
6822
- return (value &&
6823
- (Object.prototype.toString.call(value) === '[object Function]' ||
6824
- 'function' === typeof value ||
6825
- value instanceof Function));
6826
- }
6827
- // todo: rename so that it does not contain word "chat"
6828
- const chatCodes = {
6829
- TOKEN_EXPIRED: 40,
6830
- WS_CLOSED_SUCCESS: 1000,
6831
- };
6832
- /**
6833
- * retryInterval - A retry interval which increases acc to number of failures
6834
- *
6835
- * @return {number} Duration to wait in milliseconds
6836
- */
6837
- function retryInterval(numberOfFailures) {
6838
- // try to reconnect in 0.25-25 seconds (random to spread out the load from failures)
6839
- const max = Math.min(500 + numberOfFailures * 2000, 25000);
6840
- const min = Math.min(Math.max(250, (numberOfFailures - 1) * 2000), 25000);
6841
- return Math.floor(Math.random() * (max - min) + min);
6842
- }
6843
- function randomId() {
6844
- return generateUUIDv4();
6845
- }
6846
- function hex(bytes) {
6847
- let s = '';
6848
- for (let i = 0; i < bytes.length; i++) {
6849
- s += bytes[i].toString(16).padStart(2, '0');
6850
- }
6851
- return s;
6852
- }
6853
- // https://tools.ietf.org/html/rfc4122
6854
- function generateUUIDv4() {
6855
- const bytes = getRandomBytes(16);
6856
- bytes[6] = (bytes[6] & 0x0f) | 0x40; // version
6857
- bytes[8] = (bytes[8] & 0xbf) | 0x80; // variant
6858
- return (hex(bytes.subarray(0, 4)) +
6859
- '-' +
6860
- hex(bytes.subarray(4, 6)) +
6861
- '-' +
6862
- hex(bytes.subarray(6, 8)) +
6863
- '-' +
6864
- hex(bytes.subarray(8, 10)) +
6865
- '-' +
6866
- hex(bytes.subarray(10, 16)));
6867
- }
6868
- function getRandomValuesWithMathRandom(bytes) {
6869
- const max = Math.pow(2, (8 * bytes.byteLength) / bytes.length);
6870
- for (let i = 0; i < bytes.length; i++) {
6871
- bytes[i] = Math.random() * max;
6872
- }
6873
- }
6874
- const getRandomValues = (() => {
6875
- if (typeof crypto !== 'undefined' &&
6876
- typeof (crypto === null || crypto === void 0 ? void 0 : crypto.getRandomValues) !== 'undefined') {
6877
- return crypto.getRandomValues.bind(crypto);
6878
- }
6879
- else if (typeof msCrypto !== 'undefined') {
6880
- return msCrypto.getRandomValues.bind(msCrypto);
6881
- }
6882
- else {
6883
- return getRandomValuesWithMathRandom;
6884
- }
6885
- })();
6886
- function getRandomBytes(length) {
6887
- const bytes = new Uint8Array(length);
6888
- getRandomValues(bytes);
6889
- return bytes;
6890
- }
6891
- function convertErrorToJson(err) {
6892
- const jsonObj = {};
6893
- if (!err)
6894
- return jsonObj;
6895
- try {
6896
- Object.getOwnPropertyNames(err).forEach((key) => {
6897
- jsonObj[key] = Object.getOwnPropertyDescriptor(err, key);
6898
- });
6899
- }
6900
- catch (_) {
6901
- return {
6902
- error: 'failed to serialize the error',
6903
- };
6904
- }
6905
- return jsonObj;
6906
- }
6907
- /**
6908
- * isOnline safely return the navigator.online value for browser env
6909
- * if navigator is not in global object, it always return true
6910
- */
6911
- function isOnline() {
6912
- const nav = typeof navigator !== 'undefined'
6913
- ? navigator
6914
- : typeof window !== 'undefined' && window.navigator
6915
- ? window.navigator
6916
- : undefined;
6917
- if (!nav) {
6918
- console.warn('isOnline failed to access window.navigator and assume browser is online');
6919
- return true;
6920
- }
6921
- // RN navigator has undefined for onLine
6922
- if (typeof nav.onLine !== 'boolean') {
6923
- return true;
6924
- }
6925
- return nav.onLine;
6926
- }
6927
- /**
6928
- * listenForConnectionChanges - Adds an event listener fired on browser going online or offline
6929
- */
6930
- function addConnectionEventListeners(cb) {
6931
- if (typeof window !== 'undefined' && window.addEventListener) {
6932
- window.addEventListener('offline', cb);
6933
- window.addEventListener('online', cb);
6934
- }
6935
- }
6936
- function removeConnectionEventListeners(cb) {
6937
- if (typeof window !== 'undefined' && window.removeEventListener) {
6938
- window.removeEventListener('offline', cb);
6939
- window.removeEventListener('online', cb);
6940
- }
6941
- }
6942
-
6943
7121
  class InsightMetrics {
6944
7122
  constructor() {
6945
7123
  this.connectionStartTimestamp = null;
@@ -8550,7 +8728,7 @@ class StreamClient {
8550
8728
  }
8551
8729
  getUserAgent() {
8552
8730
  return (this.userAgent ||
8553
- `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.81"}`);
8731
+ `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${"0.0.1-alpha.82"}`);
8554
8732
  }
8555
8733
  setUserAgent(userAgent) {
8556
8734
  this.userAgent = userAgent;
@@ -9078,5 +9256,5 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
9078
9256
  };
9079
9257
  };
9080
9258
 
9081
- export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
9259
+ export { browsers as Browsers, CALL_CONFIG, Call, CallState, CallType, CallTypes, CallingState, DeviceFieldsRequestPushProviderEnum, ErrorFromResponse, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getScreenShareStream, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
9082
9260
  //# sourceMappingURL=index.browser.es.js.map