Skip to content

Commit 014a8b1

Browse files
committed
Merge branch 'main' into codec-negotiation
2 parents c00193e + 890c36f commit 014a8b1

File tree

27 files changed

+334
-31
lines changed

27 files changed

+334
-31
lines changed

packages/client/CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
44

5+
## [1.10.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.10.4...@stream-io/video-client-1.10.5) (2024-11-07)
6+
7+
8+
### Bug Fixes
9+
10+
* ignore maxSimulcastLayers override for SVC codecs ([#1564](https://github.com/GetStream/stream-video-js/issues/1564)) ([48f8abe](https://github.com/GetStream/stream-video-js/commit/48f8abe5fd5b48c367a04696febd582573def828))
11+
12+
## [1.10.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.10.3...@stream-io/video-client-1.10.4) (2024-11-07)
13+
14+
15+
### Bug Fixes
16+
17+
* max simulcast layers preference ([#1560](https://github.com/GetStream/stream-video-js/issues/1560)) ([2b0bf28](https://github.com/GetStream/stream-video-js/commit/2b0bf2824dce41c2709e361e0521cf85e1b2fd16))
18+
19+
## [1.10.3](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.10.2...@stream-io/video-client-1.10.3) (2024-11-05)
20+
21+
22+
### Bug Fixes
23+
24+
* camera flip did not work in react-native ([#1554](https://github.com/GetStream/stream-video-js/issues/1554)) ([423890c](https://github.com/GetStream/stream-video-js/commit/423890cb2d1925366d8a63c29f93c4c92c8104ad)), closes [#1521](https://github.com/GetStream/stream-video-js/issues/1521)
25+
526
## [1.10.2](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.10.1...@stream-io/video-client-1.10.2) (2024-11-01)
627

728

packages/client/docusaurus/docs/javascript/02-guides/03-call-and-participant-state.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ const subscription = call.state.participants$.subscribe((participants) => {
151151
subscription.unsubscribe();
152152
```
153153

154+
In a call with many participants, the value of the `participants$` call state observable is truncated to 250 participants. The participants who are publishing video, audio, or screen sharing have priority over the other participants in the list. This means, for example, that in a livestream with one host and many viewers, the host is guaranteed to be in the list.
155+
154156
## Client state
155157

156158
The client state can be accessed by `client.state`.

packages/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/video-client",
3-
"version": "1.10.2",
3+
"version": "1.10.5",
44
"packageManager": "[email protected]",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.es.js",

packages/client/src/devices/CameraManager.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getVideoDevices, getVideoStream } from './devices';
66
import { TrackType } from '../gen/video/sfu/models/models';
77
import { PreferredCodec } from '../types';
88
import { isMobile } from '../compatibility';
9+
import { isReactNative } from '../helpers/platforms';
910

1011
export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
1112
private targetResolution = {
@@ -22,13 +23,17 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
2223
super(call, new CameraManagerState(), TrackType.VIDEO);
2324
}
2425

26+
private isDirectionSupportedByDevice() {
27+
return isReactNative() || isMobile();
28+
}
29+
2530
/**
2631
* Select the camera direction.
2732
*
2833
* @param direction the direction of the camera to select.
2934
*/
3035
async selectDirection(direction: Exclude<CameraDirection, undefined>) {
31-
if (isMobile()) {
36+
if (this.isDirectionSupportedByDevice()) {
3237
this.state.setDirection(direction);
3338
// Providing both device id and direction doesn't work, so we deselect the device
3439
this.state.setDevice(undefined);
@@ -102,7 +107,12 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
102107
constraints.height = this.targetResolution.height;
103108
// We can't set both device id and facing mode
104109
// 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+
) {
106116
constraints.facingMode =
107117
this.state.direction === 'front' ? 'user' : 'environment';
108118
}

packages/client/src/devices/__tests__/CameraManager.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ vi.mock('../../compatibility.ts', () => {
4343
};
4444
});
4545

46+
vi.mock('../../helpers/platforms', () => {
47+
console.log('MOCKING mobile device');
48+
return {
49+
isReactNative: () => false,
50+
};
51+
});
52+
4653
describe('CameraManager', () => {
4754
let manager: CameraManager;
4855

packages/client/src/rtc/Publisher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ export class Publisher extends BasePeerConnection {
315315
? // for SVC, we only have one layer (q) and often rid is omitted
316316
enabledLayers[0]
317317
: // for non-SVC, we need to find the layer by rid (simulcast)
318-
enabledLayers.find((l) => l.name === encoder.rid);
318+
enabledLayers.find((l) => l.name === encoder.rid) ??
319+
(params.encodings.length === 1 ? enabledLayers[0] : undefined);
319320

320321
// flip 'active' flag only when necessary
321322
const shouldActivate = !!layer?.active;

packages/client/src/rtc/__tests__/Publisher.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,45 @@ describe('Publisher', () => {
358358
]);
359359
});
360360

361+
it('can dynamically activate/deactivate simulcast layers when rid is missing', async () => {
362+
const transceiver = new RTCRtpTransceiver();
363+
const setParametersSpy = vi
364+
.spyOn(transceiver.sender, 'setParameters')
365+
.mockResolvedValue();
366+
const getParametersSpy = vi
367+
.spyOn(transceiver.sender, 'getParameters')
368+
.mockReturnValue({
369+
// @ts-expect-error incomplete data
370+
codecs: [{ mimeType: 'video/VP8' }],
371+
encodings: [{ active: false }],
372+
});
373+
374+
// inject the transceiver
375+
publisher['transceiverCache'].set(TrackType.VIDEO, transceiver);
376+
377+
await publisher['changePublishQuality']([
378+
{
379+
name: 'q',
380+
active: true,
381+
maxBitrate: 100,
382+
scaleResolutionDownBy: 4,
383+
maxFramerate: 30,
384+
scalabilityMode: '',
385+
},
386+
]);
387+
388+
expect(getParametersSpy).toHaveBeenCalled();
389+
expect(setParametersSpy).toHaveBeenCalled();
390+
expect(setParametersSpy.mock.calls[0][0].encodings).toEqual([
391+
{
392+
active: true,
393+
maxBitrate: 100,
394+
scaleResolutionDownBy: 4,
395+
maxFramerate: 30,
396+
},
397+
]);
398+
});
399+
361400
it('can dynamically update scalability mode in SVC', async () => {
362401
const transceiver = new RTCRtpTransceiver();
363402
const setParametersSpy = vi

packages/client/src/rtc/bitrateLookup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const bitrateLookupTable: Record<
66
> = {
77
h264: {
88
2160: 5_000_000,
9-
1440: 3_500_000,
10-
1080: 2_750_000,
9+
1440: 3_000_000,
10+
1080: 2_000_000,
1111
720: 1_250_000,
1212
540: 750_000,
1313
360: 400_000,

packages/client/src/rtc/videoLayers.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ export const findOptimalVideoLayers = (
6565
const optimalVideoLayers: OptimalVideoLayer[] = [];
6666
const settings = videoTrack.getSettings();
6767
const { width = 0, height = 0 } = settings;
68-
const { scalabilityMode, bitrateDownscaleFactor = 2 } = publishOptions || {};
68+
const {
69+
scalabilityMode,
70+
bitrateDownscaleFactor = 2,
71+
maxSimulcastLayers = 3,
72+
} = publishOptions || {};
6973
const maxBitrate = getComputedMaxBitrate(
7074
targetResolution,
7175
width,
@@ -76,7 +80,8 @@ export const findOptimalVideoLayers = (
7680
let downscaleFactor = 1;
7781
let bitrateFactor = 1;
7882
const svcCodec = isSvcCodec(codecInUse);
79-
for (const rid of ['f', 'h', 'q']) {
83+
const totalLayers = svcCodec ? 3 : Math.min(3, maxSimulcastLayers);
84+
for (const rid of ['f', 'h', 'q'].slice(0, totalLayers)) {
8085
const layer: OptimalVideoLayer = {
8186
active: true,
8287
rid,

packages/client/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ export type PublishOptions = {
181181
* in simulcast mode (non-SVC).
182182
*/
183183
bitrateDownscaleFactor?: number;
184+
/**
185+
* The maximum number of simulcast layers to use when publishing the video stream.
186+
*/
187+
maxSimulcastLayers?: number;
184188
/**
185189
* Screen share settings.
186190
*/

0 commit comments

Comments
 (0)