@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.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +15 -9
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +15 -9
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +15 -9
- package/dist/index.es.js.map +1 -1
- package/dist/src/devices/CameraManager.d.ts +1 -0
- package/dist/src/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/devices/CameraManager.ts +12 -2
- package/src/devices/__tests__/CameraManager.test.ts +7 -0
- package/src/rtc/Publisher.ts +2 -1
- package/src/rtc/__tests__/Publisher.test.ts +39 -0
- package/src/rtc/bitrateLookup.ts +2 -2
- package/src/rtc/videoLayers.ts +6 -2
- package/src/types.ts +4 -0
package/dist/src/types.d.ts
CHANGED
|
@@ -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
|
@@ -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 (
|
|
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
|
-
|
|
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
|
|
package/src/rtc/Publisher.ts
CHANGED
|
@@ -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
|
package/src/rtc/bitrateLookup.ts
CHANGED
package/src/rtc/videoLayers.ts
CHANGED
|
@@ -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 {
|
|
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
|
*/
|