@stream-io/video-client 1.9.3 → 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,13 @@
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
+
5
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)
6
13
 
7
14
 
@@ -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.3";
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 || ''}` ||
@@ -9782,6 +9994,9 @@ class Call {
9782
9994
  options: statsOptions,
9783
9995
  subscriber: this.subscriber,
9784
9996
  publisher: this.publisher,
9997
+ microphone: this.microphone,
9998
+ camera: this.camera,
9999
+ state: this.state,
9785
10000
  });
9786
10001
  this.sfuStatsReporter.start();
9787
10002
  }
@@ -12093,7 +12308,7 @@ class StreamClient {
12093
12308
  }
12094
12309
  if ((this._isUsingServerAuth() || this.node) &&
12095
12310
  !this.options.allowServerSideConnect) {
12096
- 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/');
12097
12312
  }
12098
12313
  // we generate the client id client side
12099
12314
  this.userID = user.id;
@@ -12469,7 +12684,7 @@ class StreamClient {
12469
12684
  });
12470
12685
  };
12471
12686
  this.getUserAgent = () => {
12472
- const version = "1.9.3";
12687
+ const version = "1.10.0";
12473
12688
  return (this.userAgent ||
12474
12689
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
12475
12690
  };