@stream-io/video-client 1.9.3 → 1.10.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.
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.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.10.0...@stream-io/video-client-1.10.1) (2024-10-30)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * various device selector issues ([#1541](https://github.com/GetStream/stream-video-js/issues/1541)) ([f23618b](https://github.com/GetStream/stream-video-js/commit/f23618bda447eeb2d66f908bdb38b24db051f87c))
11
+
12
+ ## [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)
13
+
14
+
15
+ ### Features
16
+
17
+ * 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))
18
+
5
19
  ## [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
20
 
7
21
 
@@ -5,7 +5,7 @@ import axios, { AxiosHeaders } from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport, TwirpErrorCode } from '@protobuf-ts/twirp-transport';
7
7
  import { UAParser } from 'ua-parser-js';
8
- import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, merge, from, fromEvent, debounceTime, pairwise, of } from 'rxjs';
8
+ import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, fromEventPattern, startWith, concatMap, from, fromEvent, debounceTime, merge, pairwise, of } from 'rxjs';
9
9
  import * as SDP from 'sdp-transform';
10
10
  import WebSocket$1 from 'isomorphic-ws';
11
11
  import { fromByteArray } from 'base64-js';
@@ -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.1";
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 || ''}` ||
@@ -7618,7 +7830,7 @@ class BrowserPermission {
7618
7830
  const signal = this.disposeController.signal;
7619
7831
  this.ready = (async () => {
7620
7832
  const assumeGranted = (error) => {
7621
- this.setState('granted');
7833
+ this.setState('prompt');
7622
7834
  };
7623
7835
  if (!canQueryPermissions()) {
7624
7836
  return assumeGranted();
@@ -7664,6 +7876,7 @@ class BrowserPermission {
7664
7876
  this.wasPrompted = true;
7665
7877
  const stream = await navigator.mediaDevices.getUserMedia(this.permission.constraints);
7666
7878
  disposeOfMediaStream(stream);
7879
+ this.setState('granted');
7667
7880
  return true;
7668
7881
  }
7669
7882
  catch (e) {
@@ -7671,6 +7884,7 @@ class BrowserPermission {
7671
7884
  this.logger('info', 'Browser permission was not granted', {
7672
7885
  permission: this.permission,
7673
7886
  });
7887
+ this.setState('denied');
7674
7888
  if (throwOnNotAllowed) {
7675
7889
  throw e;
7676
7890
  }
@@ -7742,7 +7956,9 @@ const getDevices = (permission, kind) => {
7742
7956
  await permission.prompt({ throwOnNotAllowed: true });
7743
7957
  devices = await navigator.mediaDevices.enumerateDevices();
7744
7958
  }
7745
- return devices.filter((d) => d.kind === kind);
7959
+ return devices.filter((device) => device.kind === kind &&
7960
+ device.label !== '' &&
7961
+ device.deviceId !== 'default');
7746
7962
  })());
7747
7963
  };
7748
7964
  /**
@@ -7812,20 +8028,27 @@ const getAudioDevices = lazy(() => {
7812
8028
  * if devices are added/removed the list is updated, and if the permission is revoked,
7813
8029
  * the observable errors.
7814
8030
  */
7815
- const getVideoDevices = () => {
8031
+ const getVideoDevices = lazy(() => {
7816
8032
  return merge(getDeviceChangeObserver(), getVideoBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput')), shareReplay(1));
7817
- };
8033
+ });
7818
8034
  /**
7819
8035
  * Prompts the user for a permission to use video devices (if not already granted
7820
8036
  * and was not prompted before) and lists the available 'audiooutput' devices,
7821
8037
  * if devices are added/removed the list is updated, and if the permission is revoked,
7822
8038
  * the observable errors.
7823
8039
  */
7824
- const getAudioOutputDevices = () => {
8040
+ const getAudioOutputDevices = lazy(() => {
7825
8041
  return merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(startWith(undefined), concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')), shareReplay(1));
7826
- };
8042
+ });
7827
8043
  const getStream = async (constraints) => {
7828
- return await navigator.mediaDevices.getUserMedia(constraints);
8044
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
8045
+ if (isFirefox()) {
8046
+ // When enumerating devices, Firefox will hide device labels unless there's been
8047
+ // an active user media stream on the page. So we force device list updates after
8048
+ // every successful getUserMedia call.
8049
+ navigator.mediaDevices.dispatchEvent(new Event('devicechange'));
8050
+ }
8051
+ return stream;
7829
8052
  };
7830
8053
  /**
7831
8054
  * Returns an audio media stream that fulfills the given constraints.
@@ -9782,6 +10005,9 @@ class Call {
9782
10005
  options: statsOptions,
9783
10006
  subscriber: this.subscriber,
9784
10007
  publisher: this.publisher,
10008
+ microphone: this.microphone,
10009
+ camera: this.camera,
10010
+ state: this.state,
9785
10011
  });
9786
10012
  this.sfuStatsReporter.start();
9787
10013
  }
@@ -12093,7 +12319,7 @@ class StreamClient {
12093
12319
  }
12094
12320
  if ((this._isUsingServerAuth() || this.node) &&
12095
12321
  !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.');
12322
+ this.logger('warn', 'Please do not use connectUser server side. Use our @stream-io/node-sdk instead: https://getstream.io/video/docs/api/');
12097
12323
  }
12098
12324
  // we generate the client id client side
12099
12325
  this.userID = user.id;
@@ -12469,7 +12695,7 @@ class StreamClient {
12469
12695
  });
12470
12696
  };
12471
12697
  this.getUserAgent = () => {
12472
- const version = "1.9.3";
12698
+ const version = "1.10.1";
12473
12699
  return (this.userAgent ||
12474
12700
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
12475
12701
  };