@stream-io/video-client 1.26.1 → 1.27.1
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 +18 -0
- package/dist/index.browser.es.js +276 -73
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +275 -71
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +276 -73
- 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/devices/devices.d.ts +5 -5
- 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/lazy.d.ts +1 -1
- 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/stats/rtc/Tracer.d.ts +4 -1
- package/dist/src/stats/rtc/types.d.ts +1 -0
- package/dist/src/store/CallState.d.ts +2 -1
- package/dist/src/timers/index.d.ts +1 -1
- package/dist/src/types.d.ts +10 -1
- package/package.json +2 -2
- package/src/Call.ts +55 -9
- package/src/StreamSfuClient.ts +33 -14
- package/src/coordinator/connection/connection.ts +0 -2
- package/src/devices/CameraManager.ts +1 -1
- package/src/devices/InputMediaDeviceManager.ts +5 -3
- package/src/devices/MicrophoneManager.ts +2 -1
- package/src/devices/SpeakerManager.ts +1 -1
- package/src/devices/devices.ts +29 -11
- 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/lazy.ts +3 -3
- 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/__tests__/videoLayers.test.ts +4 -6
- package/src/rtc/signal.ts +3 -3
- package/src/rtc/videoLayers.ts +12 -6
- package/src/stats/rtc/Tracer.ts +19 -1
- package/src/stats/rtc/types.ts +1 -0
- 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.1";
|
|
5816
5920
|
const [major, minor, patch] = version.split('.');
|
|
5817
5921
|
let sdkInfo = {
|
|
5818
5922
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6396,6 +6500,15 @@ class Tracer {
|
|
|
6396
6500
|
return;
|
|
6397
6501
|
this.buffer.push([tag, this.id, data, Date.now()]);
|
|
6398
6502
|
};
|
|
6503
|
+
this.traceOnce = (key, tag, data) => {
|
|
6504
|
+
if (this.keys?.has(key))
|
|
6505
|
+
return;
|
|
6506
|
+
this.trace(tag, data);
|
|
6507
|
+
(this.keys ?? (this.keys = new Map())).set(key, true);
|
|
6508
|
+
};
|
|
6509
|
+
this.resetTrace = (key) => {
|
|
6510
|
+
this.keys?.delete(key);
|
|
6511
|
+
};
|
|
6399
6512
|
this.take = () => {
|
|
6400
6513
|
const snapshot = this.buffer;
|
|
6401
6514
|
this.buffer = [];
|
|
@@ -6408,6 +6521,7 @@ class Tracer {
|
|
|
6408
6521
|
};
|
|
6409
6522
|
this.dispose = () => {
|
|
6410
6523
|
this.buffer = [];
|
|
6524
|
+
this.keys?.clear();
|
|
6411
6525
|
};
|
|
6412
6526
|
this.id = id;
|
|
6413
6527
|
}
|
|
@@ -6421,7 +6535,7 @@ class BasePeerConnection {
|
|
|
6421
6535
|
/**
|
|
6422
6536
|
* Constructs a new `BasePeerConnection` instance.
|
|
6423
6537
|
*/
|
|
6424
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded,
|
|
6538
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, tag, enableTracing, iceRestartDelay = 2500, }) {
|
|
6425
6539
|
this.isIceRestarting = false;
|
|
6426
6540
|
this.isDisposed = false;
|
|
6427
6541
|
this.trackIdToTrackType = new Map();
|
|
@@ -6645,13 +6759,12 @@ class BasePeerConnection {
|
|
|
6645
6759
|
this.onReconnectionNeeded = onReconnectionNeeded;
|
|
6646
6760
|
this.logger = getLogger([
|
|
6647
6761
|
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
6648
|
-
|
|
6762
|
+
tag,
|
|
6649
6763
|
]);
|
|
6650
6764
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
6651
6765
|
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
6652
6766
|
if (enableTracing) {
|
|
6653
|
-
|
|
6654
|
-
this.tracer = new Tracer(tag);
|
|
6767
|
+
this.tracer = new Tracer(`${tag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`);
|
|
6655
6768
|
this.tracer.trace('create', {
|
|
6656
6769
|
url: sfuClient.edgeName,
|
|
6657
6770
|
...connectionConfig,
|
|
@@ -6872,9 +6985,8 @@ const computeVideoLayers = (videoTrack, publishOption) => {
|
|
|
6872
6985
|
if (isAudioTrackType(publishOption.trackType))
|
|
6873
6986
|
return;
|
|
6874
6987
|
const optimalVideoLayers = [];
|
|
6875
|
-
const
|
|
6876
|
-
const { width =
|
|
6877
|
-
const { bitrate, codec, fps, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
|
|
6988
|
+
const { bitrate, codec, fps = 30, maxSpatialLayers = 3, maxTemporalLayers = 3, videoDimension = { width: 1280, height: 720 }, useSingleLayer, } = publishOption;
|
|
6989
|
+
const { width = videoDimension.width, height = videoDimension.height } = videoTrack.getSettings();
|
|
6878
6990
|
const maxBitrate = getComputedMaxBitrate(videoDimension, width, height, bitrate);
|
|
6879
6991
|
let downscaleFactor = 1;
|
|
6880
6992
|
let bitrateFactor = 1;
|
|
@@ -6906,7 +7018,7 @@ const computeVideoLayers = (videoTrack, publishOption) => {
|
|
|
6906
7018
|
}
|
|
6907
7019
|
// for simplicity, we start with all layers enabled, then this function
|
|
6908
7020
|
// will clear/reassign the layers that are not needed
|
|
6909
|
-
return withSimulcastConstraints(
|
|
7021
|
+
return withSimulcastConstraints(width, height, optimalVideoLayers, useSingleLayer);
|
|
6910
7022
|
};
|
|
6911
7023
|
/**
|
|
6912
7024
|
* Computes the maximum bitrate for a given resolution.
|
|
@@ -6940,9 +7052,9 @@ const getComputedMaxBitrate = (targetResolution, currentWidth, currentHeight, bi
|
|
|
6940
7052
|
*
|
|
6941
7053
|
* https://chromium.googlesource.com/external/webrtc/+/refs/heads/main/media/engine/simulcast.cc#90
|
|
6942
7054
|
*/
|
|
6943
|
-
const withSimulcastConstraints = (
|
|
7055
|
+
const withSimulcastConstraints = (width, height, optimalVideoLayers, useSingleLayer) => {
|
|
6944
7056
|
let layers;
|
|
6945
|
-
const size = Math.max(
|
|
7057
|
+
const size = Math.max(width, height);
|
|
6946
7058
|
if (size <= 320) {
|
|
6947
7059
|
// provide only one layer 320x240 (f), the one with the highest quality
|
|
6948
7060
|
layers = optimalVideoLayers.filter((layer) => layer.rid === 'f');
|
|
@@ -7523,8 +7635,8 @@ class Subscriber extends BasePeerConnection {
|
|
|
7523
7635
|
}
|
|
7524
7636
|
|
|
7525
7637
|
const createWebSocketSignalChannel = (opts) => {
|
|
7526
|
-
const { endpoint, onMessage,
|
|
7527
|
-
const logger = getLogger(['SfuClientWS',
|
|
7638
|
+
const { endpoint, onMessage, tag } = opts;
|
|
7639
|
+
const logger = getLogger(['SfuClientWS', tag]);
|
|
7528
7640
|
logger('debug', 'Creating signaling WS channel:', endpoint);
|
|
7529
7641
|
const ws = new WebSocket(endpoint);
|
|
7530
7642
|
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
@@ -7626,9 +7738,9 @@ const uninitialized = Symbol('uninitialized');
|
|
|
7626
7738
|
*/
|
|
7627
7739
|
function lazy(factory) {
|
|
7628
7740
|
let value = uninitialized;
|
|
7629
|
-
return () => {
|
|
7741
|
+
return (...args) => {
|
|
7630
7742
|
if (value === uninitialized) {
|
|
7631
|
-
value = factory();
|
|
7743
|
+
value = factory(...args);
|
|
7632
7744
|
}
|
|
7633
7745
|
return value;
|
|
7634
7746
|
};
|
|
@@ -7774,7 +7886,7 @@ class StreamSfuClient {
|
|
|
7774
7886
|
/**
|
|
7775
7887
|
* Constructs a new SFU client.
|
|
7776
7888
|
*/
|
|
7777
|
-
constructor({ dispatcher, credentials, sessionId,
|
|
7889
|
+
constructor({ dispatcher, credentials, sessionId, cid, tag, joinResponseTimeout = 5000, onSignalClose, streamClient, enableTracing, }) {
|
|
7778
7890
|
/**
|
|
7779
7891
|
* A buffer for ICE Candidates that are received before
|
|
7780
7892
|
* the Publisher and Subscriber Peer Connections are ready to handle them.
|
|
@@ -7804,7 +7916,7 @@ class StreamSfuClient {
|
|
|
7804
7916
|
* A controller to abort the current requests.
|
|
7805
7917
|
*/
|
|
7806
7918
|
this.abortController = new AbortController();
|
|
7807
|
-
this.createWebSocket = () => {
|
|
7919
|
+
this.createWebSocket = (params) => {
|
|
7808
7920
|
const eventsToTrace = {
|
|
7809
7921
|
callEnded: true,
|
|
7810
7922
|
changePublishQuality: true,
|
|
@@ -7812,10 +7924,11 @@ class StreamSfuClient {
|
|
|
7812
7924
|
connectionQualityChanged: true,
|
|
7813
7925
|
error: true,
|
|
7814
7926
|
goAway: true,
|
|
7927
|
+
inboundStateNotification: true,
|
|
7815
7928
|
};
|
|
7816
7929
|
this.signalWs = createWebSocketSignalChannel({
|
|
7817
|
-
|
|
7818
|
-
endpoint: `${this.credentials.server.ws_endpoint}
|
|
7930
|
+
tag: this.tag,
|
|
7931
|
+
endpoint: `${this.credentials.server.ws_endpoint}?${new URLSearchParams(params).toString()}`,
|
|
7819
7932
|
onMessage: (message) => {
|
|
7820
7933
|
this.lastMessageTimestamp = new Date();
|
|
7821
7934
|
this.scheduleConnectionCheck();
|
|
@@ -7823,7 +7936,7 @@ class StreamSfuClient {
|
|
|
7823
7936
|
if (eventsToTrace[eventKind]) {
|
|
7824
7937
|
this.tracer?.trace(eventKind, message);
|
|
7825
7938
|
}
|
|
7826
|
-
this.dispatcher.dispatch(message, this.
|
|
7939
|
+
this.dispatcher.dispatch(message, this.tag);
|
|
7827
7940
|
},
|
|
7828
7941
|
});
|
|
7829
7942
|
this.signalReady = makeSafePromise(Promise.race([
|
|
@@ -7938,7 +8051,7 @@ class StreamSfuClient {
|
|
|
7938
8051
|
});
|
|
7939
8052
|
this.migrateAwayTimeout = setTimeout(() => {
|
|
7940
8053
|
unsubscribe();
|
|
7941
|
-
task.reject(new Error(`Migration (${this.
|
|
8054
|
+
task.reject(new Error(`Migration (${this.tag}) failed to complete in ${timeout}ms`));
|
|
7942
8055
|
}, timeout);
|
|
7943
8056
|
return task.promise;
|
|
7944
8057
|
};
|
|
@@ -8038,10 +8151,10 @@ class StreamSfuClient {
|
|
|
8038
8151
|
const { server, token } = credentials;
|
|
8039
8152
|
this.edgeName = server.edge_name;
|
|
8040
8153
|
this.joinResponseTimeout = joinResponseTimeout;
|
|
8041
|
-
this.
|
|
8042
|
-
this.logger = getLogger(['SfuClient',
|
|
8154
|
+
this.tag = tag;
|
|
8155
|
+
this.logger = getLogger(['SfuClient', tag]);
|
|
8043
8156
|
this.tracer = enableTracing
|
|
8044
|
-
? new Tracer(`${
|
|
8157
|
+
? new Tracer(`${tag}-${this.edgeName}`)
|
|
8045
8158
|
: undefined;
|
|
8046
8159
|
this.rpc = createSignalClient({
|
|
8047
8160
|
baseUrl: server.url,
|
|
@@ -8070,7 +8183,13 @@ class StreamSfuClient {
|
|
|
8070
8183
|
this.networkAvailableTask?.resolve();
|
|
8071
8184
|
}
|
|
8072
8185
|
});
|
|
8073
|
-
this.createWebSocket(
|
|
8186
|
+
this.createWebSocket({
|
|
8187
|
+
attempt: tag,
|
|
8188
|
+
user_id: streamClient.user?.id || '',
|
|
8189
|
+
api_key: streamClient.key,
|
|
8190
|
+
user_session_id: this.sessionId,
|
|
8191
|
+
cid,
|
|
8192
|
+
});
|
|
8074
8193
|
}
|
|
8075
8194
|
get isHealthy() {
|
|
8076
8195
|
return (this.signalWs.readyState === WebSocket.OPEN &&
|
|
@@ -8230,6 +8349,36 @@ const watchCallGrantsUpdated = (state) => {
|
|
|
8230
8349
|
};
|
|
8231
8350
|
};
|
|
8232
8351
|
|
|
8352
|
+
/**
|
|
8353
|
+
* Adds unique values to an array.
|
|
8354
|
+
*
|
|
8355
|
+
* @param arr the array to add to.
|
|
8356
|
+
* @param values the values to add.
|
|
8357
|
+
*/
|
|
8358
|
+
const pushToIfMissing = (arr, ...values) => {
|
|
8359
|
+
for (const v of values) {
|
|
8360
|
+
if (!arr.includes(v)) {
|
|
8361
|
+
arr.push(v);
|
|
8362
|
+
}
|
|
8363
|
+
}
|
|
8364
|
+
return arr;
|
|
8365
|
+
};
|
|
8366
|
+
/**
|
|
8367
|
+
* Removes values from an array if they are present.
|
|
8368
|
+
*
|
|
8369
|
+
* @param arr the array to remove from.
|
|
8370
|
+
* @param values the values to remove.
|
|
8371
|
+
*/
|
|
8372
|
+
const removeFromIfPresent = (arr, ...values) => {
|
|
8373
|
+
for (const v of values) {
|
|
8374
|
+
const index = arr.indexOf(v);
|
|
8375
|
+
if (index !== -1) {
|
|
8376
|
+
arr.splice(index, 1);
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
return arr;
|
|
8380
|
+
};
|
|
8381
|
+
|
|
8233
8382
|
const watchConnectionQualityChanged = (dispatcher, state) => {
|
|
8234
8383
|
return dispatcher.on('connectionQualityChanged', (e) => {
|
|
8235
8384
|
const { connectionQualityUpdates } = e;
|
|
@@ -8296,6 +8445,29 @@ const watchPinsUpdated = (state) => {
|
|
|
8296
8445
|
state.setServerSidePins(pins);
|
|
8297
8446
|
};
|
|
8298
8447
|
};
|
|
8448
|
+
/**
|
|
8449
|
+
* Watches for inbound state notifications and updates the paused tracks
|
|
8450
|
+
*
|
|
8451
|
+
* @param state the call state to update.
|
|
8452
|
+
*/
|
|
8453
|
+
const watchInboundStateNotification = (state) => {
|
|
8454
|
+
return function onInboundStateNotification(e) {
|
|
8455
|
+
const { inboundVideoStates } = e;
|
|
8456
|
+
const current = state.getParticipantLookupBySessionId();
|
|
8457
|
+
const patches = {};
|
|
8458
|
+
for (const { sessionId, trackType, paused } of inboundVideoStates) {
|
|
8459
|
+
const pausedTracks = [...(current[sessionId]?.pausedTracks ?? [])];
|
|
8460
|
+
if (paused) {
|
|
8461
|
+
pushToIfMissing(pausedTracks, trackType);
|
|
8462
|
+
}
|
|
8463
|
+
else {
|
|
8464
|
+
removeFromIfPresent(pausedTracks, trackType);
|
|
8465
|
+
}
|
|
8466
|
+
patches[sessionId] = { pausedTracks };
|
|
8467
|
+
}
|
|
8468
|
+
state.updateParticipants(patches);
|
|
8469
|
+
};
|
|
8470
|
+
};
|
|
8299
8471
|
|
|
8300
8472
|
/**
|
|
8301
8473
|
* An event handler that handles soft mutes.
|
|
@@ -8332,21 +8504,6 @@ const handleRemoteSoftMute = (call) => {
|
|
|
8332
8504
|
});
|
|
8333
8505
|
};
|
|
8334
8506
|
|
|
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
8507
|
/**
|
|
8351
8508
|
* An event responder which handles the `participantJoined` event.
|
|
8352
8509
|
*/
|
|
@@ -8428,11 +8585,14 @@ const watchTrackUnpublished = (state) => {
|
|
|
8428
8585
|
if (e.participant) {
|
|
8429
8586
|
const orphanedTracks = reconcileOrphanedTracks(state, e.participant);
|
|
8430
8587
|
const participant = Object.assign(e.participant, orphanedTracks);
|
|
8431
|
-
state.updateOrAddParticipant(sessionId, participant)
|
|
8588
|
+
state.updateOrAddParticipant(sessionId, participant, (p) => ({
|
|
8589
|
+
pausedTracks: p.pausedTracks?.filter((t) => t !== type),
|
|
8590
|
+
}));
|
|
8432
8591
|
}
|
|
8433
8592
|
else {
|
|
8434
8593
|
state.updateParticipant(sessionId, (p) => ({
|
|
8435
8594
|
publishedTracks: p.publishedTracks.filter((t) => t !== type),
|
|
8595
|
+
pausedTracks: p.pausedTracks?.filter((t) => t !== type),
|
|
8436
8596
|
}));
|
|
8437
8597
|
}
|
|
8438
8598
|
};
|
|
@@ -8524,6 +8684,7 @@ const registerEventHandlers = (call, dispatcher) => {
|
|
|
8524
8684
|
watchDominantSpeakerChanged(dispatcher, state),
|
|
8525
8685
|
call.on('callGrantsUpdated', watchCallGrantsUpdated(state)),
|
|
8526
8686
|
call.on('pinsUpdated', watchPinsUpdated(state)),
|
|
8687
|
+
call.on('inboundStateNotification', watchInboundStateNotification(state)),
|
|
8527
8688
|
handleRemoteSoftMute(call),
|
|
8528
8689
|
];
|
|
8529
8690
|
if (call.ringing) {
|
|
@@ -9393,8 +9554,9 @@ function canQueryPermissions() {
|
|
|
9393
9554
|
*
|
|
9394
9555
|
* @param permission a BrowserPermission instance.
|
|
9395
9556
|
* @param kind the kind of devices to enumerate.
|
|
9557
|
+
* @param tracer the tracer to use for tracing the device enumeration.
|
|
9396
9558
|
*/
|
|
9397
|
-
const getDevices = (permission, kind) => {
|
|
9559
|
+
const getDevices = (permission, kind, tracer) => {
|
|
9398
9560
|
return rxjs.from((async () => {
|
|
9399
9561
|
let devices = await navigator.mediaDevices.enumerateDevices();
|
|
9400
9562
|
// for privacy reasons, most browsers don't give you device labels
|
|
@@ -9403,6 +9565,7 @@ const getDevices = (permission, kind) => {
|
|
|
9403
9565
|
if (shouldPromptForBrowserPermission && (await permission.prompt())) {
|
|
9404
9566
|
devices = await navigator.mediaDevices.enumerateDevices();
|
|
9405
9567
|
}
|
|
9568
|
+
tracer?.traceOnce('device-enumeration', 'navigator.mediaDevices.enumerateDevices', devices);
|
|
9406
9569
|
return devices.filter((device) => device.kind === kind &&
|
|
9407
9570
|
device.label !== '' &&
|
|
9408
9571
|
device.deviceId !== 'default');
|
|
@@ -9453,12 +9616,12 @@ const getVideoBrowserPermission = lazy(() => new BrowserPermission({
|
|
|
9453
9616
|
constraints: videoDeviceConstraints,
|
|
9454
9617
|
queryName: 'camera',
|
|
9455
9618
|
}));
|
|
9456
|
-
const getDeviceChangeObserver = lazy(() => {
|
|
9619
|
+
const getDeviceChangeObserver = lazy((tracer) => {
|
|
9457
9620
|
// 'addEventListener' is not available in React Native, returning
|
|
9458
9621
|
// an observable that will never fire
|
|
9459
9622
|
if (!navigator.mediaDevices.addEventListener)
|
|
9460
9623
|
return rxjs.from([]);
|
|
9461
|
-
return rxjs.fromEvent(navigator.mediaDevices, 'devicechange').pipe(rxjs.map(() => undefined), rxjs.debounceTime(500));
|
|
9624
|
+
return rxjs.fromEvent(navigator.mediaDevices, 'devicechange').pipe(rxjs.tap(() => tracer?.resetTrace('device-enumeration')), rxjs.map(() => undefined), rxjs.debounceTime(500));
|
|
9462
9625
|
});
|
|
9463
9626
|
/**
|
|
9464
9627
|
* Prompts the user for a permission to use audio devices (if not already granted
|
|
@@ -9466,8 +9629,8 @@ const getDeviceChangeObserver = lazy(() => {
|
|
|
9466
9629
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9467
9630
|
* the observable errors.
|
|
9468
9631
|
*/
|
|
9469
|
-
const getAudioDevices = lazy(() => {
|
|
9470
|
-
return rxjs.merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput')), rxjs.shareReplay(1));
|
|
9632
|
+
const getAudioDevices = lazy((tracer) => {
|
|
9633
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audioinput', tracer)), rxjs.shareReplay(1));
|
|
9471
9634
|
});
|
|
9472
9635
|
/**
|
|
9473
9636
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -9475,8 +9638,8 @@ const getAudioDevices = lazy(() => {
|
|
|
9475
9638
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9476
9639
|
* the observable errors.
|
|
9477
9640
|
*/
|
|
9478
|
-
const getVideoDevices = lazy(() => {
|
|
9479
|
-
return rxjs.merge(getDeviceChangeObserver(), getVideoBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput')), rxjs.shareReplay(1));
|
|
9641
|
+
const getVideoDevices = lazy((tracer) => {
|
|
9642
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getVideoBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput', tracer)), rxjs.shareReplay(1));
|
|
9480
9643
|
});
|
|
9481
9644
|
/**
|
|
9482
9645
|
* Prompts the user for a permission to use video devices (if not already granted
|
|
@@ -9484,8 +9647,8 @@ const getVideoDevices = lazy(() => {
|
|
|
9484
9647
|
* if devices are added/removed the list is updated, and if the permission is revoked,
|
|
9485
9648
|
* the observable errors.
|
|
9486
9649
|
*/
|
|
9487
|
-
const getAudioOutputDevices = lazy(() => {
|
|
9488
|
-
return rxjs.merge(getDeviceChangeObserver(), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')), rxjs.shareReplay(1));
|
|
9650
|
+
const getAudioOutputDevices = lazy((tracer) => {
|
|
9651
|
+
return rxjs.merge(getDeviceChangeObserver(tracer), getAudioBrowserPermission().asObservable()).pipe(rxjs.startWith(undefined), rxjs.concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput', tracer)), rxjs.shareReplay(1));
|
|
9489
9652
|
});
|
|
9490
9653
|
let getUserMediaExecId = 0;
|
|
9491
9654
|
const getStream = async (constraints, tracer) => {
|
|
@@ -9857,6 +10020,7 @@ class InputMediaDeviceManager {
|
|
|
9857
10020
|
entry.stop?.();
|
|
9858
10021
|
this.filters = this.filters.filter((f) => f !== entry);
|
|
9859
10022
|
await this.applySettingsToStream();
|
|
10023
|
+
this.call.tracer.trace(`unregisterFilter.${TrackType[this.trackType]}`, null);
|
|
9860
10024
|
}),
|
|
9861
10025
|
};
|
|
9862
10026
|
}
|
|
@@ -9876,7 +10040,7 @@ class InputMediaDeviceManager {
|
|
|
9876
10040
|
*/
|
|
9877
10041
|
async select(deviceId) {
|
|
9878
10042
|
if (isReactNative()) {
|
|
9879
|
-
throw new Error('This method is not supported in React Native.
|
|
10043
|
+
throw new Error('This method is not supported in React Native.');
|
|
9880
10044
|
}
|
|
9881
10045
|
const prevDeviceId = this.state.selectedDevice;
|
|
9882
10046
|
if (deviceId === prevDeviceId) {
|
|
@@ -10443,7 +10607,7 @@ class CameraManager extends InputMediaDeviceManager {
|
|
|
10443
10607
|
}
|
|
10444
10608
|
}
|
|
10445
10609
|
getDevices() {
|
|
10446
|
-
return getVideoDevices();
|
|
10610
|
+
return getVideoDevices(this.call.tracer);
|
|
10447
10611
|
}
|
|
10448
10612
|
getStream(constraints) {
|
|
10449
10613
|
constraints.width = this.targetResolution.width;
|
|
@@ -10850,6 +11014,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10850
11014
|
.catch((err) => {
|
|
10851
11015
|
this.logger('warn', 'Failed to unregister noise cancellation', err);
|
|
10852
11016
|
});
|
|
11017
|
+
this.call.tracer.trace('noiseCancellation.disabled', true);
|
|
10853
11018
|
await this.call.notifyNoiseCancellationStopped();
|
|
10854
11019
|
}
|
|
10855
11020
|
/**
|
|
@@ -10894,7 +11059,7 @@ class MicrophoneManager extends InputMediaDeviceManager {
|
|
|
10894
11059
|
}
|
|
10895
11060
|
}
|
|
10896
11061
|
getDevices() {
|
|
10897
|
-
return getAudioDevices();
|
|
11062
|
+
return getAudioDevices(this.call.tracer);
|
|
10898
11063
|
}
|
|
10899
11064
|
getStream(constraints) {
|
|
10900
11065
|
return getAudioStream(constraints, this.call.tracer);
|
|
@@ -11159,7 +11324,7 @@ class SpeakerManager {
|
|
|
11159
11324
|
if (isReactNative()) {
|
|
11160
11325
|
throw new Error('This feature is not supported in React Native. Please visit https://getstream.io/video/docs/reactnative/core/camera-and-microphone/#speaker-management for more details');
|
|
11161
11326
|
}
|
|
11162
|
-
return getAudioOutputDevices();
|
|
11327
|
+
return getAudioOutputDevices(this.call.tracer);
|
|
11163
11328
|
}
|
|
11164
11329
|
/**
|
|
11165
11330
|
* Select a device.
|
|
@@ -11257,6 +11422,12 @@ class Call {
|
|
|
11257
11422
|
*/
|
|
11258
11423
|
this.leaveCallHooks = new Set();
|
|
11259
11424
|
this.streamClientEventHandlers = new Map();
|
|
11425
|
+
/**
|
|
11426
|
+
* A list of capabilities that the client supports and are enabled.
|
|
11427
|
+
*/
|
|
11428
|
+
this.clientCapabilities = new Set([
|
|
11429
|
+
ClientCapability.SUBSCRIBER_VIDEO_PAUSE,
|
|
11430
|
+
]);
|
|
11260
11431
|
/**
|
|
11261
11432
|
* Sets up the call instance.
|
|
11262
11433
|
*
|
|
@@ -11665,14 +11836,27 @@ class Call {
|
|
|
11665
11836
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
11666
11837
|
}
|
|
11667
11838
|
this.state.setCallingState(exports.CallingState.JOINING);
|
|
11839
|
+
// we will count the number of join failures per SFU.
|
|
11840
|
+
// once the number of failures reaches 2, we will piggyback on the `migrating_from`
|
|
11841
|
+
// field to force the coordinator to provide us another SFU
|
|
11842
|
+
const sfuJoinFailures = new Map();
|
|
11843
|
+
const joinData = data;
|
|
11668
11844
|
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
11669
11845
|
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
11670
11846
|
try {
|
|
11671
11847
|
this.logger('trace', `Joining call (${attempt})`, this.cid);
|
|
11672
|
-
|
|
11848
|
+
await this.doJoin(data);
|
|
11849
|
+
delete joinData.migrating_from;
|
|
11850
|
+
break;
|
|
11673
11851
|
}
|
|
11674
11852
|
catch (err) {
|
|
11675
11853
|
this.logger('warn', `Failed to join call (${attempt})`, this.cid);
|
|
11854
|
+
const sfuId = this.credentials?.server.edge_name || '';
|
|
11855
|
+
const failures = (sfuJoinFailures.get(sfuId) || 0) + 1;
|
|
11856
|
+
sfuJoinFailures.set(sfuId, failures);
|
|
11857
|
+
if (failures >= 2) {
|
|
11858
|
+
joinData.migrating_from = sfuId;
|
|
11859
|
+
}
|
|
11676
11860
|
if (attempt === maxJoinRetries - 1) {
|
|
11677
11861
|
// restore the previous call state if the join-flow fails
|
|
11678
11862
|
this.state.setCallingState(callingState);
|
|
@@ -11722,7 +11906,8 @@ class Call {
|
|
|
11722
11906
|
const isWsHealthy = !!previousSfuClient?.isHealthy;
|
|
11723
11907
|
const sfuClient = performingRejoin || performingMigration || !isWsHealthy
|
|
11724
11908
|
? new StreamSfuClient({
|
|
11725
|
-
|
|
11909
|
+
tag: String(this.sfuClientTag++),
|
|
11910
|
+
cid: this.cid,
|
|
11726
11911
|
dispatcher: this.dispatcher,
|
|
11727
11912
|
credentials: this.credentials,
|
|
11728
11913
|
streamClient: this.streamClient,
|
|
@@ -11764,6 +11949,7 @@ class Call {
|
|
|
11764
11949
|
reconnectDetails,
|
|
11765
11950
|
preferredPublishOptions,
|
|
11766
11951
|
preferredSubscribeOptions,
|
|
11952
|
+
capabilities: Array.from(this.clientCapabilities),
|
|
11767
11953
|
});
|
|
11768
11954
|
this.currentPublishOptions = publishOptions;
|
|
11769
11955
|
this.fastReconnectDeadlineSeconds = fastReconnectDeadlineSeconds;
|
|
@@ -11936,7 +12122,7 @@ class Call {
|
|
|
11936
12122
|
dispatcher: this.dispatcher,
|
|
11937
12123
|
state: this.state,
|
|
11938
12124
|
connectionConfig,
|
|
11939
|
-
|
|
12125
|
+
tag: sfuClient.tag,
|
|
11940
12126
|
enableTracing,
|
|
11941
12127
|
onReconnectionNeeded: (kind, reason) => {
|
|
11942
12128
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -11958,7 +12144,7 @@ class Call {
|
|
|
11958
12144
|
state: this.state,
|
|
11959
12145
|
connectionConfig,
|
|
11960
12146
|
publishOptions,
|
|
11961
|
-
|
|
12147
|
+
tag: sfuClient.tag,
|
|
11962
12148
|
enableTracing,
|
|
11963
12149
|
onReconnectionNeeded: (kind, reason) => {
|
|
11964
12150
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -12476,9 +12662,12 @@ class Call {
|
|
|
12476
12662
|
* @internal
|
|
12477
12663
|
*/
|
|
12478
12664
|
this.notifyTrackMuteState = async (muted, ...trackTypes) => {
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12665
|
+
const key = `muteState.${this.cid}.${trackTypes.join('-')}`;
|
|
12666
|
+
await withoutConcurrency(key, async () => {
|
|
12667
|
+
if (!this.sfuClient)
|
|
12668
|
+
return;
|
|
12669
|
+
await this.sfuClient.updateMuteStates(trackTypes.map((trackType) => ({ trackType, muted })));
|
|
12670
|
+
});
|
|
12482
12671
|
};
|
|
12483
12672
|
/**
|
|
12484
12673
|
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
|
|
@@ -13122,6 +13311,22 @@ class Call {
|
|
|
13122
13311
|
this.setDisconnectionTimeout = (timeoutSeconds) => {
|
|
13123
13312
|
this.disconnectionTimeoutSeconds = timeoutSeconds;
|
|
13124
13313
|
};
|
|
13314
|
+
/**
|
|
13315
|
+
* Enables the provided client capabilities.
|
|
13316
|
+
*/
|
|
13317
|
+
this.enableClientCapabilities = (...capabilities) => {
|
|
13318
|
+
for (const capability of capabilities) {
|
|
13319
|
+
this.clientCapabilities.add(capability);
|
|
13320
|
+
}
|
|
13321
|
+
};
|
|
13322
|
+
/**
|
|
13323
|
+
* Disables the provided client capabilities.
|
|
13324
|
+
*/
|
|
13325
|
+
this.disableClientCapabilities = (...capabilities) => {
|
|
13326
|
+
for (const capability of capabilities) {
|
|
13327
|
+
this.clientCapabilities.delete(capability);
|
|
13328
|
+
}
|
|
13329
|
+
};
|
|
13125
13330
|
this.type = type;
|
|
13126
13331
|
this.id = id;
|
|
13127
13332
|
this.cid = `${type}:${id}`;
|
|
@@ -13287,7 +13492,6 @@ class StableWSConnection {
|
|
|
13287
13492
|
this.onmessage = (wsID, event) => {
|
|
13288
13493
|
if (this.wsID !== wsID)
|
|
13289
13494
|
return;
|
|
13290
|
-
this._log('onmessage() - onmessage callback', { event, wsID });
|
|
13291
13495
|
const data = typeof event.data === 'string'
|
|
13292
13496
|
? JSON.parse(event.data)
|
|
13293
13497
|
: null;
|
|
@@ -13373,7 +13577,6 @@ class StableWSConnection {
|
|
|
13373
13577
|
this.totalFailures += 1;
|
|
13374
13578
|
this._setHealth(false);
|
|
13375
13579
|
this.isConnecting = false;
|
|
13376
|
-
this.rejectConnectionOpen?.(new Error(`WebSocket error: ${event}`));
|
|
13377
13580
|
this._log(`onerror() - WS connection resulted into error`, { event });
|
|
13378
13581
|
this._reconnect();
|
|
13379
13582
|
};
|
|
@@ -14301,7 +14504,7 @@ class StreamClient {
|
|
|
14301
14504
|
this.getUserAgent = () => {
|
|
14302
14505
|
if (!this.cachedUserAgent) {
|
|
14303
14506
|
const { clientAppIdentifier = {} } = this.options;
|
|
14304
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
14507
|
+
const { sdkName = 'js', sdkVersion = "1.27.1", ...extras } = clientAppIdentifier;
|
|
14305
14508
|
this.cachedUserAgent = [
|
|
14306
14509
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
14307
14510
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -14931,6 +15134,7 @@ exports.getVideoDevices = getVideoDevices;
|
|
|
14931
15134
|
exports.getVideoStream = getVideoStream;
|
|
14932
15135
|
exports.getWebRTCInfo = getWebRTCInfo;
|
|
14933
15136
|
exports.hasAudio = hasAudio;
|
|
15137
|
+
exports.hasPausedTrack = hasPausedTrack;
|
|
14934
15138
|
exports.hasScreenShare = hasScreenShare;
|
|
14935
15139
|
exports.hasScreenShareAudio = hasScreenShareAudio;
|
|
14936
15140
|
exports.hasVideo = hasVideo;
|