@stream-io/video-client 1.10.2 → 1.10.4

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.
@@ -11,6 +11,7 @@ export declare class CameraManager extends InputMediaDeviceManager<CameraManager
11
11
  * @param call the call instance.
12
12
  */
13
13
  constructor(call: Call);
14
+ private isDirectionSupportedByDevice;
14
15
  /**
15
16
  * Select the camera direction.
16
17
  *
@@ -143,6 +143,10 @@ export type PublishOptions = {
143
143
  * in simulcast mode (non-SVC).
144
144
  */
145
145
  bitrateDownscaleFactor?: number;
146
+ /**
147
+ * The maximum number of simulcast layers to use when publishing the video stream.
148
+ */
149
+ maxSimulcastLayers?: number;
146
150
  /**
147
151
  * Screen share settings.
148
152
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -6,6 +6,7 @@ import { getVideoDevices, getVideoStream } from './devices';
6
6
  import { TrackType } from '../gen/video/sfu/models/models';
7
7
  import { PreferredCodec } from '../types';
8
8
  import { isMobile } from '../compatibility';
9
+ import { isReactNative } from '../helpers/platforms';
9
10
 
10
11
  export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
11
12
  private targetResolution = {
@@ -22,13 +23,17 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
22
23
  super(call, new CameraManagerState(), TrackType.VIDEO);
23
24
  }
24
25
 
26
+ private isDirectionSupportedByDevice() {
27
+ return isReactNative() || isMobile();
28
+ }
29
+
25
30
  /**
26
31
  * Select the camera direction.
27
32
  *
28
33
  * @param direction the direction of the camera to select.
29
34
  */
30
35
  async selectDirection(direction: Exclude<CameraDirection, undefined>) {
31
- if (isMobile()) {
36
+ if (this.isDirectionSupportedByDevice()) {
32
37
  this.state.setDirection(direction);
33
38
  // Providing both device id and direction doesn't work, so we deselect the device
34
39
  this.state.setDevice(undefined);
@@ -102,7 +107,12 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
102
107
  constraints.height = this.targetResolution.height;
103
108
  // We can't set both device id and facing mode
104
109
  // Device id has higher priority
105
- if (!constraints.deviceId && this.state.direction && isMobile()) {
110
+
111
+ if (
112
+ !constraints.deviceId &&
113
+ this.state.direction &&
114
+ this.isDirectionSupportedByDevice()
115
+ ) {
106
116
  constraints.facingMode =
107
117
  this.state.direction === 'front' ? 'user' : 'environment';
108
118
  }
@@ -43,6 +43,13 @@ vi.mock('../../compatibility.ts', () => {
43
43
  };
44
44
  });
45
45
 
46
+ vi.mock('../../helpers/platforms', () => {
47
+ console.log('MOCKING mobile device');
48
+ return {
49
+ isReactNative: () => false,
50
+ };
51
+ });
52
+
46
53
  describe('CameraManager', () => {
47
54
  let manager: CameraManager;
48
55
 
@@ -394,7 +394,8 @@ export class Publisher {
394
394
  ? // for SVC, we only have one layer (q) and often rid is omitted
395
395
  enabledLayers[0]
396
396
  : // for non-SVC, we need to find the layer by rid (simulcast)
397
- enabledLayers.find((l) => l.name === encoder.rid);
397
+ enabledLayers.find((l) => l.name === encoder.rid) ??
398
+ (params.encodings.length === 1 ? enabledLayers[0] : undefined);
398
399
 
399
400
  // flip 'active' flag only when necessary
400
401
  const shouldActivate = !!layer?.active;
@@ -360,6 +360,45 @@ describe('Publisher', () => {
360
360
  ]);
361
361
  });
362
362
 
363
+ it('can dynamically activate/deactivate simulcast layers when rid is missing', async () => {
364
+ const transceiver = new RTCRtpTransceiver();
365
+ const setParametersSpy = vi
366
+ .spyOn(transceiver.sender, 'setParameters')
367
+ .mockResolvedValue();
368
+ const getParametersSpy = vi
369
+ .spyOn(transceiver.sender, 'getParameters')
370
+ .mockReturnValue({
371
+ // @ts-expect-error incomplete data
372
+ codecs: [{ mimeType: 'video/VP8' }],
373
+ encodings: [{ active: false }],
374
+ });
375
+
376
+ // inject the transceiver
377
+ publisher['transceiverCache'].set(TrackType.VIDEO, transceiver);
378
+
379
+ await publisher['changePublishQuality']([
380
+ {
381
+ name: 'q',
382
+ active: true,
383
+ maxBitrate: 100,
384
+ scaleResolutionDownBy: 4,
385
+ maxFramerate: 30,
386
+ scalabilityMode: '',
387
+ },
388
+ ]);
389
+
390
+ expect(getParametersSpy).toHaveBeenCalled();
391
+ expect(setParametersSpy).toHaveBeenCalled();
392
+ expect(setParametersSpy.mock.calls[0][0].encodings).toEqual([
393
+ {
394
+ active: true,
395
+ maxBitrate: 100,
396
+ scaleResolutionDownBy: 4,
397
+ maxFramerate: 30,
398
+ },
399
+ ]);
400
+ });
401
+
363
402
  it('can dynamically update scalability mode in SVC', async () => {
364
403
  const transceiver = new RTCRtpTransceiver();
365
404
  const setParametersSpy = vi
@@ -6,8 +6,8 @@ const bitrateLookupTable: Record<
6
6
  > = {
7
7
  h264: {
8
8
  2160: 5_000_000,
9
- 1440: 3_500_000,
10
- 1080: 2_750_000,
9
+ 1440: 3_000_000,
10
+ 1080: 2_000_000,
11
11
  720: 1_250_000,
12
12
  540: 750_000,
13
13
  360: 400_000,
@@ -65,7 +65,11 @@ export const findOptimalVideoLayers = (
65
65
  const optimalVideoLayers: OptimalVideoLayer[] = [];
66
66
  const settings = videoTrack.getSettings();
67
67
  const { width = 0, height = 0 } = settings;
68
- const { scalabilityMode, bitrateDownscaleFactor = 2 } = publishOptions || {};
68
+ const {
69
+ scalabilityMode,
70
+ bitrateDownscaleFactor = 2,
71
+ maxSimulcastLayers = 3,
72
+ } = publishOptions || {};
69
73
  const maxBitrate = getComputedMaxBitrate(
70
74
  targetResolution,
71
75
  width,
@@ -76,7 +80,7 @@ export const findOptimalVideoLayers = (
76
80
  let downscaleFactor = 1;
77
81
  let bitrateFactor = 1;
78
82
  const svcCodec = isSvcCodec(codecInUse);
79
- for (const rid of ['f', 'h', 'q']) {
83
+ for (const rid of ['f', 'h', 'q'].slice(0, Math.min(3, maxSimulcastLayers))) {
80
84
  const layer: OptimalVideoLayer = {
81
85
  active: true,
82
86
  rid,
package/src/types.ts CHANGED
@@ -181,6 +181,10 @@ export type PublishOptions = {
181
181
  * in simulcast mode (non-SVC).
182
182
  */
183
183
  bitrateDownscaleFactor?: number;
184
+ /**
185
+ * The maximum number of simulcast layers to use when publishing the video stream.
186
+ */
187
+ maxSimulcastLayers?: number;
184
188
  /**
185
189
  * Screen share settings.
186
190
  */