@stream-io/video-client 1.58.0 → 1.59.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 (57) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.browser.es.js +367 -76
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +378 -74
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.es.js +367 -76
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/Call.d.ts +15 -4
  10. package/dist/src/StreamVideoClient.d.ts +9 -1
  11. package/dist/src/coordinator/connection/utils.d.ts +10 -0
  12. package/dist/src/devices/CameraManager.d.ts +2 -1
  13. package/dist/src/devices/DeviceManager.d.ts +1 -1
  14. package/dist/src/devices/MicrophoneManager.d.ts +2 -1
  15. package/dist/src/gen/coordinator/index.d.ts +26 -0
  16. package/dist/src/helpers/MediaStreamRecorder.d.ts +26 -0
  17. package/dist/src/helpers/concurrency.d.ts +8 -0
  18. package/dist/src/helpers/loopback.d.ts +55 -0
  19. package/dist/src/reporting/ClientEventReporter.d.ts +0 -2
  20. package/dist/src/rtc/Subscriber.d.ts +5 -0
  21. package/dist/src/sorting/participants.d.ts +13 -0
  22. package/index.ts +2 -0
  23. package/package.json +1 -1
  24. package/src/Call.ts +156 -134
  25. package/src/StreamVideoClient.ts +35 -8
  26. package/src/__tests__/Call.lifecycle.test.ts +33 -2
  27. package/src/__tests__/Call.publishing.test.ts +42 -1
  28. package/src/__tests__/StreamVideoClient.api.test.ts +33 -0
  29. package/src/__tests__/StreamVideoClient.rewatch.test.ts +213 -0
  30. package/src/__tests__/StreamVideoClient.ringing.test.ts +43 -24
  31. package/src/__tests__/StreamVideoClient.test.ts +14 -12
  32. package/src/coordinator/connection/__tests__/eventListenerIsolation.test.ts +115 -0
  33. package/src/coordinator/connection/client.ts +3 -2
  34. package/src/coordinator/connection/utils.ts +28 -0
  35. package/src/devices/CameraManager.ts +16 -4
  36. package/src/devices/DeviceManager.ts +7 -4
  37. package/src/devices/MicrophoneManager.ts +11 -3
  38. package/src/devices/__tests__/CameraManager.test.ts +27 -1
  39. package/src/devices/__tests__/DeviceManager.test.ts +49 -1
  40. package/src/devices/__tests__/MicrophoneManager.test.ts +23 -1
  41. package/src/events/call.ts +6 -3
  42. package/src/gen/coordinator/index.ts +26 -0
  43. package/src/helpers/MediaStreamRecorder.ts +104 -0
  44. package/src/helpers/__tests__/MediaStreamRecorder.test.ts +324 -0
  45. package/src/helpers/__tests__/concurrency.test.ts +74 -0
  46. package/src/helpers/__tests__/loopback.test.ts +367 -0
  47. package/src/helpers/concurrency.ts +24 -0
  48. package/src/helpers/loopback.ts +200 -0
  49. package/src/reporting/ClientEventReporter.ts +15 -28
  50. package/src/reporting/__tests__/ClientEventReporter.test.ts +3 -7
  51. package/src/rtc/Subscriber.ts +8 -0
  52. package/src/rtc/__tests__/Subscriber.test.ts +101 -0
  53. package/src/sorting/__tests__/presets.test.ts +188 -3
  54. package/src/sorting/__tests__/sorting.test.ts +22 -1
  55. package/src/sorting/participants.ts +20 -0
  56. package/src/sorting/presets.ts +4 -0
  57. package/src/store/__tests__/CallState.test.ts +2 -2
@@ -7,7 +7,7 @@ import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transpor
7
7
  import * as scopedLogger from '@stream-io/logger';
8
8
  export { LogLevelEnum } from '@stream-io/logger';
9
9
  import { parse, write } from 'sdp-transform';
10
- import { Subject, Observable, combineLatest, BehaviorSubject, ReplaySubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, firstValueFrom, of } from 'rxjs';
10
+ import { Subject, Observable, combineLatest, BehaviorSubject, ReplaySubject, shareReplay, map, distinctUntilChanged, startWith, takeWhile, distinctUntilKeyChanged, fromEventPattern, concatMap, merge, from, fromEvent, tap, debounceTime, pairwise, firstValueFrom, of, Subscription } from 'rxjs';
11
11
  import { UAParser } from 'ua-parser-js';
12
12
  import { WorkerTimer } from '@stream-io/worker-timer';
13
13
 
@@ -4411,6 +4411,28 @@ function isErrorResponse(res) {
4411
4411
  function isCloseEvent(res) {
4412
4412
  return res.code !== undefined;
4413
4413
  }
4414
+ /**
4415
+ * Invokes a single event listener in isolation, so that a faulty listener
4416
+ * cannot abort the dispatch loop nor the bookkeeping that follows it.
4417
+ *
4418
+ * @param listener the listener to invoke.
4419
+ * @param event the event to pass to the listener.
4420
+ * @param logger the logger to report listener failures with.
4421
+ */
4422
+ const invokeEventListener = (listener, event, logger) => {
4423
+ try {
4424
+ const result = listener(event);
4425
+ if (result && typeof result.then === 'function') {
4426
+ Promise.resolve(result).catch((error) => report(logger, error, event));
4427
+ }
4428
+ }
4429
+ catch (error) {
4430
+ report(logger, error, event);
4431
+ }
4432
+ };
4433
+ const report = (logger, error, event) => {
4434
+ logger.error('Unhandled error in event listener', error, event);
4435
+ };
4414
4436
 
4415
4437
  /**
4416
4438
  * Checks whether we are using React Native
@@ -5059,7 +5081,7 @@ const toIceCandidate = (iceTrickle) => {
5059
5081
  }
5060
5082
  };
5061
5083
 
5062
- const version = "1.58.0";
5084
+ const version = "1.59.1";
5063
5085
  const [major, minor, patch] = version.split('.');
5064
5086
  let sdkInfo = {
5065
5087
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -5316,6 +5338,27 @@ const withoutConcurrency = createRunner(wrapWithContinuationTracking);
5316
5338
  */
5317
5339
  const withCancellation = createRunner(wrapWithCancellation);
5318
5340
  const pendingPromises = new Map();
5341
+ /**
5342
+ * Wraps an async function so concurrent calls share the in-flight promise
5343
+ * instead of queueing another execution.
5344
+ *
5345
+ * @param cb Async function to wrap.
5346
+ * @returns Wrapped async function.
5347
+ */
5348
+ function singleFlight(cb) {
5349
+ let pending;
5350
+ return (...args) => {
5351
+ if (pending)
5352
+ return pending;
5353
+ const promise = cb(...args).finally(() => {
5354
+ if (pending === promise) {
5355
+ pending = undefined;
5356
+ }
5357
+ });
5358
+ pending = promise;
5359
+ return promise;
5360
+ };
5361
+ }
5319
5362
  function hasPending(tag) {
5320
5363
  return pendingPromises.has(tag);
5321
5364
  }
@@ -5887,6 +5930,27 @@ const publishingAudio = (a, b) => {
5887
5930
  return 1;
5888
5931
  return 0;
5889
5932
  };
5933
+ /**
5934
+ * A comparator which prioritizes participants who are publishing any track
5935
+ * over participants who are not publishing at all.
5936
+ *
5937
+ * Unlike `publishingVideo` and `publishingAudio`, this comparator does not
5938
+ * distinguish between track types. Muting the microphone or turning off the
5939
+ * camera does not change a participant's rank as long as they still publish
5940
+ * something, which keeps the ordering stable across ordinary device toggles.
5941
+ *
5942
+ * @param a the first participant.
5943
+ * @param b the second participant.
5944
+ */
5945
+ const publishing = (a, b) => {
5946
+ const hasA = a.publishedTracks.length > 0;
5947
+ const hasB = b.publishedTracks.length > 0;
5948
+ if (hasA && !hasB)
5949
+ return -1;
5950
+ if (!hasA && hasB)
5951
+ return 1;
5952
+ return 0;
5953
+ };
5890
5954
  /**
5891
5955
  * A comparator which prioritizes participants who are pinned.
5892
5956
  *
@@ -5997,11 +6061,11 @@ const withVideoIngressSource = withParticipantSource(ParticipantSource.RTMP, Par
5997
6061
  /**
5998
6062
  * The default sorting preset.
5999
6063
  */
6000
- const defaultSortPreset = combineComparators(screenSharing, pinned, ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
6064
+ const defaultSortPreset = combineComparators(screenSharing, pinned, publishing, ifInvisibleBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), publishingVideo, publishingAudio)));
6001
6065
  /**
6002
6066
  * The sorting preset for speaker layout.
6003
6067
  */
6004
- const speakerLayoutSortPreset = combineComparators(screenSharing, pinned, dominantSpeaker, ifInvisibleBy(combineComparators(speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)));
6068
+ const speakerLayoutSortPreset = combineComparators(screenSharing, pinned, dominantSpeaker, publishing, ifInvisibleBy(combineComparators(speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)));
6005
6069
  /**
6006
6070
  * The sorting preset for layouts that don't render all participants but
6007
6071
  * instead, render them in pages.
@@ -6010,7 +6074,7 @@ const paginatedLayoutSortPreset = combineComparators(pinned, ifInvisibleOrUnknow
6010
6074
  /**
6011
6075
  * The sorting preset for livestreams and audio rooms.
6012
6076
  */
6013
- const livestreamOrAudioRoomSortPreset = combineComparators(ifInvisibleOrUnknownBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)), role('admin', 'host', 'speaker'));
6077
+ const livestreamOrAudioRoomSortPreset = combineComparators(publishing, ifInvisibleOrUnknownBy(combineComparators(dominantSpeaker, speaking, reactionType('raised-hand'), withVideoIngressSource, publishingVideo, publishingAudio)), role('admin', 'host', 'speaker'));
6014
6078
 
6015
6079
  /**
6016
6080
  * Returns the default egress object - when no egress data is available.
@@ -9804,6 +9868,11 @@ class Subscriber extends BasePeerConnection {
9804
9868
  throw e;
9805
9869
  }
9806
9870
  };
9871
+ /**
9872
+ * Whether the given stream is one the SFU echoed back to us
9873
+ * (`allowOwnTracksLoopback`) rather than a local capture stream.
9874
+ */
9875
+ isSelfSubscribedStream = (stream) => !!stream && !!this.trackedStreams?.has(stream);
9807
9876
  handleOnTrack = (e) => {
9808
9877
  const { streams, track } = e;
9809
9878
  const [primaryStream] = streams;
@@ -9893,6 +9962,8 @@ class Subscriber extends BasePeerConnection {
9893
9962
  const target = this.state.participants.find((p) => p.trackLookupPrefix === trackId);
9894
9963
  if (!target)
9895
9964
  return;
9965
+ if (target.isLocalParticipant)
9966
+ return;
9896
9967
  this.state.updateParticipant(target.sessionId, (p) => {
9897
9968
  const current = p.interruptedTracks ?? [];
9898
9969
  const has = current.includes(trackType);
@@ -10635,7 +10706,6 @@ const watchCallRejected = (call) => {
10635
10706
  */
10636
10707
  const watchCallEnded = (call) => {
10637
10708
  return function onCallEnded() {
10638
- globalThis.streamRNVideoSDK?.callingX?.endCall(call, 'remote');
10639
10709
  const { callingState } = call.state;
10640
10710
  if (callingState !== CallingState.IDLE &&
10641
10711
  callingState !== CallingState.LEFT) {
@@ -10644,9 +10714,13 @@ const watchCallEnded = (call) => {
10644
10714
  reason: 'call.ended event received',
10645
10715
  });
10646
10716
  call
10647
- .leave({ message: 'call.ended event received', reject: false })
10717
+ .leave({
10718
+ message: 'call.ended event received',
10719
+ reject: false,
10720
+ reason: 'ended',
10721
+ })
10648
10722
  .catch((err) => {
10649
- call.logger.error('Failed to leave call after call.ended ', err);
10723
+ call.logger.error('Failed to leave call after call.ended', err);
10650
10724
  });
10651
10725
  }
10652
10726
  };
@@ -13452,7 +13526,7 @@ class DeviceManager {
13452
13526
  createSyntheticDevice(selectedDevice, deviceKind);
13453
13527
  writePreferences(currentDevice, deviceKey, muted, storageKey);
13454
13528
  }
13455
- async applyPersistedPreferences(enabledInCallType) {
13529
+ async applyPersistedPreferences(enabledInCallType, forceDisabled = false) {
13456
13530
  const deviceKey = this.trackType === TrackType.AUDIO ? 'microphone' : 'camera';
13457
13531
  const preferences = readPreferences(this.devicePersistence.storageKey);
13458
13532
  const preferenceList = toPreferenceList(preferences[deviceKey]);
@@ -13479,13 +13553,13 @@ class DeviceManager {
13479
13553
  }
13480
13554
  const canPublish = this.call.permissionsContext.canPublish(this.trackType);
13481
13555
  if (typeof muted === 'boolean' && enabledInCallType && canPublish) {
13482
- await this.applyMutedState(muted);
13556
+ await this.applyMutedState(muted, forceDisabled);
13483
13557
  appliedMute = true;
13484
13558
  }
13485
13559
  return appliedDevice || appliedMute;
13486
13560
  }
13487
- async applyMutedState(muted) {
13488
- if (this.state.status !== undefined)
13561
+ async applyMutedState(muted, forceDisabled) {
13562
+ if (forceDisabled || this.state.status !== undefined)
13489
13563
  return;
13490
13564
  if (muted) {
13491
13565
  await this.disable();
@@ -13837,8 +13911,9 @@ class CameraManager extends DeviceManager {
13837
13911
  *
13838
13912
  * @param settings the video settings to apply.
13839
13913
  * @param publish whether to publish the stream after applying the settings.
13914
+ * @param forceDisabled whether to keep the camera disabled, regardless of server-side or local preferences.
13840
13915
  */
13841
- async apply(settings, publish) {
13916
+ async apply(settings, publish, forceDisabled = false) {
13842
13917
  // Wait for any in progress camera operation
13843
13918
  await this.statusChangeSettled();
13844
13919
  await this.selectTargetResolution(settings.target_resolution);
@@ -13850,8 +13925,7 @@ class CameraManager extends DeviceManager {
13850
13925
  if (shouldApplyDefaults &&
13851
13926
  this.devicePersistence.enabled &&
13852
13927
  permissionState === 'granted') {
13853
- persistedPreferencesApplied =
13854
- await this.applyPersistedPreferences(enabledInCallType);
13928
+ persistedPreferencesApplied = await this.applyPersistedPreferences(enabledInCallType, forceDisabled);
13855
13929
  }
13856
13930
  // apply a direction and enable the camera only if in "pristine" state,
13857
13931
  // and there are no persisted preferences
@@ -13861,7 +13935,10 @@ class CameraManager extends DeviceManager {
13861
13935
  const direction = settings.camera_facing === 'front' ? 'front' : 'back';
13862
13936
  await this.selectDirection(direction, { enableCamera: false });
13863
13937
  }
13864
- if (canPublish && settings.camera_default_on && enabledInCallType) {
13938
+ if (!forceDisabled &&
13939
+ canPublish &&
13940
+ settings.camera_default_on &&
13941
+ enabledInCallType) {
13865
13942
  await this.enable();
13866
13943
  }
13867
13944
  }
@@ -14456,8 +14533,9 @@ class MicrophoneManager extends AudioDeviceManager {
14456
14533
  * Applies the audio settings to the microphone.
14457
14534
  * @param settings the audio settings to apply.
14458
14535
  * @param publish whether to publish the stream after applying the settings.
14536
+ * @param forceDisabled whether to keep the mic disabled, regardless of server-side or local preferences.
14459
14537
  */
14460
- async apply(settings, publish) {
14538
+ async apply(settings, publish, forceDisabled = false) {
14461
14539
  // Wait for any in progress mic operation
14462
14540
  await this.statusChangeSettled();
14463
14541
  // apply server-side settings only when the device state is pristine
@@ -14469,11 +14547,11 @@ class MicrophoneManager extends AudioDeviceManager {
14469
14547
  if (shouldApplyDefaults &&
14470
14548
  this.devicePersistence.enabled &&
14471
14549
  permissionState === 'granted') {
14472
- persistedPreferencesApplied = await this.applyPersistedPreferences(true);
14550
+ persistedPreferencesApplied = await this.applyPersistedPreferences(true, forceDisabled);
14473
14551
  }
14474
14552
  const canPublish = this.call.permissionsContext.canPublish(this.trackType);
14475
14553
  if (shouldApplyDefaults && !persistedPreferencesApplied) {
14476
- if (canPublish && settings.mic_default_on) {
14554
+ if (!forceDisabled && canPublish && settings.mic_default_on) {
14477
14555
  await this.enable();
14478
14556
  }
14479
14557
  }
@@ -15442,7 +15520,7 @@ class Call {
15442
15520
  }
15443
15521
  }
15444
15522
  }
15445
- globalThis.streamRNVideoSDK?.callingX?.endCall(this);
15523
+ globalThis.streamRNVideoSDK?.callingX?.endCall(this, reason === 'ended' ? 'remote' : undefined);
15446
15524
  this.statsReporter?.stop();
15447
15525
  this.statsReporter = undefined;
15448
15526
  const leaveReason = message ?? reason ?? 'user is leaving the call';
@@ -15605,8 +15683,18 @@ class Call {
15605
15683
  // const calls = useCalls().filter((c) => c.ringing);
15606
15684
  const calls = this.clientStore.calls.filter((c) => c.cid !== this.cid);
15607
15685
  this.clientStore.setCalls([this, ...calls]);
15608
- const skipSpeakerApply = isReactNative();
15609
- await this.applyDeviceConfig(settings, false, skipSpeakerApply);
15686
+ await this.applyDeviceConfig(settings, { publish: false });
15687
+ };
15688
+ /**
15689
+ * Applies a backend-provided call state snapshot
15690
+ * (call, members, own capabilities) to this instance.
15691
+ *
15692
+ * @internal an internal method and should not be used outside the SDK.
15693
+ */
15694
+ updateFromCallStateResponse = (response) => {
15695
+ this.state.updateFromCallResponse(response.call);
15696
+ this.state.setMembers(response.members);
15697
+ this.state.setOwnCapabilities(response.own_capabilities);
15610
15698
  };
15611
15699
  /**
15612
15700
  * Loads the information about the call.
@@ -15619,9 +15707,7 @@ class Call {
15619
15707
  get = async (params) => {
15620
15708
  await this.setup();
15621
15709
  const response = await this.streamClient.get(this.streamClientBasePath, params);
15622
- this.state.updateFromCallResponse(response.call);
15623
- this.state.setMembers(response.members);
15624
- this.state.setOwnCapabilities(response.own_capabilities);
15710
+ this.updateFromCallStateResponse(response);
15625
15711
  if (params?.ring) {
15626
15712
  this.ringingSubject.next(true);
15627
15713
  }
@@ -15629,9 +15715,7 @@ class Call {
15629
15715
  this.watching = true;
15630
15716
  this.clientStore.registerOrUpdateCall(this);
15631
15717
  }
15632
- // Skip speaker setup on RN if ringing was requested or the call is already ringing
15633
- const skipSpeakerApply = isReactNative();
15634
- await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
15718
+ await this.applyDeviceConfig(response.call.settings, { publish: false });
15635
15719
  return response;
15636
15720
  };
15637
15721
  /**
@@ -15642,9 +15726,7 @@ class Call {
15642
15726
  getOrCreate = async (data) => {
15643
15727
  await this.setup();
15644
15728
  const response = await this.streamClient.post(this.streamClientBasePath, data);
15645
- this.state.updateFromCallResponse(response.call);
15646
- this.state.setMembers(response.members);
15647
- this.state.setOwnCapabilities(response.own_capabilities);
15729
+ this.updateFromCallStateResponse(response);
15648
15730
  if (data?.ring) {
15649
15731
  this.ringingSubject.next(true);
15650
15732
  }
@@ -15652,9 +15734,7 @@ class Call {
15652
15734
  this.watching = true;
15653
15735
  this.clientStore.registerOrUpdateCall(this);
15654
15736
  }
15655
- // Skip speaker setup on RN if ringing was requested or the call is already ringing
15656
- const skipSpeakerApply = isReactNative();
15657
- await this.applyDeviceConfig(response.call.settings, false, skipSpeakerApply);
15737
+ await this.applyDeviceConfig(response.call.settings, { publish: false });
15658
15738
  return response;
15659
15739
  };
15660
15740
  /**
@@ -15718,7 +15798,7 @@ class Call {
15718
15798
  *
15719
15799
  * @returns a promise which resolves once the call join-flow has finished.
15720
15800
  */
15721
- join = async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, allowOwnTracksLoopback = false, ...data } = {}) => {
15801
+ join = singleFlight(async ({ maxJoinRetries = 3, joinResponseTimeout, rpcRequestTimeout, allowOwnTracksLoopback = false, ...data } = {}) => {
15722
15802
  const callingState = this.state.callingState;
15723
15803
  if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
15724
15804
  throw new Error(`Illegal State: call.join() shall be called only once`);
@@ -15740,7 +15820,6 @@ class Call {
15740
15820
  callId: this.id,
15741
15821
  getCallSessionId: () => this.state.session?.id ?? '',
15742
15822
  getSfuId: () => this.credentials?.server.edge_name ?? '',
15743
- getUserSessionId: () => this.sfuClient?.sessionId ?? '',
15744
15823
  });
15745
15824
  this.joinResponseTimeout = joinResponseTimeout;
15746
15825
  this.rpcRequestTimeout = rpcRequestTimeout;
@@ -15792,7 +15871,7 @@ class Call {
15792
15871
  callingX?.endCall(this, 'error');
15793
15872
  throw error;
15794
15873
  }
15795
- };
15874
+ });
15796
15875
  /**
15797
15876
  * Will make a single attempt to watch for call related WebSocket events
15798
15877
  * and initiate a call session with the server.
@@ -15964,7 +16043,10 @@ class Call {
15964
16043
  if (!this.deviceSettingsAppliedOnce &&
15965
16044
  this.state.settings &&
15966
16045
  !supersededByLeave()) {
15967
- await this.applyDeviceConfig(this.state.settings, true, false);
16046
+ await this.applyDeviceConfig(this.state.settings, {
16047
+ publish: true,
16048
+ skipSpeakerApply: false,
16049
+ });
15968
16050
  this.deviceSettingsAppliedOnce = true;
15969
16051
  }
15970
16052
  if (!this.callManagerStarted && !supersededByLeave()) {
@@ -16754,12 +16836,16 @@ class Call {
16754
16836
  const streamStateProp = trackTypeToParticipantStreamKey(trackType);
16755
16837
  if (!streamStateProp)
16756
16838
  continue;
16757
- this.state.updateParticipant(sessionId, (p) => ({
16758
- publishedTracks: mediaStream
16759
- ? pushToIfMissing([...p.publishedTracks], trackType)
16760
- : p.publishedTracks.filter((t) => t !== trackType),
16761
- [streamStateProp]: mediaStream,
16762
- }));
16839
+ this.state.updateParticipant(sessionId, (p) => {
16840
+ const currentStream = p[streamStateProp];
16841
+ const isEchoedStream = this.subscriber?.isSelfSubscribedStream(currentStream);
16842
+ return {
16843
+ publishedTracks: mediaStream
16844
+ ? pushToIfMissing([...p.publishedTracks], trackType)
16845
+ : p.publishedTracks.filter((t) => t !== trackType),
16846
+ [streamStateProp]: isEchoedStream ? currentStream : mediaStream,
16847
+ };
16848
+ });
16763
16849
  }
16764
16850
  };
16765
16851
  /**
@@ -17449,18 +17535,21 @@ class Call {
17449
17535
  *
17450
17536
  * @internal
17451
17537
  */
17452
- applyDeviceConfig = async (settings, publish, skipSpeakerApply) => {
17538
+ applyDeviceConfig = async (settings, opts) => {
17539
+ const { publish,
17540
+ // Skip speaker setup on RN until joining (explicitly passed as false during join())
17541
+ skipSpeakerApply = isReactNative(), withDisabledDevices, } = opts;
17453
17542
  if (!skipSpeakerApply) {
17454
- await this.speaker.apply(settings).catch((err) => {
17455
- this.logger.warn('Speaker init failed', err);
17456
- });
17543
+ await this.speaker
17544
+ .apply(settings)
17545
+ .catch((err) => this.logger.warn('Speaker init failed', err));
17457
17546
  }
17458
- await this.camera.apply(settings.video, publish).catch((err) => {
17459
- this.logger.warn('Camera init failed', err);
17460
- });
17461
- await this.microphone.apply(settings.audio, publish).catch((err) => {
17462
- this.logger.warn('Mic init failed', err);
17463
- });
17547
+ await this.camera
17548
+ .apply(settings.video, publish, withDisabledDevices)
17549
+ .catch((err) => this.logger.warn('Camera init failed', err));
17550
+ await this.microphone
17551
+ .apply(settings.audio, publish, withDisabledDevices)
17552
+ .catch((err) => this.logger.warn('Mic init failed', err));
17464
17553
  };
17465
17554
  /**
17466
17555
  * Will begin tracking the given element for visibility changes within the
@@ -18946,11 +19035,11 @@ class StreamClient {
18946
19035
  return;
18947
19036
  // call generic listeners
18948
19037
  for (const listener of this.listeners.all || []) {
18949
- listener(event);
19038
+ invokeEventListener(listener, event, this.logger);
18950
19039
  }
18951
19040
  // call type specific listeners
18952
19041
  for (const listener of this.listeners[event.type] || []) {
18953
- listener(event);
19042
+ invokeEventListener(listener, event, this.logger);
18954
19043
  }
18955
19044
  };
18956
19045
  /**
@@ -18970,11 +19059,11 @@ class StreamClient {
18970
19059
  return await this.wsConnection.connect(this.defaultWSTimeout);
18971
19060
  };
18972
19061
  getSdkVersion = () => this.options.clientAppIdentifier?.sdkVersion ||
18973
- "1.58.0";
19062
+ "1.59.1";
18974
19063
  getUserAgent = () => {
18975
19064
  if (!this.cachedUserAgent) {
18976
19065
  const { clientAppIdentifier = {} } = this.options;
18977
- const { sdkName = 'js', sdkVersion = "1.58.0", ...extras } = clientAppIdentifier;
19066
+ const { sdkName = 'js', sdkVersion = "1.59.1", ...extras } = clientAppIdentifier;
18978
19067
  this.cachedUserAgent = [
18979
19068
  `stream-video-${sdkName}-v${sdkVersion}`,
18980
19069
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
@@ -19337,7 +19426,6 @@ class ClientEventReporter {
19337
19426
  user_id: this.streamClient.userID || this.coordinatorConnectUserId,
19338
19427
  type: ctx?.callType,
19339
19428
  id: ctx?.callId,
19340
- call_cid: cid,
19341
19429
  stage: 'JoinInitiated',
19342
19430
  join_attempt_id: joinAttemptId,
19343
19431
  ...(coordinatorConnectId && {
@@ -19519,7 +19607,6 @@ class ClientEventReporter {
19519
19607
  startedAt: Date.now(),
19520
19608
  joinAttemptIdSnapshot: this.joinAttemptIds.get(cid),
19521
19609
  sfuId: this.getSfuId(cid),
19522
- userSessionId: this.getUserSessionId(cid),
19523
19610
  wasPreviouslyConnected: this.pcEverConnected.get(key) === true,
19524
19611
  lastIceState: iceConnectionState,
19525
19612
  };
@@ -19530,9 +19617,6 @@ class ClientEventReporter {
19530
19617
  peer_connection: role,
19531
19618
  was_previously_connected: pair.wasPreviouslyConnected,
19532
19619
  ...(pair.sfuId && { sfu_id: pair.sfuId }),
19533
- ...(pair.userSessionId && {
19534
- user_session_id: pair.userSessionId,
19535
- }),
19536
19620
  event_type: 'initiated',
19537
19621
  });
19538
19622
  };
@@ -19547,9 +19631,6 @@ class ClientEventReporter {
19547
19631
  peer_connection: role,
19548
19632
  was_previously_connected: pair.wasPreviouslyConnected,
19549
19633
  ...(pair.sfuId && { sfu_id: pair.sfuId }),
19550
- ...(pair.userSessionId && {
19551
- user_session_id: pair.userSessionId,
19552
- }),
19553
19634
  event_type: 'completed',
19554
19635
  outcome: 'success',
19555
19636
  retry_count_attempt: 0,
@@ -19567,9 +19648,6 @@ class ClientEventReporter {
19567
19648
  ...this.sessionIdField(cid),
19568
19649
  peer_connection: role,
19569
19650
  was_previously_connected: pair.wasPreviouslyConnected,
19570
- ...(pair.userSessionId && {
19571
- user_session_id: pair.userSessionId,
19572
- }),
19573
19651
  ...(pair.sfuId && { sfu_id: pair.sfuId }),
19574
19652
  event_type: 'completed',
19575
19653
  outcome: 'failure',
@@ -19582,7 +19660,6 @@ class ClientEventReporter {
19582
19660
  this.peerConnectionPairs.delete(key);
19583
19661
  };
19584
19662
  getSfuId = (cid) => this.callContexts.get(cid)?.getSfuId() ?? '';
19585
- getUserSessionId = (cid) => this.callContexts.get(cid)?.getUserSessionId() ?? '';
19586
19663
  sessionIdField = (cid) => {
19587
19664
  const callSessionId = this.callContexts.get(cid)?.getCallSessionId() ?? '';
19588
19665
  return callSessionId ? { call_session_id: callSessionId } : {};
@@ -19594,7 +19671,6 @@ class ClientEventReporter {
19594
19671
  user_id: this.streamClient.userID || this.coordinatorConnectUserId,
19595
19672
  type: ctx?.callType ?? '',
19596
19673
  id: ctx?.callId ?? '',
19597
- call_cid: cid,
19598
19674
  stage,
19599
19675
  stage_id: pair.sid,
19600
19676
  ...(pair.joinAttemptIdSnapshot && {
@@ -19845,6 +19921,7 @@ class StreamVideoClient {
19845
19921
  }
19846
19922
  else {
19847
19923
  await call.updateFromRingingEvent(e);
19924
+ await call.get();
19848
19925
  }
19849
19926
  }
19850
19927
  else {
@@ -19883,6 +19960,13 @@ class StreamVideoClient {
19883
19960
  this.logger.error(`Failed to init call from event ${e.type}`, err);
19884
19961
  }
19885
19962
  };
19963
+ /**
19964
+ * Queries the API for calls matching the given filters.
19965
+ * @param data the query data.
19966
+ */
19967
+ doQueryCalls = (data) => {
19968
+ return this.streamClient.post('/calls', data);
19969
+ };
19886
19970
  /**
19887
19971
  * Rewatches the given calls with retry logic.
19888
19972
  * @param callsToReWatch array of call IDs to rewatch
@@ -19893,10 +19977,16 @@ class StreamVideoClient {
19893
19977
  for (let attempt = 0; attempt < maxRetries; attempt++) {
19894
19978
  try {
19895
19979
  this.logger.info(`Rewatching calls ${callsToReWatch.join(', ')} attempt ${attempt + 1}`);
19896
- await this.queryCalls({
19980
+ const response = await this.doQueryCalls({
19897
19981
  watch: true,
19898
19982
  filter_conditions: { cid: { $in: callsToReWatch } },
19899
19983
  });
19984
+ for (const c of response.calls) {
19985
+ const call = this.writeableStateStore.findCall(c.call.type, c.call.id);
19986
+ if (call) {
19987
+ call.updateFromCallStateResponse(c);
19988
+ }
19989
+ }
19900
19990
  return;
19901
19991
  }
19902
19992
  catch (err) {
@@ -20039,9 +20129,11 @@ class StreamVideoClient {
20039
20129
  * Will query the API for calls matching the given filters.
20040
20130
  *
20041
20131
  * @param data the query data.
20132
+ * @param opts additional options, for tweaking the API behavior.
20042
20133
  */
20043
- queryCalls = async (data = {}) => {
20044
- const response = await this.streamClient.post('/calls', data);
20134
+ queryCalls = async (data = {}, opts = {}) => {
20135
+ const { withDisabledDevices = true } = opts;
20136
+ const response = await this.doQueryCalls(data);
20045
20137
  const calls = [];
20046
20138
  for (const c of response.calls) {
20047
20139
  const call = new Call({
@@ -20055,7 +20147,10 @@ class StreamVideoClient {
20055
20147
  clientStore: this.writeableStateStore,
20056
20148
  });
20057
20149
  call.state.updateFromCallResponse(c.call);
20058
- await call.applyDeviceConfig(c.call.settings, false, isReactNative());
20150
+ await call.applyDeviceConfig(c.call.settings, {
20151
+ publish: false,
20152
+ withDisabledDevices,
20153
+ });
20059
20154
  if (data.watch) {
20060
20155
  await call.setup();
20061
20156
  this.writeableStateStore.registerCall(call);
@@ -20207,6 +20302,202 @@ const humanize = (n) => {
20207
20302
  return String(n);
20208
20303
  };
20209
20304
 
20305
+ /** Default loopback stream wait timeout. */
20306
+ const LOOPBACK_STREAMS_WAIT_TIMEOUT_MS = 10 * 1000;
20307
+ /** Default loopback recording duration. */
20308
+ const DEFAULT_LOOPBACK_RECORDING_DURATION_MS = 10 * 1000;
20309
+ /** Shortest permitted loopback recording duration. */
20310
+ const MIN_LOOPBACK_RECORDING_DURATION_MS = 5 * 1000;
20311
+ /** Longest permitted loopback recording duration. */
20312
+ const MAX_LOOPBACK_RECORDING_DURATION_MS = 2 * 60 * 1000;
20313
+ /** Thrown when loopback tracks do not arrive in time. */
20314
+ class LoopbackStreamsTimeoutError extends Error {
20315
+ constructor() {
20316
+ super('Timed out waiting for loopback streams. Ensure the call was joined ' +
20317
+ 'with `allowOwnTracksLoopback: true` and that the SFU is configured ' +
20318
+ 'to echo self-sub tracks.');
20319
+ this.name = 'LoopbackStreamsTimeoutError';
20320
+ }
20321
+ }
20322
+ /**
20323
+ * Returns the streams on the given participant that the SFU echoed back to us,
20324
+ * ignoring the local capture streams the publishing path writes to the same
20325
+ * fields.
20326
+ */
20327
+ const getLoopbackStreams = (call, participant) => {
20328
+ const { subscriber } = call ?? {};
20329
+ const { audioStream, videoStream } = participant ?? {};
20330
+ return {
20331
+ loopbackVideoStream: subscriber?.isSelfSubscribedStream(videoStream)
20332
+ ? videoStream
20333
+ : undefined,
20334
+ loopbackAudioStream: subscriber?.isSelfSubscribedStream(audioStream)
20335
+ ? audioStream
20336
+ : undefined,
20337
+ };
20338
+ };
20339
+ /**
20340
+ * Returns loopback tracks when all required tracks are present.
20341
+ *
20342
+ * Prefer {@link waitForLoopbackStreams}, which observes call state until the
20343
+ * tracks arrive. This is the synchronous snapshot it is built from.
20344
+ *
20345
+ * @internal
20346
+ */
20347
+ const getLoopbackTracks = (call, participant, includeVideo) => {
20348
+ if (!participant)
20349
+ return undefined;
20350
+ const { loopbackAudioStream, loopbackVideoStream } = getLoopbackStreams(call, participant);
20351
+ const audioTrack = loopbackAudioStream?.getAudioTracks()[0];
20352
+ const videoTrack = includeVideo
20353
+ ? loopbackVideoStream?.getVideoTracks()[0]
20354
+ : undefined;
20355
+ if (!audioTrack || (includeVideo && !videoTrack)) {
20356
+ return undefined;
20357
+ }
20358
+ return { audioTrack, videoTrack };
20359
+ };
20360
+ /** Waits for the requested loopback tracks. */
20361
+ const waitForLoopbackStreams = (call, opts) => {
20362
+ const callState = call.state;
20363
+ const { includeVideo, signal, timeoutMs = LOOPBACK_STREAMS_WAIT_TIMEOUT_MS, } = opts;
20364
+ return new Promise((resolve, reject) => {
20365
+ const initial = getLoopbackTracks(call, callState.localParticipant, includeVideo);
20366
+ if (initial) {
20367
+ resolve(initial);
20368
+ return;
20369
+ }
20370
+ if (signal.aborted) {
20371
+ resolve(null);
20372
+ return;
20373
+ }
20374
+ let settled = false;
20375
+ const subscriptions = new Subscription();
20376
+ const cleanup = () => {
20377
+ if (settled)
20378
+ return;
20379
+ settled = true;
20380
+ clearTimeout(timeoutId);
20381
+ signal.removeEventListener('abort', onAbort);
20382
+ subscriptions.unsubscribe();
20383
+ };
20384
+ const onAbort = () => {
20385
+ cleanup();
20386
+ resolve(null);
20387
+ };
20388
+ const timeoutId = setTimeout(() => {
20389
+ cleanup();
20390
+ reject(new LoopbackStreamsTimeoutError());
20391
+ }, timeoutMs);
20392
+ signal.addEventListener('abort', onAbort);
20393
+ subscriptions.add(callState.localParticipant$.subscribe((participant) => {
20394
+ const ready = getLoopbackTracks(call, participant, includeVideo);
20395
+ if (ready) {
20396
+ cleanup();
20397
+ resolve(ready);
20398
+ }
20399
+ }));
20400
+ });
20401
+ };
20402
+ /** Enables loopback audio while `fn` runs, then restores it. */
20403
+ const withLoopbackAudioEnabled = async (tracks, fn) => {
20404
+ const restore = enableLoopbackAudio(tracks);
20405
+ try {
20406
+ return await fn();
20407
+ }
20408
+ finally {
20409
+ restore();
20410
+ }
20411
+ };
20412
+ /** Enables loopback audio and returns a restore function. */
20413
+ const enableLoopbackAudio = (tracks) => {
20414
+ const { audioTrack } = tracks;
20415
+ const previous = audioTrack?.enabled ?? false;
20416
+ if (audioTrack) {
20417
+ audioTrack.enabled = true;
20418
+ }
20419
+ return () => {
20420
+ if (audioTrack) {
20421
+ audioTrack.enabled = previous;
20422
+ }
20423
+ };
20424
+ };
20425
+ /** Clamps and rounds a loopback recording duration. */
20426
+ const clampLoopbackRecordingDuration = (durationMs) => Math.round(Math.min(MAX_LOOPBACK_RECORDING_DURATION_MS, Math.max(MIN_LOOPBACK_RECORDING_DURATION_MS, durationMs)));
20427
+
20428
+ /** Single-use `MediaRecorder` wrapper that returns an in-memory blob. */
20429
+ class MediaStreamRecorder {
20430
+ recording = promiseWithResolvers();
20431
+ logger = videoLoggerSystem.getLogger('MediaStreamRecorder');
20432
+ chunks = [];
20433
+ timeoutId;
20434
+ recorder;
20435
+ tracks;
20436
+ maxDurationMs;
20437
+ constructor(stream, options = {}) {
20438
+ if (typeof MediaRecorder === 'undefined') {
20439
+ throw new Error('MediaRecorder is not available in this environment');
20440
+ }
20441
+ this.tracks = stream.getTracks();
20442
+ if (this.tracks.length === 0) {
20443
+ throw new Error('cannot record a MediaStream that has no tracks');
20444
+ }
20445
+ this.maxDurationMs = options.maxDurationMs;
20446
+ this.recorder = new MediaRecorder(new MediaStream(this.tracks));
20447
+ this.recorder.ondataavailable = (event) => {
20448
+ if (event.data?.size)
20449
+ this.chunks.push(event.data);
20450
+ };
20451
+ this.recorder.onstop = this.handleStop;
20452
+ this.recorder.onerror = this.handleError;
20453
+ }
20454
+ /** Starts recording and resolves when recording stops. */
20455
+ start = () => {
20456
+ if (this.isSettled()) {
20457
+ throw new Error('MediaStreamRecorder is single-use: this instance has already ' +
20458
+ 'recorded. Construct a new one.');
20459
+ }
20460
+ if (this.recorder.state === 'inactive') {
20461
+ this.tracks.forEach((track) => track.addEventListener('ended', this.stop));
20462
+ if (this.maxDurationMs !== undefined) {
20463
+ this.timeoutId = setTimeout(this.stop, this.maxDurationMs);
20464
+ }
20465
+ this.recorder.start();
20466
+ }
20467
+ return this.recording.promise;
20468
+ };
20469
+ /** Stops recording early. */
20470
+ stop = () => {
20471
+ if (this.recorder.state !== 'inactive')
20472
+ this.recorder.stop();
20473
+ };
20474
+ isSettled = () => this.recording.isResolved() || this.recording.isRejected();
20475
+ detach = () => {
20476
+ clearTimeout(this.timeoutId);
20477
+ this.tracks.forEach((track) => track.removeEventListener('ended', this.stop));
20478
+ };
20479
+ handleStop = () => {
20480
+ this.detach();
20481
+ if (this.chunks.length === 0) {
20482
+ this.logger.warn('MediaRecorder recording produced no data');
20483
+ this.recording.resolve(null);
20484
+ return;
20485
+ }
20486
+ const mimeType = this.chunks[0].type || this.recorder.mimeType;
20487
+ this.recording.resolve({
20488
+ blob: new Blob(this.chunks, { type: mimeType }),
20489
+ });
20490
+ };
20491
+ handleError = (event) => {
20492
+ if (this.isSettled())
20493
+ return;
20494
+ this.detach();
20495
+ const error = event.error;
20496
+ this.logger.error('MediaRecorder error', error);
20497
+ this.recording.reject(error ?? new Error('MediaRecorder failed'));
20498
+ };
20499
+ }
20500
+
20210
20501
  /**
20211
20502
  * Tiny, type-safe event emitter.
20212
20503
  *
@@ -20525,5 +20816,5 @@ class EncryptionManager extends TypedEventEmitter {
20525
20816
  };
20526
20817
  }
20527
20818
 
20528
- export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, EncryptionManager, EncryptionSettingsRequestModeEnum, EncryptionSettingsResponseModeEnum, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, HLSSettingsRequestQualityTracksEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsRequestOutputTypesEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, RequestPermissionRequestPermissionsEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SfuTimeoutError, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, TranslationSettingsLanguagesEnum, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, WSAuthMessageProductsEnum, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getCallMediaEngineProvider, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasInterruptedTrack, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setCallMediaEngineProvider, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
20819
+ export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DEFAULT_LOOPBACK_RECORDING_DURATION_MS, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, EncryptionManager, EncryptionSettingsRequestModeEnum, EncryptionSettingsResponseModeEnum, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, HLSSettingsRequestQualityTracksEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsRequestOutputTypesEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LOOPBACK_STREAMS_WAIT_TIMEOUT_MS, LayoutSettingsRequestNameEnum, LoopbackStreamsTimeoutError, MAX_LOOPBACK_RECORDING_DURATION_MS, MIN_LOOPBACK_RECORDING_DURATION_MS, MediaStreamRecorder, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, RequestPermissionRequestPermissionsEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SfuTimeoutError, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, TranslationSettingsLanguagesEnum, UpdateUserPermissionsRequestGrantPermissionsEnum, UpdateUserPermissionsRequestRevokePermissionsEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, WSAuthMessageProductsEnum, checkIfAudioOutputChangeSupported, clampLoopbackRecordingDuration, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, enableLoopbackAudio, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getCallMediaEngineProvider, getClientDetails, getDeviceState, getLoopbackStreams, getLoopbackTracks, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasInterruptedTrack, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishing, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setCallMediaEngineProvider, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, waitForLoopbackStreams, withLoopbackAudioEnabled, withParticipantSource };
20529
20820
  //# sourceMappingURL=index.browser.es.js.map