@stream-io/video-client 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ ## [1.10.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.9.3...@stream-io/video-client-1.10.0) (2024-10-30)
6
+
7
+
8
+ ### Features
9
+
10
+ * report input devices in call stats ([#1533](https://github.com/GetStream/stream-video-js/issues/1533)) ([f34fe0a](https://github.com/GetStream/stream-video-js/commit/f34fe0a0444903099565ae55a9639e39fc19b76c))
11
+
12
+ ## [1.9.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.9.2...@stream-io/video-client-1.9.3) (2024-10-28)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * make device selection by device id exact ([#1538](https://github.com/GetStream/stream-video-js/issues/1538)) ([6274cac](https://github.com/GetStream/stream-video-js/commit/6274cac2ecf155aa6ce0c6d764229e0e9cd39a6a))
18
+
5
19
  ## [1.9.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.9.1...@stream-io/video-client-1.9.2) (2024-10-21)
6
20
 
7
21
 
@@ -841,6 +841,76 @@ var WebsocketReconnectStrategy;
841
841
  */
842
842
  WebsocketReconnectStrategy[WebsocketReconnectStrategy["MIGRATE"] = 4] = "MIGRATE";
843
843
  })(WebsocketReconnectStrategy || (WebsocketReconnectStrategy = {}));
844
+ /**
845
+ * AndroidThermalState is reported by the Android API. The full list of values is documented here
846
+ * https://developer.android.com/reference/android/os/PowerManager.html#getCurrentThermalStatus()
847
+ *
848
+ * @generated from protobuf enum stream.video.sfu.models.AndroidThermalState
849
+ */
850
+ var AndroidThermalState;
851
+ (function (AndroidThermalState) {
852
+ /**
853
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_UNSPECIFIED = 0;
854
+ */
855
+ AndroidThermalState[AndroidThermalState["UNSPECIFIED"] = 0] = "UNSPECIFIED";
856
+ /**
857
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_NONE = 1;
858
+ */
859
+ AndroidThermalState[AndroidThermalState["NONE"] = 1] = "NONE";
860
+ /**
861
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_LIGHT = 2;
862
+ */
863
+ AndroidThermalState[AndroidThermalState["LIGHT"] = 2] = "LIGHT";
864
+ /**
865
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_MODERATE = 3;
866
+ */
867
+ AndroidThermalState[AndroidThermalState["MODERATE"] = 3] = "MODERATE";
868
+ /**
869
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_SEVERE = 4;
870
+ */
871
+ AndroidThermalState[AndroidThermalState["SEVERE"] = 4] = "SEVERE";
872
+ /**
873
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_CRITICAL = 5;
874
+ */
875
+ AndroidThermalState[AndroidThermalState["CRITICAL"] = 5] = "CRITICAL";
876
+ /**
877
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_EMERGENCY = 6;
878
+ */
879
+ AndroidThermalState[AndroidThermalState["EMERGENCY"] = 6] = "EMERGENCY";
880
+ /**
881
+ * @generated from protobuf enum value: ANDROID_THERMAL_STATE_SHUTDOWN = 7;
882
+ */
883
+ AndroidThermalState[AndroidThermalState["SHUTDOWN"] = 7] = "SHUTDOWN";
884
+ })(AndroidThermalState || (AndroidThermalState = {}));
885
+ /**
886
+ * AppleThermalState is the thermal state as reported by Apple devices when available or applicable to the platform.
887
+ * The full list of states (enum) is available here: https://developer.apple.com/documentation/foundation/processinfo/thermalstate
888
+ *
889
+ * @generated from protobuf enum stream.video.sfu.models.AppleThermalState
890
+ */
891
+ var AppleThermalState;
892
+ (function (AppleThermalState) {
893
+ /**
894
+ * @generated from protobuf enum value: APPLE_THERMAL_STATE_UNSPECIFIED = 0;
895
+ */
896
+ AppleThermalState[AppleThermalState["UNSPECIFIED"] = 0] = "UNSPECIFIED";
897
+ /**
898
+ * @generated from protobuf enum value: APPLE_THERMAL_STATE_NOMINAL = 1;
899
+ */
900
+ AppleThermalState[AppleThermalState["NOMINAL"] = 1] = "NOMINAL";
901
+ /**
902
+ * @generated from protobuf enum value: APPLE_THERMAL_STATE_FAIR = 2;
903
+ */
904
+ AppleThermalState[AppleThermalState["FAIR"] = 2] = "FAIR";
905
+ /**
906
+ * @generated from protobuf enum value: APPLE_THERMAL_STATE_SERIOUS = 3;
907
+ */
908
+ AppleThermalState[AppleThermalState["SERIOUS"] = 3] = "SERIOUS";
909
+ /**
910
+ * @generated from protobuf enum value: APPLE_THERMAL_STATE_CRITICAL = 4;
911
+ */
912
+ AppleThermalState[AppleThermalState["CRITICAL"] = 4] = "CRITICAL";
913
+ })(AppleThermalState || (AppleThermalState = {}));
844
914
  // @generated message type with reflection information, may provide speed optimized methods
845
915
  class CallState$Type extends MessageType {
846
916
  constructor() {
@@ -1279,9 +1349,92 @@ class CallGrants$Type extends MessageType {
1279
1349
  * @generated MessageType for protobuf message stream.video.sfu.models.CallGrants
1280
1350
  */
1281
1351
  const CallGrants = new CallGrants$Type();
1352
+ // @generated message type with reflection information, may provide speed optimized methods
1353
+ class InputDevices$Type extends MessageType {
1354
+ constructor() {
1355
+ super('stream.video.sfu.models.InputDevices', [
1356
+ {
1357
+ no: 1,
1358
+ name: 'available_devices',
1359
+ kind: 'scalar',
1360
+ repeat: 2 /*RepeatType.UNPACKED*/,
1361
+ T: 9 /*ScalarType.STRING*/,
1362
+ },
1363
+ {
1364
+ no: 2,
1365
+ name: 'current_device',
1366
+ kind: 'scalar',
1367
+ T: 9 /*ScalarType.STRING*/,
1368
+ },
1369
+ { no: 3, name: 'is_permitted', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1370
+ ]);
1371
+ }
1372
+ }
1373
+ /**
1374
+ * @generated MessageType for protobuf message stream.video.sfu.models.InputDevices
1375
+ */
1376
+ const InputDevices = new InputDevices$Type();
1377
+ // @generated message type with reflection information, may provide speed optimized methods
1378
+ class AndroidState$Type extends MessageType {
1379
+ constructor() {
1380
+ super('stream.video.sfu.models.AndroidState', [
1381
+ {
1382
+ no: 1,
1383
+ name: 'thermal_state',
1384
+ kind: 'enum',
1385
+ T: () => [
1386
+ 'stream.video.sfu.models.AndroidThermalState',
1387
+ AndroidThermalState,
1388
+ 'ANDROID_THERMAL_STATE_',
1389
+ ],
1390
+ },
1391
+ {
1392
+ no: 2,
1393
+ name: 'is_power_saver_mode',
1394
+ kind: 'scalar',
1395
+ T: 8 /*ScalarType.BOOL*/,
1396
+ },
1397
+ ]);
1398
+ }
1399
+ }
1400
+ /**
1401
+ * @generated MessageType for protobuf message stream.video.sfu.models.AndroidState
1402
+ */
1403
+ const AndroidState = new AndroidState$Type();
1404
+ // @generated message type with reflection information, may provide speed optimized methods
1405
+ class AppleState$Type extends MessageType {
1406
+ constructor() {
1407
+ super('stream.video.sfu.models.AppleState', [
1408
+ {
1409
+ no: 1,
1410
+ name: 'thermal_state',
1411
+ kind: 'enum',
1412
+ T: () => [
1413
+ 'stream.video.sfu.models.AppleThermalState',
1414
+ AppleThermalState,
1415
+ 'APPLE_THERMAL_STATE_',
1416
+ ],
1417
+ },
1418
+ {
1419
+ no: 2,
1420
+ name: 'is_low_power_mode_enabled',
1421
+ kind: 'scalar',
1422
+ T: 8 /*ScalarType.BOOL*/,
1423
+ },
1424
+ ]);
1425
+ }
1426
+ }
1427
+ /**
1428
+ * @generated MessageType for protobuf message stream.video.sfu.models.AppleState
1429
+ */
1430
+ const AppleState = new AppleState$Type();
1282
1431
 
1283
1432
  var models = /*#__PURE__*/Object.freeze({
1284
1433
  __proto__: null,
1434
+ AndroidState: AndroidState,
1435
+ get AndroidThermalState () { return AndroidThermalState; },
1436
+ AppleState: AppleState,
1437
+ get AppleThermalState () { return AppleThermalState; },
1285
1438
  Browser: Browser,
1286
1439
  Call: Call$1,
1287
1440
  get CallEndedReason () { return CallEndedReason; },
@@ -1295,6 +1448,7 @@ var models = /*#__PURE__*/Object.freeze({
1295
1448
  get ErrorCode () { return ErrorCode; },
1296
1449
  get GoAwayReason () { return GoAwayReason; },
1297
1450
  ICETrickle: ICETrickle$1,
1451
+ InputDevices: InputDevices,
1298
1452
  OS: OS,
1299
1453
  Participant: Participant,
1300
1454
  ParticipantCount: ParticipantCount,
@@ -1394,6 +1548,22 @@ class SendStatsRequest$Type extends MessageType {
1394
1548
  kind: 'scalar',
1395
1549
  T: 9 /*ScalarType.STRING*/,
1396
1550
  },
1551
+ { no: 7, name: 'audio_devices', kind: 'message', T: () => InputDevices },
1552
+ { no: 8, name: 'video_devices', kind: 'message', T: () => InputDevices },
1553
+ {
1554
+ no: 9,
1555
+ name: 'android',
1556
+ kind: 'message',
1557
+ oneof: 'deviceState',
1558
+ T: () => AndroidState,
1559
+ },
1560
+ {
1561
+ no: 10,
1562
+ name: 'apple',
1563
+ kind: 'message',
1564
+ oneof: 'deviceState',
1565
+ T: () => AppleState,
1566
+ },
1397
1567
  ]);
1398
1568
  }
1399
1569
  }
@@ -2938,7 +3108,7 @@ const retryable = async (rpc, signal) => {
2938
3108
  return result;
2939
3109
  };
2940
3110
 
2941
- const version = "1.9.2";
3111
+ const version = "1.10.0";
2942
3112
  const [major, minor, patch] = version.split('.');
2943
3113
  let sdkInfo = {
2944
3114
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -3668,9 +3838,10 @@ const setCurrentValue = (subject, update) => {
3668
3838
  *
3669
3839
  * @param observable the observable to subscribe to.
3670
3840
  * @param handler the handler to call when the observable emits a value.
3841
+ * @param onError an optional error handler.
3671
3842
  */
3672
- const createSubscription = (observable, handler) => {
3673
- const subscription = observable.subscribe(handler);
3843
+ const createSubscription = (observable, handler, onError = (error) => getLogger(['RxUtils'])('warn', 'An observable emitted an error', error)) => {
3844
+ const subscription = observable.subscribe({ next: handler, error: onError });
3674
3845
  return () => {
3675
3846
  subscription.unsubscribe();
3676
3847
  };
@@ -3685,12 +3856,9 @@ const createSubscription = (observable, handler) => {
3685
3856
  */
3686
3857
  const createSafeAsyncSubscription = (observable, handler) => {
3687
3858
  const tag = Symbol();
3688
- const subscription = observable.subscribe((value) => {
3859
+ return createSubscription(observable, (value) => {
3689
3860
  withoutConcurrency(tag, () => handler(value));
3690
3861
  });
3691
- return () => {
3692
- subscription.unsubscribe();
3693
- };
3694
3862
  };
3695
3863
 
3696
3864
  var rxUtils = /*#__PURE__*/Object.freeze({
@@ -6960,8 +7128,38 @@ const aggregate = (stats) => {
6960
7128
  };
6961
7129
 
6962
7130
  class SfuStatsReporter {
6963
- constructor(sfuClient, { options, clientDetails, subscriber, publisher }) {
7131
+ constructor(sfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, }) {
6964
7132
  this.logger = getLogger(['SfuStatsReporter']);
7133
+ this.inputDevices = new Map();
7134
+ this.observeDevice = (device, kind) => {
7135
+ const { hasBrowserPermission$ } = device.state;
7136
+ this.unsubscribeDevicePermissionsSubscription?.();
7137
+ this.unsubscribeDevicePermissionsSubscription = createSubscription(combineLatest([hasBrowserPermission$, this.state.ownCapabilities$]), ([hasPermission, ownCapabilities]) => {
7138
+ // cleanup the previous listDevices() subscription in case
7139
+ // permissions or capabilities have changed.
7140
+ // we will subscribe again if everything is in order.
7141
+ this.unsubscribeListDevicesSubscription?.();
7142
+ const hasCapability = kind === 'mic'
7143
+ ? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
7144
+ : ownCapabilities.includes(OwnCapability.SEND_VIDEO);
7145
+ if (!hasPermission || !hasCapability) {
7146
+ this.inputDevices.set(kind, {
7147
+ currentDevice: '',
7148
+ availableDevices: [],
7149
+ isPermitted: false,
7150
+ });
7151
+ return;
7152
+ }
7153
+ this.unsubscribeListDevicesSubscription = createSubscription(combineLatest([device.listDevices(), device.state.selectedDevice$]), ([devices, deviceId]) => {
7154
+ const selected = devices.find((d) => d.deviceId === deviceId);
7155
+ this.inputDevices.set(kind, {
7156
+ currentDevice: selected?.label || deviceId || '',
7157
+ availableDevices: devices.map((d) => d.label),
7158
+ isPermitted: true,
7159
+ });
7160
+ });
7161
+ });
7162
+ };
6965
7163
  this.run = async () => {
6966
7164
  const [subscriberStats, publisherStats] = await Promise.all([
6967
7165
  this.subscriber.getStats().then(flatten).then(JSON.stringify),
@@ -6973,11 +7171,16 @@ class SfuStatsReporter {
6973
7171
  webrtcVersion: this.webRTCVersion,
6974
7172
  subscriberStats,
6975
7173
  publisherStats,
7174
+ audioDevices: this.inputDevices.get('mic'),
7175
+ videoDevices: this.inputDevices.get('camera'),
7176
+ deviceState: { oneofKind: undefined },
6976
7177
  });
6977
7178
  };
6978
7179
  this.start = () => {
6979
7180
  if (this.options.reporting_interval_ms <= 0)
6980
7181
  return;
7182
+ this.observeDevice(this.microphone, 'mic');
7183
+ this.observeDevice(this.camera, 'camera');
6981
7184
  clearInterval(this.intervalId);
6982
7185
  this.intervalId = setInterval(() => {
6983
7186
  this.run().catch((err) => {
@@ -6986,6 +7189,11 @@ class SfuStatsReporter {
6986
7189
  }, this.options.reporting_interval_ms);
6987
7190
  };
6988
7191
  this.stop = () => {
7192
+ this.unsubscribeDevicePermissionsSubscription?.();
7193
+ this.unsubscribeDevicePermissionsSubscription = undefined;
7194
+ this.unsubscribeListDevicesSubscription?.();
7195
+ this.unsubscribeListDevicesSubscription = undefined;
7196
+ this.inputDevices.clear();
6989
7197
  clearInterval(this.intervalId);
6990
7198
  this.intervalId = undefined;
6991
7199
  };
@@ -6993,11 +7201,15 @@ class SfuStatsReporter {
6993
7201
  this.options = options;
6994
7202
  this.subscriber = subscriber;
6995
7203
  this.publisher = publisher;
6996
- const webRTCInfo = getWebRTCInfo();
7204
+ this.microphone = microphone;
7205
+ this.camera = camera;
7206
+ this.state = state;
6997
7207
  const { sdk, browser } = clientDetails;
6998
7208
  this.sdkName = getSdkName(sdk);
6999
7209
  this.sdkVersion = getSdkVersion(sdk);
7000
- // The WebRTC version if passed from the SDK, it is taken else the browser info is sent.
7210
+ // use the WebRTC version if set by the SDK (React Native) otherwise,
7211
+ // use the browser version as a fallback
7212
+ const webRTCInfo = getWebRTCInfo();
7001
7213
  this.webRTCVersion =
7002
7214
  webRTCInfo?.version ||
7003
7215
  `${browser?.name || ''}-${browser?.version || ''}` ||
@@ -7847,14 +8059,19 @@ const getAudioStream = async (trackConstraints) => {
7847
8059
  throwOnNotAllowed: true,
7848
8060
  forcePrompt: true,
7849
8061
  });
7850
- return getStream(constraints);
8062
+ return await getStream(constraints);
7851
8063
  }
7852
- catch (e) {
8064
+ catch (error) {
8065
+ if (error instanceof OverconstrainedError && trackConstraints?.deviceId) {
8066
+ const { deviceId, ...relaxedContraints } = trackConstraints;
8067
+ getLogger(['devices'])('warn', 'Failed to get audio stream, will try again with relaxed contraints', { error, constraints, relaxedContraints });
8068
+ return getAudioStream(relaxedContraints);
8069
+ }
7853
8070
  getLogger(['devices'])('error', 'Failed to get audio stream', {
7854
- error: e,
7855
- constraints: constraints,
8071
+ error,
8072
+ constraints,
7856
8073
  });
7857
- throw e;
8074
+ throw error;
7858
8075
  }
7859
8076
  };
7860
8077
  /**
@@ -7877,14 +8094,19 @@ const getVideoStream = async (trackConstraints) => {
7877
8094
  throwOnNotAllowed: true,
7878
8095
  forcePrompt: true,
7879
8096
  });
7880
- return getStream(constraints);
8097
+ return await getStream(constraints);
7881
8098
  }
7882
- catch (e) {
8099
+ catch (error) {
8100
+ if (error instanceof OverconstrainedError && trackConstraints?.deviceId) {
8101
+ const { deviceId, ...relaxedContraints } = trackConstraints;
8102
+ getLogger(['devices'])('warn', 'Failed to get video stream, will try again with relaxed contraints', { error, constraints, relaxedContraints });
8103
+ return getVideoStream(relaxedContraints);
8104
+ }
7883
8105
  getLogger(['devices'])('error', 'Failed to get video stream', {
7884
- error: e,
7885
- constraints: constraints,
8106
+ error,
8107
+ constraints,
7886
8108
  });
7887
- throw e;
8109
+ throw error;
7888
8110
  }
7889
8111
  };
7890
8112
  /**
@@ -8102,11 +8324,18 @@ class InputMediaDeviceManager {
8102
8324
  if (isReactNative()) {
8103
8325
  throw new Error('This method is not supported in React Native. Please visit https://getstream.io/video/docs/reactnative/core/camera-and-microphone/#speaker-management for reference.');
8104
8326
  }
8105
- if (deviceId === this.state.selectedDevice) {
8327
+ const prevDeviceId = this.state.selectedDevice;
8328
+ if (deviceId === prevDeviceId) {
8106
8329
  return;
8107
8330
  }
8108
- this.state.setDevice(deviceId);
8109
- await this.applySettingsToStream();
8331
+ try {
8332
+ this.state.setDevice(deviceId);
8333
+ await this.applySettingsToStream();
8334
+ }
8335
+ catch (error) {
8336
+ this.state.setDevice(prevDeviceId);
8337
+ throw error;
8338
+ }
8110
8339
  }
8111
8340
  async applySettingsToStream() {
8112
8341
  await withCancellation(this.statusChangeConcurrencyTag, async () => {
@@ -8181,7 +8410,9 @@ class InputMediaDeviceManager {
8181
8410
  const defaultConstraints = this.state.defaultConstraints;
8182
8411
  const constraints = {
8183
8412
  ...defaultConstraints,
8184
- deviceId: this.state.selectedDevice,
8413
+ deviceId: this.state.selectedDevice
8414
+ ? { exact: this.state.selectedDevice }
8415
+ : undefined,
8185
8416
  };
8186
8417
  /**
8187
8418
  * Chains two media streams together.
@@ -9763,6 +9994,9 @@ class Call {
9763
9994
  options: statsOptions,
9764
9995
  subscriber: this.subscriber,
9765
9996
  publisher: this.publisher,
9997
+ microphone: this.microphone,
9998
+ camera: this.camera,
9999
+ state: this.state,
9766
10000
  });
9767
10001
  this.sfuStatsReporter.start();
9768
10002
  }
@@ -12074,7 +12308,7 @@ class StreamClient {
12074
12308
  }
12075
12309
  if ((this._isUsingServerAuth() || this.node) &&
12076
12310
  !this.options.allowServerSideConnect) {
12077
- this.logger('warn', 'Please do not use connectUser server side. connectUser impacts MAU and concurrent connection usage and thus your bill. If you have a valid use-case, add "allowServerSideConnect: true" to the client options to disable this warning.');
12311
+ this.logger('warn', 'Please do not use connectUser server side. Use our @stream-io/node-sdk instead: https://getstream.io/video/docs/api/');
12078
12312
  }
12079
12313
  // we generate the client id client side
12080
12314
  this.userID = user.id;
@@ -12450,7 +12684,7 @@ class StreamClient {
12450
12684
  });
12451
12685
  };
12452
12686
  this.getUserAgent = () => {
12453
- const version = "1.9.2";
12687
+ const version = "1.10.0";
12454
12688
  return (this.userAgent ||
12455
12689
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
12456
12690
  };