@stream-io/video-client 0.7.9 → 0.7.11

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [0.7.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.7.10...@stream-io/video-client-0.7.11) (2024-05-03)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **devices:** API to disable speaking while muted notifications ([#1335](https://github.com/GetStream/stream-video-js/issues/1335)) ([cdff0e0](https://github.com/GetStream/stream-video-js/commit/cdff0e036bf4afca763e4f7a1563c23e806be190)), closes [#1329](https://github.com/GetStream/stream-video-js/issues/1329)
11
+
12
+ ### [0.7.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.7.9...@stream-io/video-client-0.7.10) (2024-04-30)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **state:** optimized Call State updates ([#1330](https://github.com/GetStream/stream-video-js/issues/1330)) ([e5f9f88](https://github.com/GetStream/stream-video-js/commit/e5f9f882df95761bfecbd6b38832f013b0e7a75e))
18
+
5
19
  ### [0.7.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.7.8...@stream-io/video-client-0.7.9) (2024-04-26)
6
20
 
7
21
 
@@ -6843,6 +6843,7 @@ class CallState {
6843
6843
  this.anonymousParticipantCountSubject = new BehaviorSubject(0);
6844
6844
  this.participantsSubject = new BehaviorSubject([]);
6845
6845
  this.callStatsReportSubject = new BehaviorSubject(undefined);
6846
+ this.logger = getLogger(['CallState']);
6846
6847
  /**
6847
6848
  * A list of comparators that are used to sort the participants.
6848
6849
  *
@@ -7003,21 +7004,22 @@ class CallState {
7003
7004
  * @param participant the participant to update or add.
7004
7005
  */
7005
7006
  this.updateOrAddParticipant = (sessionId, participant) => {
7006
- if (!this.findParticipantBySessionId(sessionId)) {
7007
- return this.setParticipants((participants) => [
7008
- ...participants,
7009
- participant,
7010
- ]);
7011
- }
7012
- return this.setParticipants((participants) => participants.map((p) => {
7013
- if (p.sessionId === sessionId) {
7014
- return {
7015
- ...p,
7016
- ...participant,
7017
- };
7018
- }
7019
- return p;
7020
- }));
7007
+ return this.setParticipants((participants) => {
7008
+ let add = true;
7009
+ const nextParticipants = participants.map((p) => {
7010
+ if (p.sessionId === sessionId) {
7011
+ add = false;
7012
+ return {
7013
+ ...p,
7014
+ ...participant,
7015
+ };
7016
+ }
7017
+ return p;
7018
+ });
7019
+ if (add)
7020
+ nextParticipants.push(participant);
7021
+ return nextParticipants;
7022
+ });
7021
7023
  };
7022
7024
  /**
7023
7025
  * Updates all participants in the current call whose session ID is in the given `sessionIds`.
@@ -7242,7 +7244,6 @@ class CallState {
7242
7244
  this.setCurrentValue(this.ownCapabilitiesSubject, event.own_capabilities);
7243
7245
  }
7244
7246
  };
7245
- this.logger = getLogger(['CallState']);
7246
7247
  this.participants$ = this.participantsSubject.asObservable().pipe(
7247
7248
  // maintain stable-sort by mutating the participants stored
7248
7249
  // in the original subject
@@ -7252,30 +7253,52 @@ class CallState {
7252
7253
  this.pinnedParticipants$ = this.participants$.pipe(map$1((participants) => participants.filter((p) => !!p.pin)), shareReplay({ bufferSize: 1, refCount: true }));
7253
7254
  this.dominantSpeaker$ = this.participants$.pipe(map$1((participants) => participants.find((p) => p.isDominantSpeaker)), shareReplay({ bufferSize: 1, refCount: true }));
7254
7255
  this.hasOngoingScreenShare$ = this.participants$.pipe(map$1((participants) => participants.some((p) => p.publishedTracks.includes(TrackType.SCREEN_SHARE))), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }));
7255
- this.startedAt$ = this.startedAtSubject.asObservable();
7256
- this.participantCount$ = this.participantCountSubject.asObservable();
7257
- this.anonymousParticipantCount$ =
7258
- this.anonymousParticipantCountSubject.asObservable();
7259
- this.callStatsReport$ = this.callStatsReportSubject.asObservable();
7260
- this.members$ = this.membersSubject.asObservable();
7261
- this.ownCapabilities$ = this.ownCapabilitiesSubject.asObservable();
7262
- this.callingState$ = this.callingStateSubject.asObservable();
7263
- this.backstage$ = this.backstageSubject.asObservable();
7264
- this.blockedUserIds$ = this.blockedUserIdsSubject.asObservable();
7256
+ // dates
7265
7257
  this.createdAt$ = this.createdAtSubject.asObservable();
7266
7258
  this.endedAt$ = this.endedAtSubject.asObservable();
7267
7259
  this.startsAt$ = this.startsAtSubject.asObservable();
7260
+ this.startedAt$ = this.startedAtSubject.asObservable();
7268
7261
  this.updatedAt$ = this.updatedAtSubject.asObservable();
7262
+ this.callStatsReport$ = this.callStatsReportSubject.asObservable();
7263
+ this.members$ = this.membersSubject.asObservable();
7264
+ // complex objects should work as streams of data
7269
7265
  this.createdBy$ = this.createdBySubject.asObservable();
7270
7266
  this.custom$ = this.customSubject.asObservable();
7271
7267
  this.egress$ = this.egressSubject.asObservable();
7272
7268
  this.ingress$ = this.ingressSubject.asObservable();
7273
- this.recording$ = this.recordingSubject.asObservable();
7274
7269
  this.session$ = this.sessionSubject.asObservable();
7275
7270
  this.settings$ = this.settingsSubject.asObservable();
7276
- this.transcribing$ = this.transcribingSubject.asObservable();
7277
7271
  this.endedBy$ = this.endedBySubject.asObservable();
7278
7272
  this.thumbnails$ = this.thumbnailsSubject.asObservable();
7273
+ /**
7274
+ * Performs shallow comparison of two arrays.
7275
+ * Expects primitive values: [1, 2, 3] is equal to [2, 1, 3].
7276
+ */
7277
+ const isShallowEqual = (a, b) => {
7278
+ if (a.length !== b.length)
7279
+ return false;
7280
+ for (const item of a)
7281
+ if (!b.includes(item))
7282
+ return false;
7283
+ for (const item of b)
7284
+ if (!a.includes(item))
7285
+ return false;
7286
+ return true;
7287
+ };
7288
+ /**
7289
+ * Creates an Observable from the given subject by piping to the
7290
+ * `distinctUntilChanged()` operator.
7291
+ */
7292
+ const duc = (subject, comparator) => subject.asObservable().pipe(distinctUntilChanged(comparator));
7293
+ // primitive values should only emit once the value they hold changes
7294
+ this.anonymousParticipantCount$ = duc(this.anonymousParticipantCountSubject);
7295
+ this.blockedUserIds$ = duc(this.blockedUserIdsSubject, isShallowEqual);
7296
+ this.backstage$ = duc(this.backstageSubject);
7297
+ this.callingState$ = duc(this.callingStateSubject);
7298
+ this.ownCapabilities$ = duc(this.ownCapabilitiesSubject, isShallowEqual);
7299
+ this.participantCount$ = duc(this.participantCountSubject);
7300
+ this.recording$ = duc(this.recordingSubject);
7301
+ this.transcribing$ = duc(this.transcribingSubject);
7279
7302
  this.eventHandlers = {
7280
7303
  // these events are not updating the call state:
7281
7304
  'call.closed_caption': undefined,
@@ -9490,7 +9513,7 @@ const toRtcConfiguration = (config) => {
9490
9513
  *
9491
9514
  * @param report the report to flatten.
9492
9515
  */
9493
- const flatten$1 = (report) => {
9516
+ const flatten = (report) => {
9494
9517
  const stats = [];
9495
9518
  report.forEach((s) => {
9496
9519
  stats.push(s);
@@ -9644,7 +9667,7 @@ const createStatsReporter = ({ subscriber, publisher, state, pollingIntervalInMs
9644
9667
  const transform = (report, opts) => {
9645
9668
  const { trackKind, kind } = opts;
9646
9669
  const direction = kind === 'subscriber' ? 'inbound-rtp' : 'outbound-rtp';
9647
- const stats = flatten$1(report);
9670
+ const stats = flatten(report);
9648
9671
  const streams = stats
9649
9672
  .filter((stat) => stat.type === direction &&
9650
9673
  stat.kind === trackKind)
@@ -9740,8 +9763,8 @@ class SfuStatsReporter {
9740
9763
  this.logger = getLogger(['SfuStatsReporter']);
9741
9764
  this.run = async () => {
9742
9765
  const [subscriberStats, publisherStats] = await Promise.all([
9743
- this.subscriber.getStats().then(flatten$1).then(JSON.stringify),
9744
- this.publisher.getStats().then(flatten$1).then(JSON.stringify),
9766
+ this.subscriber.getStats().then(flatten).then(JSON.stringify),
9767
+ this.publisher.getStats().then(flatten).then(JSON.stringify),
9745
9768
  ]);
9746
9769
  await this.sfuClient.sendStats({
9747
9770
  sdk: this.sdkName,
@@ -10714,7 +10737,7 @@ class InputMediaDeviceManager {
10714
10737
  // @ts-expect-error called to dispose the stream in RN
10715
10738
  this.state.mediaStream.release();
10716
10739
  }
10717
- this.state.setMediaStream(undefined);
10740
+ this.state.setMediaStream(undefined, undefined);
10718
10741
  }
10719
10742
  }
10720
10743
  muteTracks() {
@@ -10749,6 +10772,7 @@ class InputMediaDeviceManager {
10749
10772
  async unmuteStream() {
10750
10773
  this.logger('debug', 'Starting stream');
10751
10774
  let stream;
10775
+ let rootStream;
10752
10776
  if (this.state.mediaStream &&
10753
10777
  this.getTracks().every((t) => t.readyState === 'live')) {
10754
10778
  stream = this.state.mediaStream;
@@ -10814,14 +10838,17 @@ class InputMediaDeviceManager {
10814
10838
  });
10815
10839
  return filterStream;
10816
10840
  };
10841
+ // the rootStream represents the stream coming from the actual device
10842
+ // e.g. camera or microphone stream
10843
+ rootStream = this.getStream(constraints);
10817
10844
  // we publish the last MediaStream of the chain
10818
- stream = await this.filters.reduce((parent, filter) => parent.then(filter).then(chainWith(parent)), this.getStream(constraints));
10845
+ stream = await this.filters.reduce((parent, filter) => parent.then(filter).then(chainWith(parent)), rootStream);
10819
10846
  }
10820
10847
  if (this.call.state.callingState === CallingState.JOINED) {
10821
10848
  await this.publishStream(stream);
10822
10849
  }
10823
10850
  if (this.state.mediaStream !== stream) {
10824
- this.state.setMediaStream(stream);
10851
+ this.state.setMediaStream(stream, await rootStream);
10825
10852
  this.getTracks().forEach((track) => {
10826
10853
  track.addEventListener('ended', async () => {
10827
10854
  if (this.enablePromise) {
@@ -11015,13 +11042,17 @@ class InputMediaDeviceManagerState {
11015
11042
  this.setCurrentValue(this.statusSubject, status);
11016
11043
  }
11017
11044
  /**
11045
+ * Updates the `mediaStream` state variable.
11046
+ *
11018
11047
  * @internal
11019
11048
  * @param stream the stream to set.
11049
+ * @param rootStream the root stream, applicable when filters are used
11050
+ * as this is the stream that holds the actual deviceId information.
11020
11051
  */
11021
- setMediaStream(stream) {
11052
+ setMediaStream(stream, rootStream) {
11022
11053
  this.setCurrentValue(this.mediaStreamSubject, stream);
11023
- if (stream) {
11024
- this.setDevice(this.getDeviceIdFromStream(stream));
11054
+ if (rootStream) {
11055
+ this.setDevice(this.getDeviceIdFromStream(rootStream));
11025
11056
  }
11026
11057
  }
11027
11058
  /**
@@ -11076,8 +11107,8 @@ class CameraManagerState extends InputMediaDeviceManagerState {
11076
11107
  /**
11077
11108
  * @internal
11078
11109
  */
11079
- setMediaStream(stream) {
11080
- super.setMediaStream(stream);
11110
+ setMediaStream(stream, rootStream) {
11111
+ super.setMediaStream(stream, rootStream);
11081
11112
  if (stream) {
11082
11113
  // RN getSettings() doesn't return facingMode, so we don't verify camera direction
11083
11114
  const direction = isReactNative()
@@ -11089,7 +11120,8 @@ class CameraManagerState extends InputMediaDeviceManagerState {
11089
11120
  }
11090
11121
  }
11091
11122
  getDeviceIdFromStream(stream) {
11092
- return stream.getVideoTracks()[0]?.getSettings().deviceId;
11123
+ const [track] = stream.getVideoTracks();
11124
+ return track?.getSettings().deviceId;
11093
11125
  }
11094
11126
  }
11095
11127
 
@@ -11182,8 +11214,8 @@ class CameraManager extends InputMediaDeviceManager {
11182
11214
  }
11183
11215
 
11184
11216
  class MicrophoneManagerState extends InputMediaDeviceManagerState {
11185
- constructor() {
11186
- super('disable-tracks',
11217
+ constructor(disableMode) {
11218
+ super(disableMode,
11187
11219
  // `microphone` is not in the W3C standard yet,
11188
11220
  // but it's supported by Chrome and Safari.
11189
11221
  'microphone');
@@ -11207,7 +11239,8 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
11207
11239
  this.setCurrentValue(this.speakingWhileMutedSubject, isSpeaking);
11208
11240
  }
11209
11241
  getDeviceIdFromStream(stream) {
11210
- return stream.getAudioTracks()[0]?.getSettings().deviceId;
11242
+ const [track] = stream.getAudioTracks();
11243
+ return track?.getSettings().deviceId;
11211
11244
  }
11212
11245
  }
11213
11246
 
@@ -11264,18 +11297,6 @@ const createSoundDetector = (audioStream, onSoundDetectedStateChanged, options =
11264
11297
  };
11265
11298
  };
11266
11299
 
11267
- /**
11268
- * Flatten the stats report into an array of stats objects.
11269
- *
11270
- * @param report the report to flatten.
11271
- */
11272
- const flatten = (report) => {
11273
- const stats = [];
11274
- report.forEach((s) => {
11275
- stats.push(s);
11276
- });
11277
- return stats;
11278
- };
11279
11300
  const AUDIO_LEVEL_THRESHOLD = 0.2;
11280
11301
  class RNSpeechDetector {
11281
11302
  constructor() {
@@ -11358,20 +11379,24 @@ class RNSpeechDetector {
11358
11379
  }
11359
11380
 
11360
11381
  class MicrophoneManager extends InputMediaDeviceManager {
11361
- constructor(call) {
11362
- super(call, new MicrophoneManagerState(), TrackType.AUDIO);
11363
- combineLatest([
11382
+ constructor(call, disableMode = isReactNative()
11383
+ ? 'disable-tracks'
11384
+ : 'stop-tracks') {
11385
+ super(call, new MicrophoneManagerState(disableMode), TrackType.AUDIO);
11386
+ this.speakingWhileMutedNotificationEnabled = true;
11387
+ this.subscriptions.push(createSubscription(combineLatest([
11364
11388
  this.call.state.callingState$,
11365
11389
  this.call.state.ownCapabilities$,
11366
11390
  this.state.selectedDevice$,
11367
11391
  this.state.status$,
11368
- ]).subscribe(async ([callingState, ownCapabilities, deviceId, status]) => {
11369
- if (callingState !== CallingState.JOINED) {
11370
- if (callingState === CallingState.LEFT) {
11371
- await this.stopSpeakingWhileMutedDetection();
11372
- }
11373
- return;
11392
+ ]), async ([callingState, ownCapabilities, deviceId, status]) => {
11393
+ if (callingState === CallingState.LEFT) {
11394
+ await this.stopSpeakingWhileMutedDetection();
11374
11395
  }
11396
+ if (callingState !== CallingState.JOINED)
11397
+ return;
11398
+ if (!this.speakingWhileMutedNotificationEnabled)
11399
+ return;
11375
11400
  if (ownCapabilities.includes(OwnCapability.SEND_AUDIO)) {
11376
11401
  if (status === 'disabled') {
11377
11402
  await this.startSpeakingWhileMutedDetection(deviceId);
@@ -11383,7 +11408,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
11383
11408
  else {
11384
11409
  await this.stopSpeakingWhileMutedDetection();
11385
11410
  }
11386
- });
11411
+ }));
11387
11412
  this.subscriptions.push(createSubscription(this.call.state.callingState$, (callingState) => {
11388
11413
  // do nothing when noise filtering isn't turned on
11389
11414
  if (!this.noiseCancellationRegistration || !this.noiseCancellation)
@@ -11478,6 +11503,22 @@ class MicrophoneManager extends InputMediaDeviceManager {
11478
11503
  });
11479
11504
  await this.call.notifyNoiseCancellationStopped();
11480
11505
  }
11506
+ /**
11507
+ * Enables speaking while muted notification.
11508
+ */
11509
+ async enableSpeakingWhileMutedNotification() {
11510
+ this.speakingWhileMutedNotificationEnabled = true;
11511
+ if (this.state.status === 'disabled') {
11512
+ await this.startSpeakingWhileMutedDetection(this.state.selectedDevice);
11513
+ }
11514
+ }
11515
+ /**
11516
+ * Disables speaking while muted notification.
11517
+ */
11518
+ async disableSpeakingWhileMutedNotification() {
11519
+ this.speakingWhileMutedNotificationEnabled = false;
11520
+ await this.stopSpeakingWhileMutedDetection();
11521
+ }
11481
11522
  getDevices() {
11482
11523
  return getAudioDevices();
11483
11524
  }
@@ -11515,9 +11556,8 @@ class MicrophoneManager extends InputMediaDeviceManager {
11515
11556
  }
11516
11557
  }
11517
11558
  async stopSpeakingWhileMutedDetection() {
11518
- if (!this.soundDetectorCleanup) {
11559
+ if (!this.soundDetectorCleanup)
11519
11560
  return;
11520
- }
11521
11561
  this.state.setSpeakingWhileMuted(false);
11522
11562
  try {
11523
11563
  await this.soundDetectorCleanup();
@@ -14842,7 +14882,7 @@ class StreamClient {
14842
14882
  });
14843
14883
  };
14844
14884
  this.getUserAgent = () => {
14845
- const version = "0.7.9" ;
14885
+ const version = "0.7.11" ;
14846
14886
  return (this.userAgent ||
14847
14887
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
14848
14888
  };