@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.browser.es.js
CHANGED
|
@@ -1186,6 +1186,14 @@ var SdkType;
|
|
|
1186
1186
|
* @generated from protobuf enum value: SDK_TYPE_PLAIN_JAVASCRIPT = 9;
|
|
1187
1187
|
*/
|
|
1188
1188
|
SdkType[SdkType["PLAIN_JAVASCRIPT"] = 9] = "PLAIN_JAVASCRIPT";
|
|
1189
|
+
/**
|
|
1190
|
+
* @generated from protobuf enum value: SDK_TYPE_PYTHON = 10;
|
|
1191
|
+
*/
|
|
1192
|
+
SdkType[SdkType["PYTHON"] = 10] = "PYTHON";
|
|
1193
|
+
/**
|
|
1194
|
+
* @generated from protobuf enum value: SDK_TYPE_VISION_AGENTS = 11;
|
|
1195
|
+
*/
|
|
1196
|
+
SdkType[SdkType["VISION_AGENTS"] = 11] = "VISION_AGENTS";
|
|
1189
1197
|
})(SdkType || (SdkType = {}));
|
|
1190
1198
|
/**
|
|
1191
1199
|
* @generated from protobuf enum stream.video.sfu.models.TrackUnpublishReason
|
|
@@ -1397,6 +1405,12 @@ var ClientCapability;
|
|
|
1397
1405
|
* @generated from protobuf enum value: CLIENT_CAPABILITY_SUBSCRIBER_VIDEO_PAUSE = 1;
|
|
1398
1406
|
*/
|
|
1399
1407
|
ClientCapability[ClientCapability["SUBSCRIBER_VIDEO_PAUSE"] = 1] = "SUBSCRIBER_VIDEO_PAUSE";
|
|
1408
|
+
/**
|
|
1409
|
+
* Instructs SFU that stats will be sent to the coordinator
|
|
1410
|
+
*
|
|
1411
|
+
* @generated from protobuf enum value: CLIENT_CAPABILITY_COORDINATOR_STATS = 2;
|
|
1412
|
+
*/
|
|
1413
|
+
ClientCapability[ClientCapability["COORDINATOR_STATS"] = 2] = "COORDINATOR_STATS";
|
|
1400
1414
|
})(ClientCapability || (ClientCapability = {}));
|
|
1401
1415
|
/**
|
|
1402
1416
|
* DegradationPreference represents the RTCDegradationPreference from WebRTC.
|
|
@@ -1862,6 +1876,12 @@ class ClientDetails$Type extends MessageType {
|
|
|
1862
1876
|
{ no: 2, name: 'os', kind: 'message', T: () => OS },
|
|
1863
1877
|
{ no: 3, name: 'browser', kind: 'message', T: () => Browser },
|
|
1864
1878
|
{ no: 4, name: 'device', kind: 'message', T: () => Device },
|
|
1879
|
+
{
|
|
1880
|
+
no: 5,
|
|
1881
|
+
name: 'webrtc_version',
|
|
1882
|
+
kind: 'scalar',
|
|
1883
|
+
T: 9 /*ScalarType.STRING*/,
|
|
1884
|
+
},
|
|
1865
1885
|
]);
|
|
1866
1886
|
}
|
|
1867
1887
|
}
|
|
@@ -2134,6 +2154,171 @@ class PerformanceStats$Type extends MessageType {
|
|
|
2134
2154
|
* @generated MessageType for protobuf message stream.video.sfu.models.PerformanceStats
|
|
2135
2155
|
*/
|
|
2136
2156
|
const PerformanceStats = new PerformanceStats$Type();
|
|
2157
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2158
|
+
class RtpBase$Type extends MessageType {
|
|
2159
|
+
constructor() {
|
|
2160
|
+
super('stream.video.sfu.models.RtpBase', [
|
|
2161
|
+
{ no: 1, name: 'ssrc', kind: 'scalar', T: 13 /*ScalarType.UINT32*/ },
|
|
2162
|
+
{ no: 2, name: 'kind', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2163
|
+
{
|
|
2164
|
+
no: 3,
|
|
2165
|
+
name: 'timestamp_ms',
|
|
2166
|
+
kind: 'scalar',
|
|
2167
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2168
|
+
},
|
|
2169
|
+
]);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
/**
|
|
2173
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RtpBase
|
|
2174
|
+
*/
|
|
2175
|
+
const RtpBase = new RtpBase$Type();
|
|
2176
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2177
|
+
class InboundRtp$Type extends MessageType {
|
|
2178
|
+
constructor() {
|
|
2179
|
+
super('stream.video.sfu.models.InboundRtp', [
|
|
2180
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2181
|
+
{
|
|
2182
|
+
no: 2,
|
|
2183
|
+
name: 'jitter_seconds',
|
|
2184
|
+
kind: 'scalar',
|
|
2185
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
no: 3,
|
|
2189
|
+
name: 'packets_received',
|
|
2190
|
+
kind: 'scalar',
|
|
2191
|
+
T: 4 /*ScalarType.UINT64*/,
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
no: 4,
|
|
2195
|
+
name: 'packets_lost',
|
|
2196
|
+
kind: 'scalar',
|
|
2197
|
+
T: 4 /*ScalarType.UINT64*/,
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
no: 5,
|
|
2201
|
+
name: 'packet_loss_percent',
|
|
2202
|
+
kind: 'scalar',
|
|
2203
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
no: 10,
|
|
2207
|
+
name: 'concealment_events',
|
|
2208
|
+
kind: 'scalar',
|
|
2209
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2210
|
+
},
|
|
2211
|
+
{
|
|
2212
|
+
no: 11,
|
|
2213
|
+
name: 'concealment_percent',
|
|
2214
|
+
kind: 'scalar',
|
|
2215
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2216
|
+
},
|
|
2217
|
+
{ no: 20, name: 'fps', kind: 'scalar', T: 1 /*ScalarType.DOUBLE*/ },
|
|
2218
|
+
{
|
|
2219
|
+
no: 21,
|
|
2220
|
+
name: 'freeze_duration_seconds',
|
|
2221
|
+
kind: 'scalar',
|
|
2222
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
no: 22,
|
|
2226
|
+
name: 'avg_decode_time_seconds',
|
|
2227
|
+
kind: 'scalar',
|
|
2228
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
no: 23,
|
|
2232
|
+
name: 'min_dimension_px',
|
|
2233
|
+
kind: 'scalar',
|
|
2234
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2235
|
+
},
|
|
2236
|
+
]);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.InboundRtp
|
|
2241
|
+
*/
|
|
2242
|
+
const InboundRtp = new InboundRtp$Type();
|
|
2243
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2244
|
+
class OutboundRtp$Type extends MessageType {
|
|
2245
|
+
constructor() {
|
|
2246
|
+
super('stream.video.sfu.models.OutboundRtp', [
|
|
2247
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2248
|
+
{ no: 10, name: 'fps', kind: 'scalar', T: 1 /*ScalarType.DOUBLE*/ },
|
|
2249
|
+
{
|
|
2250
|
+
no: 11,
|
|
2251
|
+
name: 'avg_encode_time_seconds',
|
|
2252
|
+
kind: 'scalar',
|
|
2253
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
no: 12,
|
|
2257
|
+
name: 'bitrate_bps',
|
|
2258
|
+
kind: 'scalar',
|
|
2259
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
no: 13,
|
|
2263
|
+
name: 'min_dimension_px',
|
|
2264
|
+
kind: 'scalar',
|
|
2265
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2266
|
+
},
|
|
2267
|
+
]);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
/**
|
|
2271
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.OutboundRtp
|
|
2272
|
+
*/
|
|
2273
|
+
const OutboundRtp = new OutboundRtp$Type();
|
|
2274
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2275
|
+
class RemoteInboundRtp$Type extends MessageType {
|
|
2276
|
+
constructor() {
|
|
2277
|
+
super('stream.video.sfu.models.RemoteInboundRtp', [
|
|
2278
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2279
|
+
{
|
|
2280
|
+
no: 2,
|
|
2281
|
+
name: 'jitter_seconds',
|
|
2282
|
+
kind: 'scalar',
|
|
2283
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2284
|
+
},
|
|
2285
|
+
{
|
|
2286
|
+
no: 3,
|
|
2287
|
+
name: 'round_trip_time_s',
|
|
2288
|
+
kind: 'scalar',
|
|
2289
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2290
|
+
},
|
|
2291
|
+
]);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RemoteInboundRtp
|
|
2296
|
+
*/
|
|
2297
|
+
const RemoteInboundRtp = new RemoteInboundRtp$Type();
|
|
2298
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2299
|
+
class RemoteOutboundRtp$Type extends MessageType {
|
|
2300
|
+
constructor() {
|
|
2301
|
+
super('stream.video.sfu.models.RemoteOutboundRtp', [
|
|
2302
|
+
{ no: 1, name: 'base', kind: 'message', T: () => RtpBase },
|
|
2303
|
+
{
|
|
2304
|
+
no: 2,
|
|
2305
|
+
name: 'jitter_seconds',
|
|
2306
|
+
kind: 'scalar',
|
|
2307
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
no: 3,
|
|
2311
|
+
name: 'round_trip_time_s',
|
|
2312
|
+
kind: 'scalar',
|
|
2313
|
+
T: 1 /*ScalarType.DOUBLE*/,
|
|
2314
|
+
},
|
|
2315
|
+
]);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* @generated MessageType for protobuf message stream.video.sfu.models.RemoteOutboundRtp
|
|
2320
|
+
*/
|
|
2321
|
+
const RemoteOutboundRtp = new RemoteOutboundRtp$Type();
|
|
2137
2322
|
|
|
2138
2323
|
var models = /*#__PURE__*/Object.freeze({
|
|
2139
2324
|
__proto__: null,
|
|
@@ -2158,8 +2343,10 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
2158
2343
|
get ErrorCode () { return ErrorCode; },
|
|
2159
2344
|
get GoAwayReason () { return GoAwayReason; },
|
|
2160
2345
|
ICETrickle: ICETrickle$1,
|
|
2346
|
+
InboundRtp: InboundRtp,
|
|
2161
2347
|
InputDevices: InputDevices,
|
|
2162
2348
|
OS: OS,
|
|
2349
|
+
OutboundRtp: OutboundRtp,
|
|
2163
2350
|
Participant: Participant,
|
|
2164
2351
|
ParticipantCount: ParticipantCount,
|
|
2165
2352
|
get ParticipantSource () { return ParticipantSource; },
|
|
@@ -2168,6 +2355,9 @@ var models = /*#__PURE__*/Object.freeze({
|
|
|
2168
2355
|
Pin: Pin,
|
|
2169
2356
|
PublishOption: PublishOption,
|
|
2170
2357
|
RTMPIngress: RTMPIngress,
|
|
2358
|
+
RemoteInboundRtp: RemoteInboundRtp,
|
|
2359
|
+
RemoteOutboundRtp: RemoteOutboundRtp,
|
|
2360
|
+
RtpBase: RtpBase,
|
|
2171
2361
|
Sdk: Sdk,
|
|
2172
2362
|
get SdkType () { return SdkType; },
|
|
2173
2363
|
StreamQuality: StreamQuality,
|
|
@@ -2285,6 +2475,62 @@ class Telemetry$Type extends MessageType {
|
|
|
2285
2475
|
*/
|
|
2286
2476
|
const Telemetry = new Telemetry$Type();
|
|
2287
2477
|
// @generated message type with reflection information, may provide speed optimized methods
|
|
2478
|
+
class SendMetricsRequest$Type extends MessageType {
|
|
2479
|
+
constructor() {
|
|
2480
|
+
super('stream.video.sfu.signal.SendMetricsRequest', [
|
|
2481
|
+
{ no: 1, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2482
|
+
{
|
|
2483
|
+
no: 2,
|
|
2484
|
+
name: 'unified_session_id',
|
|
2485
|
+
kind: 'scalar',
|
|
2486
|
+
T: 9 /*ScalarType.STRING*/,
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
no: 3,
|
|
2490
|
+
name: 'inbounds',
|
|
2491
|
+
kind: 'message',
|
|
2492
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2493
|
+
T: () => InboundRtp,
|
|
2494
|
+
},
|
|
2495
|
+
{
|
|
2496
|
+
no: 4,
|
|
2497
|
+
name: 'outbounds',
|
|
2498
|
+
kind: 'message',
|
|
2499
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2500
|
+
T: () => OutboundRtp,
|
|
2501
|
+
},
|
|
2502
|
+
{
|
|
2503
|
+
no: 5,
|
|
2504
|
+
name: 'remote_inbounds',
|
|
2505
|
+
kind: 'message',
|
|
2506
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2507
|
+
T: () => RemoteInboundRtp,
|
|
2508
|
+
},
|
|
2509
|
+
{
|
|
2510
|
+
no: 6,
|
|
2511
|
+
name: 'remote_outbounds',
|
|
2512
|
+
kind: 'message',
|
|
2513
|
+
repeat: 2 /*RepeatType.UNPACKED*/,
|
|
2514
|
+
T: () => RemoteOutboundRtp,
|
|
2515
|
+
},
|
|
2516
|
+
]);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
/**
|
|
2520
|
+
* @generated MessageType for protobuf message stream.video.sfu.signal.SendMetricsRequest
|
|
2521
|
+
*/
|
|
2522
|
+
const SendMetricsRequest = new SendMetricsRequest$Type();
|
|
2523
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2524
|
+
class SendMetricsResponse$Type extends MessageType {
|
|
2525
|
+
constructor() {
|
|
2526
|
+
super('stream.video.sfu.signal.SendMetricsResponse', []);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* @generated MessageType for protobuf message stream.video.sfu.signal.SendMetricsResponse
|
|
2531
|
+
*/
|
|
2532
|
+
const SendMetricsResponse = new SendMetricsResponse$Type();
|
|
2533
|
+
// @generated message type with reflection information, may provide speed optimized methods
|
|
2288
2534
|
class SendStatsRequest$Type extends MessageType {
|
|
2289
2535
|
constructor() {
|
|
2290
2536
|
super('stream.video.sfu.signal.SendStatsRequest', [
|
|
@@ -2558,6 +2804,12 @@ class SendAnswerRequest$Type extends MessageType {
|
|
|
2558
2804
|
},
|
|
2559
2805
|
{ no: 2, name: 'sdp', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2560
2806
|
{ no: 3, name: 'session_id', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
2807
|
+
{
|
|
2808
|
+
no: 4,
|
|
2809
|
+
name: 'negotiation_id',
|
|
2810
|
+
kind: 'scalar',
|
|
2811
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
2812
|
+
},
|
|
2561
2813
|
]);
|
|
2562
2814
|
}
|
|
2563
2815
|
}
|
|
@@ -2665,6 +2917,12 @@ const SignalServer = new ServiceType('stream.video.sfu.signal.SignalServer', [
|
|
|
2665
2917
|
I: SendStatsRequest,
|
|
2666
2918
|
O: SendStatsResponse,
|
|
2667
2919
|
},
|
|
2920
|
+
{
|
|
2921
|
+
name: 'SendMetrics',
|
|
2922
|
+
options: {},
|
|
2923
|
+
I: SendMetricsRequest,
|
|
2924
|
+
O: SendMetricsResponse,
|
|
2925
|
+
},
|
|
2668
2926
|
{
|
|
2669
2927
|
name: 'StartNoiseCancellation',
|
|
2670
2928
|
options: {},
|
|
@@ -3345,6 +3603,12 @@ class SubscriberOffer$Type extends MessageType {
|
|
|
3345
3603
|
super('stream.video.sfu.event.SubscriberOffer', [
|
|
3346
3604
|
{ no: 1, name: 'ice_restart', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
|
|
3347
3605
|
{ no: 2, name: 'sdp', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
|
|
3606
|
+
{
|
|
3607
|
+
no: 3,
|
|
3608
|
+
name: 'negotiation_id',
|
|
3609
|
+
kind: 'scalar',
|
|
3610
|
+
T: 13 /*ScalarType.UINT32*/,
|
|
3611
|
+
},
|
|
3348
3612
|
]);
|
|
3349
3613
|
}
|
|
3350
3614
|
}
|
|
@@ -3803,18 +4067,25 @@ class SignalServerClient {
|
|
|
3803
4067
|
const method = this.methods[6], opt = this._transport.mergeOptions(options);
|
|
3804
4068
|
return stackIntercept('unary', this._transport, method, opt, input);
|
|
3805
4069
|
}
|
|
4070
|
+
/**
|
|
4071
|
+
* @generated from protobuf rpc: SendMetrics(stream.video.sfu.signal.SendMetricsRequest) returns (stream.video.sfu.signal.SendMetricsResponse);
|
|
4072
|
+
*/
|
|
4073
|
+
sendMetrics(input, options) {
|
|
4074
|
+
const method = this.methods[7], opt = this._transport.mergeOptions(options);
|
|
4075
|
+
return stackIntercept('unary', this._transport, method, opt, input);
|
|
4076
|
+
}
|
|
3806
4077
|
/**
|
|
3807
4078
|
* @generated from protobuf rpc: StartNoiseCancellation(stream.video.sfu.signal.StartNoiseCancellationRequest) returns (stream.video.sfu.signal.StartNoiseCancellationResponse);
|
|
3808
4079
|
*/
|
|
3809
4080
|
startNoiseCancellation(input, options) {
|
|
3810
|
-
const method = this.methods[
|
|
4081
|
+
const method = this.methods[8], opt = this._transport.mergeOptions(options);
|
|
3811
4082
|
return stackIntercept('unary', this._transport, method, opt, input);
|
|
3812
4083
|
}
|
|
3813
4084
|
/**
|
|
3814
4085
|
* @generated from protobuf rpc: StopNoiseCancellation(stream.video.sfu.signal.StopNoiseCancellationRequest) returns (stream.video.sfu.signal.StopNoiseCancellationResponse);
|
|
3815
4086
|
*/
|
|
3816
4087
|
stopNoiseCancellation(input, options) {
|
|
3817
|
-
const method = this.methods[
|
|
4088
|
+
const method = this.methods[9], opt = this._transport.mergeOptions(options);
|
|
3818
4089
|
return stackIntercept('unary', this._transport, method, opt, input);
|
|
3819
4090
|
}
|
|
3820
4091
|
}
|
|
@@ -4994,6 +5265,16 @@ const hasScreenShareAudio = (p) => p.publishedTracks.includes(TrackType.SCREEN_S
|
|
|
4994
5265
|
* @param p the participant.
|
|
4995
5266
|
*/
|
|
4996
5267
|
const isPinned = (p) => !!p.pin && (p.pin.isLocalPin || p.pin.pinnedAt > 0);
|
|
5268
|
+
/**
|
|
5269
|
+
* Check if a participant has a track that is currently interrupted: the
|
|
5270
|
+
* participant intends to publish it (it is in `publishedTracks`) but no
|
|
5271
|
+
* media is flowing right now (it is in `interruptedTracks`).
|
|
5272
|
+
*
|
|
5273
|
+
* @param p the participant to check.
|
|
5274
|
+
* @param trackType the track type to check.
|
|
5275
|
+
*/
|
|
5276
|
+
const hasInterruptedTrack = (p, trackType) => !!p.interruptedTracks?.includes(trackType) &&
|
|
5277
|
+
p.publishedTracks.includes(trackType);
|
|
4997
5278
|
/**
|
|
4998
5279
|
* Check if a participant has a paused track of the specified type.
|
|
4999
5280
|
*
|
|
@@ -6359,7 +6640,7 @@ const getSdkVersion = (sdk) => {
|
|
|
6359
6640
|
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
6360
6641
|
};
|
|
6361
6642
|
|
|
6362
|
-
const version = "1.
|
|
6643
|
+
const version = "1.52.0";
|
|
6363
6644
|
const [major, minor, patch] = version.split('.');
|
|
6364
6645
|
let sdkInfo = {
|
|
6365
6646
|
type: SdkType.PLAIN_JAVASCRIPT,
|
|
@@ -6457,6 +6738,7 @@ const getClientDetails = async () => {
|
|
|
6457
6738
|
sdk: sdkInfo,
|
|
6458
6739
|
os: osInfo,
|
|
6459
6740
|
device: deviceInfo,
|
|
6741
|
+
webrtcVersion: webRtcInfo?.version || '',
|
|
6460
6742
|
};
|
|
6461
6743
|
}
|
|
6462
6744
|
// @ts-expect-error - userAgentData is not yet in the TS types
|
|
@@ -6484,11 +6766,12 @@ const getClientDetails = async () => {
|
|
|
6484
6766
|
// Eliminates the generic "Chromium" name and "Not)A_Brand" name from the list.
|
|
6485
6767
|
// https://wicg.github.io/ua-client-hints/#create-arbitrary-brands-section
|
|
6486
6768
|
const uaBrowser = userAgentData?.fullVersionList?.find((v) => !v.brand.includes('Chromium') && !v.brand.match(/[()\-./:;=?_]/g));
|
|
6769
|
+
const browserVersion = uaBrowser?.version || browser.version || '';
|
|
6487
6770
|
return {
|
|
6488
6771
|
sdk: sdkInfo,
|
|
6489
6772
|
browser: {
|
|
6490
6773
|
name: uaBrowser?.brand || browser.name || navigator.userAgent,
|
|
6491
|
-
version:
|
|
6774
|
+
version: browserVersion,
|
|
6492
6775
|
},
|
|
6493
6776
|
os: {
|
|
6494
6777
|
name: userAgentData?.platform || os.name || '',
|
|
@@ -6501,6 +6784,7 @@ const getClientDetails = async () => {
|
|
|
6501
6784
|
.join(' '),
|
|
6502
6785
|
version: '',
|
|
6503
6786
|
},
|
|
6787
|
+
webrtcVersion: browserVersion,
|
|
6504
6788
|
};
|
|
6505
6789
|
};
|
|
6506
6790
|
|
|
@@ -7164,7 +7448,7 @@ class StatsTracer {
|
|
|
7164
7448
|
/**
|
|
7165
7449
|
* Creates a new StatsTracer instance.
|
|
7166
7450
|
*/
|
|
7167
|
-
constructor(pc, peerType, trackIdToTrackType) {
|
|
7451
|
+
constructor(pc, peerType, trackIdToTrackType, statsTimestampDriftThresholdMs = 0) {
|
|
7168
7452
|
this.previousStats = {};
|
|
7169
7453
|
this.frameTimeHistory = [];
|
|
7170
7454
|
this.fpsHistory = [];
|
|
@@ -7178,7 +7462,7 @@ class StatsTracer {
|
|
|
7178
7462
|
*/
|
|
7179
7463
|
this.get = async () => {
|
|
7180
7464
|
const stats = await this.pc.getStats();
|
|
7181
|
-
const currentStats =
|
|
7465
|
+
const currentStats = toObjectWithCorrectedTimestamp(stats, Date.now(), this.driftThresholdMs);
|
|
7182
7466
|
const performanceStats = this.withOverrides(this.peerType === PeerType.SUBSCRIBER
|
|
7183
7467
|
? this.getDecodeStats(currentStats)
|
|
7184
7468
|
: this.getEncodeStats(currentStats));
|
|
@@ -7297,17 +7581,28 @@ class StatsTracer {
|
|
|
7297
7581
|
this.pc = pc;
|
|
7298
7582
|
this.peerType = peerType;
|
|
7299
7583
|
this.trackIdToTrackType = trackIdToTrackType;
|
|
7584
|
+
this.driftThresholdMs = statsTimestampDriftThresholdMs;
|
|
7300
7585
|
}
|
|
7301
7586
|
}
|
|
7302
7587
|
/**
|
|
7303
|
-
* Convert the stat report to an object.
|
|
7588
|
+
* Convert the stat report to an object, correcting clock drift along the way.
|
|
7589
|
+
* Entries whose `timestamp` differs from `wallNow` by more than `thresholdMs`
|
|
7590
|
+
* are replaced with a clone whose `timestamp` is set to `wallNow`. The platform
|
|
7591
|
+
* clock backing `DOMHighResTimeStamp` can desynchronise from `Date.now()` after
|
|
7592
|
+
* system sleep or clock-jump events (notably on Electron/Chromium), which
|
|
7593
|
+
* corrupts the delta-compressed stats payload. A non-positive `thresholdMs`
|
|
7594
|
+
* disables correction.
|
|
7304
7595
|
*
|
|
7305
7596
|
* @param report the stat report to convert.
|
|
7597
|
+
* @param wallNow current wall-clock time used as the drift reference.
|
|
7598
|
+
* @param thresholdMs maximum tolerated drift in milliseconds.
|
|
7306
7599
|
*/
|
|
7307
|
-
const
|
|
7600
|
+
const toObjectWithCorrectedTimestamp = (report, wallNow, thresholdMs) => {
|
|
7308
7601
|
const obj = {};
|
|
7602
|
+
const correct = thresholdMs > 0;
|
|
7309
7603
|
report.forEach((v, k) => {
|
|
7310
|
-
|
|
7604
|
+
const drift = Math.abs(v.timestamp - wallNow);
|
|
7605
|
+
obj[k] = correct && drift > thresholdMs ? { ...v, timestamp: wallNow } : v;
|
|
7311
7606
|
});
|
|
7312
7607
|
return obj;
|
|
7313
7608
|
};
|
|
@@ -7439,7 +7734,7 @@ class BasePeerConnection {
|
|
|
7439
7734
|
/**
|
|
7440
7735
|
* Constructs a new `BasePeerConnection` instance.
|
|
7441
7736
|
*/
|
|
7442
|
-
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, }) {
|
|
7737
|
+
constructor(peerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, tag, enableTracing, clientPublishOptions, iceRestartDelay = 2500, statsTimestampDriftThresholdMs = 0, }) {
|
|
7443
7738
|
this.iceHasEverConnected = false;
|
|
7444
7739
|
this.isIceRestarting = false;
|
|
7445
7740
|
this.isDisposed = false;
|
|
@@ -7478,7 +7773,7 @@ class BasePeerConnection {
|
|
|
7478
7773
|
this.on = (event, fn) => {
|
|
7479
7774
|
const getTag = () => this.tag;
|
|
7480
7775
|
this.subscriptions.push(this.dispatcher.on(event, getTag, (e) => {
|
|
7481
|
-
const lockKey =
|
|
7776
|
+
const lockKey = this.eventLockKey(event);
|
|
7482
7777
|
withoutConcurrency(lockKey, async () => fn(e)).catch((err) => {
|
|
7483
7778
|
if (this.isDisposed)
|
|
7484
7779
|
return;
|
|
@@ -7486,6 +7781,13 @@ class BasePeerConnection {
|
|
|
7486
7781
|
});
|
|
7487
7782
|
}));
|
|
7488
7783
|
};
|
|
7784
|
+
/**
|
|
7785
|
+
* Returns the per-event `withoutConcurrency` tag used to serialize the
|
|
7786
|
+
* dispatcher handler for `event` on this peer connection.
|
|
7787
|
+
*/
|
|
7788
|
+
this.eventLockKey = (event) => {
|
|
7789
|
+
return `pc.${this.lock}.${event}`;
|
|
7790
|
+
};
|
|
7489
7791
|
/**
|
|
7490
7792
|
* Appends the trickled ICE candidates to the `RTCPeerConnection`.
|
|
7491
7793
|
*/
|
|
@@ -7726,7 +8028,7 @@ class BasePeerConnection {
|
|
|
7726
8028
|
this.onIceConnected = onIceConnected;
|
|
7727
8029
|
this.logger = videoLoggerSystem.getLogger(peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher', { tags: [tag] });
|
|
7728
8030
|
this.pc = this.createPeerConnection(connectionConfig);
|
|
7729
|
-
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType);
|
|
8031
|
+
this.stats = new StatsTracer(this.pc, peerType, this.trackIdToTrackType, statsTimestampDriftThresholdMs);
|
|
7730
8032
|
if (enableTracing) {
|
|
7731
8033
|
this.tracer = new Tracer(`${tag}-${peerType === PeerType.SUBSCRIBER ? 'sub' : 'pub'}`);
|
|
7732
8034
|
this.tracer.trace('create', {
|
|
@@ -7739,7 +8041,7 @@ class BasePeerConnection {
|
|
|
7739
8041
|
/**
|
|
7740
8042
|
* Disposes the `RTCPeerConnection` instance.
|
|
7741
8043
|
*/
|
|
7742
|
-
dispose() {
|
|
8044
|
+
async dispose() {
|
|
7743
8045
|
clearTimeout(this.iceRestartTimeout);
|
|
7744
8046
|
this.iceRestartTimeout = undefined;
|
|
7745
8047
|
clearTimeout(this.preConnectStuckTimeout);
|
|
@@ -7761,6 +8063,7 @@ class BasePeerConnection {
|
|
|
7761
8063
|
pc.removeEventListener('signalingstatechange', this.onSignalingChange);
|
|
7762
8064
|
pc.removeEventListener('iceconnectionstatechange', this.onIceConnectionStateChange);
|
|
7763
8065
|
pc.removeEventListener('icegatheringstatechange', this.onIceGatherChange);
|
|
8066
|
+
pc.removeEventListener('connectionstatechange', this.onConnectionStateChange);
|
|
7764
8067
|
this.unsubscribeIceTrickle?.();
|
|
7765
8068
|
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
7766
8069
|
this.subscriptions = [];
|
|
@@ -7788,8 +8091,14 @@ class TransceiverCache {
|
|
|
7788
8091
|
* Gets the transceiver for the given publish option.
|
|
7789
8092
|
*/
|
|
7790
8093
|
this.get = (publishOption) => {
|
|
7791
|
-
return this.
|
|
7792
|
-
|
|
8094
|
+
return this.getBy(publishOption.id, publishOption.trackType);
|
|
8095
|
+
};
|
|
8096
|
+
/**
|
|
8097
|
+
* Gets the transceiver for the given publish option id and track type.
|
|
8098
|
+
*/
|
|
8099
|
+
this.getBy = (publishOptionId, trackType) => {
|
|
8100
|
+
return this.cache.find((bundle) => bundle.publishOption.id === publishOptionId &&
|
|
8101
|
+
bundle.publishOption.trackType === trackType);
|
|
7793
8102
|
};
|
|
7794
8103
|
/**
|
|
7795
8104
|
* Updates the cached bundle with the given patch.
|
|
@@ -8074,6 +8383,21 @@ const toRTCDegradationPreference = (preference) => {
|
|
|
8074
8383
|
ensureExhausted(preference, 'Unknown degradation preference');
|
|
8075
8384
|
}
|
|
8076
8385
|
};
|
|
8386
|
+
const fromRTCDegradationPreference = (preference) => {
|
|
8387
|
+
switch (preference) {
|
|
8388
|
+
case 'balanced':
|
|
8389
|
+
return DegradationPreference.BALANCED;
|
|
8390
|
+
case 'maintain-framerate':
|
|
8391
|
+
return DegradationPreference.MAINTAIN_FRAMERATE;
|
|
8392
|
+
case 'maintain-resolution':
|
|
8393
|
+
return DegradationPreference.MAINTAIN_RESOLUTION;
|
|
8394
|
+
// @ts-expect-error not in the typedefs yet
|
|
8395
|
+
case 'maintain-framerate-and-resolution':
|
|
8396
|
+
return DegradationPreference.MAINTAIN_FRAMERATE_AND_RESOLUTION;
|
|
8397
|
+
default:
|
|
8398
|
+
return DegradationPreference.UNSPECIFIED;
|
|
8399
|
+
}
|
|
8400
|
+
};
|
|
8077
8401
|
|
|
8078
8402
|
/**
|
|
8079
8403
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
@@ -8108,13 +8432,13 @@ class Publisher extends BasePeerConnection {
|
|
|
8108
8432
|
// create a clone of the track as otherwise the same trackId will
|
|
8109
8433
|
// appear in the SDP in multiple transceivers
|
|
8110
8434
|
const trackToPublish = this.cloneTrack(track);
|
|
8111
|
-
const
|
|
8112
|
-
if (!
|
|
8435
|
+
const bundle = this.transceiverCache.get(publishOption);
|
|
8436
|
+
if (!bundle) {
|
|
8113
8437
|
await this.addTransceiver(trackToPublish, publishOption, options);
|
|
8114
8438
|
}
|
|
8115
8439
|
else {
|
|
8116
|
-
const previousTrack = transceiver.sender.track;
|
|
8117
|
-
await this.updateTransceiver(
|
|
8440
|
+
const previousTrack = bundle.transceiver.sender.track;
|
|
8441
|
+
await this.updateTransceiver(bundle, trackToPublish, options);
|
|
8118
8442
|
if (!isReactNative()) {
|
|
8119
8443
|
this.stopTrack(previousTrack);
|
|
8120
8444
|
}
|
|
@@ -8149,13 +8473,20 @@ class Publisher extends BasePeerConnection {
|
|
|
8149
8473
|
/**
|
|
8150
8474
|
* Updates the transceiver with the given track and track type.
|
|
8151
8475
|
*/
|
|
8152
|
-
this.updateTransceiver = async (
|
|
8476
|
+
this.updateTransceiver = async (bundle, track, options = {}) => {
|
|
8477
|
+
const { transceiver, publishOption } = bundle;
|
|
8478
|
+
const trackType = publishOption.trackType;
|
|
8153
8479
|
const sender = transceiver.sender;
|
|
8154
8480
|
if (sender.track)
|
|
8155
8481
|
this.trackIdToTrackType.delete(sender.track.id);
|
|
8156
8482
|
await sender.replaceTrack(track);
|
|
8157
|
-
if (track)
|
|
8483
|
+
if (track) {
|
|
8158
8484
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
8485
|
+
if (isFirefox() && bundle.videoSender) {
|
|
8486
|
+
// restore the encoding config from the cache, if any
|
|
8487
|
+
await this.changePublishQuality(bundle.videoSender, bundle);
|
|
8488
|
+
}
|
|
8489
|
+
}
|
|
8159
8490
|
if (isAudioTrackType(trackType)) {
|
|
8160
8491
|
await this.updateAudioPublishOptions(trackType, options);
|
|
8161
8492
|
}
|
|
@@ -8215,7 +8546,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8215
8546
|
continue;
|
|
8216
8547
|
// it is safe to stop the track here, it is a clone
|
|
8217
8548
|
this.stopTrack(transceiver.sender.track);
|
|
8218
|
-
await this.updateTransceiver(
|
|
8549
|
+
await this.updateTransceiver(item, null);
|
|
8219
8550
|
}
|
|
8220
8551
|
};
|
|
8221
8552
|
/**
|
|
@@ -8272,33 +8603,38 @@ class Publisher extends BasePeerConnection {
|
|
|
8272
8603
|
/**
|
|
8273
8604
|
* Stops the cloned track that is being published to the SFU.
|
|
8274
8605
|
*/
|
|
8275
|
-
this.stopTracks = (...trackTypes) => {
|
|
8276
|
-
|
|
8277
|
-
const
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8606
|
+
this.stopTracks = async (...trackTypes) => {
|
|
8607
|
+
return withoutConcurrency(this.eventLockKey('changePublishQuality'), async () => {
|
|
8608
|
+
for (const item of this.transceiverCache.items()) {
|
|
8609
|
+
const { publishOption, transceiver } = item;
|
|
8610
|
+
if (!trackTypes.includes(publishOption.trackType))
|
|
8611
|
+
continue;
|
|
8612
|
+
const track = transceiver.sender.track;
|
|
8613
|
+
await this.silenceSenderOnFirefox(item);
|
|
8614
|
+
this.stopTrack(track);
|
|
8615
|
+
}
|
|
8616
|
+
});
|
|
8282
8617
|
};
|
|
8283
8618
|
/**
|
|
8284
8619
|
* Stops all the cloned tracks that are being published to the SFU.
|
|
8285
8620
|
*/
|
|
8286
|
-
this.stopAllTracks = () => {
|
|
8287
|
-
|
|
8288
|
-
this.
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8621
|
+
this.stopAllTracks = async () => {
|
|
8622
|
+
return withoutConcurrency(this.eventLockKey('changePublishQuality'), async () => {
|
|
8623
|
+
for (const item of this.transceiverCache.items()) {
|
|
8624
|
+
const track = item.transceiver.sender.track;
|
|
8625
|
+
await this.silenceSenderOnFirefox(item);
|
|
8626
|
+
this.stopTrack(track);
|
|
8627
|
+
}
|
|
8628
|
+
for (const track of this.clonedTracks) {
|
|
8629
|
+
this.stopTrack(track);
|
|
8630
|
+
}
|
|
8631
|
+
});
|
|
8293
8632
|
};
|
|
8294
|
-
this.changePublishQuality = async (videoSender) => {
|
|
8295
|
-
const
|
|
8296
|
-
const enabledLayers = layers.filter((l) => l.active);
|
|
8633
|
+
this.changePublishQuality = async (videoSender, bundle) => {
|
|
8634
|
+
const enabledLayers = videoSender.layers.filter((l) => l.active);
|
|
8297
8635
|
const tag = 'Update publish quality:';
|
|
8298
8636
|
this.logger.info(`${tag} requested layers by SFU:`, enabledLayers);
|
|
8299
|
-
const
|
|
8300
|
-
t.publishOption.trackType === trackType);
|
|
8301
|
-
const sender = transceiverId?.transceiver.sender;
|
|
8637
|
+
const sender = bundle?.transceiver.sender;
|
|
8302
8638
|
if (!sender) {
|
|
8303
8639
|
return this.logger.warn(`${tag} no video sender found.`);
|
|
8304
8640
|
}
|
|
@@ -8306,7 +8642,7 @@ class Publisher extends BasePeerConnection {
|
|
|
8306
8642
|
if (params.encodings.length === 0) {
|
|
8307
8643
|
return this.logger.warn(`${tag} there are no encodings set.`);
|
|
8308
8644
|
}
|
|
8309
|
-
const codecInUse =
|
|
8645
|
+
const codecInUse = bundle?.publishOption.codec?.name;
|
|
8310
8646
|
const usesSvcCodec = codecInUse && isSvcCodec(codecInUse);
|
|
8311
8647
|
let changed = false;
|
|
8312
8648
|
for (const encoder of params.encodings) {
|
|
@@ -8506,6 +8842,72 @@ class Publisher extends BasePeerConnection {
|
|
|
8506
8842
|
track.stop();
|
|
8507
8843
|
this.clonedTracks.delete(track);
|
|
8508
8844
|
};
|
|
8845
|
+
/**
|
|
8846
|
+
* Silences a Firefox sender on the wire during unpublish.
|
|
8847
|
+
*
|
|
8848
|
+
* Firefox keeps emitting RTP after track.stop(), but the right lever
|
|
8849
|
+
* differs by track type:
|
|
8850
|
+
* - audio: `replaceTrack(null)` is the only reliable silencer;
|
|
8851
|
+
* `setParameters({encodings:[...active:false]})` does NOT stop
|
|
8852
|
+
* the Opus encoder.
|
|
8853
|
+
* - video: `setParameters({encodings:[...active:false]})` pauses
|
|
8854
|
+
* the encoder; `replaceTrack(null)` does NOT reliably stop the
|
|
8855
|
+
* video encoder. The prior active=true configuration is captured
|
|
8856
|
+
* onto `bundle.videoSender` so `updateTransceiver` can restore
|
|
8857
|
+
* it on the next publish.
|
|
8858
|
+
*
|
|
8859
|
+
* No-op on non-Firefox browsers and during teardown.
|
|
8860
|
+
*/
|
|
8861
|
+
this.silenceSenderOnFirefox = async (bundle) => {
|
|
8862
|
+
if (this.isDisposed || !isFirefox())
|
|
8863
|
+
return;
|
|
8864
|
+
const { transceiver, publishOption } = bundle;
|
|
8865
|
+
if (isAudioTrackType(publishOption.trackType)) {
|
|
8866
|
+
await transceiver.sender.replaceTrack(null).catch((err) => {
|
|
8867
|
+
this.logger.warn('Failed to clear audio sender track', err);
|
|
8868
|
+
});
|
|
8869
|
+
return;
|
|
8870
|
+
}
|
|
8871
|
+
await this.disableAllEncodings(bundle);
|
|
8872
|
+
};
|
|
8873
|
+
this.disableAllEncodings = async (bundle) => {
|
|
8874
|
+
const { transceiver, publishOption } = bundle;
|
|
8875
|
+
const sender = transceiver.sender;
|
|
8876
|
+
const params = sender.getParameters();
|
|
8877
|
+
if (!params.encodings || params.encodings.length === 0)
|
|
8878
|
+
return;
|
|
8879
|
+
if (!bundle.videoSender) {
|
|
8880
|
+
this.transceiverCache.update(publishOption, {
|
|
8881
|
+
videoSender: {
|
|
8882
|
+
trackType: publishOption.trackType,
|
|
8883
|
+
publishOptionId: publishOption.id,
|
|
8884
|
+
codec: publishOption.codec,
|
|
8885
|
+
degradationPreference: fromRTCDegradationPreference(params.degradationPreference),
|
|
8886
|
+
layers: params.encodings.map((e) => ({
|
|
8887
|
+
name: e.rid ?? 'q',
|
|
8888
|
+
active: e.active ?? true,
|
|
8889
|
+
maxBitrate: e.maxBitrate ?? 0,
|
|
8890
|
+
scaleResolutionDownBy: e.scaleResolutionDownBy ?? 0,
|
|
8891
|
+
maxFramerate: e.maxFramerate ?? 0,
|
|
8892
|
+
// @ts-expect-error scalabilityMode is not in the typedefs yet
|
|
8893
|
+
scalabilityMode: e.scalabilityMode ?? '',
|
|
8894
|
+
})),
|
|
8895
|
+
},
|
|
8896
|
+
});
|
|
8897
|
+
}
|
|
8898
|
+
let changed = false;
|
|
8899
|
+
for (const encoding of params.encodings) {
|
|
8900
|
+
if (encoding.active !== false) {
|
|
8901
|
+
encoding.active = false;
|
|
8902
|
+
changed = true;
|
|
8903
|
+
}
|
|
8904
|
+
}
|
|
8905
|
+
if (!changed)
|
|
8906
|
+
return;
|
|
8907
|
+
await sender.setParameters(params).catch((err) => {
|
|
8908
|
+
this.logger.error('Failed to disable video sender encodings:', err);
|
|
8909
|
+
});
|
|
8910
|
+
};
|
|
8509
8911
|
this.publishOptions = publishOptions;
|
|
8510
8912
|
this.on('iceRestart', (iceRestart) => {
|
|
8511
8913
|
if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED)
|
|
@@ -8514,7 +8916,16 @@ class Publisher extends BasePeerConnection {
|
|
|
8514
8916
|
});
|
|
8515
8917
|
this.on('changePublishQuality', async (event) => {
|
|
8516
8918
|
for (const videoSender of event.videoSenders) {
|
|
8517
|
-
|
|
8919
|
+
// if not publishing, update the encodingConfigCache and don't modify the state.
|
|
8920
|
+
// we'll apply this config on the next publish/unmute.
|
|
8921
|
+
const { trackType, publishOptionId } = videoSender;
|
|
8922
|
+
const bundle = this.transceiverCache.getBy(publishOptionId, trackType);
|
|
8923
|
+
if (bundle) {
|
|
8924
|
+
this.transceiverCache.update(bundle.publishOption, { videoSender });
|
|
8925
|
+
}
|
|
8926
|
+
if (isFirefox() && !this.isPublishing(trackType))
|
|
8927
|
+
continue;
|
|
8928
|
+
await this.changePublishQuality(videoSender, bundle);
|
|
8518
8929
|
}
|
|
8519
8930
|
});
|
|
8520
8931
|
this.on('changePublishOptions', (event) => {
|
|
@@ -8525,9 +8936,14 @@ class Publisher extends BasePeerConnection {
|
|
|
8525
8936
|
/**
|
|
8526
8937
|
* Disposes this Publisher instance.
|
|
8527
8938
|
*/
|
|
8528
|
-
dispose() {
|
|
8529
|
-
super.dispose();
|
|
8530
|
-
|
|
8939
|
+
async dispose() {
|
|
8940
|
+
await super.dispose();
|
|
8941
|
+
try {
|
|
8942
|
+
await this.stopAllTracks();
|
|
8943
|
+
}
|
|
8944
|
+
catch (err) {
|
|
8945
|
+
this.logger.warn('Failed to stop tracks during dispose', err);
|
|
8946
|
+
}
|
|
8531
8947
|
this.clonedTracks.clear();
|
|
8532
8948
|
}
|
|
8533
8949
|
}
|
|
@@ -8698,6 +9114,7 @@ class Subscriber extends BasePeerConnection {
|
|
|
8698
9114
|
await this.sfuClient.sendAnswer({
|
|
8699
9115
|
peerType: PeerType.SUBSCRIBER,
|
|
8700
9116
|
sdp: answer.sdp || '',
|
|
9117
|
+
negotiationId: subscriberOffer.negotiationId,
|
|
8701
9118
|
});
|
|
8702
9119
|
this.isIceRestarting = false;
|
|
8703
9120
|
};
|
|
@@ -11311,8 +11728,8 @@ const normalize = (options) => {
|
|
|
11311
11728
|
: false,
|
|
11312
11729
|
};
|
|
11313
11730
|
};
|
|
11314
|
-
const createSyntheticDevice = (deviceId, kind) => {
|
|
11315
|
-
return { deviceId, kind, label
|
|
11731
|
+
const createSyntheticDevice = (deviceId, kind, label = '') => {
|
|
11732
|
+
return { deviceId, kind, label, groupId: '' };
|
|
11316
11733
|
};
|
|
11317
11734
|
const readPreferences = (storageKey) => {
|
|
11318
11735
|
try {
|
|
@@ -11366,6 +11783,8 @@ class DeviceManager {
|
|
|
11366
11783
|
this.areSubscriptionsSetUp = false;
|
|
11367
11784
|
this.isTrackStoppedDueToTrackEnd = false;
|
|
11368
11785
|
this.filters = [];
|
|
11786
|
+
this.virtualDevicesSubject = new BehaviorSubject([]);
|
|
11787
|
+
this.virtualDeviceConcurrencyTag = Symbol('virtualDeviceConcurrencyTag');
|
|
11369
11788
|
this.statusChangeConcurrencyTag = Symbol('statusChangeConcurrencyTag');
|
|
11370
11789
|
this.filterRegistrationConcurrencyTag = Symbol('filterRegistrationConcurrencyTag');
|
|
11371
11790
|
/**
|
|
@@ -11378,6 +11797,7 @@ class DeviceManager {
|
|
|
11378
11797
|
this.subscriptions.forEach((s) => s());
|
|
11379
11798
|
this.subscriptions = [];
|
|
11380
11799
|
this.areSubscriptionsSetUp = false;
|
|
11800
|
+
this.virtualDevicesSubject.next([]);
|
|
11381
11801
|
};
|
|
11382
11802
|
this.runCurrentStreamCleanups = () => {
|
|
11383
11803
|
this.currentStreamCleanups.forEach((c) => c());
|
|
@@ -11436,7 +11856,93 @@ class DeviceManager {
|
|
|
11436
11856
|
* @returns an Observable that will be updated if a device is connected or disconnected
|
|
11437
11857
|
*/
|
|
11438
11858
|
listDevices() {
|
|
11439
|
-
return this.getDevices()
|
|
11859
|
+
return combineLatest([this.getDevices(), this.virtualDevicesSubject]).pipe(map(([real, virtual]) => [
|
|
11860
|
+
...real,
|
|
11861
|
+
...virtual.map((d) => createSyntheticDevice(d.deviceId, d.kind, d.label)),
|
|
11862
|
+
]));
|
|
11863
|
+
}
|
|
11864
|
+
/**
|
|
11865
|
+
* Registers a virtual camera or microphone backed by a caller-supplied
|
|
11866
|
+
* stream factory. The device appears in `listDevices()` and can be selected
|
|
11867
|
+
* via `select()` like any real device.
|
|
11868
|
+
*
|
|
11869
|
+
* Web only. React Native is not supported.
|
|
11870
|
+
*
|
|
11871
|
+
* Only supported for camera and microphone managers; calling on any other
|
|
11872
|
+
* manager throws.
|
|
11873
|
+
*/
|
|
11874
|
+
registerVirtualDevice(virtualDevice) {
|
|
11875
|
+
if (isReactNative()) {
|
|
11876
|
+
throw new Error('Virtual devices are not supported on React Native.');
|
|
11877
|
+
}
|
|
11878
|
+
if (this.trackType !== TrackType.AUDIO &&
|
|
11879
|
+
this.trackType !== TrackType.VIDEO) {
|
|
11880
|
+
throw new Error('Virtual devices are only supported for camera and microphone.');
|
|
11881
|
+
}
|
|
11882
|
+
const deviceId = `stream-virtual:${generateUUIDv4()}`;
|
|
11883
|
+
const entry = {
|
|
11884
|
+
deviceId,
|
|
11885
|
+
kind: this.mediaDeviceKind,
|
|
11886
|
+
...virtualDevice,
|
|
11887
|
+
};
|
|
11888
|
+
setCurrentValue(this.virtualDevicesSubject, (current) => [
|
|
11889
|
+
...current,
|
|
11890
|
+
entry,
|
|
11891
|
+
]);
|
|
11892
|
+
return {
|
|
11893
|
+
deviceId: entry.deviceId,
|
|
11894
|
+
unregister: async () => {
|
|
11895
|
+
await withoutConcurrency(this.virtualDeviceConcurrencyTag, async () => {
|
|
11896
|
+
setCurrentValue(this.virtualDevicesSubject, (current) => current.filter((d) => d !== entry));
|
|
11897
|
+
if (this.activeVirtualSession?.deviceId === deviceId) {
|
|
11898
|
+
await this.stopActiveVirtualSession();
|
|
11899
|
+
}
|
|
11900
|
+
});
|
|
11901
|
+
if (this.state.selectedDevice === deviceId) {
|
|
11902
|
+
await this.statusChangeSettled();
|
|
11903
|
+
await this.disable({ forceStop: true });
|
|
11904
|
+
await this.select(undefined);
|
|
11905
|
+
}
|
|
11906
|
+
},
|
|
11907
|
+
};
|
|
11908
|
+
}
|
|
11909
|
+
sanitizeVirtualStream(stream) {
|
|
11910
|
+
stream.getTracks().forEach((track) => {
|
|
11911
|
+
const originalGetSettings = track.getSettings.bind(track);
|
|
11912
|
+
track.getSettings = () => {
|
|
11913
|
+
const settings = originalGetSettings();
|
|
11914
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11915
|
+
const { deviceId, ...rest } = settings;
|
|
11916
|
+
return rest;
|
|
11917
|
+
};
|
|
11918
|
+
});
|
|
11919
|
+
return stream;
|
|
11920
|
+
}
|
|
11921
|
+
findVirtualDevice(deviceId) {
|
|
11922
|
+
if (!deviceId)
|
|
11923
|
+
return undefined;
|
|
11924
|
+
return getCurrentValue(this.virtualDevicesSubject).find((d) => d.deviceId === deviceId);
|
|
11925
|
+
}
|
|
11926
|
+
async stopActiveVirtualSession() {
|
|
11927
|
+
const session = this.activeVirtualSession;
|
|
11928
|
+
this.activeVirtualSession = undefined;
|
|
11929
|
+
await session?.stop?.();
|
|
11930
|
+
}
|
|
11931
|
+
async getSelectedStream(constraints) {
|
|
11932
|
+
const deviceId = this.state.selectedDevice;
|
|
11933
|
+
if (!deviceId?.startsWith('stream-virtual')) {
|
|
11934
|
+
return this.getStream(constraints);
|
|
11935
|
+
}
|
|
11936
|
+
return withoutConcurrency(this.virtualDeviceConcurrencyTag, async () => {
|
|
11937
|
+
const virtualDevice = this.findVirtualDevice(deviceId);
|
|
11938
|
+
if (!virtualDevice) {
|
|
11939
|
+
throw new Error(`Virtual device is not registered: ${deviceId}`);
|
|
11940
|
+
}
|
|
11941
|
+
await this.stopActiveVirtualSession();
|
|
11942
|
+
const { stream, stop } = await virtualDevice.getUserMedia(constraints);
|
|
11943
|
+
this.activeVirtualSession = { deviceId, stop };
|
|
11944
|
+
return this.sanitizeVirtualStream(stream);
|
|
11945
|
+
});
|
|
11440
11946
|
}
|
|
11441
11947
|
/**
|
|
11442
11948
|
* Returns `true` when this device is in enabled state.
|
|
@@ -11596,6 +12102,9 @@ class DeviceManager {
|
|
|
11596
12102
|
}
|
|
11597
12103
|
});
|
|
11598
12104
|
}
|
|
12105
|
+
getResolvedConstraints(constraints) {
|
|
12106
|
+
return constraints;
|
|
12107
|
+
}
|
|
11599
12108
|
publishStream(stream, options) {
|
|
11600
12109
|
return this.call.publish(stream, this.trackType, options);
|
|
11601
12110
|
}
|
|
@@ -11616,6 +12125,7 @@ class DeviceManager {
|
|
|
11616
12125
|
this.muteLocalStream(stopTracks);
|
|
11617
12126
|
const allEnded = this.getTracks().every((t) => t.readyState === 'ended');
|
|
11618
12127
|
if (allEnded) {
|
|
12128
|
+
await this.stopActiveVirtualSession();
|
|
11619
12129
|
// @ts-expect-error release() is present in react-native-webrtc
|
|
11620
12130
|
if (typeof mediaStream.release === 'function') {
|
|
11621
12131
|
// @ts-expect-error called to dispose the stream in RN
|
|
@@ -11671,12 +12181,12 @@ class DeviceManager {
|
|
|
11671
12181
|
// before chainWith below registers new ones for the new chain.
|
|
11672
12182
|
this.runCurrentStreamCleanups();
|
|
11673
12183
|
const defaultConstraints = this.state.defaultConstraints;
|
|
11674
|
-
const constraints = {
|
|
12184
|
+
const constraints = this.getResolvedConstraints({
|
|
11675
12185
|
...defaultConstraints,
|
|
11676
12186
|
deviceId: this.state.selectedDevice
|
|
11677
12187
|
? { exact: this.state.selectedDevice }
|
|
11678
12188
|
: undefined,
|
|
11679
|
-
};
|
|
12189
|
+
});
|
|
11680
12190
|
/**
|
|
11681
12191
|
* Chains two media streams together.
|
|
11682
12192
|
*
|
|
@@ -11733,7 +12243,7 @@ class DeviceManager {
|
|
|
11733
12243
|
};
|
|
11734
12244
|
// the rootStream represents the stream coming from the actual device
|
|
11735
12245
|
// e.g. camera or microphone stream
|
|
11736
|
-
rootStreamPromise = this.
|
|
12246
|
+
rootStreamPromise = this.getSelectedStream(constraints);
|
|
11737
12247
|
// we publish the last MediaStream of the chain
|
|
11738
12248
|
stream = await this.filters.reduce((parent, entry) => parent
|
|
11739
12249
|
.then((inputStream) => {
|
|
@@ -12050,7 +12560,10 @@ class DeviceManagerState {
|
|
|
12050
12560
|
setCurrentValue(this.mediaStreamSubject, stream);
|
|
12051
12561
|
setCurrentValue(this.rootMediaStreamSubject, rootStream);
|
|
12052
12562
|
if (rootStream) {
|
|
12053
|
-
this.
|
|
12563
|
+
const derived = this.getDeviceIdFromStream(rootStream);
|
|
12564
|
+
if (derived) {
|
|
12565
|
+
this.setDevice(derived);
|
|
12566
|
+
}
|
|
12054
12567
|
}
|
|
12055
12568
|
}
|
|
12056
12569
|
/**
|
|
@@ -12263,7 +12776,7 @@ class CameraManager extends DeviceManager {
|
|
|
12263
12776
|
getDevices() {
|
|
12264
12777
|
return getVideoDevices(this.call.tracer);
|
|
12265
12778
|
}
|
|
12266
|
-
|
|
12779
|
+
getResolvedConstraints(constraints) {
|
|
12267
12780
|
constraints.width = this.targetResolution.width;
|
|
12268
12781
|
constraints.height = this.targetResolution.height;
|
|
12269
12782
|
// We can't set both device id and facing mode
|
|
@@ -12274,6 +12787,9 @@ class CameraManager extends DeviceManager {
|
|
|
12274
12787
|
constraints.facingMode =
|
|
12275
12788
|
this.state.direction === 'front' ? 'user' : 'environment';
|
|
12276
12789
|
}
|
|
12790
|
+
return constraints;
|
|
12791
|
+
}
|
|
12792
|
+
getStream(constraints) {
|
|
12277
12793
|
return getVideoStream(constraints, this.call.tracer);
|
|
12278
12794
|
}
|
|
12279
12795
|
}
|
|
@@ -13601,9 +14117,10 @@ class Call {
|
|
|
13601
14117
|
this.sfuStatsReporter?.flush();
|
|
13602
14118
|
this.sfuStatsReporter?.stop();
|
|
13603
14119
|
this.sfuStatsReporter = undefined;
|
|
13604
|
-
this.
|
|
14120
|
+
this.lastStatsOptions = undefined;
|
|
14121
|
+
await this.subscriber?.dispose();
|
|
13605
14122
|
this.subscriber = undefined;
|
|
13606
|
-
this.publisher?.dispose();
|
|
14123
|
+
await this.publisher?.dispose();
|
|
13607
14124
|
this.publisher = undefined;
|
|
13608
14125
|
await this.sfuClient?.leaveAndClose(leaveReason);
|
|
13609
14126
|
this.sfuClient = undefined;
|
|
@@ -13879,15 +14396,17 @@ class Call {
|
|
|
13879
14396
|
const performingMigration = this.reconnectStrategy === WebsocketReconnectStrategy.MIGRATE;
|
|
13880
14397
|
const performingRejoin = this.reconnectStrategy === WebsocketReconnectStrategy.REJOIN;
|
|
13881
14398
|
const performingFastReconnect = this.reconnectStrategy === WebsocketReconnectStrategy.FAST;
|
|
13882
|
-
let statsOptions = this.
|
|
14399
|
+
let statsOptions = this.lastStatsOptions;
|
|
13883
14400
|
if (!this.credentials ||
|
|
13884
14401
|
!statsOptions ||
|
|
13885
14402
|
performingRejoin ||
|
|
13886
|
-
performingMigration
|
|
14403
|
+
performingMigration ||
|
|
14404
|
+
data?.migrating_from) {
|
|
13887
14405
|
try {
|
|
13888
14406
|
const joinResponse = await this.doJoinRequest(data);
|
|
13889
14407
|
this.credentials = joinResponse.credentials;
|
|
13890
14408
|
statsOptions = joinResponse.stats_options;
|
|
14409
|
+
this.lastStatsOptions = statsOptions;
|
|
13891
14410
|
}
|
|
13892
14411
|
catch (error) {
|
|
13893
14412
|
// prevent triggering reconnect flow if the state is OFFLINE
|
|
@@ -13994,7 +14513,7 @@ class Call {
|
|
|
13994
14513
|
}
|
|
13995
14514
|
else {
|
|
13996
14515
|
const connectionConfig = toRtcConfiguration(this.credentials.ice_servers);
|
|
13997
|
-
this.initPublisherAndSubscriber({
|
|
14516
|
+
await this.initPublisherAndSubscriber({
|
|
13998
14517
|
sfuClient,
|
|
13999
14518
|
connectionConfig,
|
|
14000
14519
|
clientDetails,
|
|
@@ -14139,11 +14658,11 @@ class Call {
|
|
|
14139
14658
|
* Initializes the Publisher and Subscriber Peer Connections.
|
|
14140
14659
|
* @internal
|
|
14141
14660
|
*/
|
|
14142
|
-
this.initPublisherAndSubscriber = (opts) => {
|
|
14661
|
+
this.initPublisherAndSubscriber = async (opts) => {
|
|
14143
14662
|
const { sfuClient, connectionConfig, clientDetails, statsOptions, publishOptions, closePreviousInstances, unifiedSessionId, } = opts;
|
|
14144
|
-
const { enable_rtc_stats: enableTracing } = statsOptions;
|
|
14663
|
+
const { enable_rtc_stats: enableTracing, reporting_interval_ms: reportingIntervalMs, } = statsOptions;
|
|
14145
14664
|
if (closePreviousInstances && this.subscriber) {
|
|
14146
|
-
this.subscriber.dispose();
|
|
14665
|
+
await this.subscriber.dispose();
|
|
14147
14666
|
}
|
|
14148
14667
|
const basePeerConnectionOptions = {
|
|
14149
14668
|
sfuClient,
|
|
@@ -14152,6 +14671,7 @@ class Call {
|
|
|
14152
14671
|
connectionConfig,
|
|
14153
14672
|
tag: sfuClient.tag,
|
|
14154
14673
|
enableTracing,
|
|
14674
|
+
statsTimestampDriftThresholdMs: reportingIntervalMs / 2,
|
|
14155
14675
|
clientPublishOptions: this.clientPublishOptions,
|
|
14156
14676
|
onReconnectionNeeded: (kind, reason, peerType) => {
|
|
14157
14677
|
this.reconnect(kind, reason).catch((err) => {
|
|
@@ -14172,7 +14692,7 @@ class Call {
|
|
|
14172
14692
|
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
14173
14693
|
if (!isAnonymous) {
|
|
14174
14694
|
if (closePreviousInstances && this.publisher) {
|
|
14175
|
-
this.publisher.dispose();
|
|
14695
|
+
await this.publisher.dispose();
|
|
14176
14696
|
}
|
|
14177
14697
|
this.publisher = new Publisher(basePeerConnectionOptions, publishOptions);
|
|
14178
14698
|
}
|
|
@@ -14275,10 +14795,17 @@ class Call {
|
|
|
14275
14795
|
* `ICE_NEVER_CONNECTED` increments the unsupported-network counter).
|
|
14276
14796
|
*/
|
|
14277
14797
|
this.reconnect = async (strategy, reason) => {
|
|
14278
|
-
if (this.state.callingState === CallingState.
|
|
14798
|
+
if (this.state.callingState === CallingState.JOINING ||
|
|
14799
|
+
this.state.callingState === CallingState.RECONNECTING ||
|
|
14279
14800
|
this.state.callingState === CallingState.MIGRATING ||
|
|
14280
14801
|
this.state.callingState === CallingState.RECONNECTING_FAILED)
|
|
14281
14802
|
return;
|
|
14803
|
+
// Drop redundant reconnect calls. If a reconnect is already queued or
|
|
14804
|
+
// running for this Call, that entry will resolve whatever broke;
|
|
14805
|
+
// queueing more entries just replays the full REJOIN cycle (one extra
|
|
14806
|
+
// `POST /join` per entry) once the call is already healthy again.
|
|
14807
|
+
if (hasPending(this.reconnectConcurrencyTag))
|
|
14808
|
+
return;
|
|
14282
14809
|
return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
|
|
14283
14810
|
const reconnectStartTime = Date.now();
|
|
14284
14811
|
this.reconnectStrategy = strategy;
|
|
@@ -14483,8 +15010,8 @@ class Call {
|
|
|
14483
15010
|
this.state.setCallingState(CallingState.JOINED);
|
|
14484
15011
|
}
|
|
14485
15012
|
finally {
|
|
14486
|
-
currentSubscriber?.dispose();
|
|
14487
|
-
currentPublisher?.dispose();
|
|
15013
|
+
await currentSubscriber?.dispose();
|
|
15014
|
+
await currentPublisher?.dispose();
|
|
14488
15015
|
// and close the previous SFU client, without specifying close code
|
|
14489
15016
|
currentSfuClient.close(StreamSfuClient.NORMAL_CLOSURE, 'Migrating away');
|
|
14490
15017
|
}
|
|
@@ -14673,7 +15200,7 @@ class Call {
|
|
|
14673
15200
|
this.stopPublish = async (...trackTypes) => {
|
|
14674
15201
|
if (!this.sfuClient || !this.publisher)
|
|
14675
15202
|
return;
|
|
14676
|
-
this.publisher.stopTracks(...trackTypes);
|
|
15203
|
+
await this.publisher.stopTracks(...trackTypes);
|
|
14677
15204
|
await this.updateLocalStreamState(undefined, ...trackTypes);
|
|
14678
15205
|
};
|
|
14679
15206
|
/**
|
|
@@ -16788,7 +17315,7 @@ class StreamClient {
|
|
|
16788
17315
|
this.getUserAgent = () => {
|
|
16789
17316
|
if (!this.cachedUserAgent) {
|
|
16790
17317
|
const { clientAppIdentifier = {} } = this.options;
|
|
16791
|
-
const { sdkName = 'js', sdkVersion = "1.
|
|
17318
|
+
const { sdkName = 'js', sdkVersion = "1.52.0", ...extras } = clientAppIdentifier;
|
|
16792
17319
|
this.cachedUserAgent = [
|
|
16793
17320
|
`stream-video-${sdkName}-v${sdkVersion}`,
|
|
16794
17321
|
...Object.entries(extras).map(([key, value]) => `${key}=${value}`),
|
|
@@ -17424,5 +17951,5 @@ const humanize = (n) => {
|
|
|
17424
17951
|
return String(n);
|
|
17425
17952
|
};
|
|
17426
17953
|
|
|
17427
|
-
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
|
|
17954
|
+
export { AudioSettingsRequestDefaultDeviceEnum, AudioSettingsResponseDefaultDeviceEnum, browsers as Browsers, Call, CallRecordingFailedEventRecordingTypeEnum, CallRecordingReadyEventRecordingTypeEnum, CallRecordingStartedEventRecordingTypeEnum, CallRecordingStoppedEventRecordingTypeEnum, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DeviceManager, DeviceManagerState, DynascaleManager, ErrorFromResponse, FrameRecordingSettingsRequestModeEnum, FrameRecordingSettingsRequestQualityEnum, FrameRecordingSettingsResponseModeEnum, IndividualRecordingSettingsRequestModeEnum, IndividualRecordingSettingsResponseModeEnum, IngressAudioEncodingOptionsRequestChannelsEnum, IngressSourceRequestFpsEnum, IngressVideoLayerRequestCodecEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, NoiseCancellationSettingsModeEnum, OwnCapability, RTMPBroadcastRequestQualityEnum, RTMPSettingsRequestQualityEnum, RawRecordingSettingsRequestModeEnum, RawRecordingSettingsResponseModeEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, SfuJoinError, models as SfuModels, SpeakerManager, SpeakerState, StartClosedCaptionsRequestLanguageEnum, StartTranscriptionRequestLanguageEnum, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore, TranscriptionSettingsRequestClosedCaptionModeEnum, TranscriptionSettingsRequestLanguageEnum, TranscriptionSettingsRequestModeEnum, TranscriptionSettingsResponseClosedCaptionModeEnum, TranscriptionSettingsResponseLanguageEnum, TranscriptionSettingsResponseModeEnum, VideoSettingsRequestCameraFacingEnum, VideoSettingsResponseCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioBrowserPermission, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceState, getScreenShareStream, getSdkInfo, getVideoBrowserPermission, getVideoDevices, getVideoStream, getWebRTCInfo, hasAudio$1 as hasAudio, hasInterruptedTrack, hasPausedTrack, hasScreenShare, hasScreenShareAudio, hasVideo, humanize, isPinned, livestreamOrAudioRoomSortPreset, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, resolveDeviceId, role, screenSharing, setDeviceInfo, setOSInfo, setPowerState, setSdkInfo, setThermalState, setWebRTCInfo, speakerLayoutSortPreset, speaking, videoLoggerSystem, withParticipantSource };
|
|
17428
17955
|
//# sourceMappingURL=index.browser.es.js.map
|