@stream-io/video-client 1.31.0 → 1.32.0
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 +11 -0
- package/dist/index.browser.es.js +45 -11
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +45 -11
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +45 -11
- package/dist/index.es.js.map +1 -1
- package/dist/src/StreamVideoClient.d.ts +2 -0
- package/dist/src/coordinator/connection/types.d.ts +4 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +8 -0
- package/package.json +1 -1
- package/src/Call.ts +6 -6
- package/src/StreamVideoClient.ts +42 -3
- package/src/coordinator/connection/types.ts +5 -0
- package/src/gen/video/sfu/event/events.ts +14 -0
|
@@ -30,6 +30,7 @@ export declare class StreamVideoClient {
|
|
|
30
30
|
private eventHandlersToUnregister;
|
|
31
31
|
private readonly connectionConcurrencyTag;
|
|
32
32
|
private static _instances;
|
|
33
|
+
private rejectCallWhenBusy;
|
|
33
34
|
/**
|
|
34
35
|
* You should create only one instance of `StreamVideoClient`.
|
|
35
36
|
*/
|
|
@@ -176,4 +177,5 @@ export declare class StreamVideoClient {
|
|
|
176
177
|
* @param tokenOrProvider a token or a function that returns a token.
|
|
177
178
|
*/
|
|
178
179
|
private connectAnonymousUser;
|
|
180
|
+
private shouldRejectCall;
|
|
179
181
|
}
|
|
@@ -149,6 +149,10 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
149
149
|
* @param allErrors all errors.
|
|
150
150
|
*/
|
|
151
151
|
onConnectUserError?: (lastError: Error, allErrors: Error[]) => void;
|
|
152
|
+
/**
|
|
153
|
+
* When set to true, the incoming calls are rejected when the user is busy in an another call.
|
|
154
|
+
*/
|
|
155
|
+
rejectCallWhenBusy?: boolean;
|
|
152
156
|
};
|
|
153
157
|
export type ClientAppIdentifier = {
|
|
154
158
|
sdkName?: 'react' | 'react-native' | 'plain-javascript' | (string & {});
|
|
@@ -420,6 +420,14 @@ export interface JoinRequest {
|
|
|
420
420
|
* @generated from protobuf field: string session_id = 2;
|
|
421
421
|
*/
|
|
422
422
|
sessionId: string;
|
|
423
|
+
/**
|
|
424
|
+
* user_session id can change during reconnects, this helps us to
|
|
425
|
+
* identify the user across reconnects and should remain consistent until the user explicitly
|
|
426
|
+
* disconnects, is kicked or the call is ended.
|
|
427
|
+
*
|
|
428
|
+
* @generated from protobuf field: string unified_session_id = 13;
|
|
429
|
+
*/
|
|
430
|
+
unifiedSessionId: string;
|
|
423
431
|
/**
|
|
424
432
|
* dumb SDP that allow us to extract subscriber's decode codecs
|
|
425
433
|
*
|
package/package.json
CHANGED
package/src/Call.ts
CHANGED
|
@@ -874,8 +874,6 @@ export class Call {
|
|
|
874
874
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
875
875
|
}
|
|
876
876
|
|
|
877
|
-
this.state.setCallingState(CallingState.JOINING);
|
|
878
|
-
|
|
879
877
|
// we will count the number of join failures per SFU.
|
|
880
878
|
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
881
879
|
// field to force the coordinator to provide us another SFU
|
|
@@ -905,8 +903,6 @@ export class Call {
|
|
|
905
903
|
}
|
|
906
904
|
|
|
907
905
|
if (attempt === maxJoinRetries - 1) {
|
|
908
|
-
// restore the previous call state if the join-flow fails
|
|
909
|
-
this.state.setCallingState(callingState);
|
|
910
906
|
throw err;
|
|
911
907
|
}
|
|
912
908
|
}
|
|
@@ -981,6 +977,7 @@ export class Call {
|
|
|
981
977
|
})
|
|
982
978
|
: previousSfuClient;
|
|
983
979
|
this.sfuClient = sfuClient;
|
|
980
|
+
this.unifiedSessionId ??= sfuClient.sessionId;
|
|
984
981
|
this.dynascaleManager.setSfuClient(sfuClient);
|
|
985
982
|
|
|
986
983
|
const clientDetails = await getClientDetails();
|
|
@@ -1008,6 +1005,7 @@ export class Call {
|
|
|
1008
1005
|
try {
|
|
1009
1006
|
const { callState, fastReconnectDeadlineSeconds, publishOptions } =
|
|
1010
1007
|
await sfuClient.join({
|
|
1008
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
1011
1009
|
subscriberSdp,
|
|
1012
1010
|
publisherSdp,
|
|
1013
1011
|
clientDetails,
|
|
@@ -1061,6 +1059,7 @@ export class Call {
|
|
|
1061
1059
|
statsOptions,
|
|
1062
1060
|
publishOptions: this.currentPublishOptions || [],
|
|
1063
1061
|
closePreviousInstances: !performingMigration,
|
|
1062
|
+
unifiedSessionId: this.unifiedSessionId,
|
|
1064
1063
|
});
|
|
1065
1064
|
}
|
|
1066
1065
|
|
|
@@ -1222,6 +1221,7 @@ export class Call {
|
|
|
1222
1221
|
clientDetails: ClientDetails;
|
|
1223
1222
|
publishOptions: PublishOption[];
|
|
1224
1223
|
closePreviousInstances: boolean;
|
|
1224
|
+
unifiedSessionId: string;
|
|
1225
1225
|
}) => {
|
|
1226
1226
|
const {
|
|
1227
1227
|
sfuClient,
|
|
@@ -1230,6 +1230,7 @@ export class Call {
|
|
|
1230
1230
|
statsOptions,
|
|
1231
1231
|
publishOptions,
|
|
1232
1232
|
closePreviousInstances,
|
|
1233
|
+
unifiedSessionId,
|
|
1233
1234
|
} = opts;
|
|
1234
1235
|
const { enable_rtc_stats: enableTracing } = statsOptions;
|
|
1235
1236
|
if (closePreviousInstances && this.subscriber) {
|
|
@@ -1288,7 +1289,6 @@ export class Call {
|
|
|
1288
1289
|
this.tracer.setEnabled(enableTracing);
|
|
1289
1290
|
this.sfuStatsReporter?.stop();
|
|
1290
1291
|
if (statsOptions?.reporting_interval_ms > 0) {
|
|
1291
|
-
this.unifiedSessionId ??= sfuClient.sessionId;
|
|
1292
1292
|
this.sfuStatsReporter = new SfuStatsReporter(sfuClient, {
|
|
1293
1293
|
clientDetails,
|
|
1294
1294
|
options: statsOptions,
|
|
@@ -1298,7 +1298,7 @@ export class Call {
|
|
|
1298
1298
|
camera: this.camera,
|
|
1299
1299
|
state: this.state,
|
|
1300
1300
|
tracer: this.tracer,
|
|
1301
|
-
unifiedSessionId
|
|
1301
|
+
unifiedSessionId,
|
|
1302
1302
|
});
|
|
1303
1303
|
this.sfuStatsReporter.start();
|
|
1304
1304
|
}
|
package/src/StreamVideoClient.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Call } from './Call';
|
|
2
2
|
import { StreamClient } from './coordinator/connection/client';
|
|
3
3
|
import {
|
|
4
|
+
CallingState,
|
|
4
5
|
StreamVideoReadOnlyStateStore,
|
|
5
6
|
StreamVideoWriteableStateStore,
|
|
6
7
|
} from './store';
|
|
@@ -74,6 +75,7 @@ export class StreamVideoClient {
|
|
|
74
75
|
);
|
|
75
76
|
|
|
76
77
|
private static _instances = new Map<string, StreamVideoClient>();
|
|
78
|
+
private rejectCallWhenBusy = false;
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
81
|
* You should create only one instance of `StreamVideoClient`.
|
|
@@ -95,6 +97,7 @@ export class StreamVideoClient {
|
|
|
95
97
|
setLogger(rootLogger, clientOptions?.logLevel || 'warn');
|
|
96
98
|
|
|
97
99
|
this.logger = getLogger(['client']);
|
|
100
|
+
this.rejectCallWhenBusy = clientOptions?.rejectCallWhenBusy ?? false;
|
|
98
101
|
|
|
99
102
|
this.streamClient = createCoordinatorClient(apiKey, clientOptions);
|
|
100
103
|
|
|
@@ -206,7 +209,18 @@ export class StreamVideoClient {
|
|
|
206
209
|
let call = this.writeableStateStore.findCall(e.call.type, e.call.id);
|
|
207
210
|
if (call) {
|
|
208
211
|
if (ringing) {
|
|
209
|
-
|
|
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
|
+
}
|
|
210
224
|
} else {
|
|
211
225
|
call.state.updateFromCallResponse(e.call);
|
|
212
226
|
}
|
|
@@ -221,11 +235,21 @@ export class StreamVideoClient {
|
|
|
221
235
|
clientStore: this.writeableStateStore,
|
|
222
236
|
ringing,
|
|
223
237
|
});
|
|
224
|
-
call.state.updateFromCallResponse(e.call);
|
|
225
238
|
|
|
226
239
|
if (ringing) {
|
|
227
|
-
|
|
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
|
+
}
|
|
228
251
|
} else {
|
|
252
|
+
call.state.updateFromCallResponse(e.call);
|
|
229
253
|
this.writeableStateStore.registerCall(call);
|
|
230
254
|
this.logger('info', `New call created and registered: ${call.cid}`);
|
|
231
255
|
}
|
|
@@ -572,4 +596,19 @@ export class StreamVideoClient {
|
|
|
572
596
|
this.streamClient.connectAnonymousUser(user, tokenOrProvider),
|
|
573
597
|
);
|
|
574
598
|
};
|
|
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
|
+
};
|
|
575
614
|
}
|
|
@@ -216,6 +216,11 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
216
216
|
* @param allErrors all errors.
|
|
217
217
|
*/
|
|
218
218
|
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;
|
|
219
224
|
};
|
|
220
225
|
|
|
221
226
|
export type ClientAppIdentifier = {
|
|
@@ -471,6 +471,14 @@ export interface JoinRequest {
|
|
|
471
471
|
* @generated from protobuf field: string session_id = 2;
|
|
472
472
|
*/
|
|
473
473
|
sessionId: string;
|
|
474
|
+
/**
|
|
475
|
+
* user_session id can change during reconnects, this helps us to
|
|
476
|
+
* identify the user across reconnects and should remain consistent until the user explicitly
|
|
477
|
+
* disconnects, is kicked or the call is ended.
|
|
478
|
+
*
|
|
479
|
+
* @generated from protobuf field: string unified_session_id = 13;
|
|
480
|
+
*/
|
|
481
|
+
unifiedSessionId: string;
|
|
474
482
|
/**
|
|
475
483
|
* dumb SDP that allow us to extract subscriber's decode codecs
|
|
476
484
|
*
|
|
@@ -1353,6 +1361,12 @@ class JoinRequest$Type extends MessageType<JoinRequest> {
|
|
|
1353
1361
|
super('stream.video.sfu.event.JoinRequest', [
|
|
1354
1362
|
{ no: 1, name: 'token', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
1355
1363
|
{ no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
1364
|
+
{
|
|
1365
|
+
no: 13,
|
|
1366
|
+
name: 'unified_session_id',
|
|
1367
|
+
kind: 'scalar',
|
|
1368
|
+
T: 9 /*ScalarType.STRING*/,
|
|
1369
|
+
},
|
|
1356
1370
|
{
|
|
1357
1371
|
no: 3,
|
|
1358
1372
|
name: 'subscriber_sdp',
|