@stream-io/video-client 1.50.0 → 1.52.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 +24 -0
- package/dist/index.browser.es.js +597 -70
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +597 -69
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +597 -70
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +1 -0
- package/dist/src/devices/CameraManager.d.ts +1 -0
- package/dist/src/devices/DeviceManager.d.ts +20 -0
- package/dist/src/devices/VirtualDevice.d.ts +59 -0
- package/dist/src/devices/devicePersistence.d.ts +1 -1
- package/dist/src/devices/index.d.ts +1 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +4 -0
- package/dist/src/gen/video/sfu/models/models.d.ts +204 -2
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +9 -1
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +67 -1
- package/dist/src/helpers/participantUtils.d.ts +10 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +8 -3
- package/dist/src/rtc/Publisher.d.ts +21 -3
- package/dist/src/rtc/TransceiverCache.d.ts +5 -1
- package/dist/src/rtc/helpers/degradationPreference.d.ts +1 -0
- package/dist/src/rtc/types.d.ts +3 -0
- package/dist/src/stats/rtc/StatsTracer.d.ts +2 -1
- package/dist/src/stats/utils.d.ts +1 -0
- package/package.json +14 -14
- package/src/Call.ts +27 -12
- package/src/devices/CameraManager.ts +9 -2
- package/src/devices/DeviceManager.ts +148 -8
- package/src/devices/DeviceManagerState.ts +4 -1
- package/src/devices/VirtualDevice.ts +69 -0
- package/src/devices/__tests__/CameraManager.test.ts +22 -1
- package/src/devices/__tests__/DeviceManager.test.ts +124 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +3 -1
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +3 -1
- package/src/devices/__tests__/ScreenShareManager.test.ts +3 -1
- package/src/devices/__tests__/web-audio.mocks.ts +3 -1
- package/src/devices/devicePersistence.ts +2 -1
- package/src/devices/index.ts +1 -0
- package/src/gen/video/sfu/event/events.ts +10 -0
- package/src/gen/video/sfu/models/models.ts +338 -0
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +28 -2
- package/src/gen/video/sfu/signal_rpc/signal.ts +121 -15
- package/src/helpers/__tests__/DynascaleManager.test.ts +8 -7
- package/src/helpers/__tests__/browsers.test.ts +4 -4
- package/src/helpers/__tests__/participantUtils.test.ts +47 -0
- package/src/helpers/client-details.ts +4 -1
- package/src/helpers/participantUtils.ts +15 -0
- package/src/rtc/BasePeerConnection.ts +22 -4
- package/src/rtc/Publisher.ts +140 -41
- package/src/rtc/Subscriber.ts +1 -0
- package/src/rtc/TransceiverCache.ts +10 -3
- package/src/rtc/__tests__/Call.reconnect.test.ts +121 -2
- package/src/rtc/__tests__/Publisher.test.ts +659 -112
- package/src/rtc/__tests__/Subscriber.test.ts +7 -3
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +16 -15
- package/src/rtc/helpers/__tests__/degradationPreference.test.ts +33 -1
- package/src/rtc/helpers/degradationPreference.ts +18 -0
- package/src/rtc/types.ts +3 -0
- package/src/stats/rtc/StatsTracer.ts +25 -4
- package/src/stats/rtc/__tests__/StatsTracer.test.ts +155 -0
package/dist/index.cjs.js
CHANGED
|
@@ -1206,6 +1206,14 @@ var SdkType;
|
|
|
1206
1206
|
* @generated from protobuf enum value: SDK_TYPE_PLAIN_JAVASCRIPT = 9;
|
|
1207
1207
|
*/
|
|
1208
1208
|
SdkType[SdkType["PLAIN_JAVASCRIPT"] = 9] = "PLAIN_JAVASCRIPT";
|
|
1209
|
+
/**
|
|
1210
|
+
* @generated from protobuf enum value: SDK_TYPE_PYTHON = 10;
|
|
1211
|
+
*/
|
|
1212
|
+
SdkType[SdkType["PYTHON"] = 10] = "PYTHON";
|
|
1213
|
+
/**
|
|
1214
|
+
* @generated from protobuf enum value: SDK_TYPE_VISION_AGENTS = 11;
|
|
1215
|
+
*/
|
|
1216
|
+
SdkType[SdkType["VISION_AGENTS"] = 11] = "VISION_AGENTS";
|
|
1209
1217
|
})(SdkType || (SdkType = {}));
|
|
1210
1218
|
/**
|
|
1211
1219
|
* @generated from protobuf enum stream.video.sfu.models.TrackUnpublishReason
|
|
@@ -1417,6 +1425,12 @@ var ClientCapability;
|
|
|
1417
1425
|
* @generated from protobuf enum value: CLIENT_CAPABILITY_SUBSCRIBER_VIDEO_PAUSE = 1;
|
|
1418
1426
|
*/
|
|
1419
1427
|
ClientCapability[ClientCapability["SUBSCRIBER_VIDEO_PAUSE"] = 1] = "SUBSCRIBER_VIDEO_PAUSE";
|
|
1428
|
+
/**
|
|
1429
|
+
* Instructs SFU that stats will be sent to the coordinator
|
|
1430
|
+
*
|
|
1431
|
+
* @generated from protobuf enum value: CLIENT_CAPABILITY_COORDINATOR_STATS = 2;
|
|
1432
|
+
*/
|
|
1433
|
+
ClientCapability[ClientCapability["COORDINATOR_STATS"] = 2] = "COORDINATOR_STATS";
|
|
1420
1434
|
})(ClientCapability || (ClientCapability = {}));
|
|
1421
1435
|
/**
|
|
1422
1436
|
* DegradationPreference represents the RTCDegradationPreference from WebRTC.
|
|
@@ -1882,6 +1896,12 @@ class ClientDetails$Type extends runtime.MessageType {
|
|
|
1882
1896
|
{ no: 2, name: 'os', kind: 'message', T: () => OS },
|
|
1883
1897
|
{ no: 3, name: 'browser', kind: 'message', T: () => Browser },
|
|
1884
1898
|
{ no: 4, name: 'device', kind: 'message', T: () => Device },
|
|
1899
|
+
{
|
|
1900
|
+
no: 5,
|
|
1901
|
+
name: 'webrtc_version',
|
|
1902
|
+
kind: 'scalar',
|
|
1903
|
+
T: 9 /*ScalarType.STRING*/,
|
|
1904
|
+
},
|
|
1885
1905
|
]);
|
|
1886
1906
|
}
|
|
1887
1907
|
}
|
|
@@ -2154,6 +2174,171 @@ class PerformanceStats$Type extends runtime.MessageType {
|
|
|
2154
2174
|
* @generated MessageType for protobuf message stream.video.sfu.models.PerformanceStats
|
|
2155
2175
|
*/
|
|
2156
2176
|
const PerformanceStats = new PerformanceStats$Type();
|
|
2177
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2178
|
+
class RtpBase$Type extends runtime.MessageType {
|
|
2179
|
+
constructor() {
|
|
2180
|
+
super('stream.video.sfu.models.RtpBase', [
|
|
2181
|
+
{ no: 1, name: 'ssrc', kind: 'scalar', T: 13 /*ScalarType.UINT32*/ },
|
|
2182
|
+
{ no: 2, name: 'kind', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2183
|
+
{
|
|
2184
|
+
no: 3,
|
|
2185
|
+
name: 'timestamp_ms',
|
|
2186
|
+
kind: 'scalar',
|
|
2187
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2188
|
+
},
|
|
2189
|
+
]);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RtpBase
|
|
2194
|
+
*/
|
|
2195
|
+
const RtpBase = new RtpBase$Type();
|
|
2196
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2197
|
+
class InboundRtp$Type extends runtime.MessageType {
|
|
2198
|
+
constructor() {
|
|
2199
|
+
super('stream.video.sfu.models.InboundRtp', [
|
|
2200
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2201
|
+
{
|
|
2202
|
+
no: 2,
|
|
2203
|
+
name: 'jitter_seconds',
|
|
2204
|
+
kind: 'scalar',
|
|
2205
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
no: 3,
|
|
2209
|
+
name: 'packets_received',
|
|
2210
|
+
kind: 'scalar',
|
|
2211
|
+
T: 4 /*ScalarType.UINT64*/,
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
no: 4,
|
|
2215
|
+
name: 'packets_lost',
|
|
2216
|
+
kind: 'scalar',
|
|
2217
|
+
T: 4 /*ScalarType.UINT64*/,
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
no: 5,
|
|
2221
|
+
name: 'packet_loss_percent',
|
|
2222
|
+
kind: 'scalar',
|
|
2223
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2224
|
+
},
|
|
2225
|
+
{
|
|
2226
|
+
no: 10,
|
|
2227
|
+
name: 'concealment_events',
|
|
2228
|
+
kind: 'scalar',
|
|
2229
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
no: 11,
|
|
2233
|
+
name: 'concealment_percent',
|
|
2234
|
+
kind: 'scalar',
|
|
2235
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2236
|
+
},
|
|
2237
|
+
{ no: 20, name: 'fps', kind: 'scalar', T: 1 /*ScalarType.DOUBLE*/ },
|
|
2238
|
+
{
|
|
2239
|
+
no: 21,
|
|
2240
|
+
name: 'freeze_duration_seconds',
|
|
2241
|
+
kind: 'scalar',
|
|
2242
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2243
|
+
},
|
|
2244
|
+
{
|
|
2245
|
+
no: 22,
|
|
2246
|
+
name: 'avg_decode_time_seconds',
|
|
2247
|
+
kind: 'scalar',
|
|
2248
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2249
|
+
},
|
|
2250
|
+
{
|
|
2251
|
+
no: 23,
|
|
2252
|
+
name: 'min_dimension_px',
|
|
2253
|
+
kind: 'scalar',
|
|
2254
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2255
|
+
},
|
|
2256
|
+
]);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.InboundRtp
|
|
2261
|
+
*/
|
|
2262
|
+
const InboundRtp = new InboundRtp$Type();
|
|
2263
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2264
|
+
class OutboundRtp$Type extends runtime.MessageType {
|
|
2265
|
+
constructor() {
|
|
2266
|
+
super('stream.video.sfu.models.OutboundRtp', [
|
|
2267
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2268
|
+
{ no: 10, name: 'fps', kind: 'scalar', T: 1 /*ScalarType.DOUBLE*/ },
|
|
2269
|
+
{
|
|
2270
|
+
no: 11,
|
|
2271
|
+
name: 'avg_encode_time_seconds',
|
|
2272
|
+
kind: 'scalar',
|
|
2273
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
no: 12,
|
|
2277
|
+
name: 'bitrate_bps',
|
|
2278
|
+
kind: 'scalar',
|
|
2279
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
no: 13,
|
|
2283
|
+
name: 'min_dimension_px',
|
|
2284
|
+
kind: 'scalar',
|
|
2285
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2286
|
+
},
|
|
2287
|
+
]);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.OutboundRtp
|
|
2292
|
+
*/
|
|
2293
|
+
const OutboundRtp = new OutboundRtp$Type();
|
|
2294
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2295
|
+
class RemoteInboundRtp$Type extends runtime.MessageType {
|
|
2296
|
+
constructor() {
|
|
2297
|
+
super('stream.video.sfu.models.RemoteInboundRtp', [
|
|
2298
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2299
|
+
{
|
|
2300
|
+
no: 2,
|
|
2301
|
+
name: 'jitter_seconds',
|
|
2302
|
+
kind: 'scalar',
|
|
2303
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2304
|
+
},
|
|
2305
|
+
{
|
|
2306
|
+
no: 3,
|
|
2307
|
+
name: 'round_trip_time_s',
|
|
2308
|
+
kind: 'scalar',
|
|
2309
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2310
|
+
},
|
|
2311
|
+
]);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RemoteInboundRtp
|
|
2316
|
+
*/
|
|
2317
|
+
const RemoteInboundRtp = new RemoteInboundRtp$Type();
|
|
2318
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2319
|
+
class RemoteOutboundRtp$Type extends runtime.MessageType {
|
|
2320
|
+
constructor() {
|
|
2321
|
+
super('stream.video.sfu.models.RemoteOutboundRtp', [
|
|
2322
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2323
|
+
{
|
|
2324
|
+
no: 2,
|
|
2325
|
+
name: 'jitter_seconds',
|
|
2326
|
+
kind: 'scalar',
|
|
2327
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2328
|
+
},
|
|
2329
|
+
{
|
|
2330
|
+
no: 3,
|
|
2331
|
+
name: 'round_trip_time_s',
|
|
2332
|
+
kind: 'scalar',
|
|
2333
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2334
|
+
},
|
|
2335
|
+
]);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RemoteOutboundRtp
|
|
2340
|
+
*/
|
|
2341
|
+
const RemoteOutboundRtp = new RemoteOutboundRtp$Type();
|
|
2157
2342
|
|
|
2158
2343
|
var models = /*#__PURE__*/Object.freeze({
|
|
2159
2344
|
__proto__: null,
|
|
@@ -2178,8 +2363,10 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
2178
2363
|
get ErrorCode () { return ErrorCode; },
|
|
2179
2364
|
get GoAwayReason () { return GoAwayReason; },
|
|
2180
2365
|
ICETrickle: ICETrickle$1,
|
|
2366
|
+
InboundRtp: InboundRtp,
|
|
2181
2367
|
InputDevices: InputDevices,
|
|
2182
2368
|
OS: OS,
|
|
2369
|
+
OutboundRtp: OutboundRtp,
|
|
2183
2370
|
Participant: Participant,
|
|
2184
2371
|
ParticipantCount: ParticipantCount,
|
|
2185
2372
|
get ParticipantSource () { return ParticipantSource; },
|
|
@@ -2188,6 +2375,9 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
2188
2375
|
Pin: Pin,
|
|
2189
2376
|
PublishOption: PublishOption,
|
|
2190
2377
|
RTMPIngress: RTMPIngress,
|
|
2378
|
+
RemoteInboundRtp: RemoteInboundRtp,
|
|
2379
|
+
RemoteOutboundRtp: RemoteOutboundRtp,
|
|
2380
|
+
RtpBase: RtpBase,
|
|
2191
2381
|
Sdk: Sdk,
|
|
2192
2382
|
get SdkType () { return SdkType; },
|
|
2193
2383
|
StreamQuality: StreamQuality,
|
|
@@ -2305,6 +2495,62 @@ class Telemetry$Type extends runtime.MessageType {
|
|
|
2305
2495
|
*/
|
|
2306
2496
|
const Telemetry = new Telemetry$Type();
|
|
2307
2497
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
2498
|
+
class SendMetricsRequest$Type extends runtime.MessageType {
|
|
2499
|
+
constructor() {
|
|
2500
|
+
super('stream.video.sfu.signal.SendMetricsRequest', [
|
|
2501
|
+
{ no: 1, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2502
|
+
{
|
|
2503
|
+
no: 2,
|
|
2504
|
+
name: 'unified_session_id',
|
|
2505
|
+
kind: 'scalar',
|
|
2506
|
+
T: 9 /*ScalarType.STRING*/,
|
|
2507
|
+
},
|
|
2508
|
+
{
|
|
2509
|
+
no: 3,
|
|
2510
|
+
name: 'inbounds',
|
|
2511
|
+
kind: 'message',
|
|
2512
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2513
|
+
T: () => InboundRtp,
|
|
2514
|
+
},
|
|
2515
|
+
{
|
|
2516
|
+
no: 4,
|
|
2517
|
+
name: 'outbounds',
|
|
2518
|
+
kind: 'message',
|
|
2519
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2520
|
+
T: () => OutboundRtp,
|
|
2521
|
+
},
|
|
2522
|
+
{
|
|
2523
|
+
no: 5,
|
|
2524
|
+
name: 'remote_inbounds',
|
|
2525
|
+
kind: 'message',
|
|
2526
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2527
|
+
T: () => RemoteInboundRtp,
|
|
2528
|
+
},
|
|
2529
|
+
{
|
|
2530
|
+
no: 6,
|
|
2531
|
+
name: 'remote_outbounds',
|
|
2532
|
+
kind: 'message',
|
|
2533
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2534
|
+
T: () => RemoteOutboundRtp,
|
|
2535
|
+
},
|
|
2536
|
+
]);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* @generated MessageType for protobuf message stream.video.sfu.signal.SendMetricsRequest
|
|
2541
|
+
*/
|
|
2542
|
+
const SendMetricsRequest = new SendMetricsRequest$Type();
|
|
2543
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2544
|
+
class SendMetricsResponse$Type extends runtime.MessageType {
|
|
2545
|
+
constructor() {
|
|
2546
|
+
super('stream.video.sfu.signal.SendMetricsResponse', []);
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
/**
|
|
2550
|
+
* @generated MessageType for protobuf message stream.video.sfu.signal.SendMetricsResponse
|
|
2551
|
+
*/
|
|
2552
|
+
const SendMetricsResponse = new SendMetricsResponse$Type();
|
|
2553
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2308
2554
|
class SendStatsRequest$Type extends runtime.MessageType {
|
|
2309
2555
|
constructor() {
|
|
2310
2556
|
super('stream.video.sfu.signal.SendStatsRequest', [
|
|
@@ -2578,6 +2824,12 @@ class SendAnswerRequest$Type extends runtime.MessageType {
|
|
|
2578
2824
|
},
|
|
2579
2825
|
{ no: 2, name: 'sdp', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2580
2826
|
{ no: 3, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2827
|
+
{
|
|
2828
|
+
no: 4,
|
|
2829
|
+
name: 'negotiation_id',
|
|
2830
|
+
kind: 'scalar',
|
|
2831
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2832
|
+
},
|
|
2581
2833
|
]);
|
|
2582
2834
|
}
|
|
2583
2835
|
}
|
|
@@ -2685,6 +2937,12 @@ const SignalServer = new runtimeRpc.ServiceType('stream.video.sfu.signal.SignalS
|
|
|
2685
2937
|
I: SendStatsRequest,
|
|
2686
2938
|
O: SendStatsResponse,
|
|
2687
2939
|
},
|
|
2940
|
+
{
|
|
2941
|
+
name: 'SendMetrics',
|
|
2942
|
+
options: {},
|
|
2943
|
+
I: SendMetricsRequest,
|
|
2944
|
+
O: SendMetricsResponse,
|
|
2945
|
+
},
|
|
2688
2946
|
{
|
|
2689
2947
|
name: 'StartNoiseCancellation',
|
|
2690
2948
|
options: {},
|
|
@@ -3365,6 +3623,12 @@ class SubscriberOffer$Type extends runtime.MessageType {
|
|
|
3365
3623
|
super('stream.video.sfu.event.SubscriberOffer', [
|
|
3366
3624
|
{ no: 1, name: 'ice_restart', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
|
3367
3625
|
{ no: 2, name: 'sdp', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3626
|
+
{
|
|
3627
|
+
no: 3,
|
|
3628
|
+
name: 'negotiation_id',
|
|
3629
|
+
kind: 'scalar',
|
|
3630
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
3631
|
+
},
|
|
3368
3632
|
]);
|
|
3369
3633
|
}
|
|
3370
3634
|
}
|
|
@@ -3823,18 +4087,25 @@ class SignalServerClient {
|
|
|
3823
4087
|
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
|
3824
4088
|
return runtimeRpc.stackIntercept('unary', this._transport, method, opt, input);
|
|
3825
4089
|
}
|
|
4090
|
+
/**
|
|
4091
|
+
* @generated from protobuf rpc: SendMetrics(stream.video.sfu.signal.SendMetricsRequest) returns (stream.video.sfu.signal.SendMetricsResponse);
|
|
4092
|
+
*/
|
|
4093
|
+
sendMetrics(input, options) {
|
|
4094
|
+
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
|
4095
|
+
return runtimeRpc.stackIntercept('unary', this._transport, method, opt, input);
|
|
4096
|
+
}
|
|
3826
4097
|
/**
|
|
3827
4098
|
* @generated from protobuf rpc: StartNoiseCancellation(stream.video.sfu.signal.StartNoiseCancellationRequest) returns (stream.video.sfu.signal.StartNoiseCancellationResponse);
|
|
3828
4099
|
*/
|
|
3829
4100
|
startNoiseCancellation(input, options) {
|
|
3830
|
-
const method = this.methods[
|
|
4101
|
+
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
|
3831
4102
|
return runtimeRpc.stackIntercept('unary', this._transport, method, opt, input);
|
|
3832
4103
|
}
|
|
3833
4104
|
/**
|
|
3834
4105
|
* @generated from protobuf rpc: StopNoiseCancellation(stream.video.sfu.signal.StopNoiseCancellationRequest) returns (stream.video.sfu.signal.StopNoiseCancellationResponse);
|
|
3835
4106
|
*/
|
|
3836
4107
|
stopNoiseCancellation(input, options) {
|
|
3837
|
-
const method = this.methods[
|
|
4108
|
+
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
|
3838
4109
|
return runtimeRpc.stackIntercept('unary', this._transport, method, opt, input);
|
|
3839
4110
|
}
|
|
3840
4111
|
}
|
|
@@ -5014,6 +5285,16 @@ const hasScreenShareAudio = (p) => p.publishedTracks.includes(TrackType.SCREEN_S
|
|
|
5014
5285
|
* @param p the participant.
|
|
5015
5286
|
*/
|
|
5016
5287
|
const isPinned = (p) => !!p.pin && (p.pin.isLocalPin || p.pin.pinnedAt > 0);
|
|
5288
|
+
/**
|
|
5289
|
+
* Check if a participant has a track that is currently interrupted: the
|
|
5290
|
+
* participant intends to publish it (it is in `publishedTracks`) but no
|
|
5291
|
+
* media is flowing right now (it is in `interruptedTracks`).
|
|
5292
|
+
*
|
|
5293
|
+
* @param p the participant to check.
|
|
5294
|
+
* @param trackType the track type to check.
|
|
5295
|
+
*/
|
|
5296
|
+
const hasInterruptedTrack = (p, trackType) => !!p.interruptedTracks?.includes(trackType) &&
|
|
5297
|
+
p.publishedTracks.includes(trackType);
|
|
5017
5298
|
/**
|
|
5018
5299
|
* Check if a participant has a paused track of the specified type.
|
|
5019
5300
|
*
|
|
@@ -6379,7 +6660,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6379
6660
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6380
6661
|
};
|
|
6381
6662
|
|
|
6382
|
-
const version = "1.
|
|
6663
|
+
const version = "1.52.0";
|
|
6383
6664
|
const [major, minor, patch] = version.split('.');
|
|
6384
6665
|
let sdkInfo = {
|
|
6385
6666
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6477,6 +6758,7 @@ const getClientDetails = async () => {
|
|
|
6477
6758
|
sdk: sdkInfo,
|
|
6478
6759
|
os: osInfo,
|
|
6479
6760
|
device: deviceInfo,
|
|
6761
|
+
webrtcVersion: webRtcInfo?.version || '',
|
|
6480
6762
|
};
|
|
6481
6763
|
}
|
|
6482
6764
|
// @ts-expect-error - userAgentData is not yet in the TS types
|
|
@@ -6504,11 +6786,12 @@ const getClientDetails = async () => {
|
|
|
6504
6786
|
// Eliminates the generic "Chromium" name and "Not)A_Brand" name from the list.
|
|
6505
6787
|
// https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section
|
|
6506
6788
|
const uaBrowser = userAgentData?.fullVersionList?.find((v) => !v.brand.includes('Chromium') && !v.brand.match(/[()\-./:;=?_]/g));
|
|
6789
|
+
const browserVersion = uaBrowser?.version || browser.version || '';
|
|
6507
6790
|
return {
|
|
6508
6791
|
sdk: sdkInfo,
|
|
6509
6792
|
browser: {
|
|
6510
6793
|
name: uaBrowser?.brand || browser.name || navigator.userAgent,
|
|
6511
|
-
version:
|
|
6794
|
+
version: browserVersion,
|
|
6512
6795
|
},
|
|
6513
6796
|
os: {
|
|
6514
6797
|
name: userAgentData?.platform || os.name || '',
|
|
@@ -6521,6 +6804,7 @@ const getClientDetails = async () => {
|
|
|
6521
6804
|
.join(' '),
|
|
6522
6805
|
version: '',
|
|
6523
6806
|
},
|
|
6807
|
+
webrtcVersion: browserVersion,
|
|
6524
6808
|
};
|
|
6525
6809
|
};
|
|
6526
6810
|
|
|
@@ -7184,7 +7468,7 @@ class StatsTracer {
|
|
|
7184
7468
|
/**
|
|
7185
7469
|
* Creates a new StatsTracer instance.
|
|
7186
7470
|
*/
|
|
7187
|
-
constructor(pc, peerType, trackIdToTrackType) {
|
|
7471
|
+
constructor(pc, peerType, trackIdToTrackType, statsTimestampDriftThresholdMs = 0) {
|
|
7188
7472
|
this.previousStats = {};
|
|
7189
7473
|
this.frameTimeHistory = [];
|
|
7190
7474
|
this.fpsHistory = [];
|
|
@@ -7198,7 +7482,7 @@ class StatsTracer {
|
|
|
7198
7482
|
*/
|
|
7199
7483
|
this.get = async () => {
|
|
7200
7484
|
const stats = await this.pc.getStats();
|
|
7201
|
-
const currentStats =
|
|
7485
|
+
const currentStats = toObjectWithCorrectedTimestamp(stats, Date.now(), this.driftThresholdMs);
|
|
7202
7486
|
const performanceStats = this.withOverrides(this.peerType === PeerType.SUBSCRIBER
|
|
7203
7487
|
? this.getDecodeStats(currentStats)
|
|
7204
7488
|
: this.getEncodeStats(currentStats));
|
|
@@ -7317,17 +7601,28 @@ class StatsTracer {
|
|
|
7317
7601
|
this.pc = pc;
|
|
7318
7602
|
this.peerType = peerType;
|
|
7319
7603
|
this.trackIdToTrackType = trackIdToTrackType;
|
|
7604
|
+
this.driftThresholdMs = statsTimestampDriftThresholdMs;
|
|
7320
7605
|
}
|
|
7321
7606
|
}
|
|
7322
7607
|
/**
|
|
7323
|
-
* Convert the stat report to an object.
|
|
7608
|
+
* Convert the stat report to an object, correcting clock drift along the way.
|
|
7609
|
+
* Entries whose `timestamp` differs from `wallNow` by more than `thresholdMs`
|
|
7610
|
+
* are replaced with a clone whose `timestamp` is set to `wallNow`. The platform
|
|
7611
|
+
* clock backing `DOMHighResTimeStamp` can desynchronise from `Date.now()` after
|
|
7612
|
+
* system sleep or clock-jump events (notably on Electron/Chromium), which
|
|
7613
|
+
* corrupts the delta-compressed stats payload. A non-positive `thresholdMs`
|
|
7614
|
+
* disables correction.
|
|
7324
7615
|
*
|
|
7325
7616
|
* @param report the stat report to convert.
|
|
7617
|
+
* @param wallNow current wall-clock time used as the drift reference.
|
|
7618
|
+
* @param thresholdMs maximum tolerated drift in milliseconds.
|
|
7326
7619
|
*/
|
|
7327
|
-
const
|
|
7620
|
+
const toObjectWithCorrectedTimestamp = (report, wallNow, thresholdMs) => {
|
|
7328
7621
|
const obj = {};
|
|
7622
|
+
const correct = thresholdMs > 0;
|
|
7329
7623
|
report.forEach((v, k) => {
|
|
7330
|
-
|
|
7624
|
+
const drift = Math.abs(v.timestamp - wallNow);
|
|
7625
|
+
obj[k] = correct && drift > thresholdMs ? { ...v, timestamp: wallNow } : v;
|
|
7331
7626
|
});
|
|
7332
7627
|
return obj;
|
|
7333
7628
|
};
|
|
@@ -7459,7 +7754,7 @@ class BasePeerConnection {
|
|
|
7459
7754
|
/**
|
|
7460
7755
|
* Constructs a new `BasePeerConnection` instance.
|
|
7461
7756
|
*/
|
|
7462
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
|
|
7757
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, statsTimestampDriftThresholdMs = 0, }) {
|
|
7463
7758
|
this.iceHasEverConnected = false;
|
|
7464
7759
|
this.isIceRestarting = false;
|
|
7465
7760
|
this.isDisposed = false;
|
|
@@ -7498,7 +7793,7 @@ class BasePeerConnection {
|
|
|
7498
7793
|
this.on = (event, fn) => {
|
|
7499
7794
|
const getTag = () => this.tag;
|
|
7500
7795
|
this.subscriptions.push(this.dispatcher.on(event, getTag, (e) => {
|
|
7501
|
-
const lockKey =
|
|
7796
|
+
const lockKey = this.eventLockKey(event);
|
|
7502
7797
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
7503
7798
|
if (this.isDisposed)
|
|
7504
7799
|
return;
|
|
@@ -7506,6 +7801,13 @@ class BasePeerConnection {
|
|
|
7506
7801
|
});
|
|
7507
7802
|
}));
|
|
7508
7803
|
};
|
|
7804
|
+
/**
|
|
7805
|
+
* Returns the per-event `withoutConcurrency` tag used to serialize the
|
|
7806
|
+
* dispatcher handler for `event` on this peer connection.
|
|
7807
|
+
*/
|
|
7808
|
+
this.eventLockKey = (event) => {
|
|
7809
|
+
return `pc.${this.lock}.${event}`;
|
|
7810
|
+
};
|
|
7509
7811
|
/**
|
|
7510
7812
|
* Appends the trickled ICE candidates to the `RTCPeerConnection`.
|
|
7511
7813
|
*/
|
|
@@ -7746,7 +8048,7 @@ class BasePeerConnection {
|
|
|
7746
8048
|
this.onIceConnected = onIceConnected;
|
|
7747
8049
|
this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
|
|
7748
8050
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
7749
|
-
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
8051
|
+
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType, statsTimestampDriftThresholdMs);
|
|
7750
8052
|
if (enableTracing) {
|
|
7751
8053
|
this.tracer = new Tracer(`${tag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`);
|
|
7752
8054
|
this.tracer.trace('create', {
|
|
@@ -7759,7 +8061,7 @@ class BasePeerConnection {
|
|
|
7759
8061
|
/**
|
|
7760
8062
|
* Disposes the `RTCPeerConnection` instance.
|
|
7761
8063
|
*/
|
|
7762
|
-
dispose() {
|
|
8064
|
+
async dispose() {
|
|
7763
8065
|
clearTimeout(this.iceRestartTimeout);
|
|
7764
8066
|
this.iceRestartTimeout = undefined;
|
|
7765
8067
|
clearTimeout(this.preConnectStuckTimeout);
|
|
@@ -7781,6 +8083,7 @@ class BasePeerConnection {
|
|
|
7781
8083
|
pc.removeEventListener('signalingstatechange', this.onSignalingChange);
|
|
7782
8084
|
pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
|
|
7783
8085
|
pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
|
|
8086
|
+
pc.removeEventListener('connectionstatechange', this.onConnectionStateChange);
|
|
7784
8087
|
this.unsubscribeIceTrickle?.();
|
|
7785
8088
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
7786
8089
|
this.subscriptions = [];
|
|
@@ -7808,8 +8111,14 @@ class TransceiverCache {
|
|
|
7808
8111
|
* Gets the transceiver for the given publish option.
|
|
7809
8112
|
*/
|
|
7810
8113
|
this.get = (publishOption) => {
|
|
7811
|
-
return this.
|
|
7812
|
-
|
|
8114
|
+
return this.getBy(publishOption.id, publishOption.trackType);
|
|
8115
|
+
};
|
|
8116
|
+
/**
|
|
8117
|
+
* Gets the transceiver for the given publish option id and track type.
|
|
8118
|
+
*/
|
|
8119
|
+
this.getBy = (publishOptionId, trackType) => {
|
|
8120
|
+
return this.cache.find((bundle) => bundle.publishOption.id === publishOptionId &&
|
|
8121
|
+
bundle.publishOption.trackType === trackType);
|
|
7813
8122
|
};
|
|
7814
8123
|
/**
|
|
7815
8124
|
* Updates the cached bundle with the given patch.
|
|
@@ -8094,6 +8403,21 @@ const toRTCDegradationPreference = (preference) => {
|
|
|
8094
8403
|
ensureExhausted(preference, 'Unknown degradation preference');
|
|
8095
8404
|
}
|
|
8096
8405
|
};
|
|
8406
|
+
const fromRTCDegradationPreference = (preference) => {
|
|
8407
|
+
switch (preference) {
|
|
8408
|
+
case 'balanced':
|
|
8409
|
+
return DegradationPreference.BALANCED;
|
|
8410
|
+
case 'maintain-framerate':
|
|
8411
|
+
return DegradationPreference.MAINTAIN_FRAMERATE;
|
|
8412
|
+
case 'maintain-resolution':
|
|
8413
|
+
return DegradationPreference.MAINTAIN_RESOLUTION;
|
|
8414
|
+
// @ts-expect-error not in the typedefs yet
|
|
8415
|
+
case 'maintain-framerate-and-resolution':
|
|
8416
|
+
return DegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION;
|
|
8417
|
+
default:
|
|
8418
|
+
return DegradationPreference.UNSPECIFIED;
|
|
8419
|
+
}
|
|
8420
|
+
};
|
|
8097
8421
|
|
|
8098
8422
|
/**
|
|
8099
8423
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
@@ -8128,13 +8452,13 @@ class Publisher extends BasePeerConnection {
|
|
|
8128
8452
|
// create a clone of the track as otherwise the same trackId will
|
|
8129
8453
|
// appear in the SDP in multiple transceivers
|
|
8130
8454
|
const trackToPublish = this.cloneTrack(track);
|
|
8131
|
-
const
|
|
8132
|
-
if (!
|
|
8455
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
8456
|
+
if (!bundle) {
|
|
8133
8457
|
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
8134
8458
|
}
|
|
8135
8459
|
else {
|
|
8136
|
-
const previousTrack = transceiver.sender.track;
|
|
8137
|
-
await this.updateTransceiver(
|
|
8460
|
+
const previousTrack = bundle.transceiver.sender.track;
|
|
8461
|
+
await this.updateTransceiver(bundle, trackToPublish, options);
|
|
8138
8462
|
if (!isReactNative()) {
|
|
8139
8463
|
this.stopTrack(previousTrack);
|
|
8140
8464
|
}
|
|
@@ -8169,13 +8493,20 @@ class Publisher extends BasePeerConnection {
|
|
|
8169
8493
|
/**
|
|
8170
8494
|
* Updates the transceiver with the given track and track type.
|
|
8171
8495
|
*/
|
|
8172
|
-
this.updateTransceiver = async (
|
|
8496
|
+
this.updateTransceiver = async (bundle, track, options = {}) => {
|
|
8497
|
+
const { transceiver, publishOption } = bundle;
|
|
8498
|
+
const trackType = publishOption.trackType;
|
|
8173
8499
|
const sender = transceiver.sender;
|
|
8174
8500
|
if (sender.track)
|
|
8175
8501
|
this.trackIdToTrackType.delete(sender.track.id);
|
|
8176
8502
|
await sender.replaceTrack(track);
|
|
8177
|
-
if (track)
|
|
8503
|
+
if (track) {
|
|
8178
8504
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
8505
|
+
if (isFirefox() && bundle.videoSender) {
|
|
8506
|
+
// restore the encoding config from the cache, if any
|
|
8507
|
+
await this.changePublishQuality(bundle.videoSender, bundle);
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8179
8510
|
if (isAudioTrackType(trackType)) {
|
|
8180
8511
|
await this.updateAudioPublishOptions(trackType, options);
|
|
8181
8512
|
}
|
|
@@ -8235,7 +8566,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8235
8566
|
continue;
|
|
8236
8567
|
// it is safe to stop the track here, it is a clone
|
|
8237
8568
|
this.stopTrack(transceiver.sender.track);
|
|
8238
|
-
await this.updateTransceiver(
|
|
8569
|
+
await this.updateTransceiver(item, null);
|
|
8239
8570
|
}
|
|
8240
8571
|
};
|
|
8241
8572
|
/**
|
|
@@ -8292,33 +8623,38 @@ class Publisher extends BasePeerConnection {
|
|
|
8292
8623
|
/**
|
|
8293
8624
|
* Stops the cloned track that is being published to the SFU.
|
|
8294
8625
|
*/
|
|
8295
|
-
this.stopTracks = (...trackTypes) => {
|
|
8296
|
-
|
|
8297
|
-
const
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8626
|
+
this.stopTracks = async (...trackTypes) => {
|
|
8627
|
+
return withoutConcurrency(this.eventLockKey('changePublishQuality'), async () => {
|
|
8628
|
+
for (const item of this.transceiverCache.items()) {
|
|
8629
|
+
const { publishOption, transceiver } = item;
|
|
8630
|
+
if (!trackTypes.includes(publishOption.trackType))
|
|
8631
|
+
continue;
|
|
8632
|
+
const track = transceiver.sender.track;
|
|
8633
|
+
await this.silenceSenderOnFirefox(item);
|
|
8634
|
+
this.stopTrack(track);
|
|
8635
|
+
}
|
|
8636
|
+
});
|
|
8302
8637
|
};
|
|
8303
8638
|
/**
|
|
8304
8639
|
* Stops all the cloned tracks that are being published to the SFU.
|
|
8305
8640
|
*/
|
|
8306
|
-
this.stopAllTracks = () => {
|
|
8307
|
-
|
|
8308
|
-
this.
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8641
|
+
this.stopAllTracks = async () => {
|
|
8642
|
+
return withoutConcurrency(this.eventLockKey('changePublishQuality'), async () => {
|
|
8643
|
+
for (const item of this.transceiverCache.items()) {
|
|
8644
|
+
const track = item.transceiver.sender.track;
|
|
8645
|
+
await this.silenceSenderOnFirefox(item);
|
|
8646
|
+
this.stopTrack(track);
|
|
8647
|
+
}
|
|
8648
|
+
for (const track of this.clonedTracks) {
|
|
8649
|
+
this.stopTrack(track);
|
|
8650
|
+
}
|
|
8651
|
+
});
|
|
8313
8652
|
};
|
|
8314
|
-
this.changePublishQuality = async (videoSender) => {
|
|
8315
|
-
const
|
|
8316
|
-
const enabledLayers = layers.filter((l) => l.active);
|
|
8653
|
+
this.changePublishQuality = async (videoSender, bundle) => {
|
|
8654
|
+
const enabledLayers = videoSender.layers.filter((l) => l.active);
|
|
8317
8655
|
const tag = 'Update publish quality:';
|
|
8318
8656
|
this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
|
|
8319
|
-
const
|
|
8320
|
-
t.publishOption.trackType === trackType);
|
|
8321
|
-
const sender = transceiverId?.transceiver.sender;
|
|
8657
|
+
const sender = bundle?.transceiver.sender;
|
|
8322
8658
|
if (!sender) {
|
|
8323
8659
|
return this.logger.warn(`${tag} no video sender found.`);
|
|
8324
8660
|
}
|
|
@@ -8326,7 +8662,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8326
8662
|
if (params.encodings.length === 0) {
|
|
8327
8663
|
return this.logger.warn(`${tag} there are no encodings set.`);
|
|
8328
8664
|
}
|
|
8329
|
-
const codecInUse =
|
|
8665
|
+
const codecInUse = bundle?.publishOption.codec?.name;
|
|
8330
8666
|
const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
|
|
8331
8667
|
let changed = false;
|
|
8332
8668
|
for (const encoder of params.encodings) {
|
|
@@ -8526,6 +8862,72 @@ class Publisher extends BasePeerConnection {
|
|
|
8526
8862
|
track.stop();
|
|
8527
8863
|
this.clonedTracks.delete(track);
|
|
8528
8864
|
};
|
|
8865
|
+
/**
|
|
8866
|
+
* Silences a Firefox sender on the wire during unpublish.
|
|
8867
|
+
*
|
|
8868
|
+
* Firefox keeps emitting RTP after track.stop(), but the right lever
|
|
8869
|
+
* differs by track type:
|
|
8870
|
+
* - audio: `replaceTrack(null)` is the only reliable silencer;
|
|
8871
|
+
* `setParameters({encodings:[...active:false]})` does NOT stop
|
|
8872
|
+
* the Opus encoder.
|
|
8873
|
+
* - video: `setParameters({encodings:[...active:false]})` pauses
|
|
8874
|
+
* the encoder; `replaceTrack(null)` does NOT reliably stop the
|
|
8875
|
+
* video encoder. The prior active=true configuration is captured
|
|
8876
|
+
* onto `bundle.videoSender` so `updateTransceiver` can restore
|
|
8877
|
+
* it on the next publish.
|
|
8878
|
+
*
|
|
8879
|
+
* No-op on non-Firefox browsers and during teardown.
|
|
8880
|
+
*/
|
|
8881
|
+
this.silenceSenderOnFirefox = async (bundle) => {
|
|
8882
|
+
if (this.isDisposed || !isFirefox())
|
|
8883
|
+
return;
|
|
8884
|
+
const { transceiver, publishOption } = bundle;
|
|
8885
|
+
if (isAudioTrackType(publishOption.trackType)) {
|
|
8886
|
+
await transceiver.sender.replaceTrack(null).catch((err) => {
|
|
8887
|
+
this.logger.warn('Failed to clear audio sender track', err);
|
|
8888
|
+
});
|
|
8889
|
+
return;
|
|
8890
|
+
}
|
|
8891
|
+
await this.disableAllEncodings(bundle);
|
|
8892
|
+
};
|
|
8893
|
+
this.disableAllEncodings = async (bundle) => {
|
|
8894
|
+
const { transceiver, publishOption } = bundle;
|
|
8895
|
+
const sender = transceiver.sender;
|
|
8896
|
+
const params = sender.getParameters();
|
|
8897
|
+
if (!params.encodings || params.encodings.length === 0)
|
|
8898
|
+
return;
|
|
8899
|
+
if (!bundle.videoSender) {
|
|
8900
|
+
this.transceiverCache.update(publishOption, {
|
|
8901
|
+
videoSender: {
|
|
8902
|
+
trackType: publishOption.trackType,
|
|
8903
|
+
publishOptionId: publishOption.id,
|
|
8904
|
+
codec: publishOption.codec,
|
|
8905
|
+
degradationPreference: fromRTCDegradationPreference(params.degradationPreference),
|
|
8906
|
+
layers: params.encodings.map((e) => ({
|
|
8907
|
+
name: e.rid ?? 'q',
|
|
8908
|
+
active: e.active ?? true,
|
|
8909
|
+
maxBitrate: e.maxBitrate ?? 0,
|
|
8910
|
+
scaleResolutionDownBy: e.scaleResolutionDownBy ?? 0,
|
|
8911
|
+
maxFramerate: e.maxFramerate ?? 0,
|
|
8912
|
+
// @ts-expect-error scalabilityMode is not in the typedefs yet
|
|
8913
|
+
scalabilityMode: e.scalabilityMode ?? '',
|
|
8914
|
+
})),
|
|
8915
|
+
},
|
|
8916
|
+
});
|
|
8917
|
+
}
|
|
8918
|
+
let changed = false;
|
|
8919
|
+
for (const encoding of params.encodings) {
|
|
8920
|
+
if (encoding.active !== false) {
|
|
8921
|
+
encoding.active = false;
|
|
8922
|
+
changed = true;
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8925
|
+
if (!changed)
|
|
8926
|
+
return;
|
|
8927
|
+
await sender.setParameters(params).catch((err) => {
|
|
8928
|
+
this.logger.error('Failed to disable video sender encodings:', err);
|
|
8929
|
+
});
|
|
8930
|
+
};
|
|
8529
8931
|
this.publishOptions = publishOptions;
|
|
8530
8932
|
this.on('iceRestart', (iceRestart) => {
|
|
8531
8933
|
if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
|
|
@@ -8534,7 +8936,16 @@ class Publisher extends BasePeerConnection {
|
|
|
8534
8936
|
});
|
|
8535
8937
|
this.on('changePublishQuality', async (event) => {
|
|
8536
8938
|
for (const videoSender of event.videoSenders) {
|
|
8537
|
-
|
|
8939
|
+
// if not publishing, update the encodingConfigCache and don't modify the state.
|
|
8940
|
+
// we'll apply this config on the next publish/unmute.
|
|
8941
|
+
const { trackType, publishOptionId } = videoSender;
|
|
8942
|
+
const bundle = this.transceiverCache.getBy(publishOptionId, trackType);
|
|
8943
|
+
if (bundle) {
|
|
8944
|
+
this.transceiverCache.update(bundle.publishOption, { videoSender });
|
|
8945
|
+
}
|
|
8946
|
+
if (isFirefox() && !this.isPublishing(trackType))
|
|
8947
|
+
continue;
|
|
8948
|
+
await this.changePublishQuality(videoSender, bundle);
|
|
8538
8949
|
}
|
|
8539
8950
|
});
|
|
8540
8951
|
this.on('changePublishOptions', (event) => {
|
|
@@ -8545,9 +8956,14 @@ class Publisher extends BasePeerConnection {
|
|
|
8545
8956
|
/**
|
|
8546
8957
|
* Disposes this Publisher instance.
|
|
8547
8958
|
*/
|
|
8548
|
-
dispose() {
|
|
8549
|
-
super.dispose();
|
|
8550
|
-
|
|
8959
|
+
async dispose() {
|
|
8960
|
+
await super.dispose();
|
|
8961
|
+
try {
|
|
8962
|
+
await this.stopAllTracks();
|
|
8963
|
+
}
|
|
8964
|
+
catch (err) {
|
|
8965
|
+
this.logger.warn('Failed to stop tracks during dispose', err);
|
|
8966
|
+
}
|
|
8551
8967
|
this.clonedTracks.clear();
|
|
8552
8968
|
}
|
|
8553
8969
|
}
|
|
@@ -8718,6 +9134,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
8718
9134
|
await this.sfuClient.sendAnswer({
|
|
8719
9135
|
peerType: PeerType.SUBSCRIBER,
|
|
8720
9136
|
sdp: answer.sdp || '',
|
|
9137
|
+
negotiationId: subscriberOffer.negotiationId,
|
|
8721
9138
|
});
|
|
8722
9139
|
this.isIceRestarting = false;
|
|
8723
9140
|
};
|
|
@@ -11331,8 +11748,8 @@ const normalize = (options) => {
|
|
|
11331
11748
|
: false,
|
|
11332
11749
|
};
|
|
11333
11750
|
};
|
|
11334
|
-
const createSyntheticDevice = (deviceId, kind) => {
|
|
11335
|
-
return { deviceId, kind, label
|
|
11751
|
+
const createSyntheticDevice = (deviceId, kind, label = '') => {
|
|
11752
|
+
return { deviceId, kind, label, groupId: '' };
|
|
11336
11753
|
};
|
|
11337
11754
|
const readPreferences = (storageKey) => {
|
|
11338
11755
|
try {
|
|
@@ -11386,6 +11803,8 @@ class DeviceManager {
|
|
|
11386
11803
|
this.areSubscriptionsSetUp = false;
|
|
11387
11804
|
this.isTrackStoppedDueToTrackEnd = false;
|
|
11388
11805
|
this.filters = [];
|
|
11806
|
+
this.virtualDevicesSubject = new rxjs.BehaviorSubject([]);
|
|
11807
|
+
this.virtualDeviceConcurrencyTag = Symbol('virtualDeviceConcurrencyTag');
|
|
11389
11808
|
this.statusChangeConcurrencyTag = Symbol('statusChangeConcurrencyTag');
|
|
11390
11809
|
this.filterRegistrationConcurrencyTag = Symbol('filterRegistrationConcurrencyTag');
|
|
11391
11810
|
/**
|
|
@@ -11398,6 +11817,7 @@ class DeviceManager {
|
|
|
11398
11817
|
this.subscriptions.forEach((s) => s());
|
|
11399
11818
|
this.subscriptions = [];
|
|
11400
11819
|
this.areSubscriptionsSetUp = false;
|
|
11820
|
+
this.virtualDevicesSubject.next([]);
|
|
11401
11821
|
};
|
|
11402
11822
|
this.runCurrentStreamCleanups = () => {
|
|
11403
11823
|
this.currentStreamCleanups.forEach((c) => c());
|
|
@@ -11456,7 +11876,93 @@ class DeviceManager {
|
|
|
11456
11876
|
* @returns an Observable that will be updated if a device is connected or disconnected
|
|
11457
11877
|
*/
|
|
11458
11878
|
listDevices() {
|
|
11459
|
-
return this.getDevices()
|
|
11879
|
+
return rxjs.combineLatest([this.getDevices(), this.virtualDevicesSubject]).pipe(rxjs.map(([real, virtual]) => [
|
|
11880
|
+
...real,
|
|
11881
|
+
...virtual.map((d) => createSyntheticDevice(d.deviceId, d.kind, d.label)),
|
|
11882
|
+
]));
|
|
11883
|
+
}
|
|
11884
|
+
/**
|
|
11885
|
+
* Registers a virtual camera or microphone backed by a caller-supplied
|
|
11886
|
+
* stream factory. The device appears in `listDevices()` and can be selected
|
|
11887
|
+
* via `select()` like any real device.
|
|
11888
|
+
*
|
|
11889
|
+
* Web only. React Native is not supported.
|
|
11890
|
+
*
|
|
11891
|
+
* Only supported for camera and microphone managers; calling on any other
|
|
11892
|
+
* manager throws.
|
|
11893
|
+
*/
|
|
11894
|
+
registerVirtualDevice(virtualDevice) {
|
|
11895
|
+
if (isReactNative()) {
|
|
11896
|
+
throw new Error('Virtual devices are not supported on React Native.');
|
|
11897
|
+
}
|
|
11898
|
+
if (this.trackType !== TrackType.AUDIO &&
|
|
11899
|
+
this.trackType !== TrackType.VIDEO) {
|
|
11900
|
+
throw new Error('Virtual devices are only supported for camera and microphone.');
|
|
11901
|
+
}
|
|
11902
|
+
const deviceId = `stream-virtual:${generateUUIDv4()}`;
|
|
11903
|
+
const entry = {
|
|
11904
|
+
deviceId,
|
|
11905
|
+
kind: this.mediaDeviceKind,
|
|
11906
|
+
...virtualDevice,
|
|
11907
|
+
};
|
|
11908
|
+
setCurrentValue(this.virtualDevicesSubject, (current) => [
|
|
11909
|
+
...current,
|
|
11910
|
+
entry,
|
|
11911
|
+
]);
|
|
11912
|
+
return {
|
|
11913
|
+
deviceId: entry.deviceId,
|
|
11914
|
+
unregister: async () => {
|
|
11915
|
+
await withoutConcurrency(this.virtualDeviceConcurrencyTag, async () => {
|
|
11916
|
+
setCurrentValue(this.virtualDevicesSubject, (current) => current.filter((d) => d !== entry));
|
|
11917
|
+
if (this.activeVirtualSession?.deviceId === deviceId) {
|
|
11918
|
+
await this.stopActiveVirtualSession();
|
|
11919
|
+
}
|
|
11920
|
+
});
|
|
11921
|
+
if (this.state.selectedDevice === deviceId) {
|
|
11922
|
+
await this.statusChangeSettled();
|
|
11923
|
+
await this.disable({ forceStop: true });
|
|
11924
|
+
await this.select(undefined);
|
|
11925
|
+
}
|
|
11926
|
+
},
|
|
11927
|
+
};
|
|
11928
|
+
}
|
|
11929
|
+
sanitizeVirtualStream(stream) {
|
|
11930
|
+
stream.getTracks().forEach((track) => {
|
|
11931
|
+
const originalGetSettings = track.getSettings.bind(track);
|
|
11932
|
+
track.getSettings = () => {
|
|
11933
|
+
const settings = originalGetSettings();
|
|
11934
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11935
|
+
const { deviceId, ...rest } = settings;
|
|
11936
|
+
return rest;
|
|
11937
|
+
};
|
|
11938
|
+
});
|
|
11939
|
+
return stream;
|
|
11940
|
+
}
|
|
11941
|
+
findVirtualDevice(deviceId) {
|
|
11942
|
+
if (!deviceId)
|
|
11943
|
+
return undefined;
|
|
11944
|
+
return getCurrentValue(this.virtualDevicesSubject).find((d) => d.deviceId === deviceId);
|
|
11945
|
+
}
|
|
11946
|
+
async stopActiveVirtualSession() {
|
|
11947
|
+
const session = this.activeVirtualSession;
|
|
11948
|
+
this.activeVirtualSession = undefined;
|
|
11949
|
+
await session?.stop?.();
|
|
11950
|
+
}
|
|
11951
|
+
async getSelectedStream(constraints) {
|
|
11952
|
+
const deviceId = this.state.selectedDevice;
|
|
11953
|
+
if (!deviceId?.startsWith('stream-virtual')) {
|
|
11954
|
+
return this.getStream(constraints);
|
|
11955
|
+
}
|
|
11956
|
+
return withoutConcurrency(this.virtualDeviceConcurrencyTag, async () => {
|
|
11957
|
+
const virtualDevice = this.findVirtualDevice(deviceId);
|
|
11958
|
+
if (!virtualDevice) {
|
|
11959
|
+
throw new Error(`Virtual device is not registered: ${deviceId}`);
|
|
11960
|
+
}
|
|
11961
|
+
await this.stopActiveVirtualSession();
|
|
11962
|
+
const { stream, stop } = await virtualDevice.getUserMedia(constraints);
|
|
11963
|
+
this.activeVirtualSession = { deviceId, stop };
|
|
11964
|
+
return this.sanitizeVirtualStream(stream);
|
|
11965
|
+
});
|
|
11460
11966
|
}
|
|
11461
11967
|
/**
|
|
11462
11968
|
* Returns `true` when this device is in enabled state.
|
|
@@ -11616,6 +12122,9 @@ class DeviceManager {
|
|
|
11616
12122
|
}
|
|
11617
12123
|
});
|
|
11618
12124
|
}
|
|
12125
|
+
getResolvedConstraints(constraints) {
|
|
12126
|
+
return constraints;
|
|
12127
|
+
}
|
|
11619
12128
|
publishStream(stream, options) {
|
|
11620
12129
|
return this.call.publish(stream, this.trackType, options);
|
|
11621
12130
|
}
|
|
@@ -11636,6 +12145,7 @@ class DeviceManager {
|
|
|
11636
12145
|
this.muteLocalStream(stopTracks);
|
|
11637
12146
|
const allEnded = this.getTracks().every((t) => t.readyState === 'ended');
|
|
11638
12147
|
if (allEnded) {
|
|
12148
|
+
await this.stopActiveVirtualSession();
|
|
11639
12149
|
// @ts-expect-error release() is present in react-native-webrtc
|
|
11640
12150
|
if (typeof mediaStream.release === 'function') {
|
|
11641
12151
|
// @ts-expect-error called to dispose the stream in RN
|
|
@@ -11691,12 +12201,12 @@ class DeviceManager {
|
|
|
11691
12201
|
// before chainWith below registers new ones for the new chain.
|
|
11692
12202
|
this.runCurrentStreamCleanups();
|
|
11693
12203
|
const defaultConstraints = this.state.defaultConstraints;
|
|
11694
|
-
const constraints = {
|
|
12204
|
+
const constraints = this.getResolvedConstraints({
|
|
11695
12205
|
...defaultConstraints,
|
|
11696
12206
|
deviceId: this.state.selectedDevice
|
|
11697
12207
|
? { exact: this.state.selectedDevice }
|
|
11698
12208
|
: undefined,
|
|
11699
|
-
};
|
|
12209
|
+
});
|
|
11700
12210
|
/**
|
|
11701
12211
|
* Chains two media streams together.
|
|
11702
12212
|
*
|
|
@@ -11753,7 +12263,7 @@ class DeviceManager {
|
|
|
11753
12263
|
};
|
|
11754
12264
|
// the rootStream represents the stream coming from the actual device
|
|
11755
12265
|
// e.g. camera or microphone stream
|
|
11756
|
-
rootStreamPromise = this.
|
|
12266
|
+
rootStreamPromise = this.getSelectedStream(constraints);
|
|
11757
12267
|
// we publish the last MediaStream of the chain
|
|
11758
12268
|
stream = await this.filters.reduce((parent, entry) => parent
|
|
11759
12269
|
.then((inputStream) => {
|
|
@@ -12070,7 +12580,10 @@ class DeviceManagerState {
|
|
|
12070
12580
|
setCurrentValue(this.mediaStreamSubject, stream);
|
|
12071
12581
|
setCurrentValue(this.rootMediaStreamSubject, rootStream);
|
|
12072
12582
|
if (rootStream) {
|
|
12073
|
-
this.
|
|
12583
|
+
const derived = this.getDeviceIdFromStream(rootStream);
|
|
12584
|
+
if (derived) {
|
|
12585
|
+
this.setDevice(derived);
|
|
12586
|
+
}
|
|
12074
12587
|
}
|
|
12075
12588
|
}
|
|
12076
12589
|
/**
|
|
@@ -12283,7 +12796,7 @@ class CameraManager extends DeviceManager {
|
|
|
12283
12796
|
getDevices() {
|
|
12284
12797
|
return getVideoDevices(this.call.tracer);
|
|
12285
12798
|
}
|
|
12286
|
-
|
|
12799
|
+
getResolvedConstraints(constraints) {
|
|
12287
12800
|
constraints.width = this.targetResolution.width;
|
|
12288
12801
|
constraints.height = this.targetResolution.height;
|
|
12289
12802
|
// We can't set both device id and facing mode
|
|
@@ -12294,6 +12807,9 @@ class CameraManager extends DeviceManager {
|
|
|
12294
12807
|
constraints.facingMode =
|
|
12295
12808
|
this.state.direction === 'front' ? 'user' : 'environment';
|
|
12296
12809
|
}
|
|
12810
|
+
return constraints;
|
|
12811
|
+
}
|
|
12812
|
+
getStream(constraints) {
|
|
12297
12813
|
return getVideoStream(constraints, this.call.tracer);
|
|
12298
12814
|
}
|
|
12299
12815
|
}
|
|
@@ -13621,9 +14137,10 @@ class Call {
|
|
|
13621
14137
|
this.sfuStatsReporter?.flush();
|
|
13622
14138
|
this.sfuStatsReporter?.stop();
|
|
13623
14139
|
this.sfuStatsReporter = undefined;
|
|
13624
|
-
this.
|
|
14140
|
+
this.lastStatsOptions = undefined;
|
|
14141
|
+
await this.subscriber?.dispose();
|
|
13625
14142
|
this.subscriber = undefined;
|
|
13626
|
-
this.publisher?.dispose();
|
|
14143
|
+
await this.publisher?.dispose();
|
|
13627
14144
|
this.publisher = undefined;
|
|
13628
14145
|
await this.sfuClient?.leaveAndClose(leaveReason);
|
|
13629
14146
|
this.sfuClient = undefined;
|
|
@@ -13899,15 +14416,17 @@ class Call {
|
|
|
13899
14416
|
const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
13900
14417
|
const performingRejoin = this.reconnectStrategy === WebsocketReconnectStrategy.REJOIN;
|
|
13901
14418
|
const performingFastReconnect = this.reconnectStrategy === WebsocketReconnectStrategy.FAST;
|
|
13902
|
-
let statsOptions = this.
|
|
14419
|
+
let statsOptions = this.lastStatsOptions;
|
|
13903
14420
|
if (!this.credentials ||
|
|
13904
14421
|
!statsOptions ||
|
|
13905
14422
|
performingRejoin ||
|
|
13906
|
-
performingMigration
|
|
14423
|
+
performingMigration ||
|
|
14424
|
+
data?.migrating_from) {
|
|
13907
14425
|
try {
|
|
13908
14426
|
const joinResponse = await this.doJoinRequest(data);
|
|
13909
14427
|
this.credentials = joinResponse.credentials;
|
|
13910
14428
|
statsOptions = joinResponse.stats_options;
|
|
14429
|
+
this.lastStatsOptions = statsOptions;
|
|
13911
14430
|
}
|
|
13912
14431
|
catch (error) {
|
|
13913
14432
|
// prevent triggering reconnect flow if the state is OFFLINE
|
|
@@ -14014,7 +14533,7 @@ class Call {
|
|
|
14014
14533
|
}
|
|
14015
14534
|
else {
|
|
14016
14535
|
const connectionConfig = toRtcConfiguration(this.credentials.ice_servers);
|
|
14017
|
-
this.initPublisherAndSubscriber({
|
|
14536
|
+
await this.initPublisherAndSubscriber({
|
|
14018
14537
|
sfuClient,
|
|
14019
14538
|
connectionConfig,
|
|
14020
14539
|
clientDetails,
|
|
@@ -14159,11 +14678,11 @@ class Call {
|
|
|
14159
14678
|
* Initializes the Publisher and Subscriber Peer Connections.
|
|
14160
14679
|
* @internal
|
|
14161
14680
|
*/
|
|
14162
|
-
this.initPublisherAndSubscriber = (opts) => {
|
|
14681
|
+
this.initPublisherAndSubscriber = async (opts) => {
|
|
14163
14682
|
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
14164
|
-
const { enable_rtc_stats: enableTracing } = statsOptions;
|
|
14683
|
+
const { enable_rtc_stats: enableTracing, reporting_interval_ms: reportingIntervalMs, } = statsOptions;
|
|
14165
14684
|
if (closePreviousInstances && this.subscriber) {
|
|
14166
|
-
this.subscriber.dispose();
|
|
14685
|
+
await this.subscriber.dispose();
|
|
14167
14686
|
}
|
|
14168
14687
|
const basePeerConnectionOptions = {
|
|
14169
14688
|
sfuClient,
|
|
@@ -14172,6 +14691,7 @@ class Call {
|
|
|
14172
14691
|
connectionConfig,
|
|
14173
14692
|
tag: sfuClient.tag,
|
|
14174
14693
|
enableTracing,
|
|
14694
|
+
statsTimestampDriftThresholdMs: reportingIntervalMs / 2,
|
|
14175
14695
|
clientPublishOptions: this.clientPublishOptions,
|
|
14176
14696
|
onReconnectionNeeded: (kind, reason, peerType) => {
|
|
14177
14697
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -14192,7 +14712,7 @@ class Call {
|
|
|
14192
14712
|
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
14193
14713
|
if (!isAnonymous) {
|
|
14194
14714
|
if (closePreviousInstances && this.publisher) {
|
|
14195
|
-
this.publisher.dispose();
|
|
14715
|
+
await this.publisher.dispose();
|
|
14196
14716
|
}
|
|
14197
14717
|
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
|
|
14198
14718
|
}
|
|
@@ -14295,10 +14815,17 @@ class Call {
|
|
|
14295
14815
|
* `ICE_NEVER_CONNECTED` increments the unsupported-network counter).
|
|
14296
14816
|
*/
|
|
14297
14817
|
this.reconnect = async (strategy, reason) => {
|
|
14298
|
-
if (this.state.callingState === exports.CallingState.
|
|
14818
|
+
if (this.state.callingState === exports.CallingState.JOINING ||
|
|
14819
|
+
this.state.callingState === exports.CallingState.RECONNECTING ||
|
|
14299
14820
|
this.state.callingState === exports.CallingState.MIGRATING ||
|
|
14300
14821
|
this.state.callingState === exports.CallingState.RECONNECTING_FAILED)
|
|
14301
14822
|
return;
|
|
14823
|
+
// Drop redundant reconnect calls. If a reconnect is already queued or
|
|
14824
|
+
// running for this Call, that entry will resolve whatever broke;
|
|
14825
|
+
// queueing more entries just replays the full REJOIN cycle (one extra
|
|
14826
|
+
// `POST /join` per entry) once the call is already healthy again.
|
|
14827
|
+
if (hasPending(this.reconnectConcurrencyTag))
|
|
14828
|
+
return;
|
|
14302
14829
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
14303
14830
|
const reconnectStartTime = Date.now();
|
|
14304
14831
|
this.reconnectStrategy = strategy;
|
|
@@ -14503,8 +15030,8 @@ class Call {
|
|
|
14503
15030
|
this.state.setCallingState(exports.CallingState.JOINED);
|
|
14504
15031
|
}
|
|
14505
15032
|
finally {
|
|
14506
|
-
currentSubscriber?.dispose();
|
|
14507
|
-
currentPublisher?.dispose();
|
|
15033
|
+
await currentSubscriber?.dispose();
|
|
15034
|
+
await currentPublisher?.dispose();
|
|
14508
15035
|
// and close the previous SFU client, without specifying close code
|
|
14509
15036
|
currentSfuClient.close(StreamSfuClient.NORMAL_CLOSURE, 'Migrating away');
|
|
14510
15037
|
}
|
|
@@ -14693,7 +15220,7 @@ class Call {
|
|
|
14693
15220
|
this.stopPublish = async (...trackTypes) => {
|
|
14694
15221
|
if (!this.sfuClient || !this.publisher)
|
|
14695
15222
|
return;
|
|
14696
|
-
this.publisher.stopTracks(...trackTypes);
|
|
15223
|
+
await this.publisher.stopTracks(...trackTypes);
|
|
14697
15224
|
await this.updateLocalStreamState(undefined, ...trackTypes);
|
|
14698
15225
|
};
|
|
14699
15226
|
/**
|
|
@@ -16806,7 +17333,7 @@ class StreamClient {
|
|
|
16806
17333
|
this.getUserAgent = () => {
|
|
16807
17334
|
if (!this.cachedUserAgent) {
|
|
16808
17335
|
const { clientAppIdentifier = {} } = this.options;
|
|
16809
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
17336
|
+
const { sdkName = 'js', sdkVersion = "1.52.0", ...extras } = clientAppIdentifier;
|
|
16810
17337
|
this.cachedUserAgent = [
|
|
16811
17338
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16812
17339
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -17532,6 +18059,7 @@ exports.getVideoDevices = getVideoDevices;
|
|
|
17532
18059
|
exports.getVideoStream = getVideoStream;
|
|
17533
18060
|
exports.getWebRTCInfo = getWebRTCInfo;
|
|
17534
18061
|
exports.hasAudio = hasAudio$1;
|
|
18062
|
+
exports.hasInterruptedTrack = hasInterruptedTrack;
|
|
17535
18063
|
exports.hasPausedTrack = hasPausedTrack;
|
|
17536
18064
|
exports.hasScreenShare = hasScreenShare;
|
|
17537
18065
|
exports.hasScreenShareAudio = hasScreenShareAudio;
|