@stream-io/video-client 1.20.0 → 1.20.2

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/dist/index.cjs.js CHANGED
@@ -119,6 +119,7 @@ const FrameRecordingSettingsRequestQualityEnum = {
119
119
  _720P: '720p',
120
120
  _1080P: '1080p',
121
121
  _1440P: '1440p',
122
+ _2160P: '2160p',
122
123
  };
123
124
  /**
124
125
  * @export
@@ -191,11 +192,13 @@ const RTMPBroadcastRequestQualityEnum = {
191
192
  _720P: '720p',
192
193
  _1080P: '1080p',
193
194
  _1440P: '1440p',
195
+ _2160P: '2160p',
194
196
  PORTRAIT_360X640: 'portrait-360x640',
195
197
  PORTRAIT_480X854: 'portrait-480x854',
196
198
  PORTRAIT_720X1280: 'portrait-720x1280',
197
199
  PORTRAIT_1080X1920: 'portrait-1080x1920',
198
200
  PORTRAIT_1440X2560: 'portrait-1440x2560',
201
+ PORTRAIT_2160X3840: 'portrait-2160x3840',
199
202
  };
200
203
  /**
201
204
  * @export
@@ -206,11 +209,13 @@ const RTMPSettingsRequestQualityEnum = {
206
209
  _720P: '720p',
207
210
  _1080P: '1080p',
208
211
  _1440P: '1440p',
212
+ _2160P: '2160p',
209
213
  PORTRAIT_360X640: 'portrait-360x640',
210
214
  PORTRAIT_480X854: 'portrait-480x854',
211
215
  PORTRAIT_720X1280: 'portrait-720x1280',
212
216
  PORTRAIT_1080X1920: 'portrait-1080x1920',
213
217
  PORTRAIT_1440X2560: 'portrait-1440x2560',
218
+ PORTRAIT_2160X3840: 'portrait-2160x3840',
214
219
  };
215
220
  /**
216
221
  * @export
@@ -229,11 +234,13 @@ const RecordSettingsRequestQualityEnum = {
229
234
  _720P: '720p',
230
235
  _1080P: '1080p',
231
236
  _1440P: '1440p',
237
+ _2160P: '2160p',
232
238
  PORTRAIT_360X640: 'portrait-360x640',
233
239
  PORTRAIT_480X854: 'portrait-480x854',
234
240
  PORTRAIT_720X1280: 'portrait-720x1280',
235
241
  PORTRAIT_1080X1920: 'portrait-1080x1920',
236
242
  PORTRAIT_1440X2560: 'portrait-1440x2560',
243
+ PORTRAIT_2160X3840: 'portrait-2160x3840',
237
244
  };
238
245
  /**
239
246
  * @export
@@ -4060,7 +4067,7 @@ class StreamVideoWriteableStateStore {
4060
4067
  continue;
4061
4068
  logger('info', `User disconnected, leaving call: ${call.cid}`);
4062
4069
  await call
4063
- .leave({ reason: 'client.disconnectUser() called' })
4070
+ .leave({ message: 'client.disconnectUser() called' })
4064
4071
  .catch((err) => {
4065
4072
  logger('error', `Error leaving call: ${call.cid}`, err);
4066
4073
  });
@@ -5645,7 +5652,7 @@ const aggregate = (stats) => {
5645
5652
  return report;
5646
5653
  };
5647
5654
 
5648
- const version = "1.20.0";
5655
+ const version = "1.20.2";
5649
5656
  const [major, minor, patch] = version.split('.');
5650
5657
  let sdkInfo = {
5651
5658
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -5786,9 +5793,9 @@ class SfuStatsReporter {
5786
5793
  this.logger = getLogger(['SfuStatsReporter']);
5787
5794
  this.inputDevices = new Map();
5788
5795
  this.observeDevice = (device, kind) => {
5789
- const { hasBrowserPermission$ } = device.state;
5796
+ const { browserPermissionState$ } = device.state;
5790
5797
  this.unsubscribeDevicePermissionsSubscription?.();
5791
- this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([hasBrowserPermission$, this.state.ownCapabilities$]), ([hasPermission, ownCapabilities]) => {
5798
+ this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([browserPermissionState$, this.state.ownCapabilities$]), ([browserPermissionState, ownCapabilities]) => {
5792
5799
  // cleanup the previous listDevices() subscription in case
5793
5800
  // permissions or capabilities have changed.
5794
5801
  // we will subscribe again if everything is in order.
@@ -5796,7 +5803,7 @@ class SfuStatsReporter {
5796
5803
  const hasCapability = kind === 'mic'
5797
5804
  ? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
5798
5805
  : ownCapabilities.includes(OwnCapability.SEND_VIDEO);
5799
- if (!hasPermission || !hasCapability) {
5806
+ if (browserPermissionState !== 'granted' || !hasCapability) {
5800
5807
  this.inputDevices.set(kind, {
5801
5808
  currentDevice: '',
5802
5809
  availableDevices: [],
@@ -7603,13 +7610,17 @@ const watchCallRejected = (call) => {
7603
7610
  .every((m) => rejectedBy[m.user_id]);
7604
7611
  if (everyoneElseRejected) {
7605
7612
  call.logger('info', 'everyone rejected, leaving the call');
7606
- await call.leave({ reason: 'ring: everyone rejected' });
7613
+ await call.leave({
7614
+ reject: true,
7615
+ reason: 'cancel',
7616
+ message: 'ring: everyone rejected',
7617
+ });
7607
7618
  }
7608
7619
  }
7609
7620
  else {
7610
7621
  if (rejectedBy[eventCall.created_by.id]) {
7611
7622
  call.logger('info', 'call creator rejected, leaving call');
7612
- await call.leave({ reason: 'ring: creator rejected' });
7623
+ await call.leave({ message: 'ring: creator rejected' });
7613
7624
  }
7614
7625
  }
7615
7626
  };
@@ -7623,7 +7634,7 @@ const watchCallEnded = (call) => {
7623
7634
  if (callingState !== exports.CallingState.IDLE &&
7624
7635
  callingState !== exports.CallingState.LEFT) {
7625
7636
  call
7626
- .leave({ reason: 'call.ended event received', reject: false })
7637
+ .leave({ message: 'call.ended event received', reject: false })
7627
7638
  .catch((err) => {
7628
7639
  call.logger('error', 'Failed to leave call after call.ended ', err);
7629
7640
  });
@@ -7643,7 +7654,7 @@ const watchSfuCallEnded = (call) => {
7643
7654
  // update the call state to reflect the call has ended.
7644
7655
  call.state.setEndedAt(new Date());
7645
7656
  const reason = CallEndedReason[e.reason];
7646
- await call.leave({ reason: `callEnded received: ${reason}` });
7657
+ await call.leave({ message: `callEnded received: ${reason}` });
7647
7658
  }
7648
7659
  catch (err) {
7649
7660
  call.logger('error', 'Failed to leave call after being ended by the SFU', err);
@@ -7710,7 +7721,7 @@ const watchLiveEnded = (dispatcher, call) => {
7710
7721
  return;
7711
7722
  call.state.setBackstage(true);
7712
7723
  if (!call.permissionsContext.hasPermission(OwnCapability.JOIN_BACKSTAGE)) {
7713
- call.leave({ reason: 'live ended' }).catch((err) => {
7724
+ call.leave({ message: 'live ended' }).catch((err) => {
7714
7725
  call.logger('error', 'Failed to leave call after live ended', err);
7715
7726
  });
7716
7727
  }
@@ -8730,6 +8741,9 @@ class BrowserPermission {
8730
8741
  // Instead of checking if a permission is granted, we check if it isn't denied
8731
8742
  rxjs.map((state) => state !== 'denied'));
8732
8743
  }
8744
+ asStateObservable() {
8745
+ return this.getStateObservable();
8746
+ }
8733
8747
  getIsPromptingObservable() {
8734
8748
  return this.getStateObservable().pipe(rxjs.map((state) => state === 'prompting'));
8735
8749
  }
@@ -9256,122 +9270,130 @@ class InputMediaDeviceManager {
9256
9270
  this.logger('debug', 'Starting stream');
9257
9271
  let stream;
9258
9272
  let rootStream;
9259
- if (this.state.mediaStream &&
9260
- this.getTracks().every((t) => t.readyState === 'live')) {
9261
- stream = this.state.mediaStream;
9262
- this.enableTracks();
9263
- }
9264
- else {
9265
- const defaultConstraints = this.state.defaultConstraints;
9266
- const constraints = {
9267
- ...defaultConstraints,
9268
- deviceId: this.state.selectedDevice
9269
- ? { exact: this.state.selectedDevice }
9270
- : undefined,
9271
- };
9272
- /**
9273
- * Chains two media streams together.
9274
- *
9275
- * In our case, filters MediaStreams are derived from their parent MediaStream.
9276
- * However, once a child filter's track is stopped,
9277
- * the tracks of the parent MediaStream aren't automatically stopped.
9278
- * This leads to a situation where the camera indicator light is still on
9279
- * even though the user stopped publishing video.
9280
- *
9281
- * This function works around this issue by stopping the parent MediaStream's tracks
9282
- * as well once the child filter's tracks are stopped.
9283
- *
9284
- * It works by patching the stop() method of the child filter's tracks to also stop
9285
- * the parent MediaStream's tracks of the same type. Here we assume that
9286
- * the parent MediaStream has only one track of each type.
9287
- *
9288
- * @param parentStream the parent MediaStream. Omit for the root stream.
9289
- */
9290
- const chainWith = (parentStream) => async (filterStream) => {
9291
- if (!parentStream)
9292
- return filterStream;
9293
- // TODO OL: take care of track.enabled property as well
9294
- const parent = await parentStream;
9295
- filterStream.getTracks().forEach((track) => {
9296
- const originalStop = track.stop;
9297
- track.stop = function stop() {
9298
- originalStop.call(track);
9299
- parent.getTracks().forEach((parentTrack) => {
9300
- if (parentTrack.kind === track.kind) {
9301
- parentTrack.stop();
9302
- }
9303
- });
9304
- };
9305
- });
9306
- parent.getTracks().forEach((parentTrack) => {
9307
- // When the parent stream abruptly ends, we propagate the event
9308
- // to the filter stream.
9309
- // This usually happens when the camera/microphone permissions
9310
- // are revoked or when the device is disconnected.
9311
- const handleParentTrackEnded = () => {
9312
- filterStream.getTracks().forEach((track) => {
9313
- if (parentTrack.kind !== track.kind)
9314
- return;
9315
- track.stop();
9316
- track.dispatchEvent(new Event('ended')); // propagate the event
9273
+ try {
9274
+ if (this.state.mediaStream &&
9275
+ this.getTracks().every((t) => t.readyState === 'live')) {
9276
+ stream = this.state.mediaStream;
9277
+ this.enableTracks();
9278
+ }
9279
+ else {
9280
+ const defaultConstraints = this.state.defaultConstraints;
9281
+ const constraints = {
9282
+ ...defaultConstraints,
9283
+ deviceId: this.state.selectedDevice
9284
+ ? { exact: this.state.selectedDevice }
9285
+ : undefined,
9286
+ };
9287
+ /**
9288
+ * Chains two media streams together.
9289
+ *
9290
+ * In our case, filters MediaStreams are derived from their parent MediaStream.
9291
+ * However, once a child filter's track is stopped,
9292
+ * the tracks of the parent MediaStream aren't automatically stopped.
9293
+ * This leads to a situation where the camera indicator light is still on
9294
+ * even though the user stopped publishing video.
9295
+ *
9296
+ * This function works around this issue by stopping the parent MediaStream's tracks
9297
+ * as well once the child filter's tracks are stopped.
9298
+ *
9299
+ * It works by patching the stop() method of the child filter's tracks to also stop
9300
+ * the parent MediaStream's tracks of the same type. Here we assume that
9301
+ * the parent MediaStream has only one track of each type.
9302
+ *
9303
+ * @param parentStream the parent MediaStream. Omit for the root stream.
9304
+ */
9305
+ const chainWith = (parentStream) => async (filterStream) => {
9306
+ if (!parentStream)
9307
+ return filterStream;
9308
+ // TODO OL: take care of track.enabled property as well
9309
+ const parent = await parentStream;
9310
+ filterStream.getTracks().forEach((track) => {
9311
+ const originalStop = track.stop;
9312
+ track.stop = function stop() {
9313
+ originalStop.call(track);
9314
+ parent.getTracks().forEach((parentTrack) => {
9315
+ if (parentTrack.kind === track.kind) {
9316
+ parentTrack.stop();
9317
+ }
9318
+ });
9319
+ };
9320
+ });
9321
+ parent.getTracks().forEach((parentTrack) => {
9322
+ // When the parent stream abruptly ends, we propagate the event
9323
+ // to the filter stream.
9324
+ // This usually happens when the camera/microphone permissions
9325
+ // are revoked or when the device is disconnected.
9326
+ const handleParentTrackEnded = () => {
9327
+ filterStream.getTracks().forEach((track) => {
9328
+ if (parentTrack.kind !== track.kind)
9329
+ return;
9330
+ track.stop();
9331
+ track.dispatchEvent(new Event('ended')); // propagate the event
9332
+ });
9333
+ };
9334
+ parentTrack.addEventListener('ended', handleParentTrackEnded);
9335
+ this.subscriptions.push(() => {
9336
+ parentTrack.removeEventListener('ended', handleParentTrackEnded);
9317
9337
  });
9318
- };
9319
- parentTrack.addEventListener('ended', handleParentTrackEnded);
9338
+ });
9339
+ return filterStream;
9340
+ };
9341
+ // the rootStream represents the stream coming from the actual device
9342
+ // e.g. camera or microphone stream
9343
+ rootStream = this.getStream(constraints);
9344
+ // we publish the last MediaStream of the chain
9345
+ stream = await this.filters.reduce((parent, entry) => parent
9346
+ .then((inputStream) => {
9347
+ const { stop, output } = entry.start(inputStream);
9348
+ entry.stop = stop;
9349
+ return output;
9350
+ })
9351
+ .then(chainWith(parent), (error) => {
9352
+ this.logger('warn', 'Filter failed to start and will be ignored', error);
9353
+ return parent;
9354
+ }), rootStream);
9355
+ }
9356
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
9357
+ await this.publishStream(stream);
9358
+ }
9359
+ if (this.state.mediaStream !== stream) {
9360
+ this.state.setMediaStream(stream, await rootStream);
9361
+ const handleTrackEnded = async () => {
9362
+ await this.statusChangeSettled();
9363
+ if (this.enabled) {
9364
+ this.isTrackStoppedDueToTrackEnd = true;
9365
+ setTimeout(() => {
9366
+ this.isTrackStoppedDueToTrackEnd = false;
9367
+ }, 2000);
9368
+ await this.disable();
9369
+ }
9370
+ };
9371
+ const createTrackMuteHandler = (muted) => () => {
9372
+ if (!isMobile() || this.trackType !== TrackType.VIDEO)
9373
+ return;
9374
+ this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
9375
+ this.logger('warn', 'Error while notifying track mute state', err);
9376
+ });
9377
+ };
9378
+ stream.getTracks().forEach((track) => {
9379
+ const muteHandler = createTrackMuteHandler(true);
9380
+ const unmuteHandler = createTrackMuteHandler(false);
9381
+ track.addEventListener('mute', muteHandler);
9382
+ track.addEventListener('unmute', unmuteHandler);
9383
+ track.addEventListener('ended', handleTrackEnded);
9320
9384
  this.subscriptions.push(() => {
9321
- parentTrack.removeEventListener('ended', handleParentTrackEnded);
9385
+ track.removeEventListener('mute', muteHandler);
9386
+ track.removeEventListener('unmute', unmuteHandler);
9387
+ track.removeEventListener('ended', handleTrackEnded);
9322
9388
  });
9323
9389
  });
9324
- return filterStream;
9325
- };
9326
- // the rootStream represents the stream coming from the actual device
9327
- // e.g. camera or microphone stream
9328
- rootStream = this.getStream(constraints);
9329
- // we publish the last MediaStream of the chain
9330
- stream = await this.filters.reduce((parent, entry) => parent
9331
- .then((inputStream) => {
9332
- const { stop, output } = entry.start(inputStream);
9333
- entry.stop = stop;
9334
- return output;
9335
- })
9336
- .then(chainWith(parent), (error) => {
9337
- this.logger('warn', 'Filter failed to start and will be ignored', error);
9338
- return parent;
9339
- }), rootStream);
9340
- }
9341
- if (this.call.state.callingState === exports.CallingState.JOINED) {
9342
- await this.publishStream(stream);
9390
+ }
9343
9391
  }
9344
- if (this.state.mediaStream !== stream) {
9345
- this.state.setMediaStream(stream, await rootStream);
9346
- const handleTrackEnded = async () => {
9347
- await this.statusChangeSettled();
9348
- if (this.enabled) {
9349
- this.isTrackStoppedDueToTrackEnd = true;
9350
- setTimeout(() => {
9351
- this.isTrackStoppedDueToTrackEnd = false;
9352
- }, 2000);
9353
- await this.disable();
9354
- }
9355
- };
9356
- const createTrackMuteHandler = (muted) => () => {
9357
- if (!isMobile() || this.trackType !== TrackType.VIDEO)
9358
- return;
9359
- this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
9360
- this.logger('warn', 'Error while notifying track mute state', err);
9361
- });
9362
- };
9363
- stream.getTracks().forEach((track) => {
9364
- const muteHandler = createTrackMuteHandler(true);
9365
- const unmuteHandler = createTrackMuteHandler(false);
9366
- track.addEventListener('mute', muteHandler);
9367
- track.addEventListener('unmute', unmuteHandler);
9368
- track.addEventListener('ended', handleTrackEnded);
9369
- this.subscriptions.push(() => {
9370
- track.removeEventListener('mute', muteHandler);
9371
- track.removeEventListener('unmute', unmuteHandler);
9372
- track.removeEventListener('ended', handleTrackEnded);
9373
- });
9374
- });
9392
+ catch (err) {
9393
+ if (rootStream) {
9394
+ disposeOfMediaStream(await rootStream);
9395
+ }
9396
+ throw err;
9375
9397
  }
9376
9398
  }
9377
9399
  get mediaDeviceKind() {
@@ -9493,6 +9515,9 @@ class InputMediaDeviceManagerState {
9493
9515
  this.hasBrowserPermission$ = permission
9494
9516
  ? permission.asObservable().pipe(rxjs.shareReplay(1))
9495
9517
  : rxjs.of(true);
9518
+ this.browserPermissionState$ = permission
9519
+ ? permission.asStateObservable().pipe(rxjs.shareReplay(1))
9520
+ : rxjs.of('prompt');
9496
9521
  this.isPromptingPermission$ = permission
9497
9522
  ? permission.getIsPromptingObservable().pipe(rxjs.shareReplay(1))
9498
9523
  : rxjs.of(false);
@@ -10558,7 +10583,7 @@ class Call {
10558
10583
  const currentUserId = this.currentUserId;
10559
10584
  if (currentUserId && blockedUserIds.includes(currentUserId)) {
10560
10585
  this.logger('info', 'Leaving call because of being blocked');
10561
- await this.leave({ reason: 'user blocked' }).catch((err) => {
10586
+ await this.leave({ message: 'user blocked' }).catch((err) => {
10562
10587
  this.logger('error', 'Error leaving call after being blocked', err);
10563
10588
  });
10564
10589
  }
@@ -10711,7 +10736,7 @@ class Call {
10711
10736
  /**
10712
10737
  * Leave the call and stop the media streams that were published by the call.
10713
10738
  */
10714
- this.leave = async ({ reject, reason = 'user is leaving the call', } = {}) => {
10739
+ this.leave = async ({ reject, reason, message } = {}) => {
10715
10740
  await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
10716
10741
  const callingState = this.state.callingState;
10717
10742
  if (callingState === exports.CallingState.LEFT) {
@@ -10729,7 +10754,7 @@ class Call {
10729
10754
  }
10730
10755
  if (callingState === exports.CallingState.RINGING && reject !== false) {
10731
10756
  if (reject) {
10732
- await this.reject('decline');
10757
+ await this.reject(reason ?? 'decline');
10733
10758
  }
10734
10759
  else {
10735
10760
  // if reject was undefined, we still have to cancel the call automatically
@@ -10748,7 +10773,7 @@ class Call {
10748
10773
  this.subscriber = undefined;
10749
10774
  this.publisher?.dispose();
10750
10775
  this.publisher = undefined;
10751
- await this.sfuClient?.leaveAndClose(reason);
10776
+ await this.sfuClient?.leaveAndClose(message ?? reason ?? 'user is leaving the call');
10752
10777
  this.sfuClient = undefined;
10753
10778
  this.dynascaleManager.setSfuClient(undefined);
10754
10779
  this.state.setCallingState(exports.CallingState.LEFT);
@@ -11455,7 +11480,7 @@ class Call {
11455
11480
  if (strategy === WebsocketReconnectStrategy.UNSPECIFIED)
11456
11481
  return;
11457
11482
  if (strategy === WebsocketReconnectStrategy.DISCONNECT) {
11458
- this.leave({ reason: 'SFU instructed to disconnect' }).catch((err) => {
11483
+ this.leave({ message: 'SFU instructed to disconnect' }).catch((err) => {
11459
11484
  this.logger('warn', `Can't leave call after disconnect request`, err);
11460
11485
  });
11461
11486
  }
@@ -12085,7 +12110,11 @@ class Call {
12085
12110
  // e.g. it was already accepted and joined
12086
12111
  if (this.state.callingState !== exports.CallingState.RINGING)
12087
12112
  return;
12088
- this.leave({ reject: true, reason: 'timeout' }).catch((err) => {
12113
+ this.leave({
12114
+ reject: true,
12115
+ reason: 'timeout',
12116
+ message: `ringing timeout - ${this.isCreatedByMe ? 'no one accepted' : `user didn't interact with incoming call screen`}`,
12117
+ }).catch((err) => {
12089
12118
  this.logger('error', 'Failed to drop call', err);
12090
12119
  });
12091
12120
  }, timeoutInMs);
@@ -13458,7 +13487,7 @@ class StreamClient {
13458
13487
  this.getUserAgent = () => {
13459
13488
  if (!this.cachedUserAgent) {
13460
13489
  const { clientAppIdentifier = {} } = this.options;
13461
- const { sdkName = 'js', sdkVersion = "1.20.0", ...extras } = clientAppIdentifier;
13490
+ const { sdkName = 'js', sdkVersion = "1.20.2", ...extras } = clientAppIdentifier;
13462
13491
  this.cachedUserAgent = [
13463
13492
  `stream-video-${sdkName}-v${sdkVersion}`,
13464
13493
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),