@stream-io/video-client 1.26.1 → 1.27.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 +10 -0
- package/dist/index.browser.es.js +235 -47
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +235 -46
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +235 -47
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +14 -2
- package/dist/src/StreamSfuClient.d.ts +7 -3
- package/dist/src/events/internal.d.ts +7 -1
- package/dist/src/gen/video/sfu/event/events.d.ts +57 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +21 -0
- package/dist/src/helpers/array.d.ts +7 -0
- package/dist/src/helpers/participantUtils.d.ts +8 -1
- package/dist/src/rtc/BasePeerConnection.d.ts +2 -2
- package/dist/src/rtc/Dispatcher.d.ts +1 -1
- package/dist/src/rtc/signal.d.ts +1 -1
- package/dist/src/store/CallState.d.ts +2 -1
- package/dist/src/types.d.ts +10 -1
- package/package.json +1 -1
- package/src/Call.ts +48 -5
- package/src/StreamSfuClient.ts +33 -14
- package/src/coordinator/connection/connection.ts +0 -1
- package/src/events/__tests__/internal.test.ts +78 -0
- package/src/events/__tests__/participant.test.ts +66 -0
- package/src/events/callEventHandlers.ts +2 -0
- package/src/events/internal.ts +28 -1
- package/src/events/participant.ts +4 -1
- package/src/gen/video/sfu/event/events.ts +104 -0
- package/src/gen/video/sfu/models/models.ts +21 -0
- package/src/helpers/__tests__/participantUtils.test.ts +167 -0
- package/src/helpers/array.ts +16 -0
- package/src/helpers/participantUtils.ts +23 -1
- package/src/rtc/BasePeerConnection.ts +6 -5
- package/src/rtc/Dispatcher.ts +3 -2
- package/src/rtc/__tests__/Publisher.test.ts +3 -2
- package/src/rtc/__tests__/Subscriber.test.ts +3 -2
- package/src/rtc/signal.ts +3 -3
- package/src/store/CallState.ts +7 -4
- package/src/types.ts +11 -0
package/dist/index.cjs.js
CHANGED
|
@@ -973,6 +973,10 @@ var ErrorCode;
|
|
|
973
973
|
* @generated from protobuf enum value: ERROR_CODE_CALL_NOT_FOUND = 300;
|
|
974
974
|
*/
|
|
975
975
|
ErrorCode[ErrorCode["CALL_NOT_FOUND"] = 300] = "CALL_NOT_FOUND";
|
|
976
|
+
/**
|
|
977
|
+
* @generated from protobuf enum value: ERROR_CODE_CALL_PARTICIPANT_LIMIT_REACHED = 301;
|
|
978
|
+
*/
|
|
979
|
+
ErrorCode[ErrorCode["CALL_PARTICIPANT_LIMIT_REACHED"] = 301] = "CALL_PARTICIPANT_LIMIT_REACHED";
|
|
976
980
|
/**
|
|
977
981
|
* @generated from protobuf enum value: ERROR_CODE_REQUEST_VALIDATION_FAILED = 400;
|
|
978
982
|
*/
|
|
@@ -1241,6 +1245,24 @@ var AppleThermalState;
|
|
|
1241
1245
|
*/
|
|
1242
1246
|
AppleThermalState[AppleThermalState["CRITICAL"] = 4] = "CRITICAL";
|
|
1243
1247
|
})(AppleThermalState || (AppleThermalState = {}));
|
|
1248
|
+
/**
|
|
1249
|
+
* ClientCapability defines a feature that client supports
|
|
1250
|
+
*
|
|
1251
|
+
* @generated from protobuf enum stream.video.sfu.models.ClientCapability
|
|
1252
|
+
*/
|
|
1253
|
+
var ClientCapability;
|
|
1254
|
+
(function (ClientCapability) {
|
|
1255
|
+
/**
|
|
1256
|
+
* @generated from protobuf enum value: CLIENT_CAPABILITY_UNSPECIFIED = 0;
|
|
1257
|
+
*/
|
|
1258
|
+
ClientCapability[ClientCapability["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
1259
|
+
/**
|
|
1260
|
+
* Enables SFU pausing inbound video
|
|
1261
|
+
*
|
|
1262
|
+
* @generated from protobuf enum value: CLIENT_CAPABILITY_SUBSCRIBER_VIDEO_PAUSE = 1;
|
|
1263
|
+
*/
|
|
1264
|
+
ClientCapability[ClientCapability["SUBSCRIBER_VIDEO_PAUSE"] = 1] = "SUBSCRIBER_VIDEO_PAUSE";
|
|
1265
|
+
})(ClientCapability || (ClientCapability = {}));
|
|
1244
1266
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
1245
1267
|
class CallState$Type extends runtime.MessageType {
|
|
1246
1268
|
constructor() {
|
|
@@ -1911,6 +1933,7 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
1911
1933
|
get CallEndedReason () { return CallEndedReason; },
|
|
1912
1934
|
CallGrants: CallGrants,
|
|
1913
1935
|
CallState: CallState$1,
|
|
1936
|
+
get ClientCapability () { return ClientCapability; },
|
|
1914
1937
|
ClientDetails: ClientDetails,
|
|
1915
1938
|
Codec: Codec,
|
|
1916
1939
|
get ConnectionQuality () { return ConnectionQuality; },
|
|
@@ -2600,6 +2623,13 @@ class SfuEvent$Type extends runtime.MessageType {
|
|
|
2600
2623
|
oneof: 'eventPayload',
|
|
2601
2624
|
T: () => ChangePublishOptions,
|
|
2602
2625
|
},
|
|
2626
|
+
{
|
|
2627
|
+
no: 28,
|
|
2628
|
+
name: 'inbound_state_notification',
|
|
2629
|
+
kind: 'message',
|
|
2630
|
+
oneof: 'eventPayload',
|
|
2631
|
+
T: () => InboundStateNotification,
|
|
2632
|
+
},
|
|
2603
2633
|
]);
|
|
2604
2634
|
}
|
|
2605
2635
|
}
|
|
@@ -2907,6 +2937,17 @@ class JoinRequest$Type extends runtime.MessageType {
|
|
|
2907
2937
|
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2908
2938
|
T: () => SubscribeOption,
|
|
2909
2939
|
},
|
|
2940
|
+
{
|
|
2941
|
+
no: 11,
|
|
2942
|
+
name: 'capabilities',
|
|
2943
|
+
kind: 'enum',
|
|
2944
|
+
repeat: 1 /*RepeatType.PACKED*/,
|
|
2945
|
+
T: () => [
|
|
2946
|
+
'stream.video.sfu.models.ClientCapability',
|
|
2947
|
+
ClientCapability,
|
|
2948
|
+
'CLIENT_CAPABILITY_',
|
|
2949
|
+
],
|
|
2950
|
+
},
|
|
2910
2951
|
]);
|
|
2911
2952
|
}
|
|
2912
2953
|
}
|
|
@@ -3352,6 +3393,48 @@ class CallEnded$Type extends runtime.MessageType {
|
|
|
3352
3393
|
* @generated MessageType for protobuf message stream.video.sfu.event.CallEnded
|
|
3353
3394
|
*/
|
|
3354
3395
|
const CallEnded = new CallEnded$Type();
|
|
3396
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
3397
|
+
class InboundStateNotification$Type extends runtime.MessageType {
|
|
3398
|
+
constructor() {
|
|
3399
|
+
super('stream.video.sfu.event.InboundStateNotification', [
|
|
3400
|
+
{
|
|
3401
|
+
no: 1,
|
|
3402
|
+
name: 'inbound_video_states',
|
|
3403
|
+
kind: 'message',
|
|
3404
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
3405
|
+
T: () => InboundVideoState,
|
|
3406
|
+
},
|
|
3407
|
+
]);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
/**
|
|
3411
|
+
* @generated MessageType for protobuf message stream.video.sfu.event.InboundStateNotification
|
|
3412
|
+
*/
|
|
3413
|
+
const InboundStateNotification = new InboundStateNotification$Type();
|
|
3414
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
3415
|
+
class InboundVideoState$Type extends runtime.MessageType {
|
|
3416
|
+
constructor() {
|
|
3417
|
+
super('stream.video.sfu.event.InboundVideoState', [
|
|
3418
|
+
{ no: 1, name: 'user_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3419
|
+
{ no: 2, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3420
|
+
{
|
|
3421
|
+
no: 3,
|
|
3422
|
+
name: 'track_type',
|
|
3423
|
+
kind: 'enum',
|
|
3424
|
+
T: () => [
|
|
3425
|
+
'stream.video.sfu.models.TrackType',
|
|
3426
|
+
TrackType,
|
|
3427
|
+
'TRACK_TYPE_',
|
|
3428
|
+
],
|
|
3429
|
+
},
|
|
3430
|
+
{ no: 4, name: 'paused', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
|
3431
|
+
]);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* @generated MessageType for protobuf message stream.video.sfu.event.InboundVideoState
|
|
3436
|
+
*/
|
|
3437
|
+
const InboundVideoState = new InboundVideoState$Type();
|
|
3355
3438
|
|
|
3356
3439
|
var events = /*#__PURE__*/Object.freeze({
|
|
3357
3440
|
__proto__: null,
|
|
@@ -3372,6 +3455,8 @@ var events = /*#__PURE__*/Object.freeze({
|
|
|
3372
3455
|
HealthCheckResponse: HealthCheckResponse,
|
|
3373
3456
|
ICERestart: ICERestart,
|
|
3374
3457
|
ICETrickle: ICETrickle,
|
|
3458
|
+
InboundStateNotification: InboundStateNotification,
|
|
3459
|
+
InboundVideoState: InboundVideoState,
|
|
3375
3460
|
JoinRequest: JoinRequest,
|
|
3376
3461
|
JoinResponse: JoinResponse,
|
|
3377
3462
|
LeaveCallRequest: LeaveCallRequest,
|
|
@@ -3796,6 +3881,7 @@ const sfuEventKinds = {
|
|
|
3796
3881
|
participantUpdated: undefined,
|
|
3797
3882
|
participantMigrationComplete: undefined,
|
|
3798
3883
|
changePublishOptions: undefined,
|
|
3884
|
+
inboundStateNotification: undefined,
|
|
3799
3885
|
};
|
|
3800
3886
|
const isSfuEvent = (eventName) => {
|
|
3801
3887
|
return Object.prototype.hasOwnProperty.call(sfuEventKinds, eventName);
|
|
@@ -3804,12 +3890,12 @@ class Dispatcher {
|
|
|
3804
3890
|
constructor() {
|
|
3805
3891
|
this.logger = getLogger(['Dispatcher']);
|
|
3806
3892
|
this.subscribers = {};
|
|
3807
|
-
this.dispatch = (message,
|
|
3893
|
+
this.dispatch = (message, tag = '0') => {
|
|
3808
3894
|
const eventKind = message.eventPayload.oneofKind;
|
|
3809
3895
|
if (!eventKind)
|
|
3810
3896
|
return;
|
|
3811
3897
|
const payload = message.eventPayload[eventKind];
|
|
3812
|
-
this.logger('debug', `Dispatching ${eventKind}, tag=${
|
|
3898
|
+
this.logger('debug', `Dispatching ${eventKind}, tag=${tag}`, payload);
|
|
3813
3899
|
const listeners = this.subscribers[eventKind];
|
|
3814
3900
|
if (!listeners)
|
|
3815
3901
|
return;
|
|
@@ -4326,6 +4412,24 @@ const hasScreenShareAudio = (p) => p.publishedTracks.includes(TrackType.SCREEN_S
|
|
|
4326
4412
|
* @param p the participant.
|
|
4327
4413
|
*/
|
|
4328
4414
|
const isPinned = (p) => !!p.pin && (p.pin.isLocalPin || p.pin.pinnedAt > 0);
|
|
4415
|
+
/**
|
|
4416
|
+
* Check if a participant has a paused track of the specified type.
|
|
4417
|
+
*
|
|
4418
|
+
* @param p the participant to check.
|
|
4419
|
+
* @param videoTrackType the type of video track to check for ('videoTrack' or 'screenShareTrack').
|
|
4420
|
+
*/
|
|
4421
|
+
const hasPausedTrack = (p, videoTrackType) => {
|
|
4422
|
+
if (!p.pausedTracks)
|
|
4423
|
+
return false;
|
|
4424
|
+
const trackType = videoTrackType === 'videoTrack'
|
|
4425
|
+
? TrackType.VIDEO
|
|
4426
|
+
: videoTrackType === 'screenShareTrack'
|
|
4427
|
+
? TrackType.SCREEN_SHARE
|
|
4428
|
+
: undefined;
|
|
4429
|
+
if (!trackType)
|
|
4430
|
+
return false;
|
|
4431
|
+
return p.pausedTracks.includes(trackType);
|
|
4432
|
+
};
|
|
4329
4433
|
|
|
4330
4434
|
/**
|
|
4331
4435
|
* A comparator which sorts participants by the fact that they are the dominant speaker or not.
|
|
@@ -4728,17 +4832,17 @@ class CallState {
|
|
|
4728
4832
|
*
|
|
4729
4833
|
* @param sessionId the session ID of the participant to update.
|
|
4730
4834
|
* @param participant the participant to update or add.
|
|
4835
|
+
* @param patch an optional patch to apply to the participant.
|
|
4731
4836
|
*/
|
|
4732
|
-
this.updateOrAddParticipant = (sessionId, participant) => {
|
|
4837
|
+
this.updateOrAddParticipant = (sessionId, participant, patch) => {
|
|
4733
4838
|
return this.setParticipants((participants) => {
|
|
4734
4839
|
let add = true;
|
|
4735
4840
|
const nextParticipants = participants.map((p) => {
|
|
4736
4841
|
if (p.sessionId === sessionId) {
|
|
4737
4842
|
add = false;
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
};
|
|
4843
|
+
const updated = { ...p, ...participant };
|
|
4844
|
+
const thePatch = typeof patch === 'function' ? patch(updated) : patch;
|
|
4845
|
+
return Object.assign(updated, thePatch);
|
|
4742
4846
|
}
|
|
4743
4847
|
return p;
|
|
4744
4848
|
});
|
|
@@ -5812,7 +5916,7 @@ const aggregate = (stats) => {
|
|
|
5812
5916
|
return report;
|
|
5813
5917
|
};
|
|
5814
5918
|
|
|
5815
|
-
const version = "1.
|
|
5919
|
+
const version = "1.27.0";
|
|
5816
5920
|
const [major, minor, patch] = version.split('.');
|
|
5817
5921
|
let sdkInfo = {
|
|
5818
5922
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6421,7 +6525,7 @@ class BasePeerConnection {
|
|
|
6421
6525
|
/**
|
|
6422
6526
|
* Constructs a new `BasePeerConnection` instance.
|
|
6423
6527
|
*/
|
|
6424
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded,
|
|
6528
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
|
|
6425
6529
|
this.isIceRestarting = false;
|
|
6426
6530
|
this.isDisposed = false;
|
|
6427
6531
|
this.trackIdToTrackType = new Map();
|
|
@@ -6645,13 +6749,12 @@ class BasePeerConnection {
|
|
|
6645
6749
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
6646
6750
|
this.logger = getLogger([
|
|
6647
6751
|
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
6648
|
-
|
|
6752
|
+
tag,
|
|
6649
6753
|
]);
|
|
6650
6754
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
6651
6755
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
6652
6756
|
if (enableTracing) {
|
|
6653
|
-
|
|
6654
|
-
this.tracer = new Tracer(tag);
|
|
6757
|
+
this.tracer = new Tracer(`${tag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`);
|
|
6655
6758
|
this.tracer.trace('create', {
|
|
6656
6759
|
url: sfuClient.edgeName,
|
|
6657
6760
|
...connectionConfig,
|
|
@@ -7523,8 +7626,8 @@ class Subscriber extends BasePeerConnection {
|
|
|
7523
7626
|
}
|
|
7524
7627
|
|
|
7525
7628
|
const createWebSocketSignalChannel = (opts) => {
|
|
7526
|
-
const { endpoint, onMessage,
|
|
7527
|
-
const logger = getLogger(['SfuClientWS',
|
|
7629
|
+
const { endpoint, onMessage, tag } = opts;
|
|
7630
|
+
const logger = getLogger(['SfuClientWS', tag]);
|
|
7528
7631
|
logger('debug', 'Creating signaling WS channel:', endpoint);
|
|
7529
7632
|
const ws = new WebSocket(endpoint);
|
|
7530
7633
|
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
@@ -7774,7 +7877,7 @@ class StreamSfuClient {
|
|
|
7774
7877
|
/**
|
|
7775
7878
|
* Constructs a new SFU client.
|
|
7776
7879
|
*/
|
|
7777
|
-
constructor({ dispatcher, credentials, sessionId,
|
|
7880
|
+
constructor({ dispatcher, credentials, sessionId, cid, tag, joinResponseTimeout = 5000, onSignalClose, streamClient, enableTracing, }) {
|
|
7778
7881
|
/**
|
|
7779
7882
|
* A buffer for ICE Candidates that are received before
|
|
7780
7883
|
* the Publisher and Subscriber Peer Connections are ready to handle them.
|
|
@@ -7804,7 +7907,7 @@ class StreamSfuClient {
|
|
|
7804
7907
|
* A controller to abort the current requests.
|
|
7805
7908
|
*/
|
|
7806
7909
|
this.abortController = new AbortController();
|
|
7807
|
-
this.createWebSocket = () => {
|
|
7910
|
+
this.createWebSocket = (params) => {
|
|
7808
7911
|
const eventsToTrace = {
|
|
7809
7912
|
callEnded: true,
|
|
7810
7913
|
changePublishQuality: true,
|
|
@@ -7812,10 +7915,11 @@ class StreamSfuClient {
|
|
|
7812
7915
|
connectionQualityChanged: true,
|
|
7813
7916
|
error: true,
|
|
7814
7917
|
goAway: true,
|
|
7918
|
+
inboundStateNotification: true,
|
|
7815
7919
|
};
|
|
7816
7920
|
this.signalWs = createWebSocketSignalChannel({
|
|
7817
|
-
|
|
7818
|
-
endpoint: `${this.credentials.server.ws_endpoint}
|
|
7921
|
+
tag: this.tag,
|
|
7922
|
+
endpoint: `${this.credentials.server.ws_endpoint}?${new URLSearchParams(params).toString()}`,
|
|
7819
7923
|
onMessage: (message) => {
|
|
7820
7924
|
this.lastMessageTimestamp = new Date();
|
|
7821
7925
|
this.scheduleConnectionCheck();
|
|
@@ -7823,7 +7927,7 @@ class StreamSfuClient {
|
|
|
7823
7927
|
if (eventsToTrace[eventKind]) {
|
|
7824
7928
|
this.tracer?.trace(eventKind, message);
|
|
7825
7929
|
}
|
|
7826
|
-
this.dispatcher.dispatch(message, this.
|
|
7930
|
+
this.dispatcher.dispatch(message, this.tag);
|
|
7827
7931
|
},
|
|
7828
7932
|
});
|
|
7829
7933
|
this.signalReady = makeSafePromise(Promise.race([
|
|
@@ -7938,7 +8042,7 @@ class StreamSfuClient {
|
|
|
7938
8042
|
});
|
|
7939
8043
|
this.migrateAwayTimeout = setTimeout(() => {
|
|
7940
8044
|
unsubscribe();
|
|
7941
|
-
task.reject(new Error(`Migration (${this.
|
|
8045
|
+
task.reject(new Error(`Migration (${this.tag}) failed to complete in ${timeout}ms`));
|
|
7942
8046
|
}, timeout);
|
|
7943
8047
|
return task.promise;
|
|
7944
8048
|
};
|
|
@@ -8038,10 +8142,10 @@ class StreamSfuClient {
|
|
|
8038
8142
|
const { server, token } = credentials;
|
|
8039
8143
|
this.edgeName = server.edge_name;
|
|
8040
8144
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
8041
|
-
this.
|
|
8042
|
-
this.logger = getLogger(['SfuClient',
|
|
8145
|
+
this.tag = tag;
|
|
8146
|
+
this.logger = getLogger(['SfuClient', tag]);
|
|
8043
8147
|
this.tracer = enableTracing
|
|
8044
|
-
? new Tracer(`${
|
|
8148
|
+
? new Tracer(`${tag}-${this.edgeName}`)
|
|
8045
8149
|
: undefined;
|
|
8046
8150
|
this.rpc = createSignalClient({
|
|
8047
8151
|
baseUrl: server.url,
|
|
@@ -8070,7 +8174,13 @@ class StreamSfuClient {
|
|
|
8070
8174
|
this.networkAvailableTask?.resolve();
|
|
8071
8175
|
}
|
|
8072
8176
|
});
|
|
8073
|
-
this.createWebSocket(
|
|
8177
|
+
this.createWebSocket({
|
|
8178
|
+
attempt: tag,
|
|
8179
|
+
user_id: streamClient.user?.id || '',
|
|
8180
|
+
api_key: streamClient.key,
|
|
8181
|
+
user_session_id: this.sessionId,
|
|
8182
|
+
cid,
|
|
8183
|
+
});
|
|
8074
8184
|
}
|
|
8075
8185
|
get isHealthy() {
|
|
8076
8186
|
return (this.signalWs.readyState === WebSocket.OPEN &&
|
|
@@ -8230,6 +8340,36 @@ const watchCallGrantsUpdated = (state) => {
|
|
|
8230
8340
|
};
|
|
8231
8341
|
};
|
|
8232
8342
|
|
|
8343
|
+
/**
|
|
8344
|
+
* Adds unique values to an array.
|
|
8345
|
+
*
|
|
8346
|
+
* @param arr the array to add to.
|
|
8347
|
+
* @param values the values to add.
|
|
8348
|
+
*/
|
|
8349
|
+
const pushToIfMissing = (arr, ...values) => {
|
|
8350
|
+
for (const v of values) {
|
|
8351
|
+
if (!arr.includes(v)) {
|
|
8352
|
+
arr.push(v);
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
return arr;
|
|
8356
|
+
};
|
|
8357
|
+
/**
|
|
8358
|
+
* Removes values from an array if they are present.
|
|
8359
|
+
*
|
|
8360
|
+
* @param arr the array to remove from.
|
|
8361
|
+
* @param values the values to remove.
|
|
8362
|
+
*/
|
|
8363
|
+
const removeFromIfPresent = (arr, ...values) => {
|
|
8364
|
+
for (const v of values) {
|
|
8365
|
+
const index = arr.indexOf(v);
|
|
8366
|
+
if (index !== -1) {
|
|
8367
|
+
arr.splice(index, 1);
|
|
8368
|
+
}
|
|
8369
|
+
}
|
|
8370
|
+
return arr;
|
|
8371
|
+
};
|
|
8372
|
+
|
|
8233
8373
|
const watchConnectionQualityChanged = (dispatcher, state) => {
|
|
8234
8374
|
return dispatcher.on('connectionQualityChanged', (e) => {
|
|
8235
8375
|
const { connectionQualityUpdates } = e;
|
|
@@ -8296,6 +8436,29 @@ const watchPinsUpdated = (state) => {
|
|
|
8296
8436
|
state.setServerSidePins(pins);
|
|
8297
8437
|
};
|
|
8298
8438
|
};
|
|
8439
|
+
/**
|
|
8440
|
+
* Watches for inbound state notifications and updates the paused tracks
|
|
8441
|
+
*
|
|
8442
|
+
* @param state the call state to update.
|
|
8443
|
+
*/
|
|
8444
|
+
const watchInboundStateNotification = (state) => {
|
|
8445
|
+
return function onInboundStateNotification(e) {
|
|
8446
|
+
const { inboundVideoStates } = e;
|
|
8447
|
+
const current = state.getParticipantLookupBySessionId();
|
|
8448
|
+
const patches = {};
|
|
8449
|
+
for (const { sessionId, trackType, paused } of inboundVideoStates) {
|
|
8450
|
+
const pausedTracks = [...(current[sessionId]?.pausedTracks ?? [])];
|
|
8451
|
+
if (paused) {
|
|
8452
|
+
pushToIfMissing(pausedTracks, trackType);
|
|
8453
|
+
}
|
|
8454
|
+
else {
|
|
8455
|
+
removeFromIfPresent(pausedTracks, trackType);
|
|
8456
|
+
}
|
|
8457
|
+
patches[sessionId] = { pausedTracks };
|
|
8458
|
+
}
|
|
8459
|
+
state.updateParticipants(patches);
|
|
8460
|
+
};
|
|
8461
|
+
};
|
|
8299
8462
|
|
|
8300
8463
|
/**
|
|
8301
8464
|
* An event handler that handles soft mutes.
|
|
@@ -8332,21 +8495,6 @@ const handleRemoteSoftMute = (call) => {
|
|
|
8332
8495
|
});
|
|
8333
8496
|
};
|
|
8334
8497
|
|
|
8335
|
-
/**
|
|
8336
|
-
* Adds unique values to an array.
|
|
8337
|
-
*
|
|
8338
|
-
* @param arr the array to add to.
|
|
8339
|
-
* @param values the values to add.
|
|
8340
|
-
*/
|
|
8341
|
-
const pushToIfMissing = (arr, ...values) => {
|
|
8342
|
-
for (const v of values) {
|
|
8343
|
-
if (!arr.includes(v)) {
|
|
8344
|
-
arr.push(v);
|
|
8345
|
-
}
|
|
8346
|
-
}
|
|
8347
|
-
return arr;
|
|
8348
|
-
};
|
|
8349
|
-
|
|
8350
8498
|
/**
|
|
8351
8499
|
* An event responder which handles the `participantJoined` event.
|
|
8352
8500
|
*/
|
|
@@ -8428,11 +8576,14 @@ const watchTrackUnpublished = (state) => {
|
|
|
8428
8576
|
if (e.participant) {
|
|
8429
8577
|
const orphanedTracks = reconcileOrphanedTracks(state, e.participant);
|
|
8430
8578
|
const participant = Object.assign(e.participant, orphanedTracks);
|
|
8431
|
-
state.updateOrAddParticipant(sessionId, participant)
|
|
8579
|
+
state.updateOrAddParticipant(sessionId, participant, (p) => ({
|
|
8580
|
+
pausedTracks: p.pausedTracks?.filter((t) => t !== type),
|
|
8581
|
+
}));
|
|
8432
8582
|
}
|
|
8433
8583
|
else {
|
|
8434
8584
|
state.updateParticipant(sessionId, (p) => ({
|
|
8435
8585
|
publishedTracks: p.publishedTracks.filter((t) => t !== type),
|
|
8586
|
+
pausedTracks: p.pausedTracks?.filter((t) => t !== type),
|
|
8436
8587
|
}));
|
|
8437
8588
|
}
|
|
8438
8589
|
};
|
|
@@ -8524,6 +8675,7 @@ const registerEventHandlers = (call, dispatcher) => {
|
|
|
8524
8675
|
watchDominantSpeakerChanged(dispatcher, state),
|
|
8525
8676
|
call.on('callGrantsUpdated', watchCallGrantsUpdated(state)),
|
|
8526
8677
|
call.on('pinsUpdated', watchPinsUpdated(state)),
|
|
8678
|
+
call.on('inboundStateNotification', watchInboundStateNotification(state)),
|
|
8527
8679
|
handleRemoteSoftMute(call),
|
|
8528
8680
|
];
|
|
8529
8681
|
if (call.ringing) {
|
|
@@ -11257,6 +11409,12 @@ class Call {
|
|
|
11257
11409
|
*/
|
|
11258
11410
|
this.leaveCallHooks = new Set();
|
|
11259
11411
|
this.streamClientEventHandlers = new Map();
|
|
11412
|
+
/**
|
|
11413
|
+
* A list of capabilities that the client supports and are enabled.
|
|
11414
|
+
*/
|
|
11415
|
+
this.clientCapabilities = new Set([
|
|
11416
|
+
ClientCapability.SUBSCRIBER_VIDEO_PAUSE,
|
|
11417
|
+
]);
|
|
11260
11418
|
/**
|
|
11261
11419
|
* Sets up the call instance.
|
|
11262
11420
|
*
|
|
@@ -11665,14 +11823,27 @@ class Call {
|
|
|
11665
11823
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
11666
11824
|
}
|
|
11667
11825
|
this.state.setCallingState(exports.CallingState.JOINING);
|
|
11826
|
+
// we will count the number of join failures per SFU.
|
|
11827
|
+
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
11828
|
+
// field to force the coordinator to provide us another SFU
|
|
11829
|
+
const sfuJoinFailures = new Map();
|
|
11830
|
+
const joinData = data;
|
|
11668
11831
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
11669
11832
|
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
11670
11833
|
try {
|
|
11671
11834
|
this.logger('trace', `Joining call (${attempt})`, this.cid);
|
|
11672
|
-
|
|
11835
|
+
await this.doJoin(data);
|
|
11836
|
+
delete joinData.migrating_from;
|
|
11837
|
+
break;
|
|
11673
11838
|
}
|
|
11674
11839
|
catch (err) {
|
|
11675
11840
|
this.logger('warn', `Failed to join call (${attempt})`, this.cid);
|
|
11841
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
11842
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
11843
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
11844
|
+
if (failures >= 2) {
|
|
11845
|
+
joinData.migrating_from = sfuId;
|
|
11846
|
+
}
|
|
11676
11847
|
if (attempt === maxJoinRetries - 1) {
|
|
11677
11848
|
// restore the previous call state if the join-flow fails
|
|
11678
11849
|
this.state.setCallingState(callingState);
|
|
@@ -11722,7 +11893,8 @@ class Call {
|
|
|
11722
11893
|
const isWsHealthy = !!previousSfuClient?.isHealthy;
|
|
11723
11894
|
const sfuClient = performingRejoin || performingMigration || !isWsHealthy
|
|
11724
11895
|
? new StreamSfuClient({
|
|
11725
|
-
|
|
11896
|
+
tag: String(this.sfuClientTag++),
|
|
11897
|
+
cid: this.cid,
|
|
11726
11898
|
dispatcher: this.dispatcher,
|
|
11727
11899
|
credentials: this.credentials,
|
|
11728
11900
|
streamClient: this.streamClient,
|
|
@@ -11764,6 +11936,7 @@ class Call {
|
|
|
11764
11936
|
reconnectDetails,
|
|
11765
11937
|
preferredPublishOptions,
|
|
11766
11938
|
preferredSubscribeOptions,
|
|
11939
|
+
capabilities: Array.from(this.clientCapabilities),
|
|
11767
11940
|
});
|
|
11768
11941
|
this.currentPublishOptions = publishOptions;
|
|
11769
11942
|
this.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
|
|
@@ -11936,7 +12109,7 @@ class Call {
|
|
|
11936
12109
|
dispatcher: this.dispatcher,
|
|
11937
12110
|
state: this.state,
|
|
11938
12111
|
connectionConfig,
|
|
11939
|
-
|
|
12112
|
+
tag: sfuClient.tag,
|
|
11940
12113
|
enableTracing,
|
|
11941
12114
|
onReconnectionNeeded: (kind, reason) => {
|
|
11942
12115
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -11958,7 +12131,7 @@ class Call {
|
|
|
11958
12131
|
state: this.state,
|
|
11959
12132
|
connectionConfig,
|
|
11960
12133
|
publishOptions,
|
|
11961
|
-
|
|
12134
|
+
tag: sfuClient.tag,
|
|
11962
12135
|
enableTracing,
|
|
11963
12136
|
onReconnectionNeeded: (kind, reason) => {
|
|
11964
12137
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -13122,6 +13295,22 @@ class Call {
|
|
|
13122
13295
|
this.setDisconnectionTimeout = (timeoutSeconds) => {
|
|
13123
13296
|
this.disconnectionTimeoutSeconds = timeoutSeconds;
|
|
13124
13297
|
};
|
|
13298
|
+
/**
|
|
13299
|
+
* Enables the provided client capabilities.
|
|
13300
|
+
*/
|
|
13301
|
+
this.enableClientCapabilities = (...capabilities) => {
|
|
13302
|
+
for (const capability of capabilities) {
|
|
13303
|
+
this.clientCapabilities.add(capability);
|
|
13304
|
+
}
|
|
13305
|
+
};
|
|
13306
|
+
/**
|
|
13307
|
+
* Disables the provided client capabilities.
|
|
13308
|
+
*/
|
|
13309
|
+
this.disableClientCapabilities = (...capabilities) => {
|
|
13310
|
+
for (const capability of capabilities) {
|
|
13311
|
+
this.clientCapabilities.delete(capability);
|
|
13312
|
+
}
|
|
13313
|
+
};
|
|
13125
13314
|
this.type = type;
|
|
13126
13315
|
this.id = id;
|
|
13127
13316
|
this.cid = `${type}:${id}`;
|
|
@@ -13287,7 +13476,6 @@ class StableWSConnection {
|
|
|
13287
13476
|
this.onmessage = (wsID, event) => {
|
|
13288
13477
|
if (this.wsID !== wsID)
|
|
13289
13478
|
return;
|
|
13290
|
-
this._log('onmessage() - onmessage callback', { event, wsID });
|
|
13291
13479
|
const data = typeof event.data === 'string'
|
|
13292
13480
|
? JSON.parse(event.data)
|
|
13293
13481
|
: null;
|
|
@@ -14301,7 +14489,7 @@ class StreamClient {
|
|
|
14301
14489
|
this.getUserAgent = () => {
|
|
14302
14490
|
if (!this.cachedUserAgent) {
|
|
14303
14491
|
const { clientAppIdentifier = {} } = this.options;
|
|
14304
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
14492
|
+
const { sdkName = 'js', sdkVersion = "1.27.0", ...extras } = clientAppIdentifier;
|
|
14305
14493
|
this.cachedUserAgent = [
|
|
14306
14494
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14307
14495
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -14931,6 +15119,7 @@ exports.getVideoDevices = getVideoDevices;
|
|
|
14931
15119
|
exports.getVideoStream = getVideoStream;
|
|
14932
15120
|
exports.getWebRTCInfo = getWebRTCInfo;
|
|
14933
15121
|
exports.hasAudio = hasAudio;
|
|
15122
|
+
exports.hasPausedTrack = hasPausedTrack;
|
|
14934
15123
|
exports.hasScreenShare = hasScreenShare;
|
|
14935
15124
|
exports.hasScreenShareAudio = hasScreenShareAudio;
|
|
14936
15125
|
exports.hasVideo = hasVideo;
|