livekit-client 2.20.1 → 2.21.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/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +37 -7
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +2279 -651
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.fm.worker.js +1 -1
- package/dist/livekit-client.fm.worker.js.map +1 -1
- package/dist/livekit-client.fm.worker.mjs +37 -7
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/logger.d.ts +3 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +2 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +31 -26
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +4 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts +40 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts +56 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/types.d.ts +12 -0
- package/dist/src/room/data-channel/types.d.ts.map +1 -0
- package/dist/src/room/data-stream/compression.d.ts +49 -0
- package/dist/src/room/data-stream/compression.d.ts.map +1 -0
- package/dist/src/room/data-stream/constants.d.ts +18 -0
- package/dist/src/room/data-stream/constants.d.ts.map +1 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts.map +1 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +11 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +7 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +11 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +13 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/signalToken.d.ts +26 -0
- package/dist/src/test/signalToken.d.ts.map +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts +18 -0
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/src/version.d.ts +5 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/ts4.2/index.d.ts +1 -1
- package/dist/ts4.2/logger.d.ts +3 -1
- package/dist/ts4.2/room/PCTransport.d.ts +2 -0
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +31 -26
- package/dist/ts4.2/room/Room.d.ts +4 -0
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -0
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -0
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -0
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -0
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -0
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/ts4.2/room/errors.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +11 -1
- package/dist/ts4.2/room/participant/publishUtils.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/ts4.2/room/track/options.d.ts +11 -1
- package/dist/ts4.2/room/types.d.ts +17 -0
- package/dist/ts4.2/room/utils.d.ts +13 -0
- package/dist/ts4.2/test/signalToken.d.ts +26 -0
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +18 -0
- package/dist/ts4.2/version.d.ts +5 -1
- package/package.json +8 -4
- package/src/api/SignalClient.e2e.test.ts +264 -0
- package/src/api/SignalClient.ts +34 -2
- package/src/index.ts +2 -0
- package/src/logger.ts +2 -0
- package/src/room/PCTransport.ts +66 -31
- package/src/room/PCTransportManager.ts +10 -6
- package/src/room/RTCEngine.test.ts +238 -6
- package/src/room/RTCEngine.ts +101 -338
- package/src/room/RegionUrlProvider.ts +4 -2
- package/src/room/Room.test.ts +4 -1
- package/src/room/Room.ts +47 -9
- package/src/room/data-channel/DataChannelManager.test.ts +152 -0
- package/src/room/data-channel/DataChannelManager.ts +237 -0
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +154 -0
- package/src/room/data-channel/FlowControlledDataChannel.ts +216 -0
- package/src/room/data-channel/LossyDataChannel.test.ts +118 -0
- package/src/room/data-channel/LossyDataChannel.ts +125 -0
- package/src/room/data-channel/ReliableDataChannel.test.ts +158 -0
- package/src/room/data-channel/ReliableDataChannel.ts +153 -0
- package/src/room/data-channel/types.ts +30 -0
- package/src/room/data-stream/compression.ts +117 -0
- package/src/room/data-stream/constants.ts +18 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +1601 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +438 -86
- package/src/room/data-stream/incoming/StreamReader.ts +8 -2
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +1292 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +489 -116
- package/src/room/data-stream/outgoing/header-utils.ts +87 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +303 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +70 -8
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +2 -0
- package/src/room/errors.ts +10 -4
- package/src/room/participant/LocalParticipant.ts +29 -6
- package/src/room/participant/Participant.ts +5 -0
- package/src/room/participant/RemoteParticipant.ts +19 -5
- package/src/room/participant/publishUtils.ts +14 -11
- package/src/room/rpc/client/RpcClientManager.test.ts +20 -27
- package/src/room/rpc/client/RpcClientManager.ts +1 -4
- package/src/room/rpc/server/RpcServerManager.test.ts +24 -23
- package/src/room/rpc/server/RpcServerManager.ts +1 -3
- package/src/room/track/LocalAudioTrack.ts +16 -0
- package/src/room/track/Track.ts +10 -6
- package/src/room/track/options.ts +11 -1
- package/src/room/types.ts +18 -0
- package/src/room/utils.ts +67 -0
- package/src/test/signalServerSetup.ts +151 -0
- package/src/test/signalToken.ts +59 -0
- package/src/test/vitest-context.d.ts +10 -0
- package/src/utils/dataPacketBuffer.test.ts +100 -0
- package/src/utils/dataPacketBuffer.ts +46 -1
- package/src/version.ts +5 -1
|
@@ -3983,6 +3983,9 @@ const PacketTrailerFeature = /* @__PURE__ */proto3.makeEnum("livekit.PacketTrail
|
|
|
3983
3983
|
}, {
|
|
3984
3984
|
no: 1,
|
|
3985
3985
|
name: "PTF_FRAME_ID"
|
|
3986
|
+
}, {
|
|
3987
|
+
no: 2,
|
|
3988
|
+
name: "PTF_USER_DATA"
|
|
3986
3989
|
}]);
|
|
3987
3990
|
const Room$1 = /* @__PURE__ */proto3.makeMessageType("livekit.Room", () => [{
|
|
3988
3991
|
no: 1,
|
|
@@ -4250,6 +4253,12 @@ const ParticipantInfo = /* @__PURE__ */proto3.makeMessageType("livekit.Participa
|
|
|
4250
4253
|
kind: "scalar",
|
|
4251
4254
|
T: 5
|
|
4252
4255
|
/* ScalarType.INT32 */
|
|
4256
|
+
}, {
|
|
4257
|
+
no: 21,
|
|
4258
|
+
name: "capabilities",
|
|
4259
|
+
kind: "enum",
|
|
4260
|
+
T: proto3.getEnumType(ClientInfo_Capability),
|
|
4261
|
+
repeated: true
|
|
4253
4262
|
}]);
|
|
4254
4263
|
const ParticipantInfo_State = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.State", [{
|
|
4255
4264
|
no: 0,
|
|
@@ -4301,6 +4310,9 @@ const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.Parti
|
|
|
4301
4310
|
}, {
|
|
4302
4311
|
no: 4,
|
|
4303
4312
|
name: "BRIDGE_RTSP"
|
|
4313
|
+
}, {
|
|
4314
|
+
no: 5,
|
|
4315
|
+
name: "SIMULATION"
|
|
4304
4316
|
}]);
|
|
4305
4317
|
const Encryption_Type = /* @__PURE__ */proto3.makeEnum("livekit.Encryption.Type", [{
|
|
4306
4318
|
no: 0,
|
|
@@ -4493,6 +4505,122 @@ const DataTrackInfo$1 = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrack
|
|
|
4493
4505
|
name: "encryption",
|
|
4494
4506
|
kind: "enum",
|
|
4495
4507
|
T: proto3.getEnumType(Encryption_Type)
|
|
4508
|
+
}, {
|
|
4509
|
+
no: 5,
|
|
4510
|
+
name: "frame_encoding",
|
|
4511
|
+
kind: "message",
|
|
4512
|
+
T: DataTrackFrameEncoding,
|
|
4513
|
+
opt: true
|
|
4514
|
+
}, {
|
|
4515
|
+
no: 6,
|
|
4516
|
+
name: "schema",
|
|
4517
|
+
kind: "message",
|
|
4518
|
+
T: DataTrackSchemaId,
|
|
4519
|
+
opt: true
|
|
4520
|
+
}]);
|
|
4521
|
+
const DataTrackFrameEncoding = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackFrameEncoding", () => [{
|
|
4522
|
+
no: 1,
|
|
4523
|
+
name: "well_known",
|
|
4524
|
+
kind: "enum",
|
|
4525
|
+
T: proto3.getEnumType(DataTrackFrameEncoding_WellKnownFrameEncoding),
|
|
4526
|
+
oneof: "value"
|
|
4527
|
+
}, {
|
|
4528
|
+
no: 2,
|
|
4529
|
+
name: "custom",
|
|
4530
|
+
kind: "scalar",
|
|
4531
|
+
T: 9,
|
|
4532
|
+
oneof: "value"
|
|
4533
|
+
}]);
|
|
4534
|
+
const DataTrackFrameEncoding_WellKnownFrameEncoding = /* @__PURE__ */proto3.makeEnum("livekit.DataTrackFrameEncoding.WellKnownFrameEncoding", [{
|
|
4535
|
+
no: 0,
|
|
4536
|
+
name: "WELL_KNOWN_FRAME_ENCODING_UNSPECIFIED",
|
|
4537
|
+
localName: "UNSPECIFIED"
|
|
4538
|
+
}, {
|
|
4539
|
+
no: 1,
|
|
4540
|
+
name: "WELL_KNOWN_FRAME_ENCODING_ROS1",
|
|
4541
|
+
localName: "ROS1"
|
|
4542
|
+
}, {
|
|
4543
|
+
no: 2,
|
|
4544
|
+
name: "WELL_KNOWN_FRAME_ENCODING_CDR",
|
|
4545
|
+
localName: "CDR"
|
|
4546
|
+
}, {
|
|
4547
|
+
no: 3,
|
|
4548
|
+
name: "WELL_KNOWN_FRAME_ENCODING_PROTOBUF",
|
|
4549
|
+
localName: "PROTOBUF"
|
|
4550
|
+
}, {
|
|
4551
|
+
no: 4,
|
|
4552
|
+
name: "WELL_KNOWN_FRAME_ENCODING_FLATBUFFER",
|
|
4553
|
+
localName: "FLATBUFFER"
|
|
4554
|
+
}, {
|
|
4555
|
+
no: 5,
|
|
4556
|
+
name: "WELL_KNOWN_FRAME_ENCODING_CBOR",
|
|
4557
|
+
localName: "CBOR"
|
|
4558
|
+
}, {
|
|
4559
|
+
no: 6,
|
|
4560
|
+
name: "WELL_KNOWN_FRAME_ENCODING_MSGPACK",
|
|
4561
|
+
localName: "MSGPACK"
|
|
4562
|
+
}, {
|
|
4563
|
+
no: 7,
|
|
4564
|
+
name: "WELL_KNOWN_FRAME_ENCODING_JSON",
|
|
4565
|
+
localName: "JSON"
|
|
4566
|
+
}]);
|
|
4567
|
+
const DataTrackSchemaEncoding = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSchemaEncoding", () => [{
|
|
4568
|
+
no: 1,
|
|
4569
|
+
name: "well_known",
|
|
4570
|
+
kind: "enum",
|
|
4571
|
+
T: proto3.getEnumType(DataTrackSchemaEncoding_WellKnownSchemaEncoding),
|
|
4572
|
+
oneof: "value"
|
|
4573
|
+
}, {
|
|
4574
|
+
no: 2,
|
|
4575
|
+
name: "custom",
|
|
4576
|
+
kind: "scalar",
|
|
4577
|
+
T: 9,
|
|
4578
|
+
oneof: "value"
|
|
4579
|
+
}]);
|
|
4580
|
+
const DataTrackSchemaEncoding_WellKnownSchemaEncoding = /* @__PURE__ */proto3.makeEnum("livekit.DataTrackSchemaEncoding.WellKnownSchemaEncoding", [{
|
|
4581
|
+
no: 0,
|
|
4582
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_UNSPECIFIED",
|
|
4583
|
+
localName: "UNSPECIFIED"
|
|
4584
|
+
}, {
|
|
4585
|
+
no: 1,
|
|
4586
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_PROTOBUF",
|
|
4587
|
+
localName: "PROTOBUF"
|
|
4588
|
+
}, {
|
|
4589
|
+
no: 2,
|
|
4590
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_FLATBUFFER",
|
|
4591
|
+
localName: "FLATBUFFER"
|
|
4592
|
+
}, {
|
|
4593
|
+
no: 3,
|
|
4594
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_ROS1_MSG",
|
|
4595
|
+
localName: "ROS1_MSG"
|
|
4596
|
+
}, {
|
|
4597
|
+
no: 4,
|
|
4598
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_ROS2_MSG",
|
|
4599
|
+
localName: "ROS2_MSG"
|
|
4600
|
+
}, {
|
|
4601
|
+
no: 5,
|
|
4602
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_ROS2_IDL",
|
|
4603
|
+
localName: "ROS2_IDL"
|
|
4604
|
+
}, {
|
|
4605
|
+
no: 6,
|
|
4606
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_OMG_IDL",
|
|
4607
|
+
localName: "OMG_IDL"
|
|
4608
|
+
}, {
|
|
4609
|
+
no: 7,
|
|
4610
|
+
name: "WELL_KNOWN_SCHEMA_ENCODING_JSON_SCHEMA",
|
|
4611
|
+
localName: "JSON_SCHEMA"
|
|
4612
|
+
}]);
|
|
4613
|
+
const DataTrackSchemaId = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSchemaId", () => [{
|
|
4614
|
+
no: 1,
|
|
4615
|
+
name: "name",
|
|
4616
|
+
kind: "scalar",
|
|
4617
|
+
T: 9
|
|
4618
|
+
/* ScalarType.STRING */
|
|
4619
|
+
}, {
|
|
4620
|
+
no: 2,
|
|
4621
|
+
name: "encoding",
|
|
4622
|
+
kind: "message",
|
|
4623
|
+
T: DataTrackSchemaEncoding
|
|
4496
4624
|
}]);
|
|
4497
4625
|
const DataTrackSubscriptionOptions = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriptionOptions", () => [{
|
|
4498
4626
|
no: 1,
|
|
@@ -4501,6 +4629,31 @@ const DataTrackSubscriptionOptions = /* @__PURE__ */proto3.makeMessageType("live
|
|
|
4501
4629
|
T: 13,
|
|
4502
4630
|
opt: true
|
|
4503
4631
|
}]);
|
|
4632
|
+
const DataBlobKey = /* @__PURE__ */proto3.makeMessageType("livekit.DataBlobKey", () => [{
|
|
4633
|
+
no: 1,
|
|
4634
|
+
name: "generic",
|
|
4635
|
+
kind: "scalar",
|
|
4636
|
+
T: 9,
|
|
4637
|
+
oneof: "key"
|
|
4638
|
+
}, {
|
|
4639
|
+
no: 2,
|
|
4640
|
+
name: "schema_id",
|
|
4641
|
+
kind: "message",
|
|
4642
|
+
T: DataTrackSchemaId,
|
|
4643
|
+
oneof: "key"
|
|
4644
|
+
}]);
|
|
4645
|
+
const DataBlob = /* @__PURE__ */proto3.makeMessageType("livekit.DataBlob", () => [{
|
|
4646
|
+
no: 1,
|
|
4647
|
+
name: "key",
|
|
4648
|
+
kind: "message",
|
|
4649
|
+
T: DataBlobKey
|
|
4650
|
+
}, {
|
|
4651
|
+
no: 2,
|
|
4652
|
+
name: "contents",
|
|
4653
|
+
kind: "scalar",
|
|
4654
|
+
T: 12
|
|
4655
|
+
/* ScalarType.BYTES */
|
|
4656
|
+
}]);
|
|
4504
4657
|
const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", () => [{
|
|
4505
4658
|
no: 1,
|
|
4506
4659
|
name: "quality",
|
|
@@ -5223,6 +5376,9 @@ const ClientInfo_Capability = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo
|
|
|
5223
5376
|
}, {
|
|
5224
5377
|
no: 1,
|
|
5225
5378
|
name: "CAP_PACKET_TRAILER"
|
|
5379
|
+
}, {
|
|
5380
|
+
no: 2,
|
|
5381
|
+
name: "CAP_COMPRESSION_DEFLATE_RAW"
|
|
5226
5382
|
}]);
|
|
5227
5383
|
const ClientConfiguration = /* @__PURE__ */proto3.makeMessageType("livekit.ClientConfiguration", () => [{
|
|
5228
5384
|
no: 1,
|
|
@@ -5295,6 +5451,13 @@ const DataStream_OperationType = /* @__PURE__ */proto3.makeEnum("livekit.DataStr
|
|
|
5295
5451
|
no: 3,
|
|
5296
5452
|
name: "REACTION"
|
|
5297
5453
|
}]);
|
|
5454
|
+
const DataStream_CompressionType = /* @__PURE__ */proto3.makeEnum("livekit.DataStream.CompressionType", [{
|
|
5455
|
+
no: 0,
|
|
5456
|
+
name: "NONE"
|
|
5457
|
+
}, {
|
|
5458
|
+
no: 1,
|
|
5459
|
+
name: "DEFLATE_RAW"
|
|
5460
|
+
}]);
|
|
5298
5461
|
const DataStream_TextHeader = /* @__PURE__ */proto3.makeMessageType("livekit.DataStream.TextHeader", () => [{
|
|
5299
5462
|
no: 1,
|
|
5300
5463
|
name: "operation_type",
|
|
@@ -5393,6 +5556,17 @@ const DataStream_Header = /* @__PURE__ */proto3.makeMessageType("livekit.DataStr
|
|
|
5393
5556
|
kind: "message",
|
|
5394
5557
|
T: DataStream_ByteHeader,
|
|
5395
5558
|
oneof: "content_header"
|
|
5559
|
+
}, {
|
|
5560
|
+
no: 11,
|
|
5561
|
+
name: "inline_content",
|
|
5562
|
+
kind: "scalar",
|
|
5563
|
+
T: 12,
|
|
5564
|
+
opt: true
|
|
5565
|
+
}, {
|
|
5566
|
+
no: 12,
|
|
5567
|
+
name: "compression",
|
|
5568
|
+
kind: "enum",
|
|
5569
|
+
T: proto3.getEnumType(DataStream_CompressionType)
|
|
5396
5570
|
}], {
|
|
5397
5571
|
localName: "DataStream_Header"
|
|
5398
5572
|
});
|
|
@@ -5528,6 +5702,16 @@ const RoomAgentDispatch = /* @__PURE__ */proto3.makeMessageType("livekit.RoomAge
|
|
|
5528
5702
|
kind: "scalar",
|
|
5529
5703
|
T: 9
|
|
5530
5704
|
/* ScalarType.STRING */
|
|
5705
|
+
}, {
|
|
5706
|
+
no: 5,
|
|
5707
|
+
name: "attributes",
|
|
5708
|
+
kind: "map",
|
|
5709
|
+
K: 9,
|
|
5710
|
+
V: {
|
|
5711
|
+
kind: "scalar",
|
|
5712
|
+
T: 9
|
|
5713
|
+
/* ScalarType.STRING */
|
|
5714
|
+
}
|
|
5531
5715
|
}]);
|
|
5532
5716
|
const EncodingOptionsPreset = /* @__PURE__ */proto3.makeEnum("livekit.EncodingOptionsPreset", [{
|
|
5533
5717
|
no: 0,
|
|
@@ -6426,6 +6610,18 @@ const SignalRequest = /* @__PURE__ */proto3.makeMessageType("livekit.SignalReque
|
|
|
6426
6610
|
kind: "message",
|
|
6427
6611
|
T: UpdateDataSubscription,
|
|
6428
6612
|
oneof: "message"
|
|
6613
|
+
}, {
|
|
6614
|
+
no: 22,
|
|
6615
|
+
name: "store_data_blob_request",
|
|
6616
|
+
kind: "message",
|
|
6617
|
+
T: StoreDataBlobRequest,
|
|
6618
|
+
oneof: "message"
|
|
6619
|
+
}, {
|
|
6620
|
+
no: 23,
|
|
6621
|
+
name: "get_data_blob_request",
|
|
6622
|
+
kind: "message",
|
|
6623
|
+
T: GetDataBlobRequest,
|
|
6624
|
+
oneof: "message"
|
|
6429
6625
|
}]);
|
|
6430
6626
|
const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResponse", () => [{
|
|
6431
6627
|
no: 1,
|
|
@@ -6595,6 +6791,18 @@ const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResp
|
|
|
6595
6791
|
kind: "message",
|
|
6596
6792
|
T: DataTrackSubscriberHandles,
|
|
6597
6793
|
oneof: "message"
|
|
6794
|
+
}, {
|
|
6795
|
+
no: 30,
|
|
6796
|
+
name: "store_data_blob_response",
|
|
6797
|
+
kind: "message",
|
|
6798
|
+
T: StoreDataBlobResponse,
|
|
6799
|
+
oneof: "message"
|
|
6800
|
+
}, {
|
|
6801
|
+
no: 31,
|
|
6802
|
+
name: "get_data_blob_response",
|
|
6803
|
+
kind: "message",
|
|
6804
|
+
T: GetDataBlobResponse,
|
|
6805
|
+
oneof: "message"
|
|
6598
6806
|
}]);
|
|
6599
6807
|
const SimulcastCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SimulcastCodec", () => [{
|
|
6600
6808
|
no: 1,
|
|
@@ -6742,6 +6950,18 @@ const PublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.P
|
|
|
6742
6950
|
name: "encryption",
|
|
6743
6951
|
kind: "enum",
|
|
6744
6952
|
T: proto3.getEnumType(Encryption_Type)
|
|
6953
|
+
}, {
|
|
6954
|
+
no: 4,
|
|
6955
|
+
name: "frame_encoding",
|
|
6956
|
+
kind: "message",
|
|
6957
|
+
T: DataTrackFrameEncoding,
|
|
6958
|
+
opt: true
|
|
6959
|
+
}, {
|
|
6960
|
+
no: 5,
|
|
6961
|
+
name: "schema",
|
|
6962
|
+
kind: "message",
|
|
6963
|
+
T: DataTrackSchemaId,
|
|
6964
|
+
opt: true
|
|
6745
6965
|
}]);
|
|
6746
6966
|
const PublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackResponse", () => [{
|
|
6747
6967
|
no: 1,
|
|
@@ -7035,6 +7255,60 @@ const UpdateDataSubscription_Update = /* @__PURE__ */proto3.makeMessageType("liv
|
|
|
7035
7255
|
}], {
|
|
7036
7256
|
localName: "UpdateDataSubscription_Update"
|
|
7037
7257
|
});
|
|
7258
|
+
const StoreDataBlobRequest = /* @__PURE__ */proto3.makeMessageType("livekit.StoreDataBlobRequest", () => [{
|
|
7259
|
+
no: 1,
|
|
7260
|
+
name: "request_id",
|
|
7261
|
+
kind: "scalar",
|
|
7262
|
+
T: 13
|
|
7263
|
+
/* ScalarType.UINT32 */
|
|
7264
|
+
}, {
|
|
7265
|
+
no: 2,
|
|
7266
|
+
name: "blob",
|
|
7267
|
+
kind: "message",
|
|
7268
|
+
T: DataBlob
|
|
7269
|
+
}]);
|
|
7270
|
+
const StoreDataBlobResponse = /* @__PURE__ */proto3.makeMessageType("livekit.StoreDataBlobResponse", () => [{
|
|
7271
|
+
no: 1,
|
|
7272
|
+
name: "request_id",
|
|
7273
|
+
kind: "scalar",
|
|
7274
|
+
T: 13
|
|
7275
|
+
/* ScalarType.UINT32 */
|
|
7276
|
+
}, {
|
|
7277
|
+
no: 2,
|
|
7278
|
+
name: "key",
|
|
7279
|
+
kind: "message",
|
|
7280
|
+
T: DataBlobKey
|
|
7281
|
+
}]);
|
|
7282
|
+
const GetDataBlobRequest = /* @__PURE__ */proto3.makeMessageType("livekit.GetDataBlobRequest", () => [{
|
|
7283
|
+
no: 1,
|
|
7284
|
+
name: "request_id",
|
|
7285
|
+
kind: "scalar",
|
|
7286
|
+
T: 13
|
|
7287
|
+
/* ScalarType.UINT32 */
|
|
7288
|
+
}, {
|
|
7289
|
+
no: 2,
|
|
7290
|
+
name: "participant_identity",
|
|
7291
|
+
kind: "scalar",
|
|
7292
|
+
T: 9
|
|
7293
|
+
/* ScalarType.STRING */
|
|
7294
|
+
}, {
|
|
7295
|
+
no: 3,
|
|
7296
|
+
name: "key",
|
|
7297
|
+
kind: "message",
|
|
7298
|
+
T: DataBlobKey
|
|
7299
|
+
}]);
|
|
7300
|
+
const GetDataBlobResponse = /* @__PURE__ */proto3.makeMessageType("livekit.GetDataBlobResponse", () => [{
|
|
7301
|
+
no: 1,
|
|
7302
|
+
name: "request_id",
|
|
7303
|
+
kind: "scalar",
|
|
7304
|
+
T: 13
|
|
7305
|
+
/* ScalarType.UINT32 */
|
|
7306
|
+
}, {
|
|
7307
|
+
no: 2,
|
|
7308
|
+
name: "blob",
|
|
7309
|
+
kind: "message",
|
|
7310
|
+
T: DataBlob
|
|
7311
|
+
}]);
|
|
7038
7312
|
const UpdateTrackSettings = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateTrackSettings", () => [{
|
|
7039
7313
|
no: 1,
|
|
7040
7314
|
name: "track_sids",
|
|
@@ -7697,6 +7971,9 @@ const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestRe
|
|
|
7697
7971
|
}, {
|
|
7698
7972
|
no: 10,
|
|
7699
7973
|
name: "DUPLICATE_NAME"
|
|
7974
|
+
}, {
|
|
7975
|
+
no: 11,
|
|
7976
|
+
name: "INVALID_REQUEST"
|
|
7700
7977
|
}]);
|
|
7701
7978
|
const TrackSubscribed = /* @__PURE__ */proto3.makeMessageType("livekit.TrackSubscribed", () => [{
|
|
7702
7979
|
no: 1,
|
|
@@ -8226,6 +8503,8 @@ var LoggerNames;
|
|
|
8226
8503
|
LoggerNames["PCTransport"] = "livekit-pc-transport";
|
|
8227
8504
|
LoggerNames["E2EE"] = "lk-e2ee";
|
|
8228
8505
|
LoggerNames["DataTracks"] = "livekit-data-tracks";
|
|
8506
|
+
LoggerNames["Region"] = "livekit-region";
|
|
8507
|
+
LoggerNames["ICE"] = "livekit-ice";
|
|
8229
8508
|
})(LoggerNames || (LoggerNames = {}));
|
|
8230
8509
|
let livekitLogger = loglevelExports.getLogger(LoggerNames.Default);
|
|
8231
8510
|
const livekitLoggers = Object.values(LoggerNames).map(name => loglevelExports.getLogger(name));
|
|
@@ -8810,7 +9089,7 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
|
|
|
8810
9089
|
}
|
|
8811
9090
|
const addEventListener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener');
|
|
8812
9091
|
if (!addEventListener.writable) {
|
|
8813
|
-
log$
|
|
9092
|
+
log$5('Unable to polyfill events');
|
|
8814
9093
|
return;
|
|
8815
9094
|
}
|
|
8816
9095
|
const proto = window.RTCPeerConnection.prototype;
|
|
@@ -8890,7 +9169,7 @@ function disableWarnings(bool) {
|
|
|
8890
9169
|
deprecationWarnings_ = !bool;
|
|
8891
9170
|
return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
|
|
8892
9171
|
}
|
|
8893
|
-
function log$
|
|
9172
|
+
function log$5() {
|
|
8894
9173
|
if (typeof window === 'object') {
|
|
8895
9174
|
if (logDisabled_) {
|
|
8896
9175
|
return;
|
|
@@ -9052,7 +9331,7 @@ function filterStats(result, track, outbound) {
|
|
|
9052
9331
|
* tree.
|
|
9053
9332
|
*/
|
|
9054
9333
|
/* eslint-env node */
|
|
9055
|
-
const logging = log$
|
|
9334
|
+
const logging = log$5;
|
|
9056
9335
|
function shimGetUserMedia$2(window, browserDetails) {
|
|
9057
9336
|
if (browserDetails.version >= 64) {
|
|
9058
9337
|
return;
|
|
@@ -11679,7 +11958,7 @@ function adapterFactory() {
|
|
|
11679
11958
|
shimSafari: true
|
|
11680
11959
|
};
|
|
11681
11960
|
// Utils.
|
|
11682
|
-
const logging = log$
|
|
11961
|
+
const logging = log$5;
|
|
11683
11962
|
const browserDetails = detectBrowser(window);
|
|
11684
11963
|
const adapter = {
|
|
11685
11964
|
browserDetails,
|
|
@@ -11881,16 +12160,20 @@ function getMatch(exp, ua) {
|
|
|
11881
12160
|
}
|
|
11882
12161
|
function getOSVersion(ua) {
|
|
11883
12162
|
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
|
11884
|
-
}var version$1 = "2.
|
|
12163
|
+
}var version$1 = "2.21.0";const version = version$1;
|
|
11885
12164
|
const protocolVersion = 17;
|
|
11886
12165
|
/** Initial client protocol. */
|
|
11887
12166
|
const CLIENT_PROTOCOL_DEFAULT = 0;
|
|
11888
12167
|
/** Replaces RPC v1 protocol with a v2 data streams based one to support unlimited request /
|
|
11889
12168
|
* response payload length. */
|
|
11890
12169
|
const CLIENT_PROTOCOL_DATA_STREAM_RPC = 1;
|
|
12170
|
+
/** "Data streams v2": the client knows how to receive a single-packet data stream (a stream whose
|
|
12171
|
+
* entire payload is smuggled into the header packet, with no chunk/trailer packets). Senders only
|
|
12172
|
+
* use the single-packet optimization when the recipient advertises at least this protocol. */
|
|
12173
|
+
const CLIENT_PROTOCOL_DATA_STREAM_V2 = 2;
|
|
11891
12174
|
/** The client protocol version indicates what level of support that the client has for
|
|
11892
12175
|
* client <-> client api interactions. */
|
|
11893
|
-
const clientProtocol =
|
|
12176
|
+
const clientProtocol = CLIENT_PROTOCOL_DATA_STREAM_V2;/** Base error that all LiveKit specific custom errors inherit from. */
|
|
11894
12177
|
class LivekitError extends Error {
|
|
11895
12178
|
constructor(code, message, options) {
|
|
11896
12179
|
super(message || 'an error has occurred');
|
|
@@ -11972,25 +12255,25 @@ class TrackInvalidError extends LivekitError {
|
|
|
11972
12255
|
}
|
|
11973
12256
|
class UnsupportedServer extends LivekitError {
|
|
11974
12257
|
constructor(message) {
|
|
11975
|
-
super(10, message
|
|
12258
|
+
super(10, message || 'unsupported server');
|
|
11976
12259
|
this.name = 'UnsupportedServer';
|
|
11977
12260
|
}
|
|
11978
12261
|
}
|
|
11979
12262
|
class UnexpectedConnectionState extends LivekitError {
|
|
11980
12263
|
constructor(message) {
|
|
11981
|
-
super(12, message
|
|
12264
|
+
super(12, message || 'unexpected connection state');
|
|
11982
12265
|
this.name = 'UnexpectedConnectionState';
|
|
11983
12266
|
}
|
|
11984
12267
|
}
|
|
11985
12268
|
class NegotiationError extends LivekitError {
|
|
11986
12269
|
constructor(message) {
|
|
11987
|
-
super(13, message
|
|
12270
|
+
super(13, message || 'unable to negotiate');
|
|
11988
12271
|
this.name = 'NegotiationError';
|
|
11989
12272
|
}
|
|
11990
12273
|
}
|
|
11991
12274
|
class PublishDataError extends LivekitError {
|
|
11992
12275
|
constructor(message) {
|
|
11993
|
-
super(14, message
|
|
12276
|
+
super(14, message || 'unable to publish data');
|
|
11994
12277
|
this.name = 'PublishDataError';
|
|
11995
12278
|
}
|
|
11996
12279
|
}
|
|
@@ -12026,6 +12309,10 @@ var DataStreamErrorReason;
|
|
|
12026
12309
|
DataStreamErrorReason[DataStreamErrorReason["HandlerAlreadyRegistered"] = 7] = "HandlerAlreadyRegistered";
|
|
12027
12310
|
// Encryption type mismatch.
|
|
12028
12311
|
DataStreamErrorReason[DataStreamErrorReason["EncryptionTypeMismatch"] = 8] = "EncryptionTypeMismatch";
|
|
12312
|
+
// The serialized stream header packet (driven mainly by attributes) exceeds the MTU budget.
|
|
12313
|
+
DataStreamErrorReason[DataStreamErrorReason["HeaderTooLarge"] = 9] = "HeaderTooLarge";
|
|
12314
|
+
// The stream's (decompressed) payload exceeds the maximum allowed size.
|
|
12315
|
+
DataStreamErrorReason[DataStreamErrorReason["PayloadTooLarge"] = 10] = "PayloadTooLarge";
|
|
12029
12316
|
})(DataStreamErrorReason || (DataStreamErrorReason = {}));
|
|
12030
12317
|
class DataStreamError extends LivekitReasonedError {
|
|
12031
12318
|
constructor(message, reason) {
|
|
@@ -13195,6 +13482,9 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13195
13482
|
}
|
|
13196
13483
|
/** @internal */
|
|
13197
13484
|
setStreamState(value) {
|
|
13485
|
+
if (this._streamState !== value) {
|
|
13486
|
+
this.log.debug("stream state changed: ".concat(this._streamState, " -> ").concat(value));
|
|
13487
|
+
}
|
|
13198
13488
|
this._streamState = value;
|
|
13199
13489
|
}
|
|
13200
13490
|
constructor(mediaTrack, kind) {
|
|
@@ -13219,8 +13509,8 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13219
13509
|
this.handleAppVisibilityChanged();
|
|
13220
13510
|
}
|
|
13221
13511
|
};
|
|
13222
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Track);
|
|
13223
13512
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
|
13513
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Track, () => this.logContext);
|
|
13224
13514
|
this.setMaxListeners(100);
|
|
13225
13515
|
this.kind = kind;
|
|
13226
13516
|
this._mediaStreamTrack = mediaTrack;
|
|
@@ -13288,9 +13578,11 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13288
13578
|
this.emit(hasAudio ? TrackEvent.AudioPlaybackFailed : TrackEvent.VideoPlaybackFailed, e);
|
|
13289
13579
|
} else if (e.name === 'AbortError') {
|
|
13290
13580
|
// commonly triggered by another `play` request, only log for debugging purposes
|
|
13291
|
-
|
|
13581
|
+
this.log.debug("".concat(hasAudio ? 'audio' : 'video', " playback aborted, likely due to new play request"));
|
|
13292
13582
|
} else {
|
|
13293
|
-
|
|
13583
|
+
this.log.warn("could not playback ".concat(hasAudio ? 'audio' : 'video'), {
|
|
13584
|
+
error: e
|
|
13585
|
+
});
|
|
13294
13586
|
}
|
|
13295
13587
|
// If audio playback isn't allowed make sure we still play back the video
|
|
13296
13588
|
if (hasAudio && element && allMediaStreamTracks.some(tr => tr.kind === 'video') && e.name === 'NotAllowedError') {
|
|
@@ -13333,6 +13625,7 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13333
13625
|
}
|
|
13334
13626
|
}
|
|
13335
13627
|
stop() {
|
|
13628
|
+
this.log.debug('stopping track');
|
|
13336
13629
|
this.stopMonitor();
|
|
13337
13630
|
this._mediaStreamTrack.stop();
|
|
13338
13631
|
}
|
|
@@ -13353,12 +13646,12 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13353
13646
|
}
|
|
13354
13647
|
/** @internal */
|
|
13355
13648
|
updateLoggerOptions(loggerOptions) {
|
|
13356
|
-
if (loggerOptions.loggerName) {
|
|
13357
|
-
this.log = getLogger(loggerOptions.loggerName);
|
|
13358
|
-
}
|
|
13359
13649
|
if (loggerOptions.loggerContextCb) {
|
|
13360
13650
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
|
13361
13651
|
}
|
|
13652
|
+
if (loggerOptions.loggerName) {
|
|
13653
|
+
this.log = getLogger(loggerOptions.loggerName, () => this.logContext);
|
|
13654
|
+
}
|
|
13362
13655
|
}
|
|
13363
13656
|
recycleElement(element) {
|
|
13364
13657
|
if (element instanceof HTMLAudioElement) {
|
|
@@ -14113,6 +14406,53 @@ function splitUtf8(s, n) {
|
|
|
14113
14406
|
}
|
|
14114
14407
|
return result;
|
|
14115
14408
|
}
|
|
14409
|
+
/** Wraps a byte array in a `ReadableStream` that yields it as a single chunk and then closes. */
|
|
14410
|
+
function readableFromBytes(bytes) {
|
|
14411
|
+
return new ReadableStream({
|
|
14412
|
+
start(controller) {
|
|
14413
|
+
controller.enqueue(bytes);
|
|
14414
|
+
controller.close();
|
|
14415
|
+
}
|
|
14416
|
+
});
|
|
14417
|
+
}
|
|
14418
|
+
/**
|
|
14419
|
+
* Re-chunks a byte stream into pieces of exactly `chunkSize` bytes (the final piece may be
|
|
14420
|
+
* smaller), coalescing or splitting the source's pieces as needed. Memory use is bounded to roughly
|
|
14421
|
+
* `chunkSize` plus one source read, so it never buffers the whole stream — used to pack
|
|
14422
|
+
* `CompressionStream`/`file.stream()` output into MTU-sized data-stream chunks.
|
|
14423
|
+
*/
|
|
14424
|
+
function readBytesInChunks(source, chunkSize) {
|
|
14425
|
+
return __asyncGenerator(this, arguments, function* readBytesInChunks_1() {
|
|
14426
|
+
const reader = source.getReader();
|
|
14427
|
+
let buffer = new Uint8Array(0);
|
|
14428
|
+
try {
|
|
14429
|
+
while (true) {
|
|
14430
|
+
const _yield$__await = yield __await(reader.read()),
|
|
14431
|
+
done = _yield$__await.done,
|
|
14432
|
+
value = _yield$__await.value;
|
|
14433
|
+
if (done) {
|
|
14434
|
+
break;
|
|
14435
|
+
}
|
|
14436
|
+
if (value.byteLength === 0) {
|
|
14437
|
+
continue;
|
|
14438
|
+
}
|
|
14439
|
+
const merged = new Uint8Array(buffer.byteLength + value.byteLength);
|
|
14440
|
+
merged.set(buffer);
|
|
14441
|
+
merged.set(value, buffer.byteLength);
|
|
14442
|
+
buffer = merged;
|
|
14443
|
+
while (buffer.byteLength >= chunkSize) {
|
|
14444
|
+
yield yield __await(buffer.slice(0, chunkSize));
|
|
14445
|
+
buffer = buffer.slice(chunkSize);
|
|
14446
|
+
}
|
|
14447
|
+
}
|
|
14448
|
+
if (buffer.byteLength > 0) {
|
|
14449
|
+
yield yield __await(buffer);
|
|
14450
|
+
}
|
|
14451
|
+
} finally {
|
|
14452
|
+
reader.releaseLock();
|
|
14453
|
+
}
|
|
14454
|
+
});
|
|
14455
|
+
}
|
|
14116
14456
|
function extractMaxAgeFromRequestHeaders(headers) {
|
|
14117
14457
|
var _a;
|
|
14118
14458
|
const cacheControl = headers.get('Cache-Control');
|
|
@@ -16317,6 +16657,10 @@ var SignalConnectionState;
|
|
|
16317
16657
|
})(SignalConnectionState || (SignalConnectionState = {}));
|
|
16318
16658
|
/** specifies how much time (in ms) we allow for the ws to close its connection gracefully before continuing */
|
|
16319
16659
|
const MAX_WS_CLOSE_TIME = 250;
|
|
16660
|
+
/**
|
|
16661
|
+
* How long (in ms) to wait for the first message after the WebSocket upgrade.
|
|
16662
|
+
*/
|
|
16663
|
+
const JOIN_RESPONSE_TIMEOUT = 5000;
|
|
16320
16664
|
/** @internal */
|
|
16321
16665
|
class SignalClient {
|
|
16322
16666
|
get currentState() {
|
|
@@ -16465,7 +16809,6 @@ class SignalClient {
|
|
|
16465
16809
|
this.ws = new WebSocketStream(rtcUrl);
|
|
16466
16810
|
try {
|
|
16467
16811
|
this.ws.closed.then(closeInfo => {
|
|
16468
|
-
var _a;
|
|
16469
16812
|
if (this.isEstablishingConnection) {
|
|
16470
16813
|
reject(ConnectionError.internal("Websocket got closed during a (re)connection attempt: ".concat(closeInfo.reason)));
|
|
16471
16814
|
}
|
|
@@ -16477,7 +16820,7 @@ class SignalClient {
|
|
|
16477
16820
|
state: this.state
|
|
16478
16821
|
});
|
|
16479
16822
|
if (this.state === SignalConnectionState.CONNECTED) {
|
|
16480
|
-
this.handleOnClose(
|
|
16823
|
+
this.handleOnClose(closeInfo.reason || 'Unexpected WS error');
|
|
16481
16824
|
}
|
|
16482
16825
|
}
|
|
16483
16826
|
return;
|
|
@@ -16505,7 +16848,26 @@ class SignalClient {
|
|
|
16505
16848
|
}
|
|
16506
16849
|
const signalReader = connection.readable.getReader();
|
|
16507
16850
|
this.streamWriter = connection.writable.getWriter();
|
|
16508
|
-
|
|
16851
|
+
// wsTimeout only guarded the upgrade; guard the first-message read with
|
|
16852
|
+
// its own timeout so a silent server can't hang join() forever.
|
|
16853
|
+
let firstMessage;
|
|
16854
|
+
let firstMessageTimeout;
|
|
16855
|
+
try {
|
|
16856
|
+
firstMessage = yield Promise.race([signalReader.read(), new Promise((_, rejectRead) => {
|
|
16857
|
+
firstMessageTimeout = setTimeout(() => {
|
|
16858
|
+
rejectRead(ConnectionError.timeout('signal connection timed out while waiting for the first message'));
|
|
16859
|
+
}, JOIN_RESPONSE_TIMEOUT);
|
|
16860
|
+
})]);
|
|
16861
|
+
} catch (e) {
|
|
16862
|
+
// No first message in time: release the reader and tear down the ws
|
|
16863
|
+
// so we surface the timeout instead of leaking an open connection.
|
|
16864
|
+
signalReader.releaseLock();
|
|
16865
|
+
reject(e);
|
|
16866
|
+
this.close();
|
|
16867
|
+
return;
|
|
16868
|
+
} finally {
|
|
16869
|
+
clearTimeout(firstMessageTimeout);
|
|
16870
|
+
}
|
|
16509
16871
|
signalReader.releaseLock();
|
|
16510
16872
|
if (!firstMessage.value) {
|
|
16511
16873
|
throw ConnectionError.internal('no message received as first message');
|
|
@@ -17239,67 +17601,26 @@ function createJoinRequestConnectionParams(token, info, opts, publisherOffer) {
|
|
|
17239
17601
|
params.set('join_request', bytesToBase64(wrappedBytes).replace(/\+/g, '-').replace(/\//g, '_'));
|
|
17240
17602
|
return params;
|
|
17241
17603
|
});
|
|
17242
|
-
}class
|
|
17243
|
-
|
|
17244
|
-
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
this.
|
|
17249
|
-
this.
|
|
17604
|
+
}class TTLMap {
|
|
17605
|
+
/**
|
|
17606
|
+
* @param ttl ttl of the key (ms)
|
|
17607
|
+
*/
|
|
17608
|
+
constructor(ttl) {
|
|
17609
|
+
this._map = new Map();
|
|
17610
|
+
this._lastCleanup = 0;
|
|
17611
|
+
this.ttl = ttl;
|
|
17250
17612
|
}
|
|
17251
|
-
|
|
17252
|
-
const
|
|
17253
|
-
if (
|
|
17254
|
-
this.
|
|
17613
|
+
set(key, value) {
|
|
17614
|
+
const now = Date.now();
|
|
17615
|
+
if (now - this._lastCleanup > this.ttl / 2) {
|
|
17616
|
+
this.cleanup();
|
|
17255
17617
|
}
|
|
17256
|
-
|
|
17257
|
-
|
|
17258
|
-
|
|
17259
|
-
|
|
17260
|
-
|
|
17261
|
-
|
|
17262
|
-
while (this.buffer.length > 0) {
|
|
17263
|
-
const first = this.buffer[0];
|
|
17264
|
-
if (first.sequence <= sequence) {
|
|
17265
|
-
this.pop();
|
|
17266
|
-
} else {
|
|
17267
|
-
break;
|
|
17268
|
-
}
|
|
17269
|
-
}
|
|
17270
|
-
}
|
|
17271
|
-
alignBufferedAmount(bufferedAmount) {
|
|
17272
|
-
while (this.buffer.length > 0) {
|
|
17273
|
-
const first = this.buffer[0];
|
|
17274
|
-
if (this._totalSize - first.data.byteLength <= bufferedAmount) {
|
|
17275
|
-
break;
|
|
17276
|
-
}
|
|
17277
|
-
this.pop();
|
|
17278
|
-
}
|
|
17279
|
-
}
|
|
17280
|
-
get length() {
|
|
17281
|
-
return this.buffer.length;
|
|
17282
|
-
}
|
|
17283
|
-
}class TTLMap {
|
|
17284
|
-
/**
|
|
17285
|
-
* @param ttl ttl of the key (ms)
|
|
17286
|
-
*/
|
|
17287
|
-
constructor(ttl) {
|
|
17288
|
-
this._map = new Map();
|
|
17289
|
-
this._lastCleanup = 0;
|
|
17290
|
-
this.ttl = ttl;
|
|
17291
|
-
}
|
|
17292
|
-
set(key, value) {
|
|
17293
|
-
const now = Date.now();
|
|
17294
|
-
if (now - this._lastCleanup > this.ttl / 2) {
|
|
17295
|
-
this.cleanup();
|
|
17296
|
-
}
|
|
17297
|
-
const expiresAt = now + this.ttl;
|
|
17298
|
-
this._map.set(key, {
|
|
17299
|
-
value,
|
|
17300
|
-
expiresAt
|
|
17301
|
-
});
|
|
17302
|
-
return this;
|
|
17618
|
+
const expiresAt = now + this.ttl;
|
|
17619
|
+
this._map.set(key, {
|
|
17620
|
+
value,
|
|
17621
|
+
expiresAt
|
|
17622
|
+
});
|
|
17623
|
+
return this;
|
|
17303
17624
|
}
|
|
17304
17625
|
get(key) {
|
|
17305
17626
|
const entry = this._map.get(key);
|
|
@@ -18056,13 +18377,20 @@ function requireLib() {
|
|
|
18056
18377
|
lib.parseImageAttributes = parser.parseImageAttributes;
|
|
18057
18378
|
lib.parseSimulcastStreamList = parser.parseSimulcastStreamList;
|
|
18058
18379
|
return lib;
|
|
18059
|
-
}var libExports = requireLib();/*
|
|
18060
|
-
|
|
18061
|
-
|
|
18062
|
-
|
|
18063
|
-
|
|
18064
|
-
|
|
18065
|
-
|
|
18380
|
+
}var libExports = requireLib();/*
|
|
18381
|
+
* Video codecs use a very low bitrate at the beginning and increase slowly by
|
|
18382
|
+
* the bandwidth estimator until they reach the target bitrate. The process commonly
|
|
18383
|
+
* costs more than 10 seconds causing subscribers to get blurry video at the first
|
|
18384
|
+
* few seconds. We use x-google-start-bitrate to hint the BWE to start higher.
|
|
18385
|
+
*
|
|
18386
|
+
* Why 90%: Gives ~10% headroom for bandwidth estimation while starting close to target.
|
|
18387
|
+
* Why same for all codecs: Target bitrate already accounts for codec efficiency
|
|
18388
|
+
* (e.g., users set lower targets for VP9/AV1 knowing they're more efficient).
|
|
18389
|
+
* Why cap at 1 Mbps: Prevents BWE from starting too aggressively on high bitrate tracks.
|
|
18390
|
+
*/
|
|
18391
|
+
const startBitrateMultiplier = 0.9;
|
|
18392
|
+
/** Maximum x-google-start-bitrate in kbps. 1 Mbps prevents BWE from starting too aggressively. */
|
|
18393
|
+
const maxStartBitrateKbps = 1000;
|
|
18066
18394
|
const debounceInterval = 20;
|
|
18067
18395
|
const PCEvents = {
|
|
18068
18396
|
NegotiationStarted: 'negotiationStarted',
|
|
@@ -18087,6 +18415,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18087
18415
|
var _a;
|
|
18088
18416
|
super();
|
|
18089
18417
|
this.log = livekitLogger;
|
|
18418
|
+
this.iceLog = livekitLogger;
|
|
18090
18419
|
this.ddExtID = 0;
|
|
18091
18420
|
this.latestOfferId = 0;
|
|
18092
18421
|
this.latestAcknowledgedOfferId = 0;
|
|
@@ -18113,6 +18442,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18113
18442
|
if (!this._pc) {
|
|
18114
18443
|
return;
|
|
18115
18444
|
}
|
|
18445
|
+
this.log.debug('closing peer connection');
|
|
18116
18446
|
this.pendingInitialOffer = undefined;
|
|
18117
18447
|
this._pc.close();
|
|
18118
18448
|
this._pc.onconnectionstatechange = null;
|
|
@@ -18128,8 +18458,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18128
18458
|
this._pc.oniceconnectionstatechange = null;
|
|
18129
18459
|
this._pc = null;
|
|
18130
18460
|
};
|
|
18131
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport);
|
|
18132
18461
|
this.loggerOptions = loggerOptions;
|
|
18462
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport, () => this.logContext);
|
|
18463
|
+
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
|
|
18133
18464
|
this.config = config;
|
|
18134
18465
|
this._pc = this.createPC();
|
|
18135
18466
|
this.offerLock = new _();
|
|
@@ -18139,26 +18470,39 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18139
18470
|
pc.onicecandidate = ev => {
|
|
18140
18471
|
var _a;
|
|
18141
18472
|
if (!ev.candidate) return;
|
|
18473
|
+
this.iceLog.debug('local ICE candidate gathered', {
|
|
18474
|
+
candidate: ev.candidate.candidate
|
|
18475
|
+
});
|
|
18142
18476
|
(_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, ev.candidate);
|
|
18143
18477
|
};
|
|
18144
18478
|
pc.onicecandidateerror = ev => {
|
|
18145
18479
|
var _a;
|
|
18480
|
+
this.iceLog.debug('ICE candidate error', {
|
|
18481
|
+
event: ev
|
|
18482
|
+
});
|
|
18146
18483
|
(_a = this.onIceCandidateError) === null || _a === void 0 ? void 0 : _a.call(this, ev);
|
|
18147
18484
|
};
|
|
18148
18485
|
pc.oniceconnectionstatechange = () => {
|
|
18149
18486
|
var _a;
|
|
18487
|
+
this.iceLog.debug("ICE connection state: ".concat(pc.iceConnectionState));
|
|
18150
18488
|
(_a = this.onIceConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.iceConnectionState);
|
|
18151
18489
|
};
|
|
18152
18490
|
pc.onsignalingstatechange = () => {
|
|
18153
18491
|
var _a;
|
|
18492
|
+
this.log.debug("signaling state: ".concat(pc.signalingState));
|
|
18154
18493
|
(_a = this.onSignalingStatechange) === null || _a === void 0 ? void 0 : _a.call(this, pc.signalingState);
|
|
18155
18494
|
};
|
|
18156
18495
|
pc.onconnectionstatechange = () => {
|
|
18157
18496
|
var _a;
|
|
18497
|
+
this.log.debug("connection state: ".concat(pc.connectionState));
|
|
18158
18498
|
(_a = this.onConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.connectionState);
|
|
18159
18499
|
};
|
|
18160
18500
|
pc.ondatachannel = ev => {
|
|
18161
18501
|
var _a;
|
|
18502
|
+
this.log.debug('data channel opened by peer', {
|
|
18503
|
+
label: ev.channel.label,
|
|
18504
|
+
id: ev.channel.id
|
|
18505
|
+
});
|
|
18162
18506
|
(_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev);
|
|
18163
18507
|
};
|
|
18164
18508
|
pc.ontrack = ev => {
|
|
@@ -18179,6 +18523,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18179
18523
|
if (this.pc.remoteDescription && !this.restartingIce) {
|
|
18180
18524
|
return this.pc.addIceCandidate(candidate);
|
|
18181
18525
|
}
|
|
18526
|
+
this.iceLog.debug('queuing remote ICE candidate until remote description applied', {
|
|
18527
|
+
pendingCount: this.pendingCandidates.length + 1
|
|
18528
|
+
});
|
|
18182
18529
|
this.pendingCandidates.push(candidate);
|
|
18183
18530
|
});
|
|
18184
18531
|
}
|
|
@@ -18186,10 +18533,10 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18186
18533
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18187
18534
|
var _a, _b;
|
|
18188
18535
|
if (sd.type === 'answer' && this.latestOfferId > 0 && offerId > 0 && offerId !== this.latestOfferId) {
|
|
18189
|
-
this.log.warn('ignoring answer for old offer',
|
|
18536
|
+
this.log.warn('ignoring answer for old offer', {
|
|
18190
18537
|
offerId,
|
|
18191
18538
|
latestOfferId: this.latestOfferId
|
|
18192
|
-
})
|
|
18539
|
+
});
|
|
18193
18540
|
return false;
|
|
18194
18541
|
}
|
|
18195
18542
|
let mungedSDP = undefined;
|
|
@@ -18207,7 +18554,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18207
18554
|
sdpParsed.media.forEach(media => {
|
|
18208
18555
|
ensureIPAddrMatchVersion(media);
|
|
18209
18556
|
});
|
|
18210
|
-
this.log.debug('setting pending initial offer before processing answer'
|
|
18557
|
+
this.log.debug('setting pending initial offer before processing answer');
|
|
18211
18558
|
yield this.setMungedSDP(initialOffer, libExports.write(sdpParsed));
|
|
18212
18559
|
}
|
|
18213
18560
|
const sdpParsed = libExports.parse((_b = sd.sdp) !== null && _b !== void 0 ? _b : '');
|
|
@@ -18265,6 +18612,11 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18265
18612
|
mungedSDP = libExports.write(sdpParsed);
|
|
18266
18613
|
}
|
|
18267
18614
|
yield this.setMungedSDP(sd, mungedSDP, true);
|
|
18615
|
+
if (this.pendingCandidates.length > 0) {
|
|
18616
|
+
this.iceLog.debug('flushing queued ICE candidates', {
|
|
18617
|
+
count: this.pendingCandidates.length
|
|
18618
|
+
});
|
|
18619
|
+
}
|
|
18268
18620
|
this.pendingCandidates.forEach(candidate => {
|
|
18269
18621
|
this.pc.addIceCandidate(candidate);
|
|
18270
18622
|
});
|
|
@@ -18300,7 +18652,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18300
18652
|
const unlock = yield this.offerLock.lock();
|
|
18301
18653
|
try {
|
|
18302
18654
|
if (this.pc.signalingState !== 'stable') {
|
|
18303
|
-
this.log.warn('signaling state is not stable, cannot create initial offer'
|
|
18655
|
+
this.log.warn('signaling state is not stable, cannot create initial offer');
|
|
18304
18656
|
return;
|
|
18305
18657
|
}
|
|
18306
18658
|
const offerId = this.latestOfferId + 1;
|
|
@@ -18333,7 +18685,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18333
18685
|
return;
|
|
18334
18686
|
}
|
|
18335
18687
|
if (options === null || options === void 0 ? void 0 : options.iceRestart) {
|
|
18336
|
-
this.
|
|
18688
|
+
this.iceLog.debug('restarting ICE');
|
|
18337
18689
|
this.restartingIce = true;
|
|
18338
18690
|
}
|
|
18339
18691
|
if (this._pc && (this._pc.signalingState === 'have-local-offer' || this.pendingInitialOffer)) {
|
|
@@ -18346,22 +18698,22 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18346
18698
|
yield this._pc.setRemoteDescription(currentSD);
|
|
18347
18699
|
} else {
|
|
18348
18700
|
this.renegotiate = true;
|
|
18349
|
-
this.log.debug('requesting renegotiation'
|
|
18701
|
+
this.log.debug('requesting renegotiation');
|
|
18350
18702
|
return;
|
|
18351
18703
|
}
|
|
18352
18704
|
} else if (!this._pc || this._pc.signalingState === 'closed') {
|
|
18353
|
-
this.log.warn('could not createOffer with closed peer connection'
|
|
18705
|
+
this.log.warn('could not createOffer with closed peer connection');
|
|
18354
18706
|
return;
|
|
18355
18707
|
}
|
|
18356
18708
|
// actually negotiate
|
|
18357
|
-
this.log.debug('starting to negotiate'
|
|
18709
|
+
this.log.debug('starting to negotiate');
|
|
18358
18710
|
// increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior
|
|
18359
18711
|
const offerId = this.latestOfferId + 1;
|
|
18360
18712
|
this.latestOfferId = offerId;
|
|
18361
18713
|
const offer = yield this.pc.createOffer(options);
|
|
18362
|
-
this.log.debug('original offer',
|
|
18714
|
+
this.log.debug('original offer', {
|
|
18363
18715
|
sdp: offer.sdp
|
|
18364
|
-
}
|
|
18716
|
+
});
|
|
18365
18717
|
const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
|
|
18366
18718
|
sdpParsed.media.forEach(media => {
|
|
18367
18719
|
ensureIPAddrMatchVersion(media);
|
|
@@ -18387,12 +18739,15 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18387
18739
|
this.ensureVideoDDExtensionForSVC(media, sdpParsed);
|
|
18388
18740
|
}
|
|
18389
18741
|
// mung sdp for bitrate setting that can't apply by sendEncoding
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
const
|
|
18742
|
+
// Use 90% of target bitrate, capped at 1 Mbps for camera to prevent BWE from starting too aggressively
|
|
18743
|
+
// Screen share is not capped since text/UI clarity requires high bitrate from the start
|
|
18744
|
+
// TODO: dynamically adjust start bitrate based on network conditions (e.g., use previous BWE estimate)
|
|
18745
|
+
const calculatedStartBitrate = Math.round(trackbr.maxbr * startBitrateMultiplier);
|
|
18746
|
+
const startBitrate = trackbr.isScreenShare ? calculatedStartBitrate : Math.min(calculatedStartBitrate, maxStartBitrateKbps);
|
|
18747
|
+
let fmtpFound = false;
|
|
18394
18748
|
for (const fmtp of media.fmtp) {
|
|
18395
18749
|
if (fmtp.payload === codecPayload) {
|
|
18750
|
+
fmtpFound = true;
|
|
18396
18751
|
// if another track's fmtp already is set, we cannot override the bitrate
|
|
18397
18752
|
// this has the unfortunate consequence of being forced to use the
|
|
18398
18753
|
// initial track's bitrate for all tracks
|
|
@@ -18402,6 +18757,13 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18402
18757
|
break;
|
|
18403
18758
|
}
|
|
18404
18759
|
}
|
|
18760
|
+
// VP8 and some codecs may not have an existing fmtp line - create one
|
|
18761
|
+
if (!fmtpFound) {
|
|
18762
|
+
media.fmtp.push({
|
|
18763
|
+
payload: codecPayload,
|
|
18764
|
+
config: "x-google-start-bitrate=".concat(startBitrate)
|
|
18765
|
+
});
|
|
18766
|
+
}
|
|
18405
18767
|
return true;
|
|
18406
18768
|
});
|
|
18407
18769
|
}
|
|
@@ -18416,10 +18778,10 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18416
18778
|
conformBundledCodecFmtp(sdpParsed.media, media => placeholderMids.has(getMidString(media.mid)));
|
|
18417
18779
|
}
|
|
18418
18780
|
if (this.latestOfferId > offerId) {
|
|
18419
|
-
this.log.warn('latestOfferId mismatch',
|
|
18781
|
+
this.log.warn('latestOfferId mismatch', {
|
|
18420
18782
|
latestOfferId: this.latestOfferId,
|
|
18421
18783
|
offerId
|
|
18422
|
-
})
|
|
18784
|
+
});
|
|
18423
18785
|
return;
|
|
18424
18786
|
}
|
|
18425
18787
|
yield this.setMungedSDP(offer, libExports.write(sdpParsed));
|
|
@@ -18567,7 +18929,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18567
18929
|
if (munged) {
|
|
18568
18930
|
sd.sdp = munged;
|
|
18569
18931
|
try {
|
|
18570
|
-
this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description")
|
|
18932
|
+
this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description"));
|
|
18571
18933
|
if (remote) {
|
|
18572
18934
|
yield this.pc.setRemoteDescription(sd);
|
|
18573
18935
|
} else {
|
|
@@ -18575,11 +18937,11 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18575
18937
|
}
|
|
18576
18938
|
return;
|
|
18577
18939
|
} catch (e) {
|
|
18578
|
-
this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"),
|
|
18940
|
+
this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"), {
|
|
18579
18941
|
error: e,
|
|
18580
18942
|
mungedSdp: munged,
|
|
18581
18943
|
originalSdp
|
|
18582
|
-
})
|
|
18944
|
+
});
|
|
18583
18945
|
sd.sdp = originalSdp;
|
|
18584
18946
|
}
|
|
18585
18947
|
}
|
|
@@ -18606,9 +18968,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18606
18968
|
if (!remote && this.pc.remoteDescription) {
|
|
18607
18969
|
fields.remoteSdp = this.pc.remoteDescription;
|
|
18608
18970
|
}
|
|
18609
|
-
this.log.error("unable to set ".concat(sd.type),
|
|
18971
|
+
this.log.error("unable to set ".concat(sd.type), {
|
|
18610
18972
|
fields
|
|
18611
|
-
})
|
|
18973
|
+
});
|
|
18612
18974
|
throw new NegotiationError(msg);
|
|
18613
18975
|
}
|
|
18614
18976
|
});
|
|
@@ -18869,6 +19231,7 @@ class PCTransportManager {
|
|
|
18869
19231
|
var _a;
|
|
18870
19232
|
this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
|
|
18871
19233
|
this.log = livekitLogger;
|
|
19234
|
+
this.iceLog = livekitLogger;
|
|
18872
19235
|
this.updateState = () => {
|
|
18873
19236
|
var _a, _b;
|
|
18874
19237
|
const previousState = this.state;
|
|
@@ -18887,12 +19250,13 @@ class PCTransportManager {
|
|
|
18887
19250
|
this.state = PCTransportState.NEW;
|
|
18888
19251
|
}
|
|
18889
19252
|
if (previousState !== this.state) {
|
|
18890
|
-
this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state])
|
|
19253
|
+
this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state]));
|
|
18891
19254
|
(_a = this.onStateChange) === null || _a === void 0 ? void 0 : _a.call(this, this.state, this.publisher.getConnectionState(), (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.getConnectionState());
|
|
18892
19255
|
}
|
|
18893
19256
|
};
|
|
18894
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager);
|
|
18895
19257
|
this.loggerOptions = loggerOptions;
|
|
19258
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager, () => this.logContext);
|
|
19259
|
+
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
|
|
18896
19260
|
this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
|
|
18897
19261
|
this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
|
|
18898
19262
|
this.publisher = new PCTransport(rtcConfig, loggerOptions);
|
|
@@ -18965,9 +19329,9 @@ class PCTransportManager {
|
|
|
18965
19329
|
publisher.removeTrack(sender);
|
|
18966
19330
|
}
|
|
18967
19331
|
} catch (e) {
|
|
18968
|
-
this.log.warn('could not removeTrack',
|
|
19332
|
+
this.log.warn('could not removeTrack', {
|
|
18969
19333
|
error: e
|
|
18970
|
-
})
|
|
19334
|
+
});
|
|
18971
19335
|
}
|
|
18972
19336
|
}
|
|
18973
19337
|
}
|
|
@@ -18977,6 +19341,7 @@ class PCTransportManager {
|
|
|
18977
19341
|
}
|
|
18978
19342
|
triggerIceRestart() {
|
|
18979
19343
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19344
|
+
this.iceLog.warn('triggering ICE restart');
|
|
18980
19345
|
if (this.subscriber) {
|
|
18981
19346
|
this.subscriber.restartingIce = true;
|
|
18982
19347
|
}
|
|
@@ -18991,6 +19356,10 @@ class PCTransportManager {
|
|
|
18991
19356
|
addIceCandidate(candidate, target) {
|
|
18992
19357
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18993
19358
|
var _a;
|
|
19359
|
+
this.iceLog.debug('adding remote ICE candidate', {
|
|
19360
|
+
target,
|
|
19361
|
+
candidate
|
|
19362
|
+
});
|
|
18994
19363
|
if (target === SignalTarget.PUBLISHER) {
|
|
18995
19364
|
yield this.publisher.addIceCandidate(candidate);
|
|
18996
19365
|
} else {
|
|
@@ -19001,11 +19370,11 @@ class PCTransportManager {
|
|
|
19001
19370
|
createSubscriberAnswerFromOffer(sd, offerId) {
|
|
19002
19371
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19003
19372
|
var _a, _b, _c;
|
|
19004
|
-
this.log.debug('received server offer',
|
|
19373
|
+
this.log.debug('received server offer', {
|
|
19005
19374
|
RTCSdpType: sd.type,
|
|
19006
19375
|
sdp: sd.sdp,
|
|
19007
19376
|
signalingState: (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.getSignallingState().toString()
|
|
19008
|
-
})
|
|
19377
|
+
});
|
|
19009
19378
|
const unlock = yield this.remoteOfferLock.lock();
|
|
19010
19379
|
try {
|
|
19011
19380
|
const success = yield (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.setRemoteDescription(sd, offerId);
|
|
@@ -19022,6 +19391,9 @@ class PCTransportManager {
|
|
|
19022
19391
|
}
|
|
19023
19392
|
updateConfiguration(config, iceRestart) {
|
|
19024
19393
|
var _a;
|
|
19394
|
+
this.log.debug('updating rtc configuration', {
|
|
19395
|
+
iceRestart
|
|
19396
|
+
});
|
|
19025
19397
|
this.publisher.setConfiguration(config);
|
|
19026
19398
|
(_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.setConfiguration(config);
|
|
19027
19399
|
if (iceRestart) {
|
|
@@ -19034,7 +19406,7 @@ class PCTransportManager {
|
|
|
19034
19406
|
const unlock = yield this.connectionLock.lock();
|
|
19035
19407
|
try {
|
|
19036
19408
|
if (this.isPublisherConnectionRequired && this.publisher.getConnectionState() !== 'connected' && this.publisher.getConnectionState() !== 'connecting') {
|
|
19037
|
-
this.log.debug('negotiation required, start negotiating'
|
|
19409
|
+
this.log.debug('negotiation required, start negotiating');
|
|
19038
19410
|
this.publisher.negotiate();
|
|
19039
19411
|
}
|
|
19040
19412
|
yield Promise.all((_a = this.requiredTransports) === null || _a === void 0 ? void 0 : _a.map(transport => this.ensureTransportConnected(transport, abortController, timeout)));
|
|
@@ -19148,7 +19520,7 @@ class PCTransportManager {
|
|
|
19148
19520
|
}
|
|
19149
19521
|
return new Promise((resolve, reject) => __awaiter(_this, void 0, void 0, function* () {
|
|
19150
19522
|
const abortHandler = () => {
|
|
19151
|
-
this.log.warn('abort transport connection'
|
|
19523
|
+
this.log.warn('abort transport connection');
|
|
19152
19524
|
CriticalTimers.clearTimeout(connectTimeout);
|
|
19153
19525
|
reject(ConnectionError.cancelled('room connection has been cancelled'));
|
|
19154
19526
|
};
|
|
@@ -19174,6 +19546,663 @@ class PCTransportManager {
|
|
|
19174
19546
|
}();
|
|
19175
19547
|
});
|
|
19176
19548
|
}
|
|
19549
|
+
}/**
|
|
19550
|
+
* Two-watermark flow control for one data channel kind.
|
|
19551
|
+
*
|
|
19552
|
+
* Owns the per-kind headroom gate: senders proceed freely while the buffer is at or below the
|
|
19553
|
+
* high-water mark and otherwise block — serialized FIFO through a mutex — until the browser's
|
|
19554
|
+
* `bufferedamountlow` event (armed at the low-water mark) signals the buffer has drained. The
|
|
19555
|
+
* serialization prevents woken senders from all refilling at once and overflowing the SCTP send
|
|
19556
|
+
* buffer (see livekit/client-sdk-js#1995).
|
|
19557
|
+
*
|
|
19558
|
+
* Waiters are parked on the channel object captured at wait entry. If that object stops being
|
|
19559
|
+
* current — replaced or torn down — its events may never fire again, so the owner must call
|
|
19560
|
+
* {@link invalidateWaiters}, which aborts parked waiters (releasing the gate); the next waiter
|
|
19561
|
+
* gets a fresh controller.
|
|
19562
|
+
*/
|
|
19563
|
+
class FlowControlledDataChannel {
|
|
19564
|
+
constructor(opts) {
|
|
19565
|
+
/** Last emitted low-water status; starts true (an empty buffer is below the mark). */
|
|
19566
|
+
this.bufferStatusLow = true;
|
|
19567
|
+
this.headroomLock = new _();
|
|
19568
|
+
/** Cancels parked headroom waiters when the handle is replaced or torn down. */
|
|
19569
|
+
this.waiterAbortController = new AbortController();
|
|
19570
|
+
this.kind = opts.kind;
|
|
19571
|
+
this.lowWaterMark = opts.lowWaterMark;
|
|
19572
|
+
this.highWaterMark = opts.highWaterMark;
|
|
19573
|
+
this.isEngineClosed = opts.isEngineClosed;
|
|
19574
|
+
this.onBufferStatusChanged = opts.onBufferStatusChanged;
|
|
19575
|
+
}
|
|
19576
|
+
/** The currently attached RTCDataChannel handle, if any. */
|
|
19577
|
+
get channelHandle() {
|
|
19578
|
+
return this.handle;
|
|
19579
|
+
}
|
|
19580
|
+
/**
|
|
19581
|
+
* Attaches the channel handle this wrapper controls. Replacing an existing handle rejects
|
|
19582
|
+
* parked waiters — their events would never fire again on the abandoned object — and installs a
|
|
19583
|
+
* fresh controller, so queued senders re-check against the new channel. Wrappers outlive their
|
|
19584
|
+
* handles: this is the one place handle turnover happens, which is what makes stranding a
|
|
19585
|
+
* waiter structurally impossible.
|
|
19586
|
+
*/
|
|
19587
|
+
attach(dc) {
|
|
19588
|
+
if (this.handle && this.handle !== dc) {
|
|
19589
|
+
this.invalidateWaiters('data channel replaced');
|
|
19590
|
+
}
|
|
19591
|
+
this.handle = dc;
|
|
19592
|
+
}
|
|
19593
|
+
/** Detaches the handle on teardown, rejecting parked waiters. */
|
|
19594
|
+
detach() {
|
|
19595
|
+
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'data channel torn down';
|
|
19596
|
+
if (this.handle) {
|
|
19597
|
+
this.invalidateWaiters(reason);
|
|
19598
|
+
}
|
|
19599
|
+
this.handle = undefined;
|
|
19600
|
+
}
|
|
19601
|
+
getChannel() {
|
|
19602
|
+
return this.handle;
|
|
19603
|
+
}
|
|
19604
|
+
/**
|
|
19605
|
+
* Whether the send buffer has room to accept more data (the send gate). Senders proceed while
|
|
19606
|
+
* this is true and block once it goes false. Callers resolve the handle (and decide what an
|
|
19607
|
+
* absent one means) before asking.
|
|
19608
|
+
*/
|
|
19609
|
+
isBelowHighWaterMark(dc) {
|
|
19610
|
+
// RTCDataChannel has no built-in high-water mark, so we compare against our static mark.
|
|
19611
|
+
return dc.bufferedAmount <= this.highWaterMark;
|
|
19612
|
+
}
|
|
19613
|
+
/**
|
|
19614
|
+
* Whether the send buffer has drained to its low-water mark. Drives the engine's public
|
|
19615
|
+
* DCBufferStatusChanged event.
|
|
19616
|
+
*/
|
|
19617
|
+
isBelowLowWaterMark(dc) {
|
|
19618
|
+
// Read the channel's own threshold: it is tuned dynamically for the lossy channel.
|
|
19619
|
+
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
|
19620
|
+
}
|
|
19621
|
+
/**
|
|
19622
|
+
* Acquires the headroom lock, resolving with the unlock function. Batch senders (the resume
|
|
19623
|
+
* replay) hold it across all of their sends so no other sender can interleave, calling
|
|
19624
|
+
* {@link waitForHeadroomWithoutLock} per message to respect flow control within the batch.
|
|
19625
|
+
*/
|
|
19626
|
+
lockHeadroom() {
|
|
19627
|
+
return this.headroomLock.lock();
|
|
19628
|
+
}
|
|
19629
|
+
/**
|
|
19630
|
+
* Resolves once the caller may send on this channel: immediately while the send buffer is at or
|
|
19631
|
+
* below its high-water mark, otherwise once the buffer has drained to the low-water mark (the
|
|
19632
|
+
* `bufferedamountlow` event). Callers are serialized through the headroom lock so that, when
|
|
19633
|
+
* the buffer drains, they refill it one at a time (up to the high-water mark) rather than all
|
|
19634
|
+
* sending at once and overflowing the SCTP send buffer (see livekit/client-sdk-js#1995). The
|
|
19635
|
+
* closed/buffer checks run inside the lock so queued callers proceed in FIFO order.
|
|
19636
|
+
*/
|
|
19637
|
+
waitForHeadroomWithLock() {
|
|
19638
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19639
|
+
const unlock = yield this.lockHeadroom();
|
|
19640
|
+
try {
|
|
19641
|
+
yield this.waitForHeadroomWithoutLock();
|
|
19642
|
+
} finally {
|
|
19643
|
+
unlock();
|
|
19644
|
+
}
|
|
19645
|
+
});
|
|
19646
|
+
}
|
|
19647
|
+
/** Core wait of {@link waitForHeadroomWithLock}. The caller must hold the headroom lock. */
|
|
19648
|
+
waitForHeadroomWithoutLock() {
|
|
19649
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19650
|
+
if (this.isEngineClosed()) {
|
|
19651
|
+
throw new UnexpectedConnectionState('engine closed');
|
|
19652
|
+
}
|
|
19653
|
+
const dc = this.getChannel();
|
|
19654
|
+
if (!dc) {
|
|
19655
|
+
throw new UnexpectedConnectionState("DataChannel not found, kind: ".concat(this.kind));
|
|
19656
|
+
}
|
|
19657
|
+
if (this.isBelowHighWaterMark(dc)) {
|
|
19658
|
+
return;
|
|
19659
|
+
}
|
|
19660
|
+
const abortSignal = this.waiterAbortController.signal;
|
|
19661
|
+
yield new TypedPromise((resolve, reject) => {
|
|
19662
|
+
const onBufferedAmountLow = () => {
|
|
19663
|
+
cleanup();
|
|
19664
|
+
resolve();
|
|
19665
|
+
};
|
|
19666
|
+
const onDCClose = () => {
|
|
19667
|
+
cleanup();
|
|
19668
|
+
reject(new UnexpectedConnectionState("DataChannel ".concat(this.kind, " closed while draining the buffer")));
|
|
19669
|
+
};
|
|
19670
|
+
const onAbort = () => {
|
|
19671
|
+
cleanup();
|
|
19672
|
+
reject(new UnexpectedConnectionState("DataChannel ".concat(this.kind, " was replaced or torn down while waiting for headroom")));
|
|
19673
|
+
};
|
|
19674
|
+
const cleanup = () => {
|
|
19675
|
+
dc.removeEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
19676
|
+
dc.removeEventListener('close', onDCClose);
|
|
19677
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
19678
|
+
};
|
|
19679
|
+
if (abortSignal.aborted) {
|
|
19680
|
+
onAbort();
|
|
19681
|
+
return;
|
|
19682
|
+
}
|
|
19683
|
+
dc.addEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
19684
|
+
// Proxy along any error caused by the data channel closing while we wait.
|
|
19685
|
+
dc.addEventListener('close', onDCClose);
|
|
19686
|
+
// The channel object we're parked on can be abandoned without ever firing another event
|
|
19687
|
+
// (e.g. the engine recreating channels); the abort is our way out.
|
|
19688
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
19689
|
+
});
|
|
19690
|
+
});
|
|
19691
|
+
}
|
|
19692
|
+
/** Rejects all parked headroom waiters; the next waiter gets a fresh controller. */
|
|
19693
|
+
invalidateWaiters(reason) {
|
|
19694
|
+
this.waiterAbortController.abort(reason);
|
|
19695
|
+
this.waiterAbortController = new AbortController();
|
|
19696
|
+
}
|
|
19697
|
+
/**
|
|
19698
|
+
* Recomputes whether the buffer has drained to the low-water mark and, if that changed since the
|
|
19699
|
+
* last check, notifies the status listener. Two independent triggers land here: a send (which
|
|
19700
|
+
* raises the buffer) and the `bufferedamountlow` drain event (which lowers it) — the latter has
|
|
19701
|
+
* no send to hang the work off, which is why this is a shared entry point rather than a tail of
|
|
19702
|
+
* `send`.
|
|
19703
|
+
*/
|
|
19704
|
+
refreshBufferStatus() {
|
|
19705
|
+
var _a;
|
|
19706
|
+
const dc = this.getChannel();
|
|
19707
|
+
if (!dc) {
|
|
19708
|
+
return;
|
|
19709
|
+
}
|
|
19710
|
+
const isLow = this.isBelowLowWaterMark(dc);
|
|
19711
|
+
if (isLow !== this.bufferStatusLow) {
|
|
19712
|
+
this.bufferStatusLow = isLow;
|
|
19713
|
+
(_a = this.onBufferStatusChanged) === null || _a === void 0 ? void 0 : _a.call(this, isLow);
|
|
19714
|
+
}
|
|
19715
|
+
}
|
|
19716
|
+
}/**
|
|
19717
|
+
* A lossy channel: flow control plus a per-instance full-buffer policy.
|
|
19718
|
+
*
|
|
19719
|
+
* Each instance owns its own byterate stat, drop counter, and (when tuning is started) the
|
|
19720
|
+
* dynamic `bufferedAmountLowThreshold` adjustment that keeps the drop gate at roughly 100ms of
|
|
19721
|
+
* buffered latency. Keeping these per instance is what prevents one channel's traffic from
|
|
19722
|
+
* steering another channel's policy.
|
|
19723
|
+
*/
|
|
19724
|
+
class LossyDataChannel extends FlowControlledDataChannel {
|
|
19725
|
+
constructor(opts) {
|
|
19726
|
+
super(opts);
|
|
19727
|
+
this.statCurrentBytes = 0;
|
|
19728
|
+
this.statByterate = 0;
|
|
19729
|
+
this.dropCount = 0;
|
|
19730
|
+
this.bufferFullBehavior = opts.bufferFullBehavior;
|
|
19731
|
+
this.shouldSkipSends = opts.shouldSkipSends;
|
|
19732
|
+
}
|
|
19733
|
+
/** Sends prepared bytes with this channel's full-buffer policy (drop or wait). */
|
|
19734
|
+
send(msg) {
|
|
19735
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19736
|
+
const dc = this.getChannel();
|
|
19737
|
+
if (!dc) {
|
|
19738
|
+
return;
|
|
19739
|
+
}
|
|
19740
|
+
// Depending on the channel's policy, either drop or wait for the buffer to drain below the
|
|
19741
|
+
// high-water mark before continuing.
|
|
19742
|
+
switch (this.bufferFullBehavior) {
|
|
19743
|
+
case 'wait':
|
|
19744
|
+
if (!this.isBelowHighWaterMark(dc)) {
|
|
19745
|
+
yield this.waitForHeadroomWithLock();
|
|
19746
|
+
}
|
|
19747
|
+
break;
|
|
19748
|
+
case 'drop':
|
|
19749
|
+
// We check against the actual threshold on the DC here, as it is tuned dynamically.
|
|
19750
|
+
if (!this.isBelowLowWaterMark(dc)) {
|
|
19751
|
+
// Drop messages to reduce latency
|
|
19752
|
+
this.dropCount += 1;
|
|
19753
|
+
if (this.dropCount % 100 === 0) {
|
|
19754
|
+
livekitLogger.warn("dropping lossy data channel messages, total dropped: ".concat(this.dropCount));
|
|
19755
|
+
}
|
|
19756
|
+
return;
|
|
19757
|
+
}
|
|
19758
|
+
}
|
|
19759
|
+
this.statCurrentBytes += msg.byteLength;
|
|
19760
|
+
if (this.shouldSkipSends()) {
|
|
19761
|
+
return;
|
|
19762
|
+
}
|
|
19763
|
+
try {
|
|
19764
|
+
dc.send(msg);
|
|
19765
|
+
this.refreshBufferStatus();
|
|
19766
|
+
} catch (error) {
|
|
19767
|
+
// Preserve prior surface behaviour: a send that fails because the channel is closing is
|
|
19768
|
+
// logged, not thrown, so lossy/data-track sends don't reject during teardown windows.
|
|
19769
|
+
if (error instanceof TypeError) {
|
|
19770
|
+
livekitLogger.error(error);
|
|
19771
|
+
} else {
|
|
19772
|
+
throw error;
|
|
19773
|
+
}
|
|
19774
|
+
}
|
|
19775
|
+
});
|
|
19776
|
+
}
|
|
19777
|
+
/**
|
|
19778
|
+
* Starts the once-per-second adjustment of the channel's `bufferedAmountLowThreshold` to the
|
|
19779
|
+
* observed byterate, keeping the drop gate at roughly 100ms of buffered latency (clamped to
|
|
19780
|
+
* the watermarks). Restarts cleanly if already running.
|
|
19781
|
+
*/
|
|
19782
|
+
startThresholdTuning() {
|
|
19783
|
+
this.stopThresholdTuning();
|
|
19784
|
+
this.statInterval = CriticalTimers.setInterval(() => {
|
|
19785
|
+
this.statByterate = this.statCurrentBytes;
|
|
19786
|
+
this.statCurrentBytes = 0;
|
|
19787
|
+
const dc = this.getChannel();
|
|
19788
|
+
if (dc) {
|
|
19789
|
+
// control buffered latency to ~100ms
|
|
19790
|
+
const threshold = this.statByterate / 10;
|
|
19791
|
+
dc.bufferedAmountLowThreshold = Math.min(Math.max(threshold, this.lowWaterMark), this.highWaterMark);
|
|
19792
|
+
}
|
|
19793
|
+
}, 1000);
|
|
19794
|
+
}
|
|
19795
|
+
/** Stops the threshold tuning and resets the stats and drop counter. */
|
|
19796
|
+
stopThresholdTuning() {
|
|
19797
|
+
this.statByterate = 0;
|
|
19798
|
+
this.statCurrentBytes = 0;
|
|
19799
|
+
if (this.statInterval) {
|
|
19800
|
+
CriticalTimers.clearInterval(this.statInterval);
|
|
19801
|
+
this.statInterval = undefined;
|
|
19802
|
+
}
|
|
19803
|
+
this.dropCount = 0;
|
|
19804
|
+
}
|
|
19805
|
+
}class DataPacketBuffer {
|
|
19806
|
+
constructor() {
|
|
19807
|
+
this.buffer = [];
|
|
19808
|
+
this._totalSize = 0;
|
|
19809
|
+
this._sentSize = 0;
|
|
19810
|
+
}
|
|
19811
|
+
push(item) {
|
|
19812
|
+
this.buffer.push(item);
|
|
19813
|
+
this._totalSize += item.data.byteLength;
|
|
19814
|
+
if (item.sent) {
|
|
19815
|
+
this._sentSize += item.data.byteLength;
|
|
19816
|
+
}
|
|
19817
|
+
}
|
|
19818
|
+
pop() {
|
|
19819
|
+
const item = this.buffer.shift();
|
|
19820
|
+
if (item) {
|
|
19821
|
+
this._totalSize -= item.data.byteLength;
|
|
19822
|
+
if (item.sent) {
|
|
19823
|
+
this._sentSize -= item.data.byteLength;
|
|
19824
|
+
}
|
|
19825
|
+
}
|
|
19826
|
+
return item;
|
|
19827
|
+
}
|
|
19828
|
+
getAll() {
|
|
19829
|
+
return this.buffer.slice();
|
|
19830
|
+
}
|
|
19831
|
+
/** Every queued packet not yet handed to the channel, in sequence order. */
|
|
19832
|
+
getUnsent() {
|
|
19833
|
+
return this.buffer.filter(item => !item.sent);
|
|
19834
|
+
}
|
|
19835
|
+
/** Marks a single queued packet as handed to the channel. */
|
|
19836
|
+
markSent(item) {
|
|
19837
|
+
if (!item.sent) {
|
|
19838
|
+
item.sent = true;
|
|
19839
|
+
this._sentSize += item.data.byteLength;
|
|
19840
|
+
}
|
|
19841
|
+
}
|
|
19842
|
+
/**
|
|
19843
|
+
* Marks every queued packet as not-yet-sent. Used at the start of a resume replay: whatever is
|
|
19844
|
+
* still buffered was sent on the previous channel (or deferred) and must be re-handed to the
|
|
19845
|
+
* current one, so none of it counts as sent until the replay actually transmits it.
|
|
19846
|
+
*/
|
|
19847
|
+
markAllUnsent() {
|
|
19848
|
+
for (const item of this.buffer) {
|
|
19849
|
+
item.sent = false;
|
|
19850
|
+
}
|
|
19851
|
+
this._sentSize = 0;
|
|
19852
|
+
}
|
|
19853
|
+
popToSequence(sequence) {
|
|
19854
|
+
while (this.buffer.length > 0) {
|
|
19855
|
+
const first = this.buffer[0];
|
|
19856
|
+
if (first.sequence <= sequence) {
|
|
19857
|
+
this.pop();
|
|
19858
|
+
} else {
|
|
19859
|
+
break;
|
|
19860
|
+
}
|
|
19861
|
+
}
|
|
19862
|
+
}
|
|
19863
|
+
alignBufferedAmount(bufferedAmount) {
|
|
19864
|
+
while (this.buffer.length > 0) {
|
|
19865
|
+
const first = this.buffer[0];
|
|
19866
|
+
// Unsent packets aren't part of the channel's bufferedAmount and are still awaiting the
|
|
19867
|
+
// resume replay — trimming them would silently lose them.
|
|
19868
|
+
if (!first.sent) {
|
|
19869
|
+
break;
|
|
19870
|
+
}
|
|
19871
|
+
if (this._sentSize - first.data.byteLength <= bufferedAmount) {
|
|
19872
|
+
break;
|
|
19873
|
+
}
|
|
19874
|
+
this.pop();
|
|
19875
|
+
}
|
|
19876
|
+
}
|
|
19877
|
+
get length() {
|
|
19878
|
+
return this.buffer.length;
|
|
19879
|
+
}
|
|
19880
|
+
}/**
|
|
19881
|
+
* The reliable channel: flow control plus delivery-across-resume semantics.
|
|
19882
|
+
*
|
|
19883
|
+
* Every packet gets a monotonic sequence (stamped into the protobuf by the caller before
|
|
19884
|
+
* serialization, via {@link nextSequence}) and is retained in a replay buffer until the channel's
|
|
19885
|
+
* `bufferedAmount` confirms it has been handed to the transport. Sends that land in a reconnect
|
|
19886
|
+
* window — or whose headroom wait is torn down transiently — are queued unsent and resolve;
|
|
19887
|
+
* {@link replay} delivers them (plus any unacked packets) after a resume. Only an engine close
|
|
19888
|
+
* rejects, because no replay is coming after that.
|
|
19889
|
+
*/
|
|
19890
|
+
class ReliableDataChannel extends FlowControlledDataChannel {
|
|
19891
|
+
constructor(opts) {
|
|
19892
|
+
super(opts);
|
|
19893
|
+
this.messageBuffer = new DataPacketBuffer();
|
|
19894
|
+
this.sequence = 1;
|
|
19895
|
+
this.isDeferringSends = opts.isDeferringSends;
|
|
19896
|
+
}
|
|
19897
|
+
/**
|
|
19898
|
+
* Claims the next packet sequence. The caller stamps it into the packet before serialization,
|
|
19899
|
+
* then passes it back to {@link send} so the replay buffer stays keyed by wire sequence.
|
|
19900
|
+
*/
|
|
19901
|
+
nextSequence() {
|
|
19902
|
+
const sequence = this.sequence;
|
|
19903
|
+
this.sequence += 1;
|
|
19904
|
+
return sequence;
|
|
19905
|
+
}
|
|
19906
|
+
/**
|
|
19907
|
+
* Sends prepared bytes with reliable semantics. Resolves once the packet has either been handed
|
|
19908
|
+
* to the channel or queued for the resume replay; throws only when the engine is closed.
|
|
19909
|
+
*/
|
|
19910
|
+
send(msg, sequence) {
|
|
19911
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19912
|
+
if (this.isDeferringSends()) {
|
|
19913
|
+
// A reconnect is already underway — queue for the resume replay instead of parking on a
|
|
19914
|
+
// channel that is being torn down. The send resolves; delivery is deferred to the replay.
|
|
19915
|
+
this.messageBuffer.push({
|
|
19916
|
+
data: msg,
|
|
19917
|
+
sequence,
|
|
19918
|
+
sent: false
|
|
19919
|
+
});
|
|
19920
|
+
return;
|
|
19921
|
+
}
|
|
19922
|
+
const dc = this.getChannel();
|
|
19923
|
+
if (!dc) {
|
|
19924
|
+
return;
|
|
19925
|
+
}
|
|
19926
|
+
try {
|
|
19927
|
+
yield this.waitForHeadroomWithLock();
|
|
19928
|
+
} catch (error) {
|
|
19929
|
+
if (this.isEngineClosed()) {
|
|
19930
|
+
// No replay is coming after an engine close — surface the failure.
|
|
19931
|
+
throw error;
|
|
19932
|
+
}
|
|
19933
|
+
// Transient teardown (the channel closed or was replaced while we waited): the reliable
|
|
19934
|
+
// channel promises delivery across resume, so queue the packet for the replay instead of
|
|
19935
|
+
// rejecting a send the app can't meaningfully retry.
|
|
19936
|
+
this.messageBuffer.push({
|
|
19937
|
+
data: msg,
|
|
19938
|
+
sequence,
|
|
19939
|
+
sent: false
|
|
19940
|
+
});
|
|
19941
|
+
return;
|
|
19942
|
+
}
|
|
19943
|
+
if (this.isDeferringSends()) {
|
|
19944
|
+
// A reconnect began while we waited for headroom — same deal as above.
|
|
19945
|
+
this.messageBuffer.push({
|
|
19946
|
+
data: msg,
|
|
19947
|
+
sequence,
|
|
19948
|
+
sent: false
|
|
19949
|
+
});
|
|
19950
|
+
return;
|
|
19951
|
+
}
|
|
19952
|
+
this.messageBuffer.push({
|
|
19953
|
+
data: msg,
|
|
19954
|
+
sequence,
|
|
19955
|
+
sent: true
|
|
19956
|
+
});
|
|
19957
|
+
dc.send(msg);
|
|
19958
|
+
this.refreshBufferStatus();
|
|
19959
|
+
});
|
|
19960
|
+
}
|
|
19961
|
+
/**
|
|
19962
|
+
* Replays the buffered backlog after a resume: drops everything the server acked
|
|
19963
|
+
* (`lastMessageSeq`), then re-sends the rest in order. The headroom lock is held across the
|
|
19964
|
+
* whole replay — releasing it between messages would let a concurrent send (whose newer
|
|
19965
|
+
* sequence was already assigned before it queued on the lock) hit the wire mid-replay, and
|
|
19966
|
+
* receivers would then discard the remaining lower-sequence resent messages as duplicates.
|
|
19967
|
+
*/
|
|
19968
|
+
replay(lastMessageSeq) {
|
|
19969
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19970
|
+
const dc = this.getChannel();
|
|
19971
|
+
if (!dc) {
|
|
19972
|
+
return;
|
|
19973
|
+
}
|
|
19974
|
+
this.messageBuffer.popToSequence(lastMessageSeq);
|
|
19975
|
+
const unlock = yield this.lockHeadroom();
|
|
19976
|
+
try {
|
|
19977
|
+
// Everything left after the ack cutoff must be re-handed to the current channel.
|
|
19978
|
+
this.messageBuffer.markAllUnsent();
|
|
19979
|
+
// Drain in passes, re-scanning the live buffer each time: a send that arrives (deferred,
|
|
19980
|
+
// sent:false) during our own awaits appends after this pass started, so we pick it up on
|
|
19981
|
+
// the next one. Mark each packet only once we've actually handed it to the channel — a
|
|
19982
|
+
// blanket "mark all sent" would flip such a late arrival to sent without transmitting it,
|
|
19983
|
+
// and a later alignBufferedAmount would then drop it for good. If the loop throws
|
|
19984
|
+
// mid-drain, unsent entries keep their flag and the next replay picks them up.
|
|
19985
|
+
for (let batch = this.messageBuffer.getUnsent(); batch.length > 0; batch = this.messageBuffer.getUnsent()) {
|
|
19986
|
+
for (const item of batch) {
|
|
19987
|
+
// Respect flow control on resume too, so a large resend doesn't overflow the buffer.
|
|
19988
|
+
yield this.waitForHeadroomWithoutLock();
|
|
19989
|
+
dc.send(item.data);
|
|
19990
|
+
this.messageBuffer.markSent(item);
|
|
19991
|
+
}
|
|
19992
|
+
}
|
|
19993
|
+
} finally {
|
|
19994
|
+
unlock();
|
|
19995
|
+
}
|
|
19996
|
+
this.refreshBufferStatus();
|
|
19997
|
+
});
|
|
19998
|
+
}
|
|
19999
|
+
/**
|
|
20000
|
+
* Before recomputing status, trim packets the transport has now delivered — a send or a drain
|
|
20001
|
+
* may have acked buffered packets, and the replay buffer is keyed off the channel's buffered
|
|
20002
|
+
* bytes.
|
|
20003
|
+
*/
|
|
20004
|
+
refreshBufferStatus() {
|
|
20005
|
+
const dc = this.channelHandle;
|
|
20006
|
+
if (dc) {
|
|
20007
|
+
this.messageBuffer.alignBufferedAmount(dc.bufferedAmount);
|
|
20008
|
+
}
|
|
20009
|
+
super.refreshBufferStatus();
|
|
20010
|
+
}
|
|
20011
|
+
/**
|
|
20012
|
+
* Drops all replay state and restarts sequencing. Only valid on a full reconnect, where the
|
|
20013
|
+
* session (and the receivers' sequence tracking) starts over.
|
|
20014
|
+
*/
|
|
20015
|
+
reset() {
|
|
20016
|
+
this.messageBuffer = new DataPacketBuffer();
|
|
20017
|
+
this.sequence = 1;
|
|
20018
|
+
}
|
|
20019
|
+
}var DataChannelKind;
|
|
20020
|
+
(function (DataChannelKind) {
|
|
20021
|
+
DataChannelKind[DataChannelKind["RELIABLE"] = 0] = "RELIABLE";
|
|
20022
|
+
DataChannelKind[DataChannelKind["LOSSY"] = 1] = "LOSSY";
|
|
20023
|
+
DataChannelKind[DataChannelKind["DATA_TRACK_LOSSY"] = 2] = "DATA_TRACK_LOSSY";
|
|
20024
|
+
})(DataChannelKind || (DataChannelKind = {}));
|
|
20025
|
+
// Two-watermark flow control for the reliable and data-track channels. Senders fill the buffer
|
|
20026
|
+
// freely up to the high-water mark; once it's exceeded they block until the browser's
|
|
20027
|
+
// `bufferedamountlow` event (which we arm at the low-water mark) signals the buffer has drained.
|
|
20028
|
+
// The gap between the marks keeps the SCTP send buffer saturated while we refill, so throughput
|
|
20029
|
+
// isn't starved, while the high-water mark bounds the buffer well below the level that would abort
|
|
20030
|
+
// the channel (see livekit/client-sdk-js#1995).
|
|
20031
|
+
const reliableDataChannelWaterMarkLow = 64 * 1024;
|
|
20032
|
+
const reliableDataChannelWaterMarkHigh = 1024 * 1024;
|
|
20033
|
+
const lossyDataChannelWaterMarkLow = 8 * 1024;
|
|
20034
|
+
const lossyDataChannelWaterMarkHigh = 256 * 1024;
|
|
20035
|
+
function dataChannelLowWaterMark(kind) {
|
|
20036
|
+
return kind === DataChannelKind.RELIABLE ? reliableDataChannelWaterMarkLow : lossyDataChannelWaterMarkLow;
|
|
20037
|
+
}
|
|
20038
|
+
function dataChannelHighWaterMark(kind) {
|
|
20039
|
+
return kind === DataChannelKind.RELIABLE ? reliableDataChannelWaterMarkHigh : lossyDataChannelWaterMarkHigh;
|
|
20040
|
+
}const lossyDataChannelLabel = '_lossy';
|
|
20041
|
+
const reliableDataChannelLabel = '_reliable';
|
|
20042
|
+
const dataTrackDataChannelLabel = '_data_track';
|
|
20043
|
+
/**
|
|
20044
|
+
* Owns the engine's data channels: the three flow-controlled publisher channel wrappers (which
|
|
20045
|
+
* live for the engine's lifetime and have RTCDataChannel handles attached/detached as peer
|
|
20046
|
+
* connections come and go) plus the subscriber-side receive handles adopted by label.
|
|
20047
|
+
*
|
|
20048
|
+
* Handle turnover goes through {@link FlowControlledDataChannel.attach}/`detach`, which reject
|
|
20049
|
+
* parked headroom waiters as a built-in — there is no separate invalidation step to forget.
|
|
20050
|
+
*/
|
|
20051
|
+
class DataChannelManager {
|
|
20052
|
+
constructor(opts) {
|
|
20053
|
+
this.opts = opts;
|
|
20054
|
+
const flowControlOptions = kind => ({
|
|
20055
|
+
kind,
|
|
20056
|
+
lowWaterMark: dataChannelLowWaterMark(kind),
|
|
20057
|
+
highWaterMark: dataChannelHighWaterMark(kind),
|
|
20058
|
+
isEngineClosed: opts.isEngineClosed,
|
|
20059
|
+
onBufferStatusChanged: isLow => opts.onBufferStatusChanged(kind, isLow)
|
|
20060
|
+
});
|
|
20061
|
+
this.reliable = new ReliableDataChannel(Object.assign(Object.assign({}, flowControlOptions(DataChannelKind.RELIABLE)), {
|
|
20062
|
+
isDeferringSends: opts.isReconnecting
|
|
20063
|
+
}));
|
|
20064
|
+
this.lossy = new LossyDataChannel(Object.assign(Object.assign({}, flowControlOptions(DataChannelKind.LOSSY)), {
|
|
20065
|
+
// Classic lossy user data: a stale packet is worthless, so drop instead of queueing.
|
|
20066
|
+
bufferFullBehavior: 'drop',
|
|
20067
|
+
shouldSkipSends: opts.isReconnecting
|
|
20068
|
+
}));
|
|
20069
|
+
this.dataTrack = new LossyDataChannel(Object.assign(Object.assign({}, flowControlOptions(DataChannelKind.DATA_TRACK_LOSSY)), {
|
|
20070
|
+
// Data tracks backpressure the producer instead — it decides what to skip at frame
|
|
20071
|
+
// granularity rather than the engine dropping arbitrary chunks out of frames.
|
|
20072
|
+
bufferFullBehavior: 'wait',
|
|
20073
|
+
shouldSkipSends: opts.isReconnecting
|
|
20074
|
+
}));
|
|
20075
|
+
}
|
|
20076
|
+
/** The flow-control wrapper for `kind`. */
|
|
20077
|
+
channelFor(kind) {
|
|
20078
|
+
switch (kind) {
|
|
20079
|
+
case DataChannelKind.RELIABLE:
|
|
20080
|
+
return this.reliable;
|
|
20081
|
+
case DataChannelKind.LOSSY:
|
|
20082
|
+
return this.lossy;
|
|
20083
|
+
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
20084
|
+
return this.dataTrack;
|
|
20085
|
+
}
|
|
20086
|
+
}
|
|
20087
|
+
/** The raw RTCDataChannel handle for `kind`, publisher side by default. */
|
|
20088
|
+
getHandle(kind) {
|
|
20089
|
+
let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
20090
|
+
if (!subscriber) {
|
|
20091
|
+
return this.channelFor(kind).channelHandle;
|
|
20092
|
+
}
|
|
20093
|
+
switch (kind) {
|
|
20094
|
+
case DataChannelKind.RELIABLE:
|
|
20095
|
+
return this.reliableSub;
|
|
20096
|
+
case DataChannelKind.LOSSY:
|
|
20097
|
+
return this.lossySub;
|
|
20098
|
+
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
20099
|
+
return this.dataTrackSub;
|
|
20100
|
+
}
|
|
20101
|
+
}
|
|
20102
|
+
get hasPublisherChannels() {
|
|
20103
|
+
return Boolean(this.reliable.channelHandle || this.lossy.channelHandle || this.dataTrack.channelHandle);
|
|
20104
|
+
}
|
|
20105
|
+
/**
|
|
20106
|
+
* Creates the three publisher data channels on the given transport, wires their handlers, and
|
|
20107
|
+
* attaches them to the wrappers — attaching rejects any waiters still parked on replaced
|
|
20108
|
+
* channel objects.
|
|
20109
|
+
*/
|
|
20110
|
+
createPublisherChannels(pcManager) {
|
|
20111
|
+
// clear old data channel callbacks if recreate
|
|
20112
|
+
for (const channel of [this.lossy, this.reliable, this.dataTrack]) {
|
|
20113
|
+
const old = channel.channelHandle;
|
|
20114
|
+
if (old) {
|
|
20115
|
+
old.onmessage = null;
|
|
20116
|
+
old.onerror = null;
|
|
20117
|
+
old.onclose = null;
|
|
20118
|
+
}
|
|
20119
|
+
}
|
|
20120
|
+
const wire = (channel, dc, onMessage) => {
|
|
20121
|
+
// also handle messages over the pub channel, for backwards compatibility
|
|
20122
|
+
dc.onmessage = onMessage;
|
|
20123
|
+
// handle datachannel errors
|
|
20124
|
+
dc.onerror = this.opts.onDataError;
|
|
20125
|
+
// detect unexpected publisher data channel closes
|
|
20126
|
+
dc.onclose = () => this.opts.onChannelClose(channel.kind);
|
|
20127
|
+
// set up dc buffer threshold - if this is not set, it will default to 0
|
|
20128
|
+
dc.bufferedAmountLowThreshold = channel.lowWaterMark;
|
|
20129
|
+
// handle buffer amount low events
|
|
20130
|
+
dc.onbufferedamountlow = () => channel.refreshBufferStatus();
|
|
20131
|
+
channel.attach(dc);
|
|
20132
|
+
};
|
|
20133
|
+
wire(this.lossy, pcManager.createPublisherDataChannel(lossyDataChannelLabel, {
|
|
20134
|
+
ordered: false,
|
|
20135
|
+
maxRetransmits: 0
|
|
20136
|
+
}), this.opts.onDataMessage);
|
|
20137
|
+
wire(this.reliable, pcManager.createPublisherDataChannel(reliableDataChannelLabel, {
|
|
20138
|
+
ordered: true
|
|
20139
|
+
}), this.opts.onDataMessage);
|
|
20140
|
+
wire(this.dataTrack, pcManager.createPublisherDataChannel(dataTrackDataChannelLabel, {
|
|
20141
|
+
ordered: false,
|
|
20142
|
+
maxRetransmits: 0
|
|
20143
|
+
}), this.opts.onDataTrackMessage);
|
|
20144
|
+
this.lossy.startThresholdTuning();
|
|
20145
|
+
}
|
|
20146
|
+
/**
|
|
20147
|
+
* Adopts a subscriber-side data channel by label, wiring the matching receive handler.
|
|
20148
|
+
* Returns false for labels this manager doesn't own.
|
|
20149
|
+
*/
|
|
20150
|
+
adoptSubscriberChannel(channel) {
|
|
20151
|
+
let handler;
|
|
20152
|
+
if (channel.label === reliableDataChannelLabel) {
|
|
20153
|
+
this.reliableSub = channel;
|
|
20154
|
+
handler = this.opts.onDataMessage;
|
|
20155
|
+
} else if (channel.label === lossyDataChannelLabel) {
|
|
20156
|
+
this.lossySub = channel;
|
|
20157
|
+
handler = this.opts.onDataMessage;
|
|
20158
|
+
} else if (channel.label === dataTrackDataChannelLabel) {
|
|
20159
|
+
this.dataTrackSub = channel;
|
|
20160
|
+
handler = this.opts.onDataTrackMessage;
|
|
20161
|
+
} else {
|
|
20162
|
+
return false;
|
|
20163
|
+
}
|
|
20164
|
+
channel.onmessage = handler;
|
|
20165
|
+
return true;
|
|
20166
|
+
}
|
|
20167
|
+
/**
|
|
20168
|
+
* Tears down all channels for a peer-connection cleanup: rejects parked waiters (detach — the
|
|
20169
|
+
* spec allows `pc.close()` to transition channels to 'closed' without firing events, so waiting
|
|
20170
|
+
* for browser close events is not an option), strips handlers, closes the handles, and resets
|
|
20171
|
+
* the reliable session state.
|
|
20172
|
+
*/
|
|
20173
|
+
teardown() {
|
|
20174
|
+
const dcCleanup = dc => {
|
|
20175
|
+
if (!dc) {
|
|
20176
|
+
return;
|
|
20177
|
+
}
|
|
20178
|
+
// Detach the data channel handlers before closing anything. Closing a peer connection tears
|
|
20179
|
+
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
|
|
20180
|
+
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
|
|
20181
|
+
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
|
|
20182
|
+
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
|
|
20183
|
+
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
|
|
20184
|
+
dc.onbufferedamountlow = null;
|
|
20185
|
+
dc.onclose = null;
|
|
20186
|
+
dc.onclosing = null;
|
|
20187
|
+
dc.onerror = null;
|
|
20188
|
+
dc.onmessage = null;
|
|
20189
|
+
dc.onopen = null;
|
|
20190
|
+
dc.close();
|
|
20191
|
+
};
|
|
20192
|
+
for (const channel of [this.lossy, this.reliable, this.dataTrack]) {
|
|
20193
|
+
const dc = channel.channelHandle;
|
|
20194
|
+
channel.detach('peer connections cleaned up');
|
|
20195
|
+
dcCleanup(dc);
|
|
20196
|
+
}
|
|
20197
|
+
dcCleanup(this.lossySub);
|
|
20198
|
+
dcCleanup(this.reliableSub);
|
|
20199
|
+
dcCleanup(this.dataTrackSub);
|
|
20200
|
+
this.lossySub = undefined;
|
|
20201
|
+
this.reliableSub = undefined;
|
|
20202
|
+
this.dataTrackSub = undefined;
|
|
20203
|
+
// Full teardown starts the session over: drop replay state and restart sequencing.
|
|
20204
|
+
this.reliable.reset();
|
|
20205
|
+
}
|
|
19177
20206
|
}// Check if MediaRecorder is available
|
|
19178
20207
|
const isMediaRecorderAvailable = typeof MediaRecorder !== 'undefined';
|
|
19179
20208
|
// Fallback class for environments without MediaRecorder
|
|
@@ -19984,6 +21013,18 @@ class LocalTrack extends Track {
|
|
|
19984
21013
|
yield this.restart(constraints);
|
|
19985
21014
|
});
|
|
19986
21015
|
}
|
|
21016
|
+
applyConstraints(constraints) {
|
|
21017
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21018
|
+
const unlock = yield this.trackChangeLock.lock();
|
|
21019
|
+
try {
|
|
21020
|
+
const res = yield this._mediaStreamTrack.applyConstraints(constraints);
|
|
21021
|
+
this._constraints = Object.assign(Object.assign({}, this._constraints), constraints);
|
|
21022
|
+
return res;
|
|
21023
|
+
} finally {
|
|
21024
|
+
unlock();
|
|
21025
|
+
}
|
|
21026
|
+
});
|
|
21027
|
+
}
|
|
19987
21028
|
restart(constraints, isUnmuting) {
|
|
19988
21029
|
const _super = Object.create(null, {
|
|
19989
21030
|
restart: {
|
|
@@ -20420,14 +21461,21 @@ class ScalabilityMode {
|
|
|
20420
21461
|
return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
|
|
20421
21462
|
}
|
|
20422
21463
|
}
|
|
21464
|
+
/**
|
|
21465
|
+
* Returns the appropriate degradation preference for a video track based on its source.
|
|
21466
|
+
*
|
|
21467
|
+
* - Camera: 'maintain-framerate' (smoother video for real-time communication)
|
|
21468
|
+
* - Screen share: 'maintain-resolution' (clarity is critical for reading text/UI)
|
|
21469
|
+
* - Other/unknown: 'balanced'
|
|
21470
|
+
*/
|
|
20423
21471
|
function getDefaultDegradationPreference(track) {
|
|
20424
|
-
|
|
20425
|
-
|
|
20426
|
-
|
|
20427
|
-
|
|
20428
|
-
|
|
20429
|
-
|
|
20430
|
-
|
|
21472
|
+
switch (track.source) {
|
|
21473
|
+
case Track.Source.Camera:
|
|
21474
|
+
return 'maintain-framerate';
|
|
21475
|
+
case Track.Source.ScreenShare:
|
|
21476
|
+
return 'maintain-resolution';
|
|
21477
|
+
default:
|
|
21478
|
+
return 'balanced';
|
|
20431
21479
|
}
|
|
20432
21480
|
}const refreshSubscribedCodecAfterNewCodec = 5000;
|
|
20433
21481
|
class LocalVideoTrack extends LocalTrack {
|
|
@@ -21189,14 +22237,9 @@ function videoLayersFromEncodings(width, height, encodings, svc) {
|
|
|
21189
22237
|
ssrc: 0
|
|
21190
22238
|
});
|
|
21191
22239
|
});
|
|
21192
|
-
}const
|
|
21193
|
-
const reliableDataChannel = '_reliable';
|
|
21194
|
-
const dataTrackDataChannel = '_data_track';
|
|
21195
|
-
const minReconnectWait = 2 * 1000;
|
|
22240
|
+
}const minReconnectWait = 2 * 1000;
|
|
21196
22241
|
const leaveReconnect = 'leave-reconnect';
|
|
21197
22242
|
const reliabeReceiveStateTTL = 30000;
|
|
21198
|
-
const lossyDataChannelBufferThresholdMin = 8 * 1024;
|
|
21199
|
-
const lossyDataChannelBufferThresholdMax = 256 * 1024;
|
|
21200
22243
|
const initialMediaSectionsAudio = 3;
|
|
21201
22244
|
const initialMediaSectionsVideo = 3;
|
|
21202
22245
|
var PCState;
|
|
@@ -21207,12 +22250,6 @@ var PCState;
|
|
|
21207
22250
|
PCState[PCState["Reconnecting"] = 3] = "Reconnecting";
|
|
21208
22251
|
PCState[PCState["Closed"] = 4] = "Closed";
|
|
21209
22252
|
})(PCState || (PCState = {}));
|
|
21210
|
-
var DataChannelKind;
|
|
21211
|
-
(function (DataChannelKind) {
|
|
21212
|
-
DataChannelKind[DataChannelKind["RELIABLE"] = 0] = "RELIABLE";
|
|
21213
|
-
DataChannelKind[DataChannelKind["LOSSY"] = 1] = "LOSSY";
|
|
21214
|
-
DataChannelKind[DataChannelKind["DATA_TRACK_LOSSY"] = 2] = "DATA_TRACK_LOSSY";
|
|
21215
|
-
})(DataChannelKind || (DataChannelKind = {}));
|
|
21216
22253
|
// Default data-channel max message size (bytes), used when the remote SDP
|
|
21217
22254
|
// answer does not advertise an `a=max-message-size` attribute (RFC 8841).
|
|
21218
22255
|
// `0` means "no limit".
|
|
@@ -21228,6 +22265,15 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21228
22265
|
get pendingReconnect() {
|
|
21229
22266
|
return !!this.reconnectTimeout;
|
|
21230
22267
|
}
|
|
22268
|
+
get reliableChannel() {
|
|
22269
|
+
return this.dataChannels.reliable;
|
|
22270
|
+
}
|
|
22271
|
+
get lossyChannel() {
|
|
22272
|
+
return this.dataChannels.lossy;
|
|
22273
|
+
}
|
|
22274
|
+
get dataTrackChannel() {
|
|
22275
|
+
return this.dataChannels.dataTrack;
|
|
22276
|
+
}
|
|
21231
22277
|
constructor(options) {
|
|
21232
22278
|
var _a;
|
|
21233
22279
|
super();
|
|
@@ -21254,16 +22300,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21254
22300
|
this.shouldFailNext = false;
|
|
21255
22301
|
this.shouldFailOnV1Path = false;
|
|
21256
22302
|
this.log = livekitLogger;
|
|
21257
|
-
this.reliableDataSequence = 1;
|
|
21258
|
-
this.reliableMessageBuffer = new DataPacketBuffer();
|
|
21259
22303
|
this.reliableReceivedState = new TTLMap(reliabeReceiveStateTTL);
|
|
21260
|
-
this.lossyDataStatCurrentBytes = 0;
|
|
21261
|
-
this.lossyDataStatByterate = 0;
|
|
21262
|
-
this.lossyDataDropCount = 0;
|
|
21263
22304
|
this.midToTrackId = {};
|
|
21264
22305
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
21265
22306
|
this.isWaitingForNetworkReconnect = false;
|
|
21266
|
-
this.bufferStatusLowClosingFuture = new Future();
|
|
21267
22307
|
this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) {
|
|
21268
22308
|
var _this = this;
|
|
21269
22309
|
let channel = _ref.channel;
|
|
@@ -21271,21 +22311,9 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21271
22311
|
if (!channel) {
|
|
21272
22312
|
return;
|
|
21273
22313
|
}
|
|
21274
|
-
|
|
21275
|
-
|
|
21276
|
-
_this.reliableDCSub = channel;
|
|
21277
|
-
handler = _this.handleDataMessage;
|
|
21278
|
-
} else if (channel.label === lossyDataChannel) {
|
|
21279
|
-
_this.lossyDCSub = channel;
|
|
21280
|
-
handler = _this.handleDataMessage;
|
|
21281
|
-
} else if (channel.label === dataTrackDataChannel) {
|
|
21282
|
-
_this.dataTrackDCSub = channel;
|
|
21283
|
-
handler = _this.handleDataTrackMessage;
|
|
21284
|
-
} else {
|
|
21285
|
-
return;
|
|
22314
|
+
if (_this.dataChannels.adoptSubscriberChannel(channel)) {
|
|
22315
|
+
_this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label));
|
|
21286
22316
|
}
|
|
21287
|
-
_this.log.debug("on data channel ".concat(channel.id, ", ").concat(channel.label));
|
|
21288
|
-
channel.onmessage = handler;
|
|
21289
22317
|
}();
|
|
21290
22318
|
});
|
|
21291
22319
|
this.handleDataMessage = message => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -21293,19 +22321,11 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21293
22321
|
// make sure to respect incoming data message order by processing message events one after the other
|
|
21294
22322
|
const unlock = yield this.dataProcessLock.lock();
|
|
21295
22323
|
try {
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
if (message.data instanceof ArrayBuffer) {
|
|
21299
|
-
buffer = message.data;
|
|
21300
|
-
} else if (message.data instanceof Blob) {
|
|
21301
|
-
buffer = yield message.data.arrayBuffer();
|
|
21302
|
-
} else {
|
|
21303
|
-
this.log.error('unsupported data type', {
|
|
21304
|
-
data: message.data
|
|
21305
|
-
});
|
|
22324
|
+
const bytes = yield this.decodeDataMessage(message);
|
|
22325
|
+
if (!bytes) {
|
|
21306
22326
|
return;
|
|
21307
22327
|
}
|
|
21308
|
-
const dp = DataPacket.fromBinary(
|
|
22328
|
+
const dp = DataPacket.fromBinary(bytes);
|
|
21309
22329
|
if (dp.sequence > 0 && dp.participantSid !== '') {
|
|
21310
22330
|
const lastSeq = this.reliableReceivedState.get(dp.participantSid);
|
|
21311
22331
|
if (lastSeq && dp.sequence <= lastSeq) {
|
|
@@ -21346,19 +22366,11 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21346
22366
|
}
|
|
21347
22367
|
});
|
|
21348
22368
|
this.handleDataTrackMessage = message => __awaiter(this, void 0, void 0, function* () {
|
|
21349
|
-
|
|
21350
|
-
|
|
21351
|
-
if (message.data instanceof ArrayBuffer) {
|
|
21352
|
-
buffer = message.data;
|
|
21353
|
-
} else if (message.data instanceof Blob) {
|
|
21354
|
-
buffer = yield message.data.arrayBuffer();
|
|
21355
|
-
} else {
|
|
21356
|
-
this.log.error('unsupported data type', {
|
|
21357
|
-
data: message.data
|
|
21358
|
-
});
|
|
22369
|
+
const bytes = yield this.decodeDataMessage(message);
|
|
22370
|
+
if (!bytes) {
|
|
21359
22371
|
return;
|
|
21360
22372
|
}
|
|
21361
|
-
this.emit('dataTrackPacketReceived',
|
|
22373
|
+
this.emit('dataTrackPacketReceived', bytes);
|
|
21362
22374
|
});
|
|
21363
22375
|
this.handleDataError = event => {
|
|
21364
22376
|
// Errors fired while we're tearing the connection down (e.g. the SCTP transport aborting as
|
|
@@ -21392,9 +22404,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21392
22404
|
this.log.error("publisher data channel '".concat(DataChannelKind[kind], "' closed unexpectedly"), this.logContext);
|
|
21393
22405
|
}
|
|
21394
22406
|
};
|
|
21395
|
-
this.handleBufferedAmountLow = channelKind => {
|
|
21396
|
-
this.updateAndEmitDCBufferStatus(channelKind);
|
|
21397
|
-
};
|
|
21398
22407
|
// websocket reconnect behavior. if websocket is interrupted, and the PeerConnection
|
|
21399
22408
|
// continues to work, we can reconnect to websocket to continue the session
|
|
21400
22409
|
// after a number of retries, we'll close and give up permanently
|
|
@@ -21450,25 +22459,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21450
22459
|
this.once(EngineEvent.Disconnected, onDisconnected);
|
|
21451
22460
|
});
|
|
21452
22461
|
};
|
|
21453
|
-
this.updateAndEmitDCBufferStatus = kind => {
|
|
21454
|
-
if (kind === DataChannelKind.RELIABLE) {
|
|
21455
|
-
const dc = this.dataChannelForKind(kind);
|
|
21456
|
-
if (dc) {
|
|
21457
|
-
this.reliableMessageBuffer.alignBufferedAmount(dc.bufferedAmount);
|
|
21458
|
-
}
|
|
21459
|
-
}
|
|
21460
|
-
const status = this.isBufferStatusLow(kind);
|
|
21461
|
-
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
|
21462
|
-
this.dcBufferStatus.set(kind, status);
|
|
21463
|
-
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
|
21464
|
-
}
|
|
21465
|
-
};
|
|
21466
|
-
this.isBufferStatusLow = kind => {
|
|
21467
|
-
const dc = this.dataChannelForKind(kind);
|
|
21468
|
-
if (dc) {
|
|
21469
|
-
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
|
21470
|
-
}
|
|
21471
|
-
};
|
|
21472
22462
|
this.onRtpMapAvailable = rtpTypes => {
|
|
21473
22463
|
const rtpMap = new Map();
|
|
21474
22464
|
rtpTypes.forEach(rtp => {
|
|
@@ -21528,7 +22518,15 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21528
22518
|
this.reconnectPolicy = this.options.reconnectPolicy;
|
|
21529
22519
|
this.closingLock = new _();
|
|
21530
22520
|
this.dataProcessLock = new _();
|
|
21531
|
-
this.
|
|
22521
|
+
this.dataChannels = new DataChannelManager({
|
|
22522
|
+
isEngineClosed: () => this.isClosed,
|
|
22523
|
+
isReconnecting: () => this.attemptingReconnect,
|
|
22524
|
+
onDataMessage: message => this.handleDataMessage(message),
|
|
22525
|
+
onDataTrackMessage: message => this.handleDataTrackMessage(message),
|
|
22526
|
+
onDataError: event => this.handleDataError(event),
|
|
22527
|
+
onChannelClose: kind => this.handleDataChannelClose(kind)(),
|
|
22528
|
+
onBufferStatusChanged: (kind, isLow) => this.emit(EngineEvent.DCBufferStatusChanged, isLow, kind)
|
|
22529
|
+
});
|
|
21532
22530
|
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
21533
22531
|
this.client.onConnectionQuality = update => this.emit(EngineEvent.ConnectionQualityUpdate, update);
|
|
21534
22532
|
this.client.onRoomUpdate = update => this.emit(EngineEvent.RoomUpdate, update);
|
|
@@ -21539,13 +22537,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21539
22537
|
this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response);
|
|
21540
22538
|
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
21541
22539
|
this.client.onJoined = joinResponse => this.emit(EngineEvent.Joined, joinResponse);
|
|
21542
|
-
this.on(EngineEvent.Closing, () => {
|
|
21543
|
-
var _a, _b;
|
|
21544
|
-
(_b = (_a = this.bufferStatusLowClosingFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, new UnexpectedConnectionState('engine closed'));
|
|
21545
|
-
});
|
|
21546
|
-
// Swallow the rejection at the source so it doesn't surface as an unhandled promise rejection
|
|
21547
|
-
// when no waitForBufferStatusLow callers are attached.
|
|
21548
|
-
this.bufferStatusLowClosingFuture.promise.catch(() => {});
|
|
21549
22540
|
}
|
|
21550
22541
|
/** @internal */
|
|
21551
22542
|
get logContext() {
|
|
@@ -21691,51 +22682,14 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21691
22682
|
cleanupPeerConnections() {
|
|
21692
22683
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21693
22684
|
var _a;
|
|
21694
|
-
|
|
21695
|
-
if (!dc) {
|
|
21696
|
-
return;
|
|
21697
|
-
}
|
|
21698
|
-
// Detach the data channel handlers before closing anything. Closing a peer connection tears
|
|
21699
|
-
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
|
|
21700
|
-
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
|
|
21701
|
-
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
|
|
21702
|
-
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
|
|
21703
|
-
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
|
|
21704
|
-
dc.onbufferedamountlow = null;
|
|
21705
|
-
dc.onclose = null;
|
|
21706
|
-
dc.onclosing = null;
|
|
21707
|
-
dc.onerror = null;
|
|
21708
|
-
dc.onmessage = null;
|
|
21709
|
-
dc.onopen = null;
|
|
21710
|
-
dc.close();
|
|
21711
|
-
};
|
|
21712
|
-
dcCleanup(this.lossyDC);
|
|
21713
|
-
dcCleanup(this.lossyDCSub);
|
|
21714
|
-
dcCleanup(this.reliableDC);
|
|
21715
|
-
dcCleanup(this.reliableDCSub);
|
|
21716
|
-
dcCleanup(this.dataTrackDC);
|
|
21717
|
-
dcCleanup(this.dataTrackDCSub);
|
|
22685
|
+
this.dataChannels.teardown();
|
|
21718
22686
|
yield (_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.close();
|
|
21719
22687
|
this.pcManager = undefined;
|
|
21720
|
-
this.lossyDC = undefined;
|
|
21721
|
-
this.lossyDCSub = undefined;
|
|
21722
|
-
this.reliableDC = undefined;
|
|
21723
|
-
this.reliableDCSub = undefined;
|
|
21724
|
-
this.dataTrackDC = undefined;
|
|
21725
|
-
this.dataTrackDCSub = undefined;
|
|
21726
|
-
this.reliableMessageBuffer = new DataPacketBuffer();
|
|
21727
|
-
this.reliableDataSequence = 1;
|
|
21728
22688
|
this.reliableReceivedState.clear();
|
|
21729
22689
|
});
|
|
21730
22690
|
}
|
|
21731
22691
|
cleanupLossyDataStats() {
|
|
21732
|
-
this.
|
|
21733
|
-
this.lossyDataStatCurrentBytes = 0;
|
|
21734
|
-
if (this.lossyDataStatInterval) {
|
|
21735
|
-
clearInterval(this.lossyDataStatInterval);
|
|
21736
|
-
this.lossyDataStatInterval = undefined;
|
|
21737
|
-
}
|
|
21738
|
-
this.lossyDataDropCount = 0;
|
|
22692
|
+
this.lossyChannel.stopThresholdTuning();
|
|
21739
22693
|
}
|
|
21740
22694
|
cleanupClient() {
|
|
21741
22695
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -21802,7 +22756,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21802
22756
|
}
|
|
21803
22757
|
get dataSubscriberReadyState() {
|
|
21804
22758
|
var _a;
|
|
21805
|
-
return (_a = this.
|
|
22759
|
+
return (_a = this.dataChannelForKind(DataChannelKind.RELIABLE, true)) === null || _a === void 0 ? void 0 : _a.readyState;
|
|
21806
22760
|
}
|
|
21807
22761
|
getConnectedServerAddress() {
|
|
21808
22762
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -22062,65 +23016,22 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22062
23016
|
if (!this.pcManager) {
|
|
22063
23017
|
return;
|
|
22064
23018
|
}
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
this.reliableDC.onmessage = null;
|
|
22073
|
-
this.reliableDC.onerror = null;
|
|
22074
|
-
this.reliableDC.onclose = null;
|
|
22075
|
-
}
|
|
22076
|
-
if (this.dataTrackDC) {
|
|
22077
|
-
this.dataTrackDC.onmessage = null;
|
|
22078
|
-
this.dataTrackDC.onerror = null;
|
|
22079
|
-
this.dataTrackDC.onclose = null;
|
|
22080
|
-
}
|
|
22081
|
-
// create data channels
|
|
22082
|
-
this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
|
|
22083
|
-
ordered: false,
|
|
22084
|
-
maxRetransmits: 0
|
|
22085
|
-
});
|
|
22086
|
-
this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
|
|
22087
|
-
ordered: true
|
|
22088
|
-
});
|
|
22089
|
-
this.dataTrackDC = this.pcManager.createPublisherDataChannel(dataTrackDataChannel, {
|
|
22090
|
-
ordered: false,
|
|
22091
|
-
maxRetransmits: 0
|
|
22092
|
-
});
|
|
22093
|
-
// also handle messages over the pub channel, for backwards compatibility
|
|
22094
|
-
this.lossyDC.onmessage = this.handleDataMessage;
|
|
22095
|
-
this.reliableDC.onmessage = this.handleDataMessage;
|
|
22096
|
-
this.dataTrackDC.onmessage = this.handleDataTrackMessage;
|
|
22097
|
-
// handle datachannel errors
|
|
22098
|
-
this.lossyDC.onerror = this.handleDataError;
|
|
22099
|
-
this.reliableDC.onerror = this.handleDataError;
|
|
22100
|
-
this.dataTrackDC.onerror = this.handleDataError;
|
|
22101
|
-
// detect unexpected publisher data channel closes
|
|
22102
|
-
this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY);
|
|
22103
|
-
this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE);
|
|
22104
|
-
this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY);
|
|
22105
|
-
// set up dc buffer threshold, set to 64kB (otherwise 0 by default)
|
|
22106
|
-
this.lossyDC.bufferedAmountLowThreshold = 65535;
|
|
22107
|
-
this.reliableDC.bufferedAmountLowThreshold = 65535;
|
|
22108
|
-
this.dataTrackDC.bufferedAmountLowThreshold = 65535;
|
|
22109
|
-
// handle buffer amount low events
|
|
22110
|
-
this.lossyDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.LOSSY);
|
|
22111
|
-
this.reliableDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.RELIABLE);
|
|
22112
|
-
this.dataTrackDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.DATA_TRACK_LOSSY);
|
|
22113
|
-
this.cleanupLossyDataStats();
|
|
22114
|
-
this.lossyDataStatInterval = setInterval(() => {
|
|
22115
|
-
this.lossyDataStatByterate = this.lossyDataStatCurrentBytes;
|
|
22116
|
-
this.lossyDataStatCurrentBytes = 0;
|
|
22117
|
-
const dc = this.dataChannelForKind(DataChannelKind.LOSSY);
|
|
22118
|
-
if (dc) {
|
|
22119
|
-
// control buffered latency to ~100ms
|
|
22120
|
-
const threshold = this.lossyDataStatByterate / 10;
|
|
22121
|
-
dc.bufferedAmountLowThreshold = Math.min(Math.max(threshold, lossyDataChannelBufferThresholdMin), lossyDataChannelBufferThresholdMax);
|
|
23019
|
+
this.dataChannels.createPublisherChannels(this.pcManager);
|
|
23020
|
+
}
|
|
23021
|
+
/** Normalizes an incoming data-channel message into bytes, or logs and returns undefined. */
|
|
23022
|
+
decodeDataMessage(message) {
|
|
23023
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23024
|
+
if (message.data instanceof ArrayBuffer) {
|
|
23025
|
+
return new Uint8Array(message.data);
|
|
22122
23026
|
}
|
|
22123
|
-
|
|
23027
|
+
if (message.data instanceof Blob) {
|
|
23028
|
+
return new Uint8Array(yield message.data.arrayBuffer());
|
|
23029
|
+
}
|
|
23030
|
+
this.log.error('unsupported data type', {
|
|
23031
|
+
data: message.data
|
|
23032
|
+
});
|
|
23033
|
+
return undefined;
|
|
23034
|
+
});
|
|
22124
23035
|
}
|
|
22125
23036
|
createSender(track, opts, encodings) {
|
|
22126
23037
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -22372,7 +23283,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22372
23283
|
}
|
|
22373
23284
|
resumeConnection(reason) {
|
|
22374
23285
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22375
|
-
var _a;
|
|
22376
23286
|
if (!this.url || !this.token) {
|
|
22377
23287
|
// permanent failure, don't attempt reconnection
|
|
22378
23288
|
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
|
@@ -22426,11 +23336,16 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22426
23336
|
this.client.setReconnected();
|
|
22427
23337
|
// recreate publish datachannel if it's id is null
|
|
22428
23338
|
// (for safari https://bugs.webkit.org/show_bug.cgi?id=184688)
|
|
22429
|
-
|
|
23339
|
+
const reliableDC = this.dataChannelForKind(DataChannelKind.RELIABLE);
|
|
23340
|
+
if ((reliableDC === null || reliableDC === void 0 ? void 0 : reliableDC.readyState) === 'open' && reliableDC.id === null) {
|
|
22430
23341
|
this.createDataChannels();
|
|
22431
23342
|
}
|
|
22432
23343
|
if (res === null || res === void 0 ? void 0 : res.lastMessageSeq) {
|
|
22433
|
-
this.resendReliableMessagesForResume(res.lastMessageSeq)
|
|
23344
|
+
this.resendReliableMessagesForResume(res.lastMessageSeq).catch(error => {
|
|
23345
|
+
this.log.warn('failed to resend reliable messages after resume', Object.assign(Object.assign({}, this.logContext), {
|
|
23346
|
+
error
|
|
23347
|
+
}));
|
|
23348
|
+
});
|
|
22434
23349
|
}
|
|
22435
23350
|
// resume success
|
|
22436
23351
|
this.emit(EngineEvent.Resumed);
|
|
@@ -22479,7 +23394,8 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22479
23394
|
});
|
|
22480
23395
|
}
|
|
22481
23396
|
/* @internal */
|
|
22482
|
-
sendDataPacket(packet,
|
|
23397
|
+
sendDataPacket(packet, /** Data-track frames don't come through here — they're sent pre-serialized via {@link sendDataTrackFrame }. */
|
|
23398
|
+
kind) {
|
|
22483
23399
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22484
23400
|
var _a, _b;
|
|
22485
23401
|
// make sure we do have a data connection
|
|
@@ -22499,8 +23415,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22499
23415
|
}
|
|
22500
23416
|
}
|
|
22501
23417
|
if (kind === DataChannelKind.RELIABLE) {
|
|
22502
|
-
packet.sequence = this.
|
|
22503
|
-
this.reliableDataSequence += 1;
|
|
23418
|
+
packet.sequence = this.reliableChannel.nextSequence();
|
|
22504
23419
|
}
|
|
22505
23420
|
const msg = packet.toBinary();
|
|
22506
23421
|
// Clamp to the SDK default - libwebrtc advertises larger (~256 KiB)
|
|
@@ -22510,98 +23425,48 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22510
23425
|
if (typeof maxPublisherMessageSizeBytes !== 'undefined' && maxPublisherMessageSizeBytes !== 0 /* 0 means "no limit" */ && msg.byteLength > maxPublisherMessageSizeBytes) {
|
|
22511
23426
|
throw new PublishDataError("cannot publish data packet larger than ".concat(maxPublisherMessageSizeBytes, " bytes (got ").concat(msg.byteLength, ")"));
|
|
22512
23427
|
}
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
if (dc) {
|
|
22520
|
-
yield this.waitForBufferStatusLow(kind);
|
|
22521
|
-
this.reliableMessageBuffer.push({
|
|
22522
|
-
data: msg,
|
|
22523
|
-
sequence: packet.sequence
|
|
22524
|
-
});
|
|
22525
|
-
if (this.attemptingReconnect) {
|
|
22526
|
-
return;
|
|
22527
|
-
}
|
|
22528
|
-
dc.send(msg);
|
|
22529
|
-
}
|
|
22530
|
-
this.updateAndEmitDCBufferStatus(kind);
|
|
22531
|
-
break;
|
|
23428
|
+
// The full-buffer policy (drop for lossy, wait/replay for reliable) is the channel's own, as
|
|
23429
|
+
// is emitting the buffer-status change once the send settles.
|
|
23430
|
+
if (kind === DataChannelKind.RELIABLE) {
|
|
23431
|
+
yield this.reliableChannel.send(msg, packet.sequence);
|
|
23432
|
+
} else {
|
|
23433
|
+
yield this.lossyChannel.send(msg);
|
|
22532
23434
|
}
|
|
22533
23435
|
});
|
|
22534
23436
|
}
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
|
|
22538
|
-
|
|
22539
|
-
|
|
22540
|
-
|
|
22541
|
-
|
|
22542
|
-
|
|
22543
|
-
|
|
22544
|
-
|
|
22545
|
-
|
|
22546
|
-
|
|
22547
|
-
|
|
22548
|
-
|
|
22549
|
-
case 'wait':
|
|
22550
|
-
yield _this3.waitForBufferStatusLow(kind);
|
|
22551
|
-
break;
|
|
22552
|
-
case 'drop':
|
|
22553
|
-
// this.log.warn(`dropping lossy data channel message`, this.logContext);
|
|
22554
|
-
// Drop messages to reduce latency
|
|
22555
|
-
_this3.lossyDataDropCount += 1;
|
|
22556
|
-
if (_this3.lossyDataDropCount % 100 === 0) {
|
|
22557
|
-
_this3.log.warn("dropping lossy data channel messages, total dropped: ".concat(_this3.lossyDataDropCount));
|
|
22558
|
-
}
|
|
22559
|
-
return;
|
|
22560
|
-
}
|
|
22561
|
-
}
|
|
22562
|
-
_this3.lossyDataStatCurrentBytes += bytes.byteLength;
|
|
22563
|
-
if (_this3.attemptingReconnect) {
|
|
22564
|
-
return;
|
|
22565
|
-
}
|
|
22566
|
-
dc.send(bytes);
|
|
22567
|
-
}
|
|
22568
|
-
_this3.updateAndEmitDCBufferStatus(kind);
|
|
22569
|
-
}();
|
|
23437
|
+
/**
|
|
23438
|
+
* Sends pre-serialized bytes on the data-track channel. This is the one send path that doesn't
|
|
23439
|
+
* go through {@link sendDataPacket} — Room's `packetAvailable` handler calls it directly with
|
|
23440
|
+
* bytes the data-track pipeline already serialized.
|
|
23441
|
+
*
|
|
23442
|
+
* @internal
|
|
23443
|
+
*/
|
|
23444
|
+
sendDataTrackFrame(bytes) {
|
|
23445
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23446
|
+
// Make sure we do have a data connection: on lazily negotiated publisher connections this is
|
|
23447
|
+
// what kicks negotiation off, and it waits for the channel to open. Memoized, so the
|
|
23448
|
+
// steady-state cost is one await on an already-resolved promise.
|
|
23449
|
+
yield this.ensurePublisherConnected(DataChannelKind.DATA_TRACK_LOSSY);
|
|
23450
|
+
yield this.dataTrackChannel.send(bytes);
|
|
22570
23451
|
});
|
|
22571
23452
|
}
|
|
22572
23453
|
resendReliableMessagesForResume(lastMessageSeq) {
|
|
22573
23454
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22574
23455
|
yield this.ensurePublisherConnected(DataChannelKind.RELIABLE);
|
|
22575
|
-
|
|
22576
|
-
if (dc) {
|
|
22577
|
-
this.reliableMessageBuffer.popToSequence(lastMessageSeq);
|
|
22578
|
-
this.reliableMessageBuffer.getAll().forEach(msg => {
|
|
22579
|
-
dc.send(msg.data);
|
|
22580
|
-
});
|
|
22581
|
-
}
|
|
22582
|
-
this.updateAndEmitDCBufferStatus(DataChannelKind.RELIABLE);
|
|
23456
|
+
yield this.reliableChannel.replay(lastMessageSeq);
|
|
22583
23457
|
});
|
|
22584
23458
|
}
|
|
22585
|
-
|
|
23459
|
+
/** The flow-control gate for `kind` — see {@link FlowControlledDataChannel}. */
|
|
23460
|
+
flowControlFor(kind) {
|
|
23461
|
+
return this.dataChannels.channelFor(kind);
|
|
23462
|
+
}
|
|
23463
|
+
/**
|
|
23464
|
+
* Resolves once the caller may send on the `kind` channel — see
|
|
23465
|
+
* {@link FlowControlledDataChannel.waitForHeadroomWithLock}.
|
|
23466
|
+
*/
|
|
23467
|
+
waitForBufferHeadroom(kind) {
|
|
22586
23468
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22587
|
-
return
|
|
22588
|
-
if (this.isClosed) {
|
|
22589
|
-
reject(new UnexpectedConnectionState('engine closed'));
|
|
22590
|
-
}
|
|
22591
|
-
if (this.isBufferStatusLow(kind)) {
|
|
22592
|
-
resolve();
|
|
22593
|
-
} else {
|
|
22594
|
-
const dc = this.dataChannelForKind(kind);
|
|
22595
|
-
if (!dc) {
|
|
22596
|
-
reject(new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind)));
|
|
22597
|
-
return;
|
|
22598
|
-
}
|
|
22599
|
-
this.bufferStatusLowClosingFuture.promise.catch(e => reject(e));
|
|
22600
|
-
dc.addEventListener('bufferedamountlow', () => resolve(), {
|
|
22601
|
-
once: true
|
|
22602
|
-
});
|
|
22603
|
-
}
|
|
22604
|
-
}));
|
|
23469
|
+
return this.flowControlFor(kind).waitForHeadroomWithLock();
|
|
22605
23470
|
});
|
|
22606
23471
|
}
|
|
22607
23472
|
/**
|
|
@@ -22609,40 +23474,40 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22609
23474
|
*/
|
|
22610
23475
|
ensureDataTransportConnected(kind_1) {
|
|
22611
23476
|
return __awaiter(this, arguments, void 0, function (kind) {
|
|
22612
|
-
var
|
|
23477
|
+
var _this3 = this;
|
|
22613
23478
|
let subscriber = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.subscriberPrimary;
|
|
22614
23479
|
return function* () {
|
|
22615
23480
|
var _a;
|
|
22616
|
-
if (!
|
|
23481
|
+
if (!_this3.pcManager) {
|
|
22617
23482
|
throw new UnexpectedConnectionState('PC manager is closed');
|
|
22618
23483
|
}
|
|
22619
|
-
const transport = subscriber ?
|
|
23484
|
+
const transport = subscriber ? _this3.pcManager.subscriber : _this3.pcManager.publisher;
|
|
22620
23485
|
const transportName = subscriber ? 'Subscriber' : 'Publisher';
|
|
22621
23486
|
if (!transport) {
|
|
22622
23487
|
throw ConnectionError.internal("".concat(transportName, " connection not set"));
|
|
22623
23488
|
}
|
|
22624
23489
|
let needNegotiation = false;
|
|
22625
|
-
if (!subscriber && !
|
|
22626
|
-
|
|
23490
|
+
if (!subscriber && !_this3.dataChannelForKind(kind, subscriber)) {
|
|
23491
|
+
_this3.createDataChannels();
|
|
22627
23492
|
needNegotiation = true;
|
|
22628
23493
|
}
|
|
22629
|
-
if (!needNegotiation && !subscriber && !
|
|
23494
|
+
if (!needNegotiation && !subscriber && !_this3.pcManager.publisher.isICEConnected && _this3.pcManager.publisher.getICEConnectionState() !== 'checking') {
|
|
22630
23495
|
needNegotiation = true;
|
|
22631
23496
|
}
|
|
22632
23497
|
if (needNegotiation) {
|
|
22633
23498
|
// start negotiation
|
|
22634
|
-
|
|
22635
|
-
|
|
23499
|
+
_this3.negotiate().catch(err => {
|
|
23500
|
+
_this3.log.error(err);
|
|
22636
23501
|
});
|
|
22637
23502
|
}
|
|
22638
|
-
const targetChannel =
|
|
23503
|
+
const targetChannel = _this3.dataChannelForKind(kind, subscriber);
|
|
22639
23504
|
if ((targetChannel === null || targetChannel === void 0 ? void 0 : targetChannel.readyState) === 'open') {
|
|
22640
23505
|
return;
|
|
22641
23506
|
}
|
|
22642
23507
|
// wait until ICE connected
|
|
22643
|
-
const endTime = new Date().getTime() +
|
|
23508
|
+
const endTime = new Date().getTime() + _this3.peerConnectionTimeout;
|
|
22644
23509
|
while (new Date().getTime() < endTime) {
|
|
22645
|
-
if (transport.isICEConnected && ((_a =
|
|
23510
|
+
if (transport.isICEConnected && ((_a = _this3.dataChannelForKind(kind, subscriber)) === null || _a === void 0 ? void 0 : _a.readyState) === 'open') {
|
|
22646
23511
|
return;
|
|
22647
23512
|
}
|
|
22648
23513
|
yield sleep(50);
|
|
@@ -22685,7 +23550,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22685
23550
|
}
|
|
22686
23551
|
this.pcManager.requirePublisher();
|
|
22687
23552
|
// don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed
|
|
22688
|
-
if (this.pcManager.publisher.getTransceivers().length == 0 && !this.
|
|
23553
|
+
if (this.pcManager.publisher.getTransceivers().length == 0 && !this.dataChannels.hasPublisherChannels) {
|
|
22689
23554
|
this.createDataChannels();
|
|
22690
23555
|
}
|
|
22691
23556
|
const abortController = new AbortController();
|
|
@@ -22729,26 +23594,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22729
23594
|
});
|
|
22730
23595
|
}
|
|
22731
23596
|
dataChannelForKind(kind, sub) {
|
|
22732
|
-
|
|
22733
|
-
case DataChannelKind.RELIABLE:
|
|
22734
|
-
if (!sub) {
|
|
22735
|
-
return this.reliableDC;
|
|
22736
|
-
} else {
|
|
22737
|
-
return this.reliableDCSub;
|
|
22738
|
-
}
|
|
22739
|
-
case DataChannelKind.LOSSY:
|
|
22740
|
-
if (!sub) {
|
|
22741
|
-
return this.lossyDC;
|
|
22742
|
-
} else {
|
|
22743
|
-
return this.lossyDCSub;
|
|
22744
|
-
}
|
|
22745
|
-
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
22746
|
-
if (!sub) {
|
|
22747
|
-
return this.dataTrackDC;
|
|
22748
|
-
} else {
|
|
22749
|
-
return this.dataTrackDCSub;
|
|
22750
|
-
}
|
|
22751
|
-
}
|
|
23597
|
+
return this.dataChannels.getHandle(kind, sub);
|
|
22752
23598
|
}
|
|
22753
23599
|
/** @internal */
|
|
22754
23600
|
sendSyncState(remoteTracks, localTracks, localDataTrackInfos) {
|
|
@@ -22883,7 +23729,8 @@ function applyUserDataCompat(newObj, oldObj) {
|
|
|
22883
23729
|
const destinationIdentities = newObj.destinationIdentities.length !== 0 ? newObj.destinationIdentities : oldObj.destinationIdentities;
|
|
22884
23730
|
newObj.destinationIdentities = destinationIdentities;
|
|
22885
23731
|
oldObj.destinationIdentities = destinationIdentities;
|
|
22886
|
-
}const
|
|
23732
|
+
}const log$4 = getLogger(LoggerNames.Region);
|
|
23733
|
+
const DEFAULT_MAX_AGE_MS = 5000;
|
|
22887
23734
|
const STOP_REFETCH_DELAY_MS = 30000;
|
|
22888
23735
|
class RegionUrlProvider {
|
|
22889
23736
|
static fetchRegionSettings(serverUrl, token, signal) {
|
|
@@ -22937,10 +23784,10 @@ class RegionUrlProvider {
|
|
|
22937
23784
|
RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings);
|
|
22938
23785
|
} catch (error) {
|
|
22939
23786
|
if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) {
|
|
22940
|
-
|
|
23787
|
+
log$4.debug('token is not valid, cancelling auto region refresh');
|
|
22941
23788
|
return;
|
|
22942
23789
|
}
|
|
22943
|
-
|
|
23790
|
+
log$4.debug('auto refetching of region settings failed', {
|
|
22944
23791
|
error
|
|
22945
23792
|
});
|
|
22946
23793
|
// continue retrying with the same max age
|
|
@@ -22973,7 +23820,7 @@ class RegionUrlProvider {
|
|
|
22973
23820
|
tracker.cleanupTimeout = setTimeout(() => {
|
|
22974
23821
|
const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname);
|
|
22975
23822
|
if (currentTracker && currentTracker.connectionCount === 0) {
|
|
22976
|
-
|
|
23823
|
+
log$4.debug('stopping region refetch after disconnect delay', {
|
|
22977
23824
|
hostname
|
|
22978
23825
|
});
|
|
22979
23826
|
RegionUrlProvider.stopRefetch(hostname);
|
|
@@ -23053,7 +23900,9 @@ class RegionUrlProvider {
|
|
|
23053
23900
|
if (regionsLeft.length > 0) {
|
|
23054
23901
|
const nextRegion = regionsLeft[0];
|
|
23055
23902
|
this.attemptedRegions.push(nextRegion);
|
|
23056
|
-
|
|
23903
|
+
log$4.info("switching to region: ".concat(nextRegion.region), {
|
|
23904
|
+
region: nextRegion.region
|
|
23905
|
+
});
|
|
23057
23906
|
return nextRegion.url;
|
|
23058
23907
|
} else {
|
|
23059
23908
|
return null;
|
|
@@ -23073,7 +23922,90 @@ RegionUrlProvider.connectionTrackers = new Map();
|
|
|
23073
23922
|
RegionUrlProvider.fetchLock = new _();
|
|
23074
23923
|
function getCloudConfigUrl(serverUrl) {
|
|
23075
23924
|
return "".concat(serverUrl.protocol.replace('ws', 'http'), "//").concat(serverUrl.host, "/settings");
|
|
23076
|
-
}
|
|
23925
|
+
}/**
|
|
23926
|
+
* A `deflate-raw` compression transform (inverse of {@link inflateRawTransform}): pipe a byte stream
|
|
23927
|
+
* through it to get the compressed bytes without buffering the whole payload. Used for the chunked
|
|
23928
|
+
* `sendText`/`sendBytes`/`sendFile` paths, where the full payload is known up front but is streamed
|
|
23929
|
+
* (e.g. from `file.stream()`) rather than held in memory.
|
|
23930
|
+
*/
|
|
23931
|
+
function deflateRawTransform() {
|
|
23932
|
+
return new CompressionStream('deflate-raw');
|
|
23933
|
+
}
|
|
23934
|
+
/**
|
|
23935
|
+
* A `deflate-raw` decompression transform (inverse of {@link deflateRawTransform}): pipe a
|
|
23936
|
+
* stream of compressed bytes through it to get the decompressed bytes. Inflate emits output greedily,
|
|
23937
|
+
* so as long as the sender flushed at write boundaries each write's content is produced as soon as
|
|
23938
|
+
* its compressed bytes arrive.
|
|
23939
|
+
*/
|
|
23940
|
+
function inflateRawTransform() {
|
|
23941
|
+
return new DecompressionStream('deflate-raw');
|
|
23942
|
+
}
|
|
23943
|
+
/**
|
|
23944
|
+
* Decompresses a raw-deflate byte array in full (inverse of {@link deflateRawCompress}).
|
|
23945
|
+
* `maxByteLength` bounds the decompressed output (decompression-bomb guard); exceeding it rejects
|
|
23946
|
+
* with a `PayloadTooLarge` error.
|
|
23947
|
+
*/
|
|
23948
|
+
function deflateRawDecompress(data, maxByteLength) {
|
|
23949
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23950
|
+
const ds = new DecompressionStream('deflate-raw');
|
|
23951
|
+
const writer = ds.writable.getWriter();
|
|
23952
|
+
// The writer promises are intentionally not awaited (output is consumed via `collect`), but
|
|
23953
|
+
// they reject when the byte cap cancels the readable mid-stream — swallow that so an enforced
|
|
23954
|
+
// cap doesn't surface as an unhandled rejection.
|
|
23955
|
+
writer.write(data).catch(() => {});
|
|
23956
|
+
writer.close().catch(() => {});
|
|
23957
|
+
return collect(ds.readable, maxByteLength);
|
|
23958
|
+
});
|
|
23959
|
+
}
|
|
23960
|
+
/**
|
|
23961
|
+
* Drains a byte stream, concatenating all of its chunks into a single array. When
|
|
23962
|
+
* `maxByteLength` is given, drops the stream and throws `PayloadTooLarge` as soon as the
|
|
23963
|
+
* accumulated output exceeds it.
|
|
23964
|
+
*/
|
|
23965
|
+
function collect(stream, maxByteLength) {
|
|
23966
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23967
|
+
const reader = stream.getReader();
|
|
23968
|
+
const chunks = [];
|
|
23969
|
+
let total = 0;
|
|
23970
|
+
while (true) {
|
|
23971
|
+
const _yield$reader$read = yield reader.read(),
|
|
23972
|
+
done = _yield$reader$read.done,
|
|
23973
|
+
value = _yield$reader$read.value;
|
|
23974
|
+
if (done) {
|
|
23975
|
+
break;
|
|
23976
|
+
}
|
|
23977
|
+
chunks.push(value);
|
|
23978
|
+
total += value.byteLength;
|
|
23979
|
+
if (typeof maxByteLength === 'number' && total > maxByteLength) {
|
|
23980
|
+
yield reader.cancel();
|
|
23981
|
+
throw new DataStreamError("Decompressed payload exceeds the maximum payload size of ".concat(maxByteLength, " bytes"), DataStreamErrorReason.PayloadTooLarge);
|
|
23982
|
+
}
|
|
23983
|
+
}
|
|
23984
|
+
const result = new Uint8Array(total);
|
|
23985
|
+
let offset = 0;
|
|
23986
|
+
for (const chunk of chunks) {
|
|
23987
|
+
result.set(chunk, offset);
|
|
23988
|
+
offset += chunk.byteLength;
|
|
23989
|
+
}
|
|
23990
|
+
return result;
|
|
23991
|
+
});
|
|
23992
|
+
}/**
|
|
23993
|
+
* Maximum size of a single data-stream chunk in bytes, and the budget used to decide whether a
|
|
23994
|
+
* payload can be sent inline as a single header packet. Kept below the ~16k data-channel MTU to
|
|
23995
|
+
* leave headroom for protocol framing and E2EE overhead.
|
|
23996
|
+
*
|
|
23997
|
+
* @internal
|
|
23998
|
+
*/
|
|
23999
|
+
const STREAM_CHUNK_SIZE_BYTES = 15000;
|
|
24000
|
+
/**
|
|
24001
|
+
* Default cap on the number of decompressed bytes a single incoming compressed data stream may
|
|
24002
|
+
* produce (5 GB). A tiny compressed payload can inflate to an arbitrarily large output
|
|
24003
|
+
* (decompression bomb), so the decompressor's output is bounded rather than trusting the wire
|
|
24004
|
+
* size; streams exceeding the cap error with `PayloadTooLarge`.
|
|
24005
|
+
*
|
|
24006
|
+
* @internal
|
|
24007
|
+
*/
|
|
24008
|
+
const DEFAULT_MAX_PAYLOAD_BYTE_LENGTH = 5000000000;class BaseStreamReader {
|
|
23077
24009
|
get info() {
|
|
23078
24010
|
return this._info;
|
|
23079
24011
|
}
|
|
@@ -23115,6 +24047,7 @@ class ByteStreamReader extends BaseStreamReader {
|
|
|
23115
24047
|
};
|
|
23116
24048
|
return {
|
|
23117
24049
|
next: () => __awaiter(this, void 0, void 0, function* () {
|
|
24050
|
+
var _a;
|
|
23118
24051
|
try {
|
|
23119
24052
|
const signal = this.signal;
|
|
23120
24053
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
@@ -23135,6 +24068,9 @@ class ByteStreamReader extends BaseStreamReader {
|
|
|
23135
24068
|
});
|
|
23136
24069
|
if (result.done) {
|
|
23137
24070
|
this.validateBytesReceived(true);
|
|
24071
|
+
if (typeof this.totalByteSize === 'number') {
|
|
24072
|
+
(_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, 1);
|
|
24073
|
+
}
|
|
23138
24074
|
return {
|
|
23139
24075
|
done: true,
|
|
23140
24076
|
value: undefined
|
|
@@ -23252,6 +24188,7 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23252
24188
|
};
|
|
23253
24189
|
return {
|
|
23254
24190
|
next: () => __awaiter(this, void 0, void 0, function* () {
|
|
24191
|
+
var _a;
|
|
23255
24192
|
try {
|
|
23256
24193
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
23257
24194
|
throw signal.reason;
|
|
@@ -23271,6 +24208,9 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23271
24208
|
});
|
|
23272
24209
|
if (result.done) {
|
|
23273
24210
|
this.validateBytesReceived(true);
|
|
24211
|
+
if (typeof this.totalByteSize === 'number') {
|
|
24212
|
+
(_a = this.onProgress) === null || _a === void 0 ? void 0 : _a.call(this, 1);
|
|
24213
|
+
}
|
|
23274
24214
|
return {
|
|
23275
24215
|
done: true,
|
|
23276
24216
|
value: undefined
|
|
@@ -23349,6 +24289,7 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23349
24289
|
}
|
|
23350
24290
|
}class IncomingDataStreamManager {
|
|
23351
24291
|
constructor() {
|
|
24292
|
+
let maxPayloadByteLength = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_MAX_PAYLOAD_BYTE_LENGTH;
|
|
23352
24293
|
this.log = livekitLogger;
|
|
23353
24294
|
this.byteStreamControllers = new Map();
|
|
23354
24295
|
this.textStreamControllers = new Map();
|
|
@@ -23356,6 +24297,7 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23356
24297
|
this.textStreamHandlers = new Map();
|
|
23357
24298
|
this.isConnected = false;
|
|
23358
24299
|
this.bufferedPackets = [];
|
|
24300
|
+
this.maxPayloadByteLength = maxPayloadByteLength;
|
|
23359
24301
|
}
|
|
23360
24302
|
setConnected(connected) {
|
|
23361
24303
|
this.isConnected = connected;
|
|
@@ -23438,74 +24380,150 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23438
24380
|
}
|
|
23439
24381
|
handleStreamHeader(streamHeader, participantIdentity, encryptionType) {
|
|
23440
24382
|
var _a;
|
|
23441
|
-
|
|
23442
|
-
|
|
23443
|
-
|
|
23444
|
-
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
|
|
23455
|
-
|
|
23456
|
-
|
|
23457
|
-
|
|
23458
|
-
|
|
23459
|
-
|
|
23460
|
-
|
|
23461
|
-
|
|
23462
|
-
|
|
24383
|
+
switch (streamHeader.contentHeader.case) {
|
|
24384
|
+
case 'byteHeader':
|
|
24385
|
+
{
|
|
24386
|
+
const streamHandlerCallback = this.byteStreamHandlers.get(streamHeader.topic);
|
|
24387
|
+
if (!streamHandlerCallback) {
|
|
24388
|
+
this.log.debug('ignoring incoming byte stream due to no handler for topic', streamHeader.topic);
|
|
24389
|
+
return;
|
|
24390
|
+
}
|
|
24391
|
+
let streamController;
|
|
24392
|
+
const info = {
|
|
24393
|
+
id: streamHeader.streamId,
|
|
24394
|
+
name: (_a = streamHeader.contentHeader.value.name) !== null && _a !== void 0 ? _a : 'unknown',
|
|
24395
|
+
mimeType: streamHeader.mimeType,
|
|
24396
|
+
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
|
24397
|
+
topic: streamHeader.topic,
|
|
24398
|
+
timestamp: bigIntToNumber(streamHeader.timestamp),
|
|
24399
|
+
attributes: streamHeader.attributes,
|
|
24400
|
+
encryptionType
|
|
24401
|
+
};
|
|
24402
|
+
// Determine if the byte payload needs to be decompressed.
|
|
24403
|
+
let compressed;
|
|
24404
|
+
switch (streamHeader.compression) {
|
|
24405
|
+
case DataStream_CompressionType.DEFLATE_RAW:
|
|
24406
|
+
if (!isCompressionStreamSupported()) {
|
|
24407
|
+
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
24408
|
+
// isn't properly abiding by the data streams v2 protocol.
|
|
24409
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not have support for DecompressionStream. Dropping..."));
|
|
24410
|
+
return;
|
|
24411
|
+
}
|
|
24412
|
+
compressed = true;
|
|
24413
|
+
break;
|
|
24414
|
+
case DataStream_CompressionType.NONE:
|
|
24415
|
+
compressed = false;
|
|
24416
|
+
break;
|
|
24417
|
+
default:
|
|
24418
|
+
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
24419
|
+
// isn't properly abiding by the data streams v2 protocol.
|
|
24420
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with unknown compression type ").concat(streamHeader.compression, ", dropping..."));
|
|
24421
|
+
return;
|
|
23463
24422
|
}
|
|
23464
|
-
|
|
23465
|
-
|
|
23466
|
-
|
|
23467
|
-
|
|
23468
|
-
|
|
24423
|
+
// Single-packet stream: the entire payload was packaged into the header's `inlineContent`.
|
|
24424
|
+
// Synthesize an already-complete stream and skip waiting for chunk/trailer packets.
|
|
24425
|
+
const inlineContent = streamHeader.inlineContent;
|
|
24426
|
+
if (typeof inlineContent !== 'undefined') {
|
|
24427
|
+
// Inline bytes are the raw payload, optionally deflate-raw compressed.
|
|
24428
|
+
streamHandlerCallback(new ByteStreamReader(info, createInlineStream(streamHeader.streamId, compressed ? deflateRawDecompress(inlineContent, this.maxPayloadByteLength) : inlineContent), bigIntToNumber(streamHeader.totalLength)), {
|
|
24429
|
+
identity: participantIdentity
|
|
24430
|
+
});
|
|
24431
|
+
return;
|
|
24432
|
+
}
|
|
24433
|
+
const stream = new ReadableStream({
|
|
24434
|
+
start: controller => {
|
|
24435
|
+
streamController = controller;
|
|
24436
|
+
if (this.byteStreamControllers.has(streamHeader.streamId)) {
|
|
24437
|
+
throw new DataStreamError("A data stream read is already in progress for a stream with id ".concat(streamHeader.streamId, "."), DataStreamErrorReason.AlreadyOpened);
|
|
24438
|
+
}
|
|
24439
|
+
this.byteStreamControllers.set(streamHeader.streamId, {
|
|
24440
|
+
info,
|
|
24441
|
+
controller: streamController,
|
|
24442
|
+
startTime: Date.now(),
|
|
24443
|
+
sendingParticipantIdentity: participantIdentity
|
|
24444
|
+
});
|
|
24445
|
+
}
|
|
23469
24446
|
});
|
|
24447
|
+
streamHandlerCallback(new ByteStreamReader(info, compressed ? inflateRawByteChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream,
|
|
24448
|
+
// `totalLength` is the pre-compression size, and the reader counts decompressed bytes,
|
|
24449
|
+
// so it applies to both paths (mirrors text).
|
|
24450
|
+
bigIntToNumber(streamHeader.totalLength)), {
|
|
24451
|
+
identity: participantIdentity
|
|
24452
|
+
});
|
|
24453
|
+
return;
|
|
23470
24454
|
}
|
|
23471
|
-
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
if (!streamHandlerCallback) {
|
|
23478
|
-
this.log.debug('ignoring incoming text stream due to no handler for topic', streamHeader.topic);
|
|
23479
|
-
return;
|
|
23480
|
-
}
|
|
23481
|
-
let streamController;
|
|
23482
|
-
const info = {
|
|
23483
|
-
id: streamHeader.streamId,
|
|
23484
|
-
mimeType: streamHeader.mimeType,
|
|
23485
|
-
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
|
23486
|
-
topic: streamHeader.topic,
|
|
23487
|
-
timestamp: Number(streamHeader.timestamp),
|
|
23488
|
-
attributes: streamHeader.attributes,
|
|
23489
|
-
encryptionType,
|
|
23490
|
-
attachedStreamIds: streamHeader.contentHeader.value.attachedStreamIds
|
|
23491
|
-
};
|
|
23492
|
-
const stream = new ReadableStream({
|
|
23493
|
-
start: controller => {
|
|
23494
|
-
streamController = controller;
|
|
23495
|
-
if (this.textStreamControllers.has(streamHeader.streamId)) {
|
|
23496
|
-
throw new DataStreamError("A data stream read is already in progress for a stream with id ".concat(streamHeader.streamId, "."), DataStreamErrorReason.AlreadyOpened);
|
|
24455
|
+
case 'textHeader':
|
|
24456
|
+
{
|
|
24457
|
+
const streamHandlerCallback = this.textStreamHandlers.get(streamHeader.topic);
|
|
24458
|
+
if (!streamHandlerCallback) {
|
|
24459
|
+
this.log.debug('ignoring incoming text stream due to no handler for topic', streamHeader.topic);
|
|
24460
|
+
return;
|
|
23497
24461
|
}
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
24462
|
+
let streamController;
|
|
24463
|
+
const info = {
|
|
24464
|
+
id: streamHeader.streamId,
|
|
24465
|
+
mimeType: streamHeader.mimeType,
|
|
24466
|
+
size: streamHeader.totalLength ? Number(streamHeader.totalLength) : undefined,
|
|
24467
|
+
topic: streamHeader.topic,
|
|
24468
|
+
timestamp: Number(streamHeader.timestamp),
|
|
24469
|
+
attributes: streamHeader.attributes,
|
|
24470
|
+
encryptionType,
|
|
24471
|
+
attachedStreamIds: streamHeader.contentHeader.value.attachedStreamIds
|
|
24472
|
+
};
|
|
24473
|
+
// Determine if the byte payload needs to be decompressed.
|
|
24474
|
+
let compressed;
|
|
24475
|
+
switch (streamHeader.compression) {
|
|
24476
|
+
case DataStream_CompressionType.DEFLATE_RAW:
|
|
24477
|
+
if (!isCompressionStreamSupported()) {
|
|
24478
|
+
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
24479
|
+
// isn't properly abiding by the data streams v2 protocol.
|
|
24480
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not have support for DecompressionStream. Dropping..."));
|
|
24481
|
+
return;
|
|
24482
|
+
}
|
|
24483
|
+
compressed = true;
|
|
24484
|
+
break;
|
|
24485
|
+
case DataStream_CompressionType.NONE:
|
|
24486
|
+
compressed = false;
|
|
24487
|
+
break;
|
|
24488
|
+
default:
|
|
24489
|
+
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
24490
|
+
// isn't properly abiding by the data streams v2 protocol.
|
|
24491
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with unknown compression type ").concat(streamHeader.compression, ", dropping..."));
|
|
24492
|
+
return;
|
|
24493
|
+
}
|
|
24494
|
+
// Single-packet stream: the entire payload was smuggled into the header's `inlineContent`.
|
|
24495
|
+
// Synthesize an already-complete stream and skip waiting for chunk/trailer packets.
|
|
24496
|
+
const inlineContent = streamHeader.inlineContent;
|
|
24497
|
+
if (typeof inlineContent !== 'undefined') {
|
|
24498
|
+
// Inline text is the raw UTF-8 payload, optionally deflate-raw compressed.
|
|
24499
|
+
const content = compressed ? deflateRawDecompress(inlineContent, this.maxPayloadByteLength) : inlineContent;
|
|
24500
|
+
streamHandlerCallback(new TextStreamReader(info, createInlineStream(streamHeader.streamId, content), bigIntToNumber(streamHeader.totalLength)), {
|
|
24501
|
+
identity: participantIdentity
|
|
24502
|
+
});
|
|
24503
|
+
return;
|
|
24504
|
+
}
|
|
24505
|
+
const stream = new ReadableStream({
|
|
24506
|
+
start: controller => {
|
|
24507
|
+
streamController = controller;
|
|
24508
|
+
if (this.textStreamControllers.has(streamHeader.streamId)) {
|
|
24509
|
+
throw new DataStreamError("A data stream read is already in progress for a stream with id ".concat(streamHeader.streamId, "."), DataStreamErrorReason.AlreadyOpened);
|
|
24510
|
+
}
|
|
24511
|
+
this.textStreamControllers.set(streamHeader.streamId, {
|
|
24512
|
+
info,
|
|
24513
|
+
controller: streamController,
|
|
24514
|
+
startTime: Date.now(),
|
|
24515
|
+
sendingParticipantIdentity: participantIdentity
|
|
24516
|
+
});
|
|
24517
|
+
}
|
|
23503
24518
|
});
|
|
24519
|
+
streamHandlerCallback(new TextStreamReader(info, compressed ? inflateRawChunkStream(stream, streamHeader.streamId, this.maxPayloadByteLength) : stream,
|
|
24520
|
+
// `totalLength` is the pre-compression size, and the reader sees decompressed bytes, so
|
|
24521
|
+
// it applies to both paths.
|
|
24522
|
+
bigIntToNumber(streamHeader.totalLength)), {
|
|
24523
|
+
identity: participantIdentity
|
|
24524
|
+
});
|
|
24525
|
+
return;
|
|
23504
24526
|
}
|
|
23505
|
-
});
|
|
23506
|
-
streamHandlerCallback(new TextStreamReader(info, stream, bigIntToNumber(streamHeader.totalLength)), {
|
|
23507
|
-
identity: participantIdentity
|
|
23508
|
-
});
|
|
23509
24527
|
}
|
|
23510
24528
|
}
|
|
23511
24529
|
handleStreamChunk(chunk, encryptionType) {
|
|
@@ -23535,21 +24553,190 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23535
24553
|
textBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(trailer.streamId, ". Expected ").concat(encryptionType, ", got ").concat(textBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch));
|
|
23536
24554
|
} else {
|
|
23537
24555
|
textBuffer.info.attributes = Object.assign(Object.assign({}, textBuffer.info.attributes), trailer.attributes);
|
|
23538
|
-
|
|
23539
|
-
|
|
24556
|
+
if (trailer.reason) {
|
|
24557
|
+
// A non-empty reason marks an abnormal close by the sender (e.g. an aborted send);
|
|
24558
|
+
// surface it as an error rather than pretending the stream completed.
|
|
24559
|
+
textBuffer.controller.error(new DataStreamError("Data stream ".concat(trailer.streamId, " closed abnormally: ").concat(trailer.reason), DataStreamErrorReason.AbnormalEnd));
|
|
24560
|
+
} else {
|
|
24561
|
+
textBuffer.controller.close();
|
|
24562
|
+
}
|
|
24563
|
+
}
|
|
24564
|
+
this.textStreamControllers.delete(trailer.streamId);
|
|
24565
|
+
}
|
|
24566
|
+
const fileBuffer = this.byteStreamControllers.get(trailer.streamId);
|
|
24567
|
+
if (fileBuffer) {
|
|
24568
|
+
if (fileBuffer.info.encryptionType !== encryptionType) {
|
|
24569
|
+
fileBuffer.controller.error(new DataStreamError("Encryption type mismatch for stream ".concat(trailer.streamId, ". Expected ").concat(encryptionType, ", got ").concat(fileBuffer.info.encryptionType), DataStreamErrorReason.EncryptionTypeMismatch));
|
|
24570
|
+
} else {
|
|
24571
|
+
fileBuffer.info.attributes = Object.assign(Object.assign({}, fileBuffer.info.attributes), trailer.attributes);
|
|
24572
|
+
if (trailer.reason) {
|
|
24573
|
+
// A non-empty reason marks an abnormal close by the sender (e.g. an aborted send);
|
|
24574
|
+
// surface it as an error rather than pretending the stream completed.
|
|
24575
|
+
fileBuffer.controller.error(new DataStreamError("Data stream ".concat(trailer.streamId, " closed abnormally: ").concat(trailer.reason), DataStreamErrorReason.AbnormalEnd));
|
|
24576
|
+
} else {
|
|
24577
|
+
fileBuffer.controller.close();
|
|
24578
|
+
}
|
|
24579
|
+
}
|
|
24580
|
+
this.byteStreamControllers.delete(trailer.streamId);
|
|
24581
|
+
}
|
|
24582
|
+
}
|
|
24583
|
+
}
|
|
24584
|
+
/**
|
|
24585
|
+
* Builds a `ReadableStream` that yields the given content as a single chunk and then immediately
|
|
24586
|
+
* closes - used to surface an inline (single-packet) data stream as a fully-formed stream. `content`
|
|
24587
|
+
* may be a promise (e.g. async gzip decompression); a rejection errors the stream.
|
|
24588
|
+
*/
|
|
24589
|
+
function createInlineStream(streamId, content) {
|
|
24590
|
+
return new ReadableStream({
|
|
24591
|
+
start: controller => __awaiter(this, void 0, void 0, function* () {
|
|
24592
|
+
try {
|
|
24593
|
+
const bytes = yield content;
|
|
24594
|
+
controller.enqueue(new DataStream_Chunk({
|
|
24595
|
+
streamId,
|
|
24596
|
+
chunkIndex: BigInt(0),
|
|
24597
|
+
content: bytes
|
|
24598
|
+
}));
|
|
24599
|
+
controller.close();
|
|
24600
|
+
} catch (err) {
|
|
24601
|
+
controller.error(err);
|
|
24602
|
+
}
|
|
24603
|
+
})
|
|
24604
|
+
});
|
|
24605
|
+
}
|
|
24606
|
+
/**
|
|
24607
|
+
* Validates that chunks are received in order, dropping duplicates and throwing if gaps are found.
|
|
24608
|
+
*
|
|
24609
|
+
* A stateful decompressor silently corrupts on duplicated or out-of-order input, so duplicates are
|
|
24610
|
+
* dropped (with a warning - in-order delivery is expected on the reliable channel, but reconnect
|
|
24611
|
+
* handling may replay) and a gap is a hard error. Shared by the text and byte deflate-raw decoders.
|
|
24612
|
+
*/
|
|
24613
|
+
function ensureOrderedChunks(streamId) {
|
|
24614
|
+
let lastChunkIndex = -1;
|
|
24615
|
+
return new TransformStream({
|
|
24616
|
+
transform: (value, controller) => {
|
|
24617
|
+
const index = bigIntToNumber(value.chunkIndex);
|
|
24618
|
+
if (index <= lastChunkIndex) {
|
|
24619
|
+
livekitLogger.warn("ignoring duplicate chunk ".concat(index, " for compressed data stream ").concat(streamId, " (last processed: ").concat(lastChunkIndex, ")"));
|
|
24620
|
+
return;
|
|
24621
|
+
}
|
|
24622
|
+
if (index > lastChunkIndex + 1) {
|
|
24623
|
+
throw new DataStreamError("Missing chunk(s) ".concat(lastChunkIndex + 1, "..").concat(index - 1, " for compressed data stream ").concat(streamId, " - cannot continue decompressing"), DataStreamErrorReason.Incomplete);
|
|
24624
|
+
}
|
|
24625
|
+
lastChunkIndex = index;
|
|
24626
|
+
controller.enqueue(value);
|
|
24627
|
+
}
|
|
24628
|
+
});
|
|
24629
|
+
}
|
|
24630
|
+
/** Unwraps compressed `DataStream_Chunk`s to their compressed bytes (in `chunkIndex` order), */
|
|
24631
|
+
function chunksToBytes() {
|
|
24632
|
+
return new TransformStream({
|
|
24633
|
+
transform: (value, controller) => {
|
|
24634
|
+
controller.enqueue(value.content);
|
|
24635
|
+
}
|
|
24636
|
+
});
|
|
24637
|
+
}
|
|
24638
|
+
/** Re-wraps decompressed bytes into contiguous `DataStream_Chunk`s, skipping inflate's empty reads. */
|
|
24639
|
+
function bytesToChunks(streamId) {
|
|
24640
|
+
let outIndex = 0;
|
|
24641
|
+
return new TransformStream({
|
|
24642
|
+
transform: (value, controller) => {
|
|
24643
|
+
// Inflate can emit empty reads; only synthesize a chunk when there is content.
|
|
24644
|
+
if (value.byteLength > 0) {
|
|
24645
|
+
controller.enqueue(new DataStream_Chunk({
|
|
24646
|
+
streamId,
|
|
24647
|
+
chunkIndex: numberToBigInt(outIndex),
|
|
24648
|
+
content: value
|
|
24649
|
+
}));
|
|
24650
|
+
outIndex += 1;
|
|
24651
|
+
}
|
|
24652
|
+
}
|
|
24653
|
+
});
|
|
24654
|
+
}
|
|
24655
|
+
/**
|
|
24656
|
+
* Reframes decompressed byte chunks onto UTF-8 character boundaries via a streaming `TextDecoder`
|
|
24657
|
+
* (a write larger than the MTU spans several packets, which may split a codepoint), so each
|
|
24658
|
+
* synthesized text chunk decodes independently. The `flush` emits the decoder's trailing bytes.
|
|
24659
|
+
*/
|
|
24660
|
+
function bytesToDecodedUtf8(streamId) {
|
|
24661
|
+
const decoder = new TextDecoder('utf-8', {
|
|
24662
|
+
fatal: true
|
|
24663
|
+
});
|
|
24664
|
+
const encoder = new TextEncoder();
|
|
24665
|
+
let outIndex = 0;
|
|
24666
|
+
const decodeOrThrow = bytes => {
|
|
24667
|
+
try {
|
|
24668
|
+
return bytes ? decoder.decode(bytes, {
|
|
24669
|
+
stream: true
|
|
24670
|
+
}) : decoder.decode();
|
|
24671
|
+
} catch (err) {
|
|
24672
|
+
throw new DataStreamError("Cannot decode compressed data stream ".concat(streamId, " as text: ").concat(err), DataStreamErrorReason.DecodeFailed);
|
|
24673
|
+
}
|
|
24674
|
+
};
|
|
24675
|
+
return new TransformStream({
|
|
24676
|
+
transform: (value, controller) => {
|
|
24677
|
+
const text = decodeOrThrow(value);
|
|
24678
|
+
// Everything so far may have been a partial codepoint; only emit once we have characters.
|
|
24679
|
+
if (text.length > 0) {
|
|
24680
|
+
controller.enqueue(new DataStream_Chunk({
|
|
24681
|
+
streamId,
|
|
24682
|
+
chunkIndex: numberToBigInt(outIndex),
|
|
24683
|
+
content: encoder.encode(text)
|
|
24684
|
+
}));
|
|
24685
|
+
outIndex += 1;
|
|
24686
|
+
}
|
|
24687
|
+
},
|
|
24688
|
+
flush: controller => {
|
|
24689
|
+
const tail = decodeOrThrow();
|
|
24690
|
+
if (tail.length > 0) {
|
|
24691
|
+
controller.enqueue(new DataStream_Chunk({
|
|
24692
|
+
streamId,
|
|
24693
|
+
chunkIndex: numberToBigInt(outIndex),
|
|
24694
|
+
content: encoder.encode(tail)
|
|
24695
|
+
}));
|
|
24696
|
+
outIndex += 1;
|
|
23540
24697
|
}
|
|
23541
24698
|
}
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
24699
|
+
});
|
|
24700
|
+
}
|
|
24701
|
+
/**
|
|
24702
|
+
* Transforms a stream of deflate-raw-compressed byte `DataStream_Chunk`s into a stream of
|
|
24703
|
+
* decompressed chunks, so `ByteStreamReader` consumes it unchanged. All chunk contents are fed (in
|
|
24704
|
+
* `chunkIndex` order) through ONE raw-deflate decompressor for the stream's lifetime; decompressed
|
|
24705
|
+
* output is re-wrapped as chunks as soon as it is produced. The sender (sendFile) compresses the
|
|
24706
|
+
* whole payload in one shot, but the format also supports a single context-takeover stream
|
|
24707
|
+
* sync-flushed at write boundaries, so a future incremental streamBytes could compress with no
|
|
24708
|
+
* protocol change. Errors and cancellation propagate through the pipe chain.
|
|
24709
|
+
*/
|
|
24710
|
+
function inflateRawByteChunkStream(raw, streamId, maxPayloadByteLength) {
|
|
24711
|
+
return raw.pipeThrough(ensureOrderedChunks(streamId)).pipeThrough(chunksToBytes()).pipeThrough(inflateRawTransform()).pipeThrough(maxDecompressedLengthGuard(streamId, maxPayloadByteLength)).pipeThrough(bytesToChunks(streamId));
|
|
24712
|
+
}
|
|
24713
|
+
/**
|
|
24714
|
+
* Transforms a stream of deflate-raw-compressed text `DataStream_Chunk`s into a stream of
|
|
24715
|
+
* decompressed chunks, so `TextStreamReader` consumes it unchanged. Builds on
|
|
24716
|
+
* {@link inflateRawByteChunkStream} (single decompressor + ordering guard) and adds a streaming
|
|
24717
|
+
* `TextDecoder` that reframes the decompressed bytes on UTF-8 character boundaries so each
|
|
24718
|
+
* synthesized chunk decodes independently. Errors and cancellation propagate through the pipe chain.
|
|
24719
|
+
*/
|
|
24720
|
+
function inflateRawChunkStream(raw, streamId, maxPayloadByteLength) {
|
|
24721
|
+
return raw.pipeThrough(ensureOrderedChunks(streamId)).pipeThrough(chunksToBytes()).pipeThrough(inflateRawTransform()).pipeThrough(maxDecompressedLengthGuard(streamId, maxPayloadByteLength)).pipeThrough(bytesToDecodedUtf8(streamId));
|
|
24722
|
+
}
|
|
24723
|
+
/**
|
|
24724
|
+
* Caps the total decompressed byte count of a compressed stream (decompression-bomb guard): a
|
|
24725
|
+
* tiny compressed payload can inflate to an arbitrarily large output, so the decompressor's
|
|
24726
|
+
* output is bounded rather than trusting the wire size. Exceeding the cap errors the stream with
|
|
24727
|
+
* `PayloadTooLarge`.
|
|
24728
|
+
*/
|
|
24729
|
+
function maxDecompressedLengthGuard(streamId, maxByteLength) {
|
|
24730
|
+
let total = 0;
|
|
24731
|
+
return new TransformStream({
|
|
24732
|
+
transform: (value, controller) => {
|
|
24733
|
+
total += value.byteLength;
|
|
24734
|
+
if (total > maxByteLength) {
|
|
24735
|
+
throw new DataStreamError("Data stream ".concat(streamId, " exceeds the maximum payload size of ").concat(maxByteLength, " bytes"), DataStreamErrorReason.PayloadTooLarge);
|
|
23549
24736
|
}
|
|
23550
|
-
|
|
24737
|
+
controller.enqueue(value);
|
|
23551
24738
|
}
|
|
23552
|
-
}
|
|
24739
|
+
});
|
|
23553
24740
|
}class BaseStreamWriter {
|
|
23554
24741
|
constructor(writableStream, info, onClose) {
|
|
23555
24742
|
this.writableStream = writableStream;
|
|
@@ -23570,15 +24757,70 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
23570
24757
|
}
|
|
23571
24758
|
}
|
|
23572
24759
|
class TextStreamWriter extends BaseStreamWriter {}
|
|
23573
|
-
class ByteStreamWriter extends BaseStreamWriter {}
|
|
24760
|
+
class ByteStreamWriter extends BaseStreamWriter {}/** Builds the `DataStream_Header` for a text stream from its info and stream options. */
|
|
24761
|
+
function buildTextStreamHeader(info, options, v2) {
|
|
24762
|
+
var _a;
|
|
24763
|
+
return new DataStream_Header({
|
|
24764
|
+
streamId: info.id,
|
|
24765
|
+
mimeType: info.mimeType,
|
|
24766
|
+
topic: info.topic,
|
|
24767
|
+
timestamp: numberToBigInt(info.timestamp),
|
|
24768
|
+
totalLength: numberToBigInt(info.size),
|
|
24769
|
+
attributes: info.attributes,
|
|
24770
|
+
compression: (_a = v2 === null || v2 === void 0 ? void 0 : v2.compression) !== null && _a !== void 0 ? _a : DataStream_CompressionType.NONE,
|
|
24771
|
+
inlineContent: v2 === null || v2 === void 0 ? void 0 : v2.inlineContent,
|
|
24772
|
+
contentHeader: {
|
|
24773
|
+
case: 'textHeader',
|
|
24774
|
+
value: new DataStream_TextHeader({
|
|
24775
|
+
version: options === null || options === void 0 ? void 0 : options.version,
|
|
24776
|
+
attachedStreamIds: info.attachedStreamIds,
|
|
24777
|
+
replyToStreamId: options === null || options === void 0 ? void 0 : options.replyToStreamId,
|
|
24778
|
+
operationType: (options === null || options === void 0 ? void 0 : options.type) === 'update' ? DataStream_OperationType.UPDATE : DataStream_OperationType.CREATE
|
|
24779
|
+
})
|
|
24780
|
+
}
|
|
24781
|
+
});
|
|
24782
|
+
}
|
|
24783
|
+
/** Builds the `DataStream_Header` for a byte stream from its info. */
|
|
24784
|
+
function buildByteStreamHeader(info, v2) {
|
|
24785
|
+
var _a;
|
|
24786
|
+
return new DataStream_Header({
|
|
24787
|
+
streamId: info.id,
|
|
24788
|
+
mimeType: info.mimeType,
|
|
24789
|
+
topic: info.topic,
|
|
24790
|
+
timestamp: numberToBigInt(info.timestamp),
|
|
24791
|
+
totalLength: numberToBigInt(info.size),
|
|
24792
|
+
attributes: info.attributes,
|
|
24793
|
+
compression: (_a = v2 === null || v2 === void 0 ? void 0 : v2.compression) !== null && _a !== void 0 ? _a : DataStream_CompressionType.NONE,
|
|
24794
|
+
inlineContent: v2 === null || v2 === void 0 ? void 0 : v2.inlineContent,
|
|
24795
|
+
contentHeader: {
|
|
24796
|
+
case: 'byteHeader',
|
|
24797
|
+
value: new DataStream_ByteHeader({
|
|
24798
|
+
name: info.name
|
|
24799
|
+
})
|
|
24800
|
+
}
|
|
24801
|
+
});
|
|
24802
|
+
}
|
|
24803
|
+
/** Wraps a `DataStream_Header` in a `DataPacket` ready to be sent over a data channel. */
|
|
24804
|
+
function createStreamHeaderPacket(header, destinationIdentities) {
|
|
24805
|
+
return new DataPacket({
|
|
24806
|
+
destinationIdentities,
|
|
24807
|
+
value: {
|
|
24808
|
+
case: 'streamHeader',
|
|
24809
|
+
value: header
|
|
24810
|
+
}
|
|
24811
|
+
});
|
|
24812
|
+
}const textEncoder = new TextEncoder();
|
|
23574
24813
|
/**
|
|
23575
24814
|
* Manages sending custom user data via data channels.
|
|
23576
24815
|
* @internal
|
|
23577
24816
|
*/
|
|
23578
24817
|
class OutgoingDataStreamManager {
|
|
23579
|
-
constructor(engine, log) {
|
|
24818
|
+
constructor(engine, log, getRemoteParticipantClientProtocol, getRemoteParticipantCapabilities, getAllRemoteParticipantIdentities) {
|
|
23580
24819
|
this.engine = engine;
|
|
23581
24820
|
this.log = log;
|
|
24821
|
+
this.getRemoteParticipantClientProtocol = getRemoteParticipantClientProtocol;
|
|
24822
|
+
this.getRemoteParticipantCapabilities = getRemoteParticipantCapabilities;
|
|
24823
|
+
this.getAllRemoteParticipantIdentities = getAllRemoteParticipantIdentities;
|
|
23582
24824
|
}
|
|
23583
24825
|
setupEngine(engine) {
|
|
23584
24826
|
this.engine = engine;
|
|
@@ -23586,42 +24828,236 @@ class OutgoingDataStreamManager {
|
|
|
23586
24828
|
/** {@inheritDoc LocalParticipant.sendText} */
|
|
23587
24829
|
sendText(text, options) {
|
|
23588
24830
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23589
|
-
var _a;
|
|
24831
|
+
var _a, _b, _c, _d, _e;
|
|
23590
24832
|
const streamId = crypto.randomUUID();
|
|
23591
|
-
const textInBytes =
|
|
24833
|
+
const textInBytes = textEncoder.encode(text);
|
|
23592
24834
|
const totalTextLength = textInBytes.byteLength;
|
|
23593
|
-
const
|
|
23594
|
-
|
|
24835
|
+
const compress = (_a = options === null || options === void 0 ? void 0 : options.compress) !== null && _a !== void 0 ? _a : true;
|
|
24836
|
+
let info = {
|
|
24837
|
+
id: streamId,
|
|
24838
|
+
mimeType: 'text/plain',
|
|
24839
|
+
timestamp: Date.now(),
|
|
24840
|
+
topic: (_b = options === null || options === void 0 ? void 0 : options.topic) !== null && _b !== void 0 ? _b : '',
|
|
24841
|
+
size: totalTextLength,
|
|
24842
|
+
// NOTE: size is always the pre-compression byte length
|
|
24843
|
+
attributes: options === null || options === void 0 ? void 0 : options.attributes,
|
|
24844
|
+
encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
24845
|
+
};
|
|
24846
|
+
const compressEligible = compress && isCompressionStreamSupported() && this.allRecipientsSupportV2(options === null || options === void 0 ? void 0 : options.destinationIdentities) && this.allRecipientsSupportCompression(options === null || options === void 0 ? void 0 : options.destinationIdentities);
|
|
24847
|
+
let compressedStream = compressEligible ? MaybeCollectedStream.fromStream(readableFromBytes(textInBytes).pipeThrough(deflateRawTransform())) : null;
|
|
24848
|
+
// Phase 1: Try to send as a single packet data stream
|
|
24849
|
+
const noAttachments = !(options === null || options === void 0 ? void 0 : options.attachments) || options.attachments.length === 0;
|
|
24850
|
+
if (noAttachments && this.allRecipientsSupportV2(options === null || options === void 0 ? void 0 : options.destinationIdentities)) {
|
|
24851
|
+
// The payload rides in the header's `inlineContent` (raw bytes). Keep the compressed form only
|
|
24852
|
+
// if it actually shrinks the payload (deflate framing makes tiny strings larger). The
|
|
24853
|
+
// compression flag is carried in the header's `compression` field; user attributes are left
|
|
24854
|
+
// untouched.
|
|
24855
|
+
let inlineContent = textInBytes;
|
|
24856
|
+
let compression = DataStream_CompressionType.NONE;
|
|
24857
|
+
if (compressedStream) {
|
|
24858
|
+
const collectedBytes = yield compressedStream.collect();
|
|
24859
|
+
if (collectedBytes.byteLength < textInBytes.byteLength) {
|
|
24860
|
+
inlineContent = collectedBytes;
|
|
24861
|
+
compression = DataStream_CompressionType.DEFLATE_RAW;
|
|
24862
|
+
}
|
|
24863
|
+
}
|
|
24864
|
+
const header = buildTextStreamHeader(info, undefined, {
|
|
24865
|
+
compression,
|
|
24866
|
+
inlineContent
|
|
24867
|
+
});
|
|
24868
|
+
const packet = createStreamHeaderPacket(header, options === null || options === void 0 ? void 0 : options.destinationIdentities);
|
|
24869
|
+
if (packet.toBinary().byteLength <= STREAM_CHUNK_SIZE_BYTES) {
|
|
24870
|
+
yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
24871
|
+
(_d = options === null || options === void 0 ? void 0 : options.onProgress) === null || _d === void 0 ? void 0 : _d.call(options, 1);
|
|
24872
|
+
return info;
|
|
24873
|
+
}
|
|
24874
|
+
}
|
|
24875
|
+
const fileIds = (_e = options === null || options === void 0 ? void 0 : options.attachments) === null || _e === void 0 ? void 0 : _e.map(() => crypto.randomUUID());
|
|
24876
|
+
// Progress is split evenly across the text part (slot 0) and one slot per attachment, then
|
|
24877
|
+
// normalized to a [0,1] fraction. Each slot climbs monotonically to 1, so the aggregate ends at
|
|
24878
|
+
// exactly 1 once every part has completed.
|
|
24879
|
+
const parts = fileIds ? fileIds.length + 1 : 1;
|
|
24880
|
+
const progresses = new Array(parts).fill(0);
|
|
23595
24881
|
const handleProgress = (progress, idx) => {
|
|
23596
24882
|
var _a;
|
|
23597
24883
|
progresses[idx] = progress;
|
|
23598
|
-
|
|
23599
|
-
(_a = options === null || options === void 0 ? void 0 : options.onProgress) === null || _a === void 0 ? void 0 : _a.call(options, totalProgress);
|
|
24884
|
+
(_a = options === null || options === void 0 ? void 0 : options.onProgress) === null || _a === void 0 ? void 0 : _a.call(options, progresses.reduce((acc, val) => acc + val, 0) / parts);
|
|
23600
24885
|
};
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
24886
|
+
// Phase 2: Try to send a multi packet data stream with compressed bytes
|
|
24887
|
+
if (compressedStream) {
|
|
24888
|
+
info.attachedStreamIds = fileIds;
|
|
24889
|
+
const header = buildTextStreamHeader(info, undefined, {
|
|
24890
|
+
compression: DataStream_CompressionType.DEFLATE_RAW
|
|
24891
|
+
});
|
|
24892
|
+
const packet = createStreamHeaderPacket(header, options === null || options === void 0 ? void 0 : options.destinationIdentities);
|
|
24893
|
+
yield this.sendChunkedByteStream(packet, streamId, options === null || options === void 0 ? void 0 : options.destinationIdentities, compressedStream.stream().pipeThrough(progressReportingStream(textInBytes.length, progress => handleProgress(progress, 0))));
|
|
24894
|
+
// Ensure there's always a 100% progress event fired, even if the string is zero bytes long
|
|
24895
|
+
if (textInBytes.length === 0) {
|
|
24896
|
+
handleProgress(1, 0);
|
|
24897
|
+
}
|
|
24898
|
+
} else {
|
|
24899
|
+
// Phase 3 / fallback: header + plain uncompressed chunk packets + trailer.
|
|
24900
|
+
const writer = yield this.streamText({
|
|
24901
|
+
streamId,
|
|
24902
|
+
totalSize: totalTextLength,
|
|
24903
|
+
destinationIdentities: options === null || options === void 0 ? void 0 : options.destinationIdentities,
|
|
24904
|
+
topic: options === null || options === void 0 ? void 0 : options.topic,
|
|
24905
|
+
attachedStreamIds: fileIds,
|
|
24906
|
+
attributes: options === null || options === void 0 ? void 0 : options.attributes
|
|
24907
|
+
});
|
|
24908
|
+
yield writer.write(text);
|
|
24909
|
+
// set text part of progress to 1
|
|
24910
|
+
handleProgress(1, 0);
|
|
24911
|
+
yield writer.close();
|
|
24912
|
+
info = writer.info;
|
|
24913
|
+
}
|
|
23613
24914
|
if ((options === null || options === void 0 ? void 0 : options.attachments) && fileIds) {
|
|
23614
24915
|
yield Promise.all(options.attachments.map((file, idx) => __awaiter(this, void 0, void 0, function* () {
|
|
23615
24916
|
return this._sendFile(fileIds[idx], file, {
|
|
23616
24917
|
topic: options.topic,
|
|
23617
24918
|
mimeType: file.type,
|
|
24919
|
+
destinationIdentities: options.destinationIdentities,
|
|
24920
|
+
compress: options.compress,
|
|
23618
24921
|
onProgress: progress => {
|
|
23619
24922
|
handleProgress(progress, idx + 1);
|
|
23620
24923
|
}
|
|
23621
24924
|
});
|
|
23622
24925
|
})));
|
|
23623
24926
|
}
|
|
23624
|
-
return
|
|
24927
|
+
return info;
|
|
24928
|
+
});
|
|
24929
|
+
}
|
|
24930
|
+
/**
|
|
24931
|
+
* Sends a complete in-memory byte payload. Mirrors {@link sendText}'s semantics: when every
|
|
24932
|
+
* recipient supports data streams v2 the payload rides inline in a single header packet
|
|
24933
|
+
* (optionally deflate-raw compressed), otherwise it is sent as a (optionally compressed)
|
|
24934
|
+
* chunked byte stream. Unlike {@link sendFile}, the whole payload is already in memory, so the
|
|
24935
|
+
* inline single-packet fast path applies.
|
|
24936
|
+
*/
|
|
24937
|
+
sendBytes(bytes, options) {
|
|
24938
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24939
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
24940
|
+
const streamId = crypto.randomUUID();
|
|
24941
|
+
const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities;
|
|
24942
|
+
const compress = (_a = options === null || options === void 0 ? void 0 : options.compress) !== null && _a !== void 0 ? _a : true;
|
|
24943
|
+
const info = {
|
|
24944
|
+
id: streamId,
|
|
24945
|
+
name: (_b = options === null || options === void 0 ? void 0 : options.name) !== null && _b !== void 0 ? _b : 'unknown',
|
|
24946
|
+
mimeType: (_c = options === null || options === void 0 ? void 0 : options.mimeType) !== null && _c !== void 0 ? _c : 'application/octet-stream',
|
|
24947
|
+
timestamp: Date.now(),
|
|
24948
|
+
topic: (_d = options === null || options === void 0 ? void 0 : options.topic) !== null && _d !== void 0 ? _d : '',
|
|
24949
|
+
size: bytes.byteLength,
|
|
24950
|
+
// NOTE: size is always the pre-compression byte length
|
|
24951
|
+
attributes: options === null || options === void 0 ? void 0 : options.attributes,
|
|
24952
|
+
encryptionType: ((_e = this.engine.e2eeManager) === null || _e === void 0 ? void 0 : _e.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
24953
|
+
};
|
|
24954
|
+
const progressMonitorTap = progressReportingStream(bytes.length, options === null || options === void 0 ? void 0 : options.onProgress);
|
|
24955
|
+
const compressEligible = compress && isCompressionStreamSupported() && this.allRecipientsSupportV2(destinationIdentities) && this.allRecipientsSupportCompression(destinationIdentities);
|
|
24956
|
+
let compressedStream = compressEligible ? MaybeCollectedStream.fromStream(readableFromBytes(bytes).pipeThrough(progressMonitorTap).pipeThrough(deflateRawTransform())) : null;
|
|
24957
|
+
// Phase 1: Try to send as a single packet data stream
|
|
24958
|
+
if (this.allRecipientsSupportV2(destinationIdentities)) {
|
|
24959
|
+
// The payload rides in the header's `inlineContent` (raw bytes). Keep the compressed form only
|
|
24960
|
+
// if it actually shrinks the payload (deflate framing makes tiny payloads larger). The
|
|
24961
|
+
// compression flag is carried in the header's `compression` field; user attributes are left
|
|
24962
|
+
// untouched.
|
|
24963
|
+
let inlineContent = bytes;
|
|
24964
|
+
let compression = DataStream_CompressionType.NONE;
|
|
24965
|
+
if (compressedStream) {
|
|
24966
|
+
const collectedBytes = yield compressedStream.collect();
|
|
24967
|
+
if (collectedBytes.byteLength < bytes.byteLength) {
|
|
24968
|
+
inlineContent = collectedBytes;
|
|
24969
|
+
compression = DataStream_CompressionType.DEFLATE_RAW;
|
|
24970
|
+
}
|
|
24971
|
+
}
|
|
24972
|
+
const header = buildByteStreamHeader(info, {
|
|
24973
|
+
compression,
|
|
24974
|
+
inlineContent
|
|
24975
|
+
});
|
|
24976
|
+
const packet = createStreamHeaderPacket(header, destinationIdentities);
|
|
24977
|
+
if (packet.toBinary().byteLength <= STREAM_CHUNK_SIZE_BYTES) {
|
|
24978
|
+
yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
24979
|
+
(_f = options === null || options === void 0 ? void 0 : options.onProgress) === null || _f === void 0 ? void 0 : _f.call(options, 1);
|
|
24980
|
+
return info;
|
|
24981
|
+
}
|
|
24982
|
+
}
|
|
24983
|
+
// Phase 2/3: header + (optionally compressed) chunk packets + trailer.
|
|
24984
|
+
const header = buildByteStreamHeader(info, {
|
|
24985
|
+
compression: compressedStream ? DataStream_CompressionType.DEFLATE_RAW : DataStream_CompressionType.NONE
|
|
24986
|
+
});
|
|
24987
|
+
const packet = createStreamHeaderPacket(header, destinationIdentities);
|
|
24988
|
+
const source = compressedStream ? compressedStream.stream() : readableFromBytes(bytes).pipeThrough(progressMonitorTap);
|
|
24989
|
+
yield this.sendChunkedByteStream(packet, streamId, destinationIdentities, source);
|
|
24990
|
+
// Ensure there's always a 100% progress event fired, even if the buffer is zero bytes long
|
|
24991
|
+
if (bytes.length === 0) {
|
|
24992
|
+
(_g = options === null || options === void 0 ? void 0 : options.onProgress) === null || _g === void 0 ? void 0 : _g.call(options, 1);
|
|
24993
|
+
}
|
|
24994
|
+
return info;
|
|
24995
|
+
});
|
|
24996
|
+
}
|
|
24997
|
+
/**
|
|
24998
|
+
* Returns true only if every recipient is known to support data streams v2 (single-packet inline
|
|
24999
|
+
* streams and compression). For a targeted send this checks the named destination identities; for
|
|
25000
|
+
* a broadcast (no explicit destinations) it checks every remote participant currently in the room.
|
|
25001
|
+
* An empty room (nobody to receive) is considered eligible.
|
|
25002
|
+
*/
|
|
25003
|
+
allRecipientsSupportV2(destinationIdentities) {
|
|
25004
|
+
const identities = destinationIdentities && destinationIdentities.length > 0 ? destinationIdentities : this.getAllRemoteParticipantIdentities();
|
|
25005
|
+
return identities.every(identity => this.getRemoteParticipantClientProtocol(identity) >= CLIENT_PROTOCOL_DATA_STREAM_V2);
|
|
25006
|
+
}
|
|
25007
|
+
/**
|
|
25008
|
+
* Returns true only if every recipient advertises the deflate-raw compression capability (so it
|
|
25009
|
+
* can decompress a compressed stream). Resolved the same way as {@link allRecipientsSupportV2}:
|
|
25010
|
+
* named destinations, or every remote participant for a broadcast; an empty room is eligible.
|
|
25011
|
+
*/
|
|
25012
|
+
allRecipientsSupportCompression(destinationIdentities) {
|
|
25013
|
+
const identities = destinationIdentities && destinationIdentities.length > 0 ? destinationIdentities : this.getAllRemoteParticipantIdentities();
|
|
25014
|
+
return identities.every(identity => this.getRemoteParticipantCapabilities(identity).includes(ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW));
|
|
25015
|
+
}
|
|
25016
|
+
/**
|
|
25017
|
+
* Shared chunked-stream send for `sendText`/`sendFile`: sends the prebuilt header packet, then
|
|
25018
|
+
* forwards `source` (optionally deflate-raw compressed) as `streamChunk` packets re-chunked to
|
|
25019
|
+
* the MTU budget with contiguous indices, then sends the trailer. The source is consumed
|
|
25020
|
+
* incrementally, so a `file.stream()` is never buffered in full. The platform compressor can't
|
|
25021
|
+
* flush mid-stream, so compression is only used when the whole payload is available as a stream
|
|
25022
|
+
* up front (not for incremental writers like `streamText`/`streamBytes`).
|
|
25023
|
+
*/
|
|
25024
|
+
sendChunkedByteStream(headerPacket, streamId, destinationIdentities, source) {
|
|
25025
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25026
|
+
var _a, e_1, _b, _c;
|
|
25027
|
+
const engine = this.engine;
|
|
25028
|
+
yield sendHeaderPacket(engine, headerPacket);
|
|
25029
|
+
let chunkId = 0;
|
|
25030
|
+
try {
|
|
25031
|
+
for (var _d = true, _e = __asyncValues(readBytesInChunks(source, STREAM_CHUNK_SIZE_BYTES)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
25032
|
+
_c = _f.value;
|
|
25033
|
+
_d = false;
|
|
25034
|
+
const chunk = _c;
|
|
25035
|
+
const chunkPacket = new DataPacket({
|
|
25036
|
+
destinationIdentities,
|
|
25037
|
+
value: {
|
|
25038
|
+
case: 'streamChunk',
|
|
25039
|
+
value: new DataStream_Chunk({
|
|
25040
|
+
content: chunk,
|
|
25041
|
+
streamId,
|
|
25042
|
+
chunkIndex: numberToBigInt(chunkId)
|
|
25043
|
+
})
|
|
25044
|
+
}
|
|
25045
|
+
});
|
|
25046
|
+
yield engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE);
|
|
25047
|
+
chunkId += 1;
|
|
25048
|
+
}
|
|
25049
|
+
} catch (e_1_1) {
|
|
25050
|
+
e_1 = {
|
|
25051
|
+
error: e_1_1
|
|
25052
|
+
};
|
|
25053
|
+
} finally {
|
|
25054
|
+
try {
|
|
25055
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
25056
|
+
} finally {
|
|
25057
|
+
if (e_1) throw e_1.error;
|
|
25058
|
+
}
|
|
25059
|
+
}
|
|
25060
|
+
yield sendStreamTrailer(streamId, destinationIdentities, engine);
|
|
23625
25061
|
});
|
|
23626
25062
|
}
|
|
23627
25063
|
/**
|
|
@@ -23631,6 +25067,7 @@ class OutgoingDataStreamManager {
|
|
|
23631
25067
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23632
25068
|
var _a, _b, _c;
|
|
23633
25069
|
const streamId = (_a = options === null || options === void 0 ? void 0 : options.streamId) !== null && _a !== void 0 ? _a : crypto.randomUUID();
|
|
25070
|
+
const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities;
|
|
23634
25071
|
const info = {
|
|
23635
25072
|
id: streamId,
|
|
23636
25073
|
mimeType: 'text/plain',
|
|
@@ -23641,39 +25078,20 @@ class OutgoingDataStreamManager {
|
|
|
23641
25078
|
encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE,
|
|
23642
25079
|
attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds
|
|
23643
25080
|
};
|
|
23644
|
-
const header =
|
|
23645
|
-
|
|
23646
|
-
|
|
23647
|
-
topic: info.topic,
|
|
23648
|
-
timestamp: numberToBigInt(info.timestamp),
|
|
23649
|
-
totalLength: numberToBigInt(info.size),
|
|
23650
|
-
attributes: info.attributes,
|
|
23651
|
-
contentHeader: {
|
|
23652
|
-
case: 'textHeader',
|
|
23653
|
-
value: new DataStream_TextHeader({
|
|
23654
|
-
version: options === null || options === void 0 ? void 0 : options.version,
|
|
23655
|
-
attachedStreamIds: info.attachedStreamIds,
|
|
23656
|
-
replyToStreamId: options === null || options === void 0 ? void 0 : options.replyToStreamId,
|
|
23657
|
-
operationType: (options === null || options === void 0 ? void 0 : options.type) === 'update' ? DataStream_OperationType.UPDATE : DataStream_OperationType.CREATE
|
|
23658
|
-
})
|
|
23659
|
-
}
|
|
23660
|
-
});
|
|
23661
|
-
const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities;
|
|
23662
|
-
const packet = new DataPacket({
|
|
23663
|
-
destinationIdentities,
|
|
23664
|
-
value: {
|
|
23665
|
-
case: 'streamHeader',
|
|
23666
|
-
value: header
|
|
23667
|
-
}
|
|
23668
|
-
});
|
|
23669
|
-
yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
25081
|
+
const header = buildTextStreamHeader(info, options);
|
|
25082
|
+
const packet = createStreamHeaderPacket(header, destinationIdentities);
|
|
25083
|
+
yield sendHeaderPacket(this.engine, packet);
|
|
23670
25084
|
let chunkId = 0;
|
|
23671
25085
|
const engine = this.engine;
|
|
25086
|
+
// Incremental text streams are never compressed (CompressionStream does not support flushing
|
|
25087
|
+
// mid-stream); one-shot compression lives in sendText.
|
|
25088
|
+
//
|
|
25089
|
+
// Note that a future streamText could send a context-takeover style deflate-raw stream with
|
|
25090
|
+
// intermedia explicit `Z_SYNC_FLUSH`s - receivers already will handle this properly today.
|
|
23672
25091
|
const writableStream = new WritableStream({
|
|
23673
|
-
// Implement the sink
|
|
23674
25092
|
write(text) {
|
|
23675
25093
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23676
|
-
for (const textByteChunk of splitUtf8(text,
|
|
25094
|
+
for (const textByteChunk of splitUtf8(text, STREAM_CHUNK_SIZE_BYTES)) {
|
|
23677
25095
|
const chunk = new DataStream_Chunk({
|
|
23678
25096
|
content: textByteChunk,
|
|
23679
25097
|
streamId,
|
|
@@ -23693,17 +25111,7 @@ class OutgoingDataStreamManager {
|
|
|
23693
25111
|
},
|
|
23694
25112
|
close() {
|
|
23695
25113
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23696
|
-
|
|
23697
|
-
streamId
|
|
23698
|
-
});
|
|
23699
|
-
const trailerPacket = new DataPacket({
|
|
23700
|
-
destinationIdentities,
|
|
23701
|
-
value: {
|
|
23702
|
-
case: 'streamTrailer',
|
|
23703
|
-
value: trailer
|
|
23704
|
-
}
|
|
23705
|
-
});
|
|
23706
|
-
yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
25114
|
+
yield sendStreamTrailer(streamId, destinationIdentities, engine);
|
|
23707
25115
|
});
|
|
23708
25116
|
},
|
|
23709
25117
|
abort(err) {
|
|
@@ -23714,6 +25122,8 @@ class OutgoingDataStreamManager {
|
|
|
23714
25122
|
let onEngineClose = () => __awaiter(this, void 0, void 0, function* () {
|
|
23715
25123
|
yield writer.close();
|
|
23716
25124
|
});
|
|
25125
|
+
// FIXME: make this a global event to ensure "max listener" warning won't get logged for lots of
|
|
25126
|
+
// in flight data streams.
|
|
23717
25127
|
engine.once(EngineEvent.Closing, onEngineClose);
|
|
23718
25128
|
const writer = new TextStreamWriter(writableStream, info, () => this.engine.off(EngineEvent.Closing, onEngineClose));
|
|
23719
25129
|
return writer;
|
|
@@ -23728,29 +25138,47 @@ class OutgoingDataStreamManager {
|
|
|
23728
25138
|
};
|
|
23729
25139
|
});
|
|
23730
25140
|
}
|
|
25141
|
+
/**
|
|
25142
|
+
* Streams a file as a chunked byte stream, compressed (deflate-raw) when the runtime supports it
|
|
25143
|
+
* and every recipient is on data streams v2. The file is piped `file.stream()` →
|
|
25144
|
+
* (`CompressionStream`) → chunk packets via {@link sendChunkedByteStream}, so it is never fully
|
|
25145
|
+
* buffered in memory — unlike {@link sendBytes}, there is no inline single-packet fast path for
|
|
25146
|
+
* files (the compressed size can't be known up front without buffering the whole file).
|
|
25147
|
+
*/
|
|
23731
25148
|
_sendFile(streamId, file, options) {
|
|
23732
25149
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23733
|
-
var _a;
|
|
23734
|
-
const
|
|
23735
|
-
|
|
23736
|
-
|
|
25150
|
+
var _a, _b, _c, _d, _e;
|
|
25151
|
+
const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities;
|
|
25152
|
+
const compress = ((_a = options === null || options === void 0 ? void 0 : options.compress) !== null && _a !== void 0 ? _a : true) && isCompressionStreamSupported() && this.allRecipientsSupportV2(destinationIdentities) && this.allRecipientsSupportCompression(destinationIdentities);
|
|
25153
|
+
const info = {
|
|
25154
|
+
id: streamId,
|
|
23737
25155
|
name: file.name,
|
|
23738
|
-
mimeType: (
|
|
23739
|
-
topic: options === null || options === void 0 ? void 0 : options.topic,
|
|
23740
|
-
|
|
25156
|
+
mimeType: (_b = options === null || options === void 0 ? void 0 : options.mimeType) !== null && _b !== void 0 ? _b : file.type,
|
|
25157
|
+
topic: (_c = options === null || options === void 0 ? void 0 : options.topic) !== null && _c !== void 0 ? _c : '',
|
|
25158
|
+
timestamp: Date.now(),
|
|
25159
|
+
size: file.size,
|
|
25160
|
+
encryptionType: ((_d = this.engine.e2eeManager) === null || _d === void 0 ? void 0 : _d.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
25161
|
+
};
|
|
25162
|
+
// Phase 1: Try to send as a single packet data stream
|
|
25163
|
+
//
|
|
25164
|
+
// This is not being done explictly for files, because it's challenging to determine ahead of
|
|
25165
|
+
// time how well the file contents will compress (and whether the total output will be under the
|
|
25166
|
+
// MTU). Revisit this in the future though.
|
|
25167
|
+
// Phase 2 (compressed) / Phase 3 (uncompressed fallback): header + chunk packets + trailer. Both
|
|
25168
|
+
// funnel through the shared sendChunkedByteStream primitive, differing only by whether the file
|
|
25169
|
+
// stream is wrapped in the deflate-raw compressor; the file is never fully buffered in memory.
|
|
25170
|
+
const header = buildByteStreamHeader(info, {
|
|
25171
|
+
compression: compress ? DataStream_CompressionType.DEFLATE_RAW : DataStream_CompressionType.NONE
|
|
23741
25172
|
});
|
|
23742
|
-
const
|
|
23743
|
-
|
|
23744
|
-
|
|
23745
|
-
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
|
|
23749
|
-
}
|
|
23750
|
-
yield writer.write(value);
|
|
25173
|
+
const packet = createStreamHeaderPacket(header, destinationIdentities);
|
|
25174
|
+
const tapped = file.stream().pipeThrough(progressReportingStream(file.size, options === null || options === void 0 ? void 0 : options.onProgress));
|
|
25175
|
+
const source = compress ? tapped.pipeThrough(deflateRawTransform()) : tapped;
|
|
25176
|
+
yield this.sendChunkedByteStream(packet, streamId, destinationIdentities, source);
|
|
25177
|
+
// Ensure there's always a 100% progress event fired, even if the file is zero bytes long
|
|
25178
|
+
if (file.size === 0) {
|
|
25179
|
+
(_e = options === null || options === void 0 ? void 0 : options.onProgress) === null || _e === void 0 ? void 0 : _e.call(options, 1);
|
|
23751
25180
|
}
|
|
23752
|
-
|
|
23753
|
-
return writer.info;
|
|
25181
|
+
return info;
|
|
23754
25182
|
});
|
|
23755
25183
|
}
|
|
23756
25184
|
streamBytes(options) {
|
|
@@ -23768,32 +25196,18 @@ class OutgoingDataStreamManager {
|
|
|
23768
25196
|
name: (_d = options === null || options === void 0 ? void 0 : options.name) !== null && _d !== void 0 ? _d : 'unknown',
|
|
23769
25197
|
encryptionType: ((_e = this.engine.e2eeManager) === null || _e === void 0 ? void 0 : _e.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
23770
25198
|
};
|
|
23771
|
-
const header =
|
|
23772
|
-
|
|
23773
|
-
|
|
23774
|
-
streamId,
|
|
23775
|
-
topic: info.topic,
|
|
23776
|
-
timestamp: numberToBigInt(Date.now()),
|
|
23777
|
-
attributes: info.attributes,
|
|
23778
|
-
contentHeader: {
|
|
23779
|
-
case: 'byteHeader',
|
|
23780
|
-
value: new DataStream_ByteHeader({
|
|
23781
|
-
name: info.name
|
|
23782
|
-
})
|
|
23783
|
-
}
|
|
23784
|
-
});
|
|
23785
|
-
const packet = new DataPacket({
|
|
23786
|
-
destinationIdentities,
|
|
23787
|
-
value: {
|
|
23788
|
-
case: 'streamHeader',
|
|
23789
|
-
value: header
|
|
23790
|
-
}
|
|
23791
|
-
});
|
|
23792
|
-
yield this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
25199
|
+
const header = buildByteStreamHeader(info);
|
|
25200
|
+
const packet = createStreamHeaderPacket(header, destinationIdentities);
|
|
25201
|
+
yield sendHeaderPacket(this.engine, packet);
|
|
23793
25202
|
let chunkId = 0;
|
|
23794
25203
|
const writeMutex = new _();
|
|
23795
25204
|
const engine = this.engine;
|
|
23796
25205
|
const logLocal = this.log;
|
|
25206
|
+
// Incremental byte streams are never compressed (CompressionStream does not support flushing
|
|
25207
|
+
// mid-stream); one-shot compression lives in sendFile.
|
|
25208
|
+
//
|
|
25209
|
+
// Note that a future streamBytes could send a context-takeover style deflate-raw stream with
|
|
25210
|
+
// intermedia explicit `Z_SYNC_FLUSH`s - receivers already will handle this properly today.
|
|
23797
25211
|
const writableStream = new WritableStream({
|
|
23798
25212
|
write(chunk) {
|
|
23799
25213
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -23801,7 +25215,7 @@ class OutgoingDataStreamManager {
|
|
|
23801
25215
|
let byteOffset = 0;
|
|
23802
25216
|
try {
|
|
23803
25217
|
while (byteOffset < chunk.byteLength) {
|
|
23804
|
-
const subChunk = chunk.slice(byteOffset, byteOffset +
|
|
25218
|
+
const subChunk = chunk.slice(byteOffset, byteOffset + STREAM_CHUNK_SIZE_BYTES);
|
|
23805
25219
|
const chunkPacket = new DataPacket({
|
|
23806
25220
|
destinationIdentities,
|
|
23807
25221
|
value: {
|
|
@@ -23824,17 +25238,7 @@ class OutgoingDataStreamManager {
|
|
|
23824
25238
|
},
|
|
23825
25239
|
close() {
|
|
23826
25240
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23827
|
-
|
|
23828
|
-
streamId
|
|
23829
|
-
});
|
|
23830
|
-
const trailerPacket = new DataPacket({
|
|
23831
|
-
destinationIdentities,
|
|
23832
|
-
value: {
|
|
23833
|
-
case: 'streamTrailer',
|
|
23834
|
-
value: trailer
|
|
23835
|
-
}
|
|
23836
|
-
});
|
|
23837
|
-
yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
25241
|
+
yield sendStreamTrailer(streamId, destinationIdentities, engine);
|
|
23838
25242
|
});
|
|
23839
25243
|
},
|
|
23840
25244
|
abort(err) {
|
|
@@ -23845,6 +25249,109 @@ class OutgoingDataStreamManager {
|
|
|
23845
25249
|
return byteWriter;
|
|
23846
25250
|
});
|
|
23847
25251
|
}
|
|
25252
|
+
}
|
|
25253
|
+
/**
|
|
25254
|
+
* Wraps a stream of compressed bytes and defers buffering it into memory until (and only if) the
|
|
25255
|
+
* full length is actually needed.
|
|
25256
|
+
*
|
|
25257
|
+
* The ideal way to send a compressed stream is incrementally: compress each chunk and send it as
|
|
25258
|
+
* soon as it's ready, without ever holding the whole payload in memory. But to decide whether a
|
|
25259
|
+
* payload is small enough to fit in a single-packet data stream, we have to know the compressed
|
|
25260
|
+
* length — and the only way to learn that is to compress everything and add up the bytes. That
|
|
25261
|
+
* forces us to buffer the entire compressed output before we can make the decision.
|
|
25262
|
+
*
|
|
25263
|
+
* This class lets us have it both ways. It holds the compressed stream unread by default and only
|
|
25264
|
+
* collects it into memory ({@link collect}) when a caller needs the length for the single-packet
|
|
25265
|
+
* size check. If that check never happens, the stream is instead {@link stream|passed straight
|
|
25266
|
+
* through} to the downstream consumer without ever being fully buffered. Once collected, later
|
|
25267
|
+
* calls reuse the buffered bytes rather than re-collecting.
|
|
25268
|
+
*/
|
|
25269
|
+
class MaybeCollectedStream {
|
|
25270
|
+
constructor(state) {
|
|
25271
|
+
this.state = state;
|
|
25272
|
+
}
|
|
25273
|
+
static fromStream(stream) {
|
|
25274
|
+
return new MaybeCollectedStream({
|
|
25275
|
+
type: 'stream',
|
|
25276
|
+
stream
|
|
25277
|
+
});
|
|
25278
|
+
}
|
|
25279
|
+
/** Collect data from the stream into memory and return as a Uint8Array. */
|
|
25280
|
+
collect() {
|
|
25281
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25282
|
+
switch (this.state.type) {
|
|
25283
|
+
case 'stream':
|
|
25284
|
+
const bytes = yield collect(this.state.stream);
|
|
25285
|
+
this.state = {
|
|
25286
|
+
type: 'collected',
|
|
25287
|
+
bytes
|
|
25288
|
+
};
|
|
25289
|
+
return bytes;
|
|
25290
|
+
case 'collected':
|
|
25291
|
+
return this.state.bytes;
|
|
25292
|
+
}
|
|
25293
|
+
});
|
|
25294
|
+
}
|
|
25295
|
+
/** Pass wrapped stream through to downstream consumer. */
|
|
25296
|
+
stream() {
|
|
25297
|
+
switch (this.state.type) {
|
|
25298
|
+
case 'stream':
|
|
25299
|
+
return this.state.stream;
|
|
25300
|
+
case 'collected':
|
|
25301
|
+
return readableFromBytes(this.state.bytes);
|
|
25302
|
+
}
|
|
25303
|
+
}
|
|
25304
|
+
}
|
|
25305
|
+
/**
|
|
25306
|
+
* A pass-through byte transform that reports progress as bytes flow through it, measured against
|
|
25307
|
+
* `totalLength`. Used as a "tap" to instrument the source of a chunked send without threading a
|
|
25308
|
+
* progress callback through the send primitive.
|
|
25309
|
+
*
|
|
25310
|
+
* IMPORTANT: This should be upstream of any compression step.
|
|
25311
|
+
*
|
|
25312
|
+
* Emits nothing when the total is unknown or zero.
|
|
25313
|
+
*/
|
|
25314
|
+
function progressReportingStream(totalPreCompressionLength, onProgress) {
|
|
25315
|
+
let sent = 0;
|
|
25316
|
+
return new TransformStream({
|
|
25317
|
+
transform(chunk, controller) {
|
|
25318
|
+
sent += chunk.byteLength;
|
|
25319
|
+
if (onProgress && typeof totalPreCompressionLength === 'number' && totalPreCompressionLength > 0) {
|
|
25320
|
+
onProgress(Math.min(sent / totalPreCompressionLength, 1));
|
|
25321
|
+
}
|
|
25322
|
+
controller.enqueue(chunk);
|
|
25323
|
+
}
|
|
25324
|
+
});
|
|
25325
|
+
}
|
|
25326
|
+
/**
|
|
25327
|
+
* Sends a stream `streamHeader` packet, enforcing that it fits the MTU budget. The header carries
|
|
25328
|
+
* the user attributes (plus topic/framing), and a single `DataPacket` larger than the MTU can't be
|
|
25329
|
+
* reliably sent — so an oversized header (almost always due to large attributes) is a hard error
|
|
25330
|
+
* rather than a malformed packet on the wire. The inline fast path does its own size check and
|
|
25331
|
+
* falls back to the chunked path instead of calling this.
|
|
25332
|
+
*/
|
|
25333
|
+
function sendHeaderPacket(engine, packet) {
|
|
25334
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25335
|
+
if (packet.toBinary().byteLength > STREAM_CHUNK_SIZE_BYTES) {
|
|
25336
|
+
throw new DataStreamError("data stream header exceeds the ".concat(STREAM_CHUNK_SIZE_BYTES, "-byte limit; reduce attribute size"), DataStreamErrorReason.HeaderTooLarge);
|
|
25337
|
+
}
|
|
25338
|
+
yield engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
|
|
25339
|
+
});
|
|
25340
|
+
}
|
|
25341
|
+
/** Sends a `streamTrailer` packet, marking the end of a stream. */
|
|
25342
|
+
function sendStreamTrailer(streamId, destinationIdentities, engine) {
|
|
25343
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
25344
|
+
const trailerPacket = new DataPacket({
|
|
25345
|
+
destinationIdentities,
|
|
25346
|
+
value: {
|
|
25347
|
+
case: 'streamTrailer',
|
|
25348
|
+
value: new DataStream_Trailer({
|
|
25349
|
+
streamId
|
|
25350
|
+
})
|
|
25351
|
+
}
|
|
25352
|
+
});
|
|
25353
|
+
yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
25354
|
+
});
|
|
23848
25355
|
}/**
|
|
23849
25356
|
* Implementation of AbortSignal.any
|
|
23850
25357
|
* Creates a signal that will be aborted when any of the given signals is aborted.
|
|
@@ -24829,6 +26336,9 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24829
26336
|
let bufferSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : READABLE_STREAM_DEFAULT_BUFFER_SIZE;
|
|
24830
26337
|
let streamController = null;
|
|
24831
26338
|
const sfuSubscriptionComplete = new Future();
|
|
26339
|
+
// Hold the descriptor by reference: SID reassignment re-keys the map, so lookups
|
|
26340
|
+
// with the SID captured at subscribe time would fail.
|
|
26341
|
+
const descriptor = this.descriptors.get(sid);
|
|
24832
26342
|
const detachSignal = () => {
|
|
24833
26343
|
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
24834
26344
|
};
|
|
@@ -24838,8 +26348,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24838
26348
|
log$1.warn("ReadableStream subscribed to ".concat(sid, " was not started."));
|
|
24839
26349
|
return;
|
|
24840
26350
|
}
|
|
24841
|
-
|
|
24842
|
-
if (!descriptor) {
|
|
26351
|
+
if (!descriptor || this.descriptors.get(descriptor.info.sid) !== descriptor) {
|
|
24843
26352
|
log$1.warn("Unknown track ".concat(sid, ", skipping cancel..."));
|
|
24844
26353
|
return;
|
|
24845
26354
|
}
|
|
@@ -24858,9 +26367,8 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24858
26367
|
if (!streamController) {
|
|
24859
26368
|
return;
|
|
24860
26369
|
}
|
|
24861
|
-
|
|
24862
|
-
|
|
24863
|
-
currentDescriptor.subscription.streamControllers.delete(streamController);
|
|
26370
|
+
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.subscription.type) === 'active') {
|
|
26371
|
+
descriptor.subscription.streamControllers.delete(streamController);
|
|
24864
26372
|
}
|
|
24865
26373
|
streamController.error(DataTrackSubscribeError.cancelled());
|
|
24866
26374
|
(_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, DataTrackSubscribeError.cancelled());
|
|
@@ -24871,8 +26379,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24871
26379
|
streamController = controller;
|
|
24872
26380
|
this.subscribeRequest(sid, signal).then(() => __awaiter(this, void 0, void 0, function* () {
|
|
24873
26381
|
var _a, _b, _c;
|
|
24874
|
-
|
|
24875
|
-
if (!descriptor) {
|
|
26382
|
+
if (!descriptor || this.descriptors.get(descriptor.info.sid) !== descriptor) {
|
|
24876
26383
|
log$1.error("Unknown track ".concat(sid));
|
|
24877
26384
|
const err = DataTrackSubscribeError.disconnected();
|
|
24878
26385
|
controller.error(err);
|
|
@@ -24987,7 +26494,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24987
26494
|
};
|
|
24988
26495
|
// Let the SFU know that the subscribe has been cancelled
|
|
24989
26496
|
this.emit('sfuUpdateSubscription', {
|
|
24990
|
-
sid,
|
|
26497
|
+
sid: descriptor.info.sid,
|
|
24991
26498
|
subscribe: false
|
|
24992
26499
|
});
|
|
24993
26500
|
if (previousDescriptorSubscription.type === 'pending') {
|
|
@@ -25101,6 +26608,9 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25101
26608
|
if (this.descriptors.has(info.sid)) {
|
|
25102
26609
|
continue;
|
|
25103
26610
|
}
|
|
26611
|
+
if (this.handleSidReassigned(publisherIdentity, info)) {
|
|
26612
|
+
continue;
|
|
26613
|
+
}
|
|
25104
26614
|
yield this.handleTrackPublished(publisherIdentity, info);
|
|
25105
26615
|
}
|
|
25106
26616
|
publisherParticipantToSidsInUpdate.set(publisherIdentity, sidsInUpdate);
|
|
@@ -25158,6 +26668,62 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25158
26668
|
});
|
|
25159
26669
|
});
|
|
25160
26670
|
}
|
|
26671
|
+
/**
|
|
26672
|
+
* Detects and handles SID reassignment, which occurs when the publisher
|
|
26673
|
+
* republishes its tracks after a full reconnect.
|
|
26674
|
+
*
|
|
26675
|
+
* Returns `true` if an SID reassignment occurred, `false` otherwise.
|
|
26676
|
+
*/
|
|
26677
|
+
handleSidReassigned(publisherIdentity, info) {
|
|
26678
|
+
// Publisher identity and pub handle are stable across republications.
|
|
26679
|
+
const existingEntry = Array.from(this.descriptors.entries()).find(_ref1 => {
|
|
26680
|
+
let _ref10 = _slicedToArray(_ref1, 2);
|
|
26681
|
+
_ref10[0];
|
|
26682
|
+
let descriptor = _ref10[1];
|
|
26683
|
+
return descriptor.publisherIdentity === publisherIdentity && descriptor.info.pubHandle === info.pubHandle;
|
|
26684
|
+
});
|
|
26685
|
+
if (!existingEntry) {
|
|
26686
|
+
return false;
|
|
26687
|
+
}
|
|
26688
|
+
const _existingEntry = _slicedToArray(existingEntry, 2),
|
|
26689
|
+
oldSid = _existingEntry[0],
|
|
26690
|
+
descriptor = _existingEntry[1];
|
|
26691
|
+
// Invariant: other than SID, info should not have changed.
|
|
26692
|
+
// TODO: consider refactoring to move SID out of info to allow for direct comparison.
|
|
26693
|
+
const _descriptor$info = descriptor.info,
|
|
26694
|
+
name = _descriptor$info.name,
|
|
26695
|
+
usesE2ee = _descriptor$info.usesE2ee;
|
|
26696
|
+
if (name !== info.name || usesE2ee !== info.usesE2ee) {
|
|
26697
|
+
log$1.warn("Info mismatch for ".concat(oldSid, ", treating as new publication"));
|
|
26698
|
+
return false;
|
|
26699
|
+
}
|
|
26700
|
+
const newSid = info.sid;
|
|
26701
|
+
log$1.debug("SID reassigned: ".concat(oldSid, " -> ").concat(newSid));
|
|
26702
|
+
if (!this.descriptors.delete(oldSid)) {
|
|
26703
|
+
return false;
|
|
26704
|
+
}
|
|
26705
|
+
descriptor.info.sid = newSid;
|
|
26706
|
+
switch (descriptor.subscription.type) {
|
|
26707
|
+
case 'none':
|
|
26708
|
+
break;
|
|
26709
|
+
case 'pending':
|
|
26710
|
+
case 'active':
|
|
26711
|
+
// The SFU does not carry subscriptions across a publisher's full
|
|
26712
|
+
// reconnect; re-request the subscription under the new SID.
|
|
26713
|
+
this.emit('sfuUpdateSubscription', {
|
|
26714
|
+
sid: newSid,
|
|
26715
|
+
subscribe: true
|
|
26716
|
+
});
|
|
26717
|
+
break;
|
|
26718
|
+
}
|
|
26719
|
+
if (descriptor.subscription.type === 'active') {
|
|
26720
|
+
// Keep the routing index consistent until the SFU assigns a new handle
|
|
26721
|
+
// (see `registerSubscriberHandle`).
|
|
26722
|
+
this.subscriptionHandles.set(descriptor.subscription.subcriptionHandle, newSid);
|
|
26723
|
+
}
|
|
26724
|
+
this.descriptors.set(newSid, descriptor);
|
|
26725
|
+
return true;
|
|
26726
|
+
}
|
|
25161
26727
|
handleTrackUnpublished(sid) {
|
|
25162
26728
|
const descriptor = this.descriptors.get(sid);
|
|
25163
26729
|
if (!descriptor) {
|
|
@@ -25179,10 +26745,10 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25179
26745
|
/** Mapping between track handles attached to incoming packets to the
|
|
25180
26746
|
* track SIDs they belong to. */
|
|
25181
26747
|
mapping) {
|
|
25182
|
-
for (const
|
|
25183
|
-
var
|
|
25184
|
-
const handle =
|
|
25185
|
-
const sid =
|
|
26748
|
+
for (const _ref11 of mapping.entries()) {
|
|
26749
|
+
var _ref12 = _slicedToArray(_ref11, 2);
|
|
26750
|
+
const handle = _ref12[0];
|
|
26751
|
+
const sid = _ref12[1];
|
|
25186
26752
|
this.registerSubscriberHandle(handle, sid);
|
|
25187
26753
|
}
|
|
25188
26754
|
}
|
|
@@ -25203,12 +26769,17 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25203
26769
|
case 'active':
|
|
25204
26770
|
{
|
|
25205
26771
|
// Update handle for an active subscription. This can occur following a full reconnect.
|
|
26772
|
+
this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle);
|
|
25206
26773
|
descriptor.subscription.subcriptionHandle = assignedHandle;
|
|
25207
26774
|
this.subscriptionHandles.set(assignedHandle, sid);
|
|
25208
26775
|
return;
|
|
25209
26776
|
}
|
|
25210
26777
|
case 'pending':
|
|
25211
26778
|
{
|
|
26779
|
+
log$1.debug("data track subscription activated", {
|
|
26780
|
+
sid,
|
|
26781
|
+
handle: assignedHandle
|
|
26782
|
+
});
|
|
25212
26783
|
const pipeline = new IncomingDataTrackPipeline({
|
|
25213
26784
|
info: descriptor.info,
|
|
25214
26785
|
publisherIdentity: descriptor.publisherIdentity,
|
|
@@ -25275,10 +26846,10 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25275
26846
|
* tracks are subscribed to locally.
|
|
25276
26847
|
*/
|
|
25277
26848
|
resendSubscriptionUpdates() {
|
|
25278
|
-
for (const
|
|
25279
|
-
var
|
|
25280
|
-
const sid =
|
|
25281
|
-
const descriptor =
|
|
26849
|
+
for (const _ref13 of this.descriptors) {
|
|
26850
|
+
var _ref14 = _slicedToArray(_ref13, 2);
|
|
26851
|
+
const sid = _ref14[0];
|
|
26852
|
+
const descriptor = _ref14[1];
|
|
25282
26853
|
if (descriptor.subscription.type === 'none') {
|
|
25283
26854
|
continue;
|
|
25284
26855
|
}
|
|
@@ -25944,10 +27515,16 @@ class OutgoingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25944
27515
|
{
|
|
25945
27516
|
if (result.type === 'ok') {
|
|
25946
27517
|
const info = result.data;
|
|
27518
|
+
log.debug("SFU accepted publish request for handle ".concat(handle), {
|
|
27519
|
+
sid: info.sid
|
|
27520
|
+
});
|
|
25947
27521
|
const e2eeManager = info.usesE2ee ? this.e2eeManager : null;
|
|
25948
27522
|
this.descriptors.set(info.pubHandle, Descriptor.active(info, e2eeManager));
|
|
25949
27523
|
(_b = (_a = descriptor.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
25950
27524
|
} else {
|
|
27525
|
+
log.debug("SFU rejected publish request for handle ".concat(handle), {
|
|
27526
|
+
error: result.error
|
|
27527
|
+
});
|
|
25951
27528
|
(_d = (_c = descriptor.completionFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, result.error);
|
|
25952
27529
|
}
|
|
25953
27530
|
return;
|
|
@@ -26265,7 +27842,7 @@ class RpcClientManager extends eventsExports.EventEmitter {
|
|
|
26265
27842
|
return __awaiter(this, void 0, void 0, function* () {
|
|
26266
27843
|
if (remoteClientProtocol >= CLIENT_PROTOCOL_DATA_STREAM_RPC) {
|
|
26267
27844
|
// Send payload as a data stream - a "version 2" rpc request.
|
|
26268
|
-
|
|
27845
|
+
yield this.outgoingDataStreamManager.sendText(payload, {
|
|
26269
27846
|
topic: RPC_REQUEST_DATA_STREAM_TOPIC,
|
|
26270
27847
|
destinationIdentities: [destinationIdentity],
|
|
26271
27848
|
attributes: {
|
|
@@ -26275,8 +27852,6 @@ class RpcClientManager extends eventsExports.EventEmitter {
|
|
|
26275
27852
|
[RpcRequestAttrs.RPC_REQUEST_VERSION]: "".concat(RPC_VERSION_V2)
|
|
26276
27853
|
}
|
|
26277
27854
|
});
|
|
26278
|
-
yield writer.write(payload);
|
|
26279
|
-
yield writer.close();
|
|
26280
27855
|
return;
|
|
26281
27856
|
}
|
|
26282
27857
|
// Fallback to sending a literal RpcRequest - a "version 1" rpc request.
|
|
@@ -26550,15 +28125,13 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
26550
28125
|
const callerClientProtocol = this.getRemoteParticipantClientProtocol(destinationIdentity);
|
|
26551
28126
|
if (callerClientProtocol >= CLIENT_PROTOCOL_DATA_STREAM_RPC) {
|
|
26552
28127
|
// Send response as a data stream
|
|
26553
|
-
|
|
28128
|
+
yield this.outgoingDataStreamManager.sendText(payload, {
|
|
26554
28129
|
topic: RPC_RESPONSE_DATA_STREAM_TOPIC,
|
|
26555
28130
|
destinationIdentities: [destinationIdentity],
|
|
26556
28131
|
attributes: {
|
|
26557
28132
|
[RpcRequestAttrs.RPC_REQUEST_ID]: requestId
|
|
26558
28133
|
}
|
|
26559
28134
|
});
|
|
26560
|
-
yield writer.write(payload);
|
|
26561
|
-
yield writer.close();
|
|
26562
28135
|
return;
|
|
26563
28136
|
}
|
|
26564
28137
|
// Legacy client: enforce size limit and send uncompressed payload inline
|
|
@@ -27403,6 +28976,11 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
27403
28976
|
this.audioTrackPublications.forEach(track => isAudioTrack(track.track) && track.track.setAudioContext(ctx));
|
|
27404
28977
|
}
|
|
27405
28978
|
addTrackPublication(publication) {
|
|
28979
|
+
this.log.debug("adding track publication", {
|
|
28980
|
+
trackSid: publication.trackSid,
|
|
28981
|
+
source: publication.source,
|
|
28982
|
+
kind: publication.kind
|
|
28983
|
+
});
|
|
27406
28984
|
// forward publication driven events
|
|
27407
28985
|
publication.on(TrackEvent.Muted, () => {
|
|
27408
28986
|
this.emit(ParticipantEvent.TrackMuted, publication);
|
|
@@ -28447,7 +30025,7 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
28447
30025
|
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
|
28448
30026
|
}
|
|
28449
30027
|
const negotiate = () => __awaiter(this, void 0, void 0, function* () {
|
|
28450
|
-
var _a, _b, _c;
|
|
30028
|
+
var _a, _b, _c, _d;
|
|
28451
30029
|
if (!this.engine.pcManager) {
|
|
28452
30030
|
throw new UnexpectedConnectionState('pcManager is not ready');
|
|
28453
30031
|
}
|
|
@@ -28483,12 +30061,22 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
28483
30061
|
maxbr: ((_b = encodings[0]) === null || _b === void 0 ? void 0 : _b.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
|
|
28484
30062
|
});
|
|
28485
30063
|
}
|
|
28486
|
-
} else if (track.codec &&
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
30064
|
+
} else if (track.codec && isVideoCodec(track.codec)) {
|
|
30065
|
+
// Apply start bitrate for all video codecs to prevent initial blurriness.
|
|
30066
|
+
// - SVC codecs: use first encoding's bitrate (single stream with built-in layers)
|
|
30067
|
+
// - Simulcast: sum all encoding bitrates (independent streams, BWE needs total)
|
|
30068
|
+
const targetBitrate = isSVCCodec(track.codec) ? (_d = (_c = encodings[0]) === null || _c === void 0 ? void 0 : _c.maxBitrate) !== null && _d !== void 0 ? _d : 0 : encodings.reduce((sum, enc) => {
|
|
30069
|
+
var _a;
|
|
30070
|
+
return sum + ((_a = enc.maxBitrate) !== null && _a !== void 0 ? _a : 0);
|
|
30071
|
+
}, 0);
|
|
30072
|
+
if (targetBitrate > 0) {
|
|
30073
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
|
30074
|
+
cid: req.cid,
|
|
30075
|
+
codec: track.codec,
|
|
30076
|
+
maxbr: targetBitrate / 1000,
|
|
30077
|
+
isScreenShare: track.source === Track.Source.ScreenShare
|
|
30078
|
+
});
|
|
30079
|
+
}
|
|
28492
30080
|
}
|
|
28493
30081
|
}
|
|
28494
30082
|
yield this.engine.negotiate();
|
|
@@ -29019,6 +30607,18 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
29019
30607
|
return this.roomOutgoingDataStreamManager.sendFile(file, options);
|
|
29020
30608
|
});
|
|
29021
30609
|
}
|
|
30610
|
+
/**
|
|
30611
|
+
* Sends the given bytes to participants in the room via the data channel.
|
|
30612
|
+
* For files, consider using {@link sendFile}; for longer/incremental payloads, {@link streamBytes}.
|
|
30613
|
+
*
|
|
30614
|
+
* @param bytes The byte payload
|
|
30615
|
+
* @param options.topic Topic identifier used to route the stream to appropriate handlers.
|
|
30616
|
+
*/
|
|
30617
|
+
sendBytes(bytes, options) {
|
|
30618
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30619
|
+
return this.roomOutgoingDataStreamManager.sendBytes(bytes, options);
|
|
30620
|
+
});
|
|
30621
|
+
}
|
|
29022
30622
|
/**
|
|
29023
30623
|
* Stream bytes incrementally to participants in the room via the data channel.
|
|
29024
30624
|
* For sending files, consider using {@link sendFile} instead.
|
|
@@ -29548,7 +31148,7 @@ class DeferrableMap extends Map {
|
|
|
29548
31148
|
return new RemoteDataTrack(info, manager, {
|
|
29549
31149
|
publisherIdentity: pi.identity
|
|
29550
31150
|
});
|
|
29551
|
-
}), pi.clientProtocol);
|
|
31151
|
+
}), pi.clientProtocol, pi.capabilities);
|
|
29552
31152
|
}
|
|
29553
31153
|
get logContext() {
|
|
29554
31154
|
return Object.assign(Object.assign({}, super.logContext), {
|
|
@@ -29561,6 +31161,7 @@ class DeferrableMap extends Map {
|
|
|
29561
31161
|
let kind = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : ParticipantInfo_Kind.STANDARD;
|
|
29562
31162
|
let remoteDataTracks = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : [];
|
|
29563
31163
|
let clientProtocol = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : CLIENT_PROTOCOL_DEFAULT;
|
|
31164
|
+
let capabilities = arguments.length > 10 && arguments[10] !== undefined ? arguments[10] : [];
|
|
29564
31165
|
super(sid, identity || '', name, metadata, attributes, loggerOptions, kind);
|
|
29565
31166
|
this.signalClient = signalClient;
|
|
29566
31167
|
this.trackPublications = new Map();
|
|
@@ -29571,6 +31172,7 @@ class DeferrableMap extends Map {
|
|
|
29571
31172
|
}));
|
|
29572
31173
|
this.volumeMap = new Map();
|
|
29573
31174
|
this.clientProtocol = clientProtocol;
|
|
31175
|
+
this.capabilities = capabilities;
|
|
29574
31176
|
}
|
|
29575
31177
|
addTrackPublication(publication) {
|
|
29576
31178
|
super.addTrackPublication(publication);
|
|
@@ -29982,11 +31584,10 @@ class Room extends eventsExports.EventEmitter {
|
|
|
29982
31584
|
return this.connectFuture.promise;
|
|
29983
31585
|
});
|
|
29984
31586
|
this.connectSignal = (url, token, engine, connectOptions, roomOptions, abortController) => __awaiter(this, void 0, void 0, function* () {
|
|
29985
|
-
var _a;
|
|
29986
31587
|
const _yield$engine$join = yield engine.join(url, token, {
|
|
29987
31588
|
autoSubscribe: connectOptions.autoSubscribe,
|
|
29988
31589
|
adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
|
29989
|
-
clientInfoCapabilities:
|
|
31590
|
+
clientInfoCapabilities: this.getClientInfoCapabilities(roomOptions),
|
|
29990
31591
|
maxRetries: connectOptions.maxRetries,
|
|
29991
31592
|
e2eeEnabled: !!this.e2eeManager,
|
|
29992
31593
|
websocketTimeout: connectOptions.websocketTimeout
|
|
@@ -30206,7 +31807,13 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30206
31807
|
});
|
|
30207
31808
|
try {
|
|
30208
31809
|
yield Promise.all([this.acquireAudioContext(), ...elements.map(e => {
|
|
30209
|
-
|
|
31810
|
+
// when webAudioMix is enabled, attached elements are deliberately kept muted by
|
|
31811
|
+
// RemoteAudioTrack.attach() and audio is routed through the web audio graph instead.
|
|
31812
|
+
// unmuting them here would cause double playback on platforms where element.volume
|
|
31813
|
+
// has no effect (e.g. iOS Safari)
|
|
31814
|
+
if (!this.options.webAudioMix) {
|
|
31815
|
+
e.muted = false;
|
|
31816
|
+
}
|
|
30210
31817
|
return e.play();
|
|
30211
31818
|
})]);
|
|
30212
31819
|
this.handleAudioPlaybackStarted();
|
|
@@ -30540,6 +32147,13 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30540
32147
|
var _a, _b;
|
|
30541
32148
|
return (_b = (_a = this.remoteParticipants.get(identity)) === null || _a === void 0 ? void 0 : _a.clientProtocol) !== null && _b !== void 0 ? _b : CLIENT_PROTOCOL_DEFAULT;
|
|
30542
32149
|
};
|
|
32150
|
+
this.getRemoteParticipantCapabilities = identity => {
|
|
32151
|
+
var _a, _b;
|
|
32152
|
+
return (_b = (_a = this.remoteParticipants.get(identity)) === null || _a === void 0 ? void 0 : _a.capabilities) !== null && _b !== void 0 ? _b : [];
|
|
32153
|
+
};
|
|
32154
|
+
this.getAllRemoteParticipantIdentities = () => {
|
|
32155
|
+
return Array.from(this.remoteParticipants.keys());
|
|
32156
|
+
};
|
|
30543
32157
|
this.onLocalParticipantMetadataChanged = metadata => {
|
|
30544
32158
|
this.emit(RoomEvent.ParticipantMetadataChanged, metadata, this.localParticipant);
|
|
30545
32159
|
};
|
|
@@ -30616,7 +32230,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30616
32230
|
this.options.publishDefaults = Object.assign(Object.assign({}, publishDefaults), options === null || options === void 0 ? void 0 : options.publishDefaults);
|
|
30617
32231
|
this.maybeCreateEngine();
|
|
30618
32232
|
this.incomingDataStreamManager = new IncomingDataStreamManager();
|
|
30619
|
-
this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log);
|
|
32233
|
+
this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log, this.getRemoteParticipantClientProtocol, this.getRemoteParticipantCapabilities, this.getAllRemoteParticipantIdentities);
|
|
30620
32234
|
this.incomingDataTrackManager = new IncomingDataTrackManager({
|
|
30621
32235
|
e2eeManager: this.e2eeManager
|
|
30622
32236
|
});
|
|
@@ -30653,7 +32267,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30653
32267
|
}).on('packetAvailable', _ref => {
|
|
30654
32268
|
let handle = _ref.handle,
|
|
30655
32269
|
bytes = _ref.bytes;
|
|
30656
|
-
this.engine.
|
|
32270
|
+
this.engine.sendDataTrackFrame(bytes).finally(() => this.outgoingDataTrackManager.handlePacketSendComplete(handle));
|
|
30657
32271
|
});
|
|
30658
32272
|
this.registerRpcDataStreamHandler();
|
|
30659
32273
|
this.rpcClientManager = new RpcClientManager(this.log, this.outgoingDataStreamManager, this.getRemoteParticipantClientProtocol, () => {
|
|
@@ -30902,10 +32516,10 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30902
32516
|
this.isResuming = false;
|
|
30903
32517
|
this.log.debug('Resumed signal connection');
|
|
30904
32518
|
this.updateSubscriptions();
|
|
30905
|
-
this.emitBufferedEvents();
|
|
30906
32519
|
if (this.setAndEmitConnectionState(ConnectionState.Connected)) {
|
|
30907
32520
|
this.emit(RoomEvent.Reconnected);
|
|
30908
32521
|
}
|
|
32522
|
+
this.emitBufferedEvents();
|
|
30909
32523
|
}).on(EngineEvent.SignalResumed, () => {
|
|
30910
32524
|
this.bufferedEvents = [];
|
|
30911
32525
|
if (this.state === ConnectionState.Reconnecting || this.isResuming) {
|
|
@@ -31736,6 +33350,20 @@ class Room extends eventsExports.EventEmitter {
|
|
|
31736
33350
|
return this.remoteParticipants.get(identity);
|
|
31737
33351
|
}
|
|
31738
33352
|
}
|
|
33353
|
+
/** The client capabilities this SDK advertises to other participants in its `ClientInfo`. */
|
|
33354
|
+
getClientInfoCapabilities(roomOptions) {
|
|
33355
|
+
var _a;
|
|
33356
|
+
const capabilities = [];
|
|
33357
|
+
if (isFrameMetadataSupported((_a = roomOptions.frameMetadata) !== null && _a !== void 0 ? _a : roomOptions.packetTrailer) || !!this.e2eeManager) {
|
|
33358
|
+
capabilities.push(ClientInfo_Capability.CAP_PACKET_TRAILER);
|
|
33359
|
+
}
|
|
33360
|
+
// Advertise deflate-raw decompression support so peers know they can send us compressed data
|
|
33361
|
+
// streams (gated separately from clientProtocol — see the data streams v2 spec).
|
|
33362
|
+
if (isCompressionStreamSupported()) {
|
|
33363
|
+
capabilities.push(ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW);
|
|
33364
|
+
}
|
|
33365
|
+
return capabilities;
|
|
33366
|
+
}
|
|
31739
33367
|
registerRpcDataStreamHandler() {
|
|
31740
33368
|
this.incomingDataStreamManager.registerTextStreamHandler(RPC_REQUEST_DATA_STREAM_TOPIC, (reader_1, _a) => __awaiter(this, [reader_1, _a], void 0, function (reader, _ref7) {
|
|
31741
33369
|
var _this4 = this;
|
|
@@ -33355,4 +34983,4 @@ const serializers = {
|
|
|
33355
34983
|
json,
|
|
33356
34984
|
raw,
|
|
33357
34985
|
custom
|
|
33358
|
-
};export{AudioPresets,BackupCodecPolicy,BaseKeyProvider,CLIENT_PROTOCOL_DATA_STREAM_RPC,CLIENT_PROTOCOL_DEFAULT,CheckStatus,Checker,ConnectionCheck,ConnectionError,ConnectionErrorReason,ConnectionQuality,ConnectionState,CriticalTimers,CryptorError,CryptorErrorReason,CryptorEvent,DataPacket_Kind,DataStreamError,DataStreamErrorReason,DataTrackPacket,DefaultReconnectPolicy,DeviceUnsupportedError,DisconnectReason,EncryptionEvent,Encryption_Type,EngineEvent,ExternalE2EEKeyProvider,FrameMetadataManager,KeyHandlerEvent,KeyProviderEvent,LivekitError,LivekitReasonedError,LocalAudioTrack,LocalDataTrack,LocalParticipant,LocalTrack,LocalTrackPublication,LocalTrackRecorder,LocalVideoTrack,LogLevel,LoggerNames,MediaDeviceFailure,_ as Mutex,NegotiationError,PacketTrailerManager,Participant,ParticipantEvent,ParticipantInfo_Kind as ParticipantKind,PublishDataError,PublishTrackError,RemoteAudioTrack,RemoteDataTrack,RemoteParticipant,RemoteTrack,RemoteTrackPublication,RemoteVideoTrack,Room,RoomEvent,RpcError,ScreenSharePresets,SignalReconnectError,SignalRequestError,SimulatedError,SubscriptionError,TokenSource,TokenSourceConfigurable,TokenSourceFixed,Track,TrackEvent,TrackInvalidError,TrackPublication,TrackType,UnexpectedConnectionState,UnsupportedServer,VideoPreset,VideoPresets,VideoPresets43,VideoQuality,areTokenSourceFetchOptionsEqual,asEncryptablePacket,attachToElement,attributeTypings as attributes,audioCodecs,clientProtocol,compareVersions,createAudioAnalyser,createE2EEKey,createKeyMaterialFromBuffer,createKeyMaterialFromString,createLocalAudioTrack,createLocalScreenTracks,createLocalTracks,createLocalVideoTrack,decodeTokenPayload,deriveKeys,detachTrack,facingModeFromDeviceLabel,facingModeFromLocalTrack,getBrowser,getEmptyAudioStreamTrack,getEmptyVideoStreamTrack,getLogger,importKey,isAudioCodec,isAudioTrack,isBackupCodec,isBackupVideoCodec,isBrowserSupported,isE2EESupported,isInsertableStreamSupported,isLocalParticipant,isLocalTrack,isRemoteParticipant,isRemoteTrack,isScriptTransformSupported,isSerializer,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,serializers,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map
|
|
34986
|
+
};export{AudioPresets,BackupCodecPolicy,BaseKeyProvider,CLIENT_PROTOCOL_DATA_STREAM_RPC,CLIENT_PROTOCOL_DATA_STREAM_V2,CLIENT_PROTOCOL_DEFAULT,CheckStatus,Checker,ConnectionCheck,ConnectionError,ConnectionErrorReason,ConnectionQuality,ConnectionState,CriticalTimers,CryptorError,CryptorErrorReason,CryptorEvent,DataPacket_Kind,DataStreamError,DataStreamErrorReason,DataTrackPacket,DefaultReconnectPolicy,DeviceUnsupportedError,DisconnectReason,EncryptionEvent,Encryption_Type,EngineEvent,ExternalE2EEKeyProvider,FrameMetadataManager,KeyHandlerEvent,KeyProviderEvent,LivekitError,LivekitReasonedError,LocalAudioTrack,LocalDataTrack,LocalParticipant,LocalTrack,LocalTrackPublication,LocalTrackRecorder,LocalVideoTrack,LogLevel,LoggerNames,MediaDeviceFailure,_ as Mutex,NegotiationError,PacketTrailerManager,Participant,ParticipantEvent,ParticipantInfo_Kind as ParticipantKind,PublishDataError,PublishTrackError,RemoteAudioTrack,RemoteDataTrack,RemoteParticipant,RemoteTrack,RemoteTrackPublication,RemoteVideoTrack,Room,RoomEvent,RpcError,ScreenSharePresets,SignalReconnectError,SignalRequestError,SimulatedError,SubscriptionError,TokenSource,TokenSourceConfigurable,TokenSourceFixed,Track,TrackEvent,TrackInvalidError,TrackPublication,TrackType,UnexpectedConnectionState,UnsupportedServer,VideoPreset,VideoPresets,VideoPresets43,VideoQuality,areTokenSourceFetchOptionsEqual,asEncryptablePacket,attachToElement,attributeTypings as attributes,audioCodecs,clientProtocol,compareVersions,createAudioAnalyser,createE2EEKey,createKeyMaterialFromBuffer,createKeyMaterialFromString,createLocalAudioTrack,createLocalScreenTracks,createLocalTracks,createLocalVideoTrack,decodeTokenPayload,deriveKeys,detachTrack,facingModeFromDeviceLabel,facingModeFromLocalTrack,getBrowser,getEmptyAudioStreamTrack,getEmptyVideoStreamTrack,getLogger,importKey,isAudioCodec,isAudioTrack,isBackupCodec,isBackupVideoCodec,isBrowserSupported,isE2EESupported,isInsertableStreamSupported,isLocalParticipant,isLocalTrack,isRemoteParticipant,isRemoteTrack,isScriptTransformSupported,isSerializer,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,serializers,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map
|