Skip to content

Commit 1a1adaa

Browse files
committed
Merge remote-tracking branch 'origin/main' into audio-sdk-impl
# Conflicts: # packages/noise-cancellation-react-native/android/build.gradle # packages/react-native-sdk/ios/StreamVideoReactNative.h # packages/react-native-sdk/ios/StreamVideoReactNative.m # sample-apps/react-native/dogfood/ios/Podfile.lock # sample-apps/react-native/expo-video-sample/package.json # yarn.lock
2 parents 84fb94c + 1e73517 commit 1a1adaa

File tree

80 files changed

+5083
-587
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+5083
-587
lines changed

packages/client/CHANGELOG.md

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

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

5+
## [1.32.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.31.0...@stream-io/video-client-1.32.0) (2025-09-29)
6+
7+
### Features
8+
9+
- **react-native:** reject call when busy ([#1856](https://github.com/GetStream/stream-video-js/issues/1856)) ([b60bc7c](https://github.com/GetStream/stream-video-js/commit/b60bc7cd2dc2e09d52496d7b5cb593cac4b89485))
10+
11+
### Bug Fixes
12+
13+
- restore calling state after unrecoverable join fail ([#1935](https://github.com/GetStream/stream-video-js/issues/1935)) ([8ab0168](https://github.com/GetStream/stream-video-js/commit/8ab01680d01cc47f9cf48078634358507f0c109d))
14+
- send unifiedSessionId in the initial join request ([#1934](https://github.com/GetStream/stream-video-js/issues/1934)) ([e6a533d](https://github.com/GetStream/stream-video-js/commit/e6a533d7e926086ac5930ebfb4648dade449d15a))
15+
16+
## [1.31.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.30.1...@stream-io/video-client-1.31.0) (2025-09-17)
17+
18+
### Features
19+
20+
- introduce @stream-io/worker-timers ([94c962b](https://github.com/GetStream/stream-video-js/commit/94c962b2c5f731c152771b7803a59664fa925477))
21+
22+
### Bug Fixes
23+
24+
- **video-filters:** prevent background tab throttling ([#1920](https://github.com/GetStream/stream-video-js/issues/1920)) ([f93d5cc](https://github.com/GetStream/stream-video-js/commit/f93d5cc5785957c7f181fcaf689ec366df9e646b))
25+
26+
## [1.30.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.30.0...@stream-io/video-client-1.30.1) (2025-09-16)
27+
28+
### Bug Fixes
29+
30+
- don't apply default camera state if video is off ([#1917](https://github.com/GetStream/stream-video-js/issues/1917)) ([9cf1d75](https://github.com/GetStream/stream-video-js/commit/9cf1d752d824a0527fbb187df21d8a020590d4bb))
31+
- **rn:** set direction state for flip after constraints are applied ([1f03c59](https://github.com/GetStream/stream-video-js/commit/1f03c59b9b3fecc0ff1f7cb6b0eccb083b4a3475))
32+
33+
## [1.30.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.29.0...@stream-io/video-client-1.30.0) (2025-09-11)
34+
35+
- Skip tests for StreamVideoClient coordinator API ([aabe1d0](https://github.com/GetStream/stream-video-js/commit/aabe1d0ad3e3a95698b422991729e46289ab0277))
36+
37+
### Features
38+
39+
- Participant Source ([#1896](https://github.com/GetStream/stream-video-js/issues/1896)) ([b1cf710](https://github.com/GetStream/stream-video-js/commit/b1cf710ac3bfda573c0379dac1e6a107d2dbabf6))
40+
541
## [1.29.0](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.28.1...@stream-io/video-client-1.29.0) (2025-09-09)
642

743
### Features

packages/client/generate-timer-worker.sh

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/client/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@stream-io/video-client",
3-
"version": "1.29.0",
3+
"version": "1.32.0",
44
"main": "dist/index.cjs.js",
55
"module": "dist/index.es.js",
66
"browser": "dist/index.browser.es.js",
@@ -14,8 +14,7 @@
1414
"test": "vitest",
1515
"test-ci": "vitest run --coverage",
1616
"generate:open-api": "./generate-openapi.sh protocol",
17-
"generate:open-api:dev": "./generate-openapi.sh chat",
18-
"generate:timer-worker": "./generate-timer-worker.sh"
17+
"generate:open-api:dev": "./generate-openapi.sh chat"
1918
},
2019
"files": [
2120
"dist",
@@ -30,6 +29,7 @@
3029
"@protobuf-ts/runtime": "^2.9.4",
3130
"@protobuf-ts/runtime-rpc": "^2.9.4",
3231
"@protobuf-ts/twirp-transport": "^2.9.4",
32+
"@stream-io/worker-timer": "^1.2.4",
3333
"axios": "^1.8.1",
3434
"rxjs": "~7.8.1",
3535
"sdp-transform": "^2.15.0",

packages/client/src/Call.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ import {
112112
ClientCapability,
113113
ClientDetails,
114114
Codec,
115+
ParticipantSource,
115116
PublishOption,
116117
SubscribeOption,
117118
TrackType,
@@ -873,8 +874,6 @@ export class Call {
873874
throw new Error(`Illegal State: call.join() shall be called only once`);
874875
}
875876

876-
this.state.setCallingState(CallingState.JOINING);
877-
878877
// we will count the number of join failures per SFU.
879878
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
880879
// field to force the coordinator to provide us another SFU
@@ -904,8 +903,6 @@ export class Call {
904903
}
905904

906905
if (attempt === maxJoinRetries - 1) {
907-
// restore the previous call state if the join-flow fails
908-
this.state.setCallingState(callingState);
909906
throw err;
910907
}
911908
}
@@ -980,6 +977,7 @@ export class Call {
980977
})
981978
: previousSfuClient;
982979
this.sfuClient = sfuClient;
980+
this.unifiedSessionId ??= sfuClient.sessionId;
983981
this.dynascaleManager.setSfuClient(sfuClient);
984982

985983
const clientDetails = await getClientDetails();
@@ -1007,6 +1005,7 @@ export class Call {
10071005
try {
10081006
const { callState, fastReconnectDeadlineSeconds, publishOptions } =
10091007
await sfuClient.join({
1008+
unifiedSessionId: this.unifiedSessionId,
10101009
subscriberSdp,
10111010
publisherSdp,
10121011
clientDetails,
@@ -1015,6 +1014,7 @@ export class Call {
10151014
preferredPublishOptions,
10161015
preferredSubscribeOptions,
10171016
capabilities: Array.from(this.clientCapabilities),
1017+
source: ParticipantSource.WEBRTC_UNSPECIFIED,
10181018
});
10191019

10201020
this.currentPublishOptions = publishOptions;
@@ -1059,6 +1059,7 @@ export class Call {
10591059
statsOptions,
10601060
publishOptions: this.currentPublishOptions || [],
10611061
closePreviousInstances: !performingMigration,
1062+
unifiedSessionId: this.unifiedSessionId,
10621063
});
10631064
}
10641065

@@ -1220,6 +1221,7 @@ export class Call {
12201221
clientDetails: ClientDetails;
12211222
publishOptions: PublishOption[];
12221223
closePreviousInstances: boolean;
1224+
unifiedSessionId: string;
12231225
}) => {
12241226
const {
12251227
sfuClient,
@@ -1228,6 +1230,7 @@ export class Call {
12281230
statsOptions,
12291231
publishOptions,
12301232
closePreviousInstances,
1233+
unifiedSessionId,
12311234
} = opts;
12321235
const { enable_rtc_stats: enableTracing } = statsOptions;
12331236
if (closePreviousInstances && this.subscriber) {
@@ -1286,7 +1289,6 @@ export class Call {
12861289
this.tracer.setEnabled(enableTracing);
12871290
this.sfuStatsReporter?.stop();
12881291
if (statsOptions?.reporting_interval_ms > 0) {
1289-
this.unifiedSessionId ??= sfuClient.sessionId;
12901292
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
12911293
clientDetails,
12921294
options: statsOptions,
@@ -1296,7 +1298,7 @@ export class Call {
12961298
camera: this.camera,
12971299
state: this.state,
12981300
tracer: this.tracer,
1299-
unifiedSessionId: this.unifiedSessionId,
1301+
unifiedSessionId,
13001302
});
13011303
this.sfuStatsReporter.start();
13021304
}

packages/client/src/StreamVideoClient.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Call } from './Call';
22
import { StreamClient } from './coordinator/connection/client';
33
import {
4+
CallingState,
45
StreamVideoReadOnlyStateStore,
56
StreamVideoWriteableStateStore,
67
} from './store';
@@ -74,6 +75,7 @@ export class StreamVideoClient {
7475
);
7576

7677
private static _instances = new Map<string, StreamVideoClient>();
78+
private rejectCallWhenBusy = false;
7779

7880
/**
7981
* You should create only one instance of `StreamVideoClient`.
@@ -95,6 +97,7 @@ export class StreamVideoClient {
9597
setLogger(rootLogger, clientOptions?.logLevel || 'warn');
9698

9799
this.logger = getLogger(['client']);
100+
this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
98101

99102
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
100103

@@ -206,7 +209,18 @@ export class StreamVideoClient {
206209
let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
207210
if (call) {
208211
if (ringing) {
209-
await call.updateFromRingingEvent(e as CallRingEvent);
212+
if (this.shouldRejectCall(call.cid)) {
213+
this.logger(
214+
'info',
215+
`Leaving call with busy reject reason ${call.cid} because user is busy`,
216+
);
217+
// remove the instance from the state store
218+
await call.leave();
219+
// explicitly reject the call with busy reason as calling state was not ringing before and leave would not call it therefore
220+
await call.reject('busy');
221+
} else {
222+
await call.updateFromRingingEvent(e as CallRingEvent);
223+
}
210224
} else {
211225
call.state.updateFromCallResponse(e.call);
212226
}
@@ -221,11 +235,21 @@ export class StreamVideoClient {
221235
clientStore: this.writeableStateStore,
222236
ringing,
223237
});
224-
call.state.updateFromCallResponse(e.call);
225238

226239
if (ringing) {
227-
await call.get();
240+
if (this.shouldRejectCall(call.cid)) {
241+
this.logger(
242+
'info',
243+
`Rejecting call ${call.cid} because user is busy`,
244+
);
245+
// call is not in the state store yet, so just reject api is enough
246+
await call.reject('busy');
247+
} else {
248+
await call.updateFromRingingEvent(e as CallRingEvent);
249+
await call.get();
250+
}
228251
} else {
252+
call.state.updateFromCallResponse(e.call);
229253
this.writeableStateStore.registerCall(call);
230254
this.logger('info', `New call created and registered: ${call.cid}`);
231255
}
@@ -572,4 +596,19 @@ export class StreamVideoClient {
572596
this.streamClient.connectAnonymousUser(user, tokenOrProvider),
573597
);
574598
};
599+
600+
private shouldRejectCall = (currentCallId: string) => {
601+
if (!this.rejectCallWhenBusy) return false;
602+
603+
const hasOngoingRingingCall = this.state.calls.some(
604+
(c) =>
605+
c.cid !== currentCallId &&
606+
c.ringing &&
607+
c.state.callingState !== CallingState.IDLE &&
608+
c.state.callingState !== CallingState.LEFT &&
609+
c.state.callingState !== CallingState.RECONNECTING_FAILED,
610+
);
611+
612+
return hasOngoingRingingCall;
613+
};
575614
}

packages/client/src/__tests__/StreamVideoClient.api.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const tokenProvider = (userId: string) => {
2525
};
2626
};
2727

28-
describe('StreamVideoClient - coordinator API', () => {
28+
describe.skip('StreamVideoClient - coordinator API', () => {
2929
let client: StreamVideoClient;
3030
const user = {
3131
id: 'sara',

packages/client/src/coordinator/connection/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
216216
* @param allErrors all errors.
217217
*/
218218
onConnectUserError?: (lastError: Error, allErrors: Error[]) => void;
219+
220+
/**
221+
* When set to true, the incoming calls are rejected when the user is busy in an another call.
222+
*/
223+
rejectCallWhenBusy?: boolean;
219224
};
220225

221226
export type ClientAppIdentifier = {

packages/client/src/devices/CameraManager.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,17 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
3838
return;
3939
}
4040

41-
// providing both device id and direction doesn't work, so we deselect the device
42-
this.state.setDirection(direction);
43-
this.state.setDevice(undefined);
44-
4541
if (isReactNative()) {
4642
const videoTrack = this.getTracks()[0] as MediaStreamTrack | undefined;
4743
await videoTrack?.applyConstraints({
4844
facingMode: direction === 'front' ? 'user' : 'environment',
4945
});
46+
this.state.setDirection(direction);
5047
return;
5148
}
52-
49+
// providing both device id and direction doesn't work, so we deselect the device
50+
this.state.setDirection(direction);
51+
this.state.setDevice(undefined);
5352
this.getTracks().forEach((track) => track.stop());
5453
try {
5554
await this.unmuteStream();
@@ -122,7 +121,8 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
122121
// Wait for any in progress camera operation
123122
await this.statusChangeSettled();
124123

125-
const { target_resolution, camera_facing, camera_default_on } = settings;
124+
const { target_resolution, camera_facing, camera_default_on, enabled } =
125+
settings;
126126
// normalize target resolution to landscape format.
127127
// on mobile devices, the device itself adjusts the resolution to portrait or landscape
128128
// depending on the orientation of the device. using portrait resolution
@@ -142,7 +142,11 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
142142
if (this.enabled && mediaStream) {
143143
// The camera is already enabled (e.g. lobby screen). Publish the stream
144144
await this.publishStream(mediaStream);
145-
} else if (this.state.status === undefined && camera_default_on) {
145+
} else if (
146+
this.state.status === undefined &&
147+
camera_default_on &&
148+
enabled
149+
) {
146150
// Start camera if backend config specifies, and there is no local setting
147151
await this.enable();
148152
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ describe('CameraManager', () => {
243243
await manager.apply(
244244
// @ts-expect-error - partial settings
245245
{
246+
enabled: true,
246247
target_resolution: { width: 640, height: 480 },
247248
camera_facing: 'front',
248249
camera_default_on: true,
@@ -261,6 +262,7 @@ describe('CameraManager', () => {
261262
await manager.apply(
262263
// @ts-expect-error - partial settings
263264
{
265+
enabled: true,
264266
target_resolution: { width: 640, height: 480 },
265267
camera_facing: 'front',
266268
camera_default_on: false,
@@ -327,6 +329,23 @@ describe('CameraManager', () => {
327329

328330
expect(manager['publishStream']).toHaveBeenCalled();
329331
});
332+
333+
it('should not turn on the camera when video is disabled', async () => {
334+
vi.spyOn(manager, 'enable');
335+
await manager.apply(
336+
// @ts-expect-error - partial settings
337+
{
338+
enabled: false,
339+
target_resolution: { width: 640, height: 480 },
340+
camera_facing: 'front',
341+
camera_default_on: true,
342+
},
343+
false,
344+
);
345+
346+
expect(manager.state.status).toBe(undefined);
347+
expect(manager.enable).not.toHaveBeenCalled();
348+
});
330349
});
331350

332351
afterEach(() => {

0 commit comments

Comments
 (0)