livekit-client 2.22.3 → 2.23.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/README.md +35 -0
- 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 +71 -8
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1746 -547
- 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 +2 -1
- 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 +4 -2
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/api/WebSocketStream.d.ts +1 -0
- package/dist/src/api/WebSocketStream.d.ts.map +1 -1
- package/dist/src/api/utils.d.ts +1 -1
- package/dist/src/api/utils.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +12 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/options.d.ts +14 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/room/DefaultReconnectPolicy.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +100 -7
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +16 -0
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +54 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +9 -8
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -1
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +25 -12
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/defaults.d.ts.map +1 -1
- package/dist/src/room/errors.d.ts +18 -0
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/events.d.ts +3 -2
- package/dist/src/room/events.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +4 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -1
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- 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/utils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +24 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +18 -0
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +9 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts +6 -0
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +11 -0
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/src/room/track/utils.d.ts +13 -0
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +60 -1
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/compressionStreamStubs.d.ts +12 -0
- package/dist/src/test/compressionStreamStubs.d.ts.map +1 -0
- package/dist/ts4.2/api/SignalClient.d.ts +187 -185
- package/dist/ts4.2/api/SignalClientStateMachine.d.ts +85 -85
- package/dist/ts4.2/api/WebSocketStream.d.ts +34 -33
- package/dist/ts4.2/api/utils.d.ts +9 -9
- package/dist/ts4.2/connectionHelper/ConnectionCheck.d.ts +30 -30
- package/dist/ts4.2/connectionHelper/checks/Checker.d.ts +62 -62
- package/dist/ts4.2/connectionHelper/checks/cloudRegion.d.ts +18 -18
- package/dist/ts4.2/connectionHelper/checks/connectionProtocol.d.ts +20 -20
- package/dist/ts4.2/connectionHelper/checks/publishAudio.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/publishVideo.d.ts +7 -7
- package/dist/ts4.2/connectionHelper/checks/reconnect.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/turn.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/webrtc.d.ts +6 -6
- package/dist/ts4.2/connectionHelper/checks/websocket.d.ts +6 -6
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +111 -111
- package/dist/ts4.2/e2ee/KeyProvider.d.ts +54 -54
- package/dist/ts4.2/e2ee/constants.d.ts +21 -21
- package/dist/ts4.2/e2ee/errors.d.ts +12 -12
- package/dist/ts4.2/e2ee/events.d.ts +39 -39
- package/dist/ts4.2/e2ee/index.d.ts +6 -6
- package/dist/ts4.2/e2ee/types.d.ts +231 -231
- package/dist/ts4.2/e2ee/utils.d.ts +32 -32
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +16 -16
- package/dist/ts4.2/e2ee/worker/ErrorRateLimiter.d.ts +21 -21
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +209 -209
- package/dist/ts4.2/e2ee/worker/ParticipantKeyHandler.d.ts +81 -84
- package/dist/ts4.2/e2ee/worker/e2ee.worker.d.ts +2 -2
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +39 -28
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +23 -23
- package/dist/ts4.2/frameMetadata/FrameMetadataManager.d.ts +53 -53
- package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +33 -33
- package/dist/ts4.2/frameMetadata/types.d.ts +62 -62
- package/dist/ts4.2/frameMetadata/utils.d.ts +9 -9
- package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -2
- package/dist/ts4.2/index.d.ts +76 -76
- package/dist/ts4.2/logger.d.ts +66 -66
- package/dist/ts4.2/options.d.ts +163 -149
- package/dist/ts4.2/room/BackOffStrategy.d.ts +27 -27
- package/dist/ts4.2/room/DefaultReconnectPolicy.d.ts +8 -8
- package/dist/ts4.2/room/DeviceManager.d.ts +12 -12
- package/dist/ts4.2/room/PCTransport.d.ts +268 -175
- package/dist/ts4.2/room/PCTransportManager.d.ts +77 -77
- package/dist/ts4.2/room/RTCEngine.d.ts +277 -262
- package/dist/ts4.2/room/ReconnectPolicy.d.ts +24 -24
- package/dist/ts4.2/room/RegionUrlProvider.d.ts +36 -36
- package/dist/ts4.2/room/Room.d.ts +407 -365
- package/dist/ts4.2/room/attribute-typings.d.ts +35 -35
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +63 -62
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +107 -95
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -41
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -57
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -12
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -49
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -18
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +29 -29
- package/dist/ts4.2/room/data-stream/incoming/StreamReader.d.ts +80 -80
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +73 -73
- package/dist/ts4.2/room/data-stream/outgoing/StreamWriter.d.ts +16 -16
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -18
- package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +79 -80
- package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +50 -50
- package/dist/ts4.2/room/data-track/depacketizer.d.ts +59 -59
- package/dist/ts4.2/room/data-track/frame.d.ts +22 -22
- package/dist/ts4.2/room/data-track/handle.d.ts +27 -27
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +124 -124
- package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -24
- package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +41 -41
- package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -20
- package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +126 -126
- package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +74 -74
- package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +23 -23
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +48 -48
- package/dist/ts4.2/room/data-track/packet/constants.d.ts +20 -20
- package/dist/ts4.2/room/data-track/packet/errors.d.ts +40 -40
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +69 -69
- package/dist/ts4.2/room/data-track/packet/index.d.ts +98 -98
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +13 -13
- package/dist/ts4.2/room/data-track/packetizer.d.ts +43 -43
- package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -23
- package/dist/ts4.2/room/data-track/types.d.ts +22 -22
- package/dist/ts4.2/room/data-track/utils.d.ts +60 -60
- package/dist/ts4.2/room/debounce.d.ts +11 -11
- package/dist/ts4.2/room/defaults.d.ts +9 -9
- package/dist/ts4.2/room/errors.d.ts +180 -162
- package/dist/ts4.2/room/events.d.ts +638 -637
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +291 -287
- package/dist/ts4.2/room/participant/Participant.d.ts +142 -144
- package/dist/ts4.2/room/participant/ParticipantTrackPermission.d.ts +26 -26
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +85 -86
- package/dist/ts4.2/room/participant/publishUtils.d.ts +53 -53
- package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -43
- package/dist/ts4.2/room/rpc/client/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/index.d.ts +7 -7
- package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -44
- package/dist/ts4.2/room/rpc/server/events.d.ts +8 -8
- package/dist/ts4.2/room/rpc/utils.d.ts +131 -131
- package/dist/ts4.2/room/stats.d.ts +71 -71
- package/dist/ts4.2/room/statsSummary.d.ts +13 -13
- package/dist/ts4.2/room/timers.d.ts +12 -12
- package/dist/ts4.2/room/token-source/TokenSource.d.ts +82 -82
- package/dist/ts4.2/room/token-source/types.d.ts +70 -70
- package/dist/ts4.2/room/token-source/utils.d.ts +8 -8
- package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -19
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +62 -42
- package/dist/ts4.2/room/track/LocalTrack.d.ts +143 -125
- package/dist/ts4.2/room/track/LocalTrackPublication.d.ts +42 -42
- package/dist/ts4.2/room/track/LocalVideoTrack.d.ts +109 -100
- package/dist/ts4.2/room/track/RemoteAudioTrack.d.ts +61 -55
- package/dist/ts4.2/room/track/RemoteTrack.d.ts +38 -38
- package/dist/ts4.2/room/track/RemoteTrackPublication.d.ts +72 -75
- package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +73 -76
- package/dist/ts4.2/room/track/Track.d.ts +156 -166
- package/dist/ts4.2/room/track/TrackPublication.d.ts +75 -81
- package/dist/ts4.2/room/track/create.d.ts +25 -25
- package/dist/ts4.2/room/track/facingMode.d.ts +41 -41
- package/dist/ts4.2/room/track/options.d.ts +403 -403
- package/dist/ts4.2/room/track/processor/types.d.ts +42 -31
- package/dist/ts4.2/room/track/record.d.ts +13 -13
- package/dist/ts4.2/room/track/types.d.ts +28 -28
- package/dist/ts4.2/room/track/utils.d.ts +60 -47
- package/dist/ts4.2/room/types.d.ts +127 -127
- package/dist/ts4.2/room/utils.d.ts +273 -214
- package/dist/ts4.2/test/MockMediaStreamTrack.d.ts +26 -26
- package/dist/ts4.2/test/compressionStreamStubs.d.ts +12 -0
- package/dist/ts4.2/test/mocks.d.ts +12 -12
- package/dist/ts4.2/test/promiseState.d.ts +12 -12
- package/dist/ts4.2/test/signalToken.d.ts +26 -26
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -2
- package/dist/ts4.2/utils/AsyncQueue.d.ts +23 -23
- package/dist/ts4.2/utils/TypedPromise.d.ts +17 -17
- package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -13
- package/dist/ts4.2/utils/browserParser.d.ts +13 -13
- package/dist/ts4.2/utils/camelToSnakeCase.d.ts +8 -8
- package/dist/ts4.2/utils/cloneDeep.d.ts +2 -2
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +34 -34
- package/dist/ts4.2/utils/deferrable-map.d.ts +32 -32
- package/dist/ts4.2/utils/machineInspector.d.ts +54 -54
- package/dist/ts4.2/utils/serializer.d.ts +48 -48
- package/dist/ts4.2/utils/subscribeToEvents.d.ts +15 -15
- package/dist/ts4.2/utils/ttlmap.d.ts +20 -20
- package/dist/ts4.2/version.d.ts +15 -15
- package/package.json +5 -4
- package/src/api/SignalClient.e2e.test.ts +1 -3
- package/src/api/SignalClient.test.ts +394 -10
- package/src/api/SignalClient.ts +132 -76
- package/src/api/WebSocketStream.test.ts +11 -0
- package/src/api/WebSocketStream.ts +22 -1
- package/src/api/utils.ts +1 -1
- package/src/e2ee/E2eeManager.test.ts +43 -0
- package/src/e2ee/E2eeManager.ts +6 -2
- package/src/e2ee/worker/FrameCryptor.test.ts +212 -90
- package/src/e2ee/worker/FrameCryptor.ts +21 -4
- package/src/e2ee/worker/naluUtils.test.ts +123 -0
- package/src/e2ee/worker/naluUtils.ts +57 -2
- package/src/index.ts +2 -0
- package/src/options.ts +16 -0
- package/src/room/DefaultReconnectPolicy.test.ts +58 -0
- package/src/room/DefaultReconnectPolicy.ts +14 -2
- package/src/room/PCTransport.test.ts +327 -9
- package/src/room/PCTransport.ts +339 -36
- package/src/room/PCTransportManager.ts +3 -1
- package/src/room/RTCEngine.test.ts +318 -2
- package/src/room/RTCEngine.ts +126 -12
- package/src/room/Room.test.ts +148 -6
- package/src/room/Room.ts +312 -80
- package/src/room/data-channel/DataChannelManager.test.ts +24 -7
- package/src/room/data-channel/DataChannelManager.ts +43 -38
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +60 -18
- package/src/room/data-channel/FlowControlledDataChannel.ts +40 -22
- package/src/room/data-channel/LossyDataChannel.test.ts +38 -0
- package/src/room/data-channel/LossyDataChannel.ts +10 -1
- package/src/room/data-channel/ReliableDataChannel.test.ts +72 -0
- package/src/room/data-channel/ReliableDataChannel.ts +16 -5
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +47 -20
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +16 -5
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +60 -39
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +4 -4
- package/src/room/defaults.ts +1 -0
- package/src/room/errors.test.ts +36 -0
- package/src/room/errors.ts +27 -0
- package/src/room/events.ts +2 -1
- package/src/room/participant/LocalParticipant.test.ts +136 -1
- package/src/room/participant/LocalParticipant.ts +66 -17
- package/src/room/participant/Participant.ts +6 -3
- package/src/room/participant/publishUtils.ts +0 -19
- package/src/room/participant/restartOnDefaultAudioDevice.test.ts +177 -0
- package/src/room/rpc/client/RpcClientManager.test.ts +51 -0
- package/src/room/rpc/client/RpcClientManager.ts +32 -15
- package/src/room/rpc/utils.test.ts +32 -0
- package/src/room/rpc/utils.ts +7 -0
- package/src/room/track/LocalAudioTrack.ts +101 -10
- package/src/room/track/LocalTrack.ts +87 -19
- package/src/room/track/LocalVideoTrack.ts +17 -3
- package/src/room/track/RemoteAudioTrack.ts +59 -14
- package/src/room/track/RemoteVideoTrack.test.ts +210 -0
- package/src/room/track/RemoteVideoTrack.ts +29 -3
- package/src/room/track/processor/types.ts +11 -0
- package/src/room/track/utils.test.ts +206 -2
- package/src/room/track/utils.ts +117 -5
- package/src/room/utils.test.ts +250 -0
- package/src/room/utils.ts +152 -19
- package/src/test/compressionStreamStubs.ts +53 -0
|
@@ -5473,6 +5473,9 @@ const ClientInfo_SDK = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo.SDK",
|
|
|
5473
5473
|
}, {
|
|
5474
5474
|
no: 14,
|
|
5475
5475
|
name: "ESP32"
|
|
5476
|
+
}, {
|
|
5477
|
+
no: 15,
|
|
5478
|
+
name: "DOTNET"
|
|
5476
5479
|
}]);
|
|
5477
5480
|
const ClientInfo_Capability = /* @__PURE__ */proto3.makeEnum("livekit.ClientInfo.Capability", [{
|
|
5478
5481
|
no: 0,
|
|
@@ -5841,6 +5844,9 @@ const EncodingOptionsPreset = /* @__PURE__ */proto3.makeEnum("livekit.EncodingOp
|
|
|
5841
5844
|
}, {
|
|
5842
5845
|
no: 7,
|
|
5843
5846
|
name: "PORTRAIT_H264_1080P_60"
|
|
5847
|
+
}, {
|
|
5848
|
+
no: 8,
|
|
5849
|
+
name: "PASSTHROUGH"
|
|
5844
5850
|
}]);
|
|
5845
5851
|
const EncodedFileType = /* @__PURE__ */proto3.makeEnum("livekit.EncodedFileType", [{
|
|
5846
5852
|
no: 0,
|
|
@@ -7825,6 +7831,11 @@ const SyncState = /* @__PURE__ */proto3.makeMessageType("livekit.SyncState", ()
|
|
|
7825
7831
|
kind: "message",
|
|
7826
7832
|
T: PublishDataTrackResponse,
|
|
7827
7833
|
repeated: true
|
|
7834
|
+
}, {
|
|
7835
|
+
no: 9,
|
|
7836
|
+
name: "data_subscription",
|
|
7837
|
+
kind: "message",
|
|
7838
|
+
T: UpdateDataSubscription
|
|
7828
7839
|
}]);
|
|
7829
7840
|
const DataChannelReceiveState = /* @__PURE__ */proto3.makeMessageType("livekit.DataChannelReceiveState", () => [{
|
|
7830
7841
|
no: 1,
|
|
@@ -8697,6 +8708,11 @@ function onWorkerLogLevelChanged(cb) {
|
|
|
8697
8708
|
workerLogLevelListeners.delete(cb);
|
|
8698
8709
|
};
|
|
8699
8710
|
}const maxRetryDelay = 7000;
|
|
8711
|
+
/**
|
|
8712
|
+
* Upper bound of the random delay added to the first retry, whose base delay is 0. It keeps a
|
|
8713
|
+
* brief network blip recovering quickly while spreading out clients that disconnect together.
|
|
8714
|
+
*/
|
|
8715
|
+
const maxFirstRetryJitterInMs = 500;
|
|
8700
8716
|
const DEFAULT_RETRY_DELAYS_IN_MS = [0, 300, 2 * 2 * 300, 3 * 3 * 300, 4 * 4 * 300, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay, maxRetryDelay];
|
|
8701
8717
|
class DefaultReconnectPolicy {
|
|
8702
8718
|
constructor(retryDelays) {
|
|
@@ -8705,8 +8721,13 @@ class DefaultReconnectPolicy {
|
|
|
8705
8721
|
nextRetryDelayInMs(context) {
|
|
8706
8722
|
if (context.retryCount >= this._retryDelays.length) return null;
|
|
8707
8723
|
const retryDelay = this._retryDelays[context.retryCount];
|
|
8708
|
-
|
|
8709
|
-
|
|
8724
|
+
// The first retry has no base delay to scale, so it gets a fixed window instead. A zero at a
|
|
8725
|
+
// later index is an explicit request for an immediate retry and passes through the formula as zero.
|
|
8726
|
+
if (context.retryCount === 0 && retryDelay === 0) {
|
|
8727
|
+
return Math.random() * maxFirstRetryJitterInMs;
|
|
8728
|
+
}
|
|
8729
|
+
// Jitter scales with the delay (+/-50%) so clients spread out further on later retries.
|
|
8730
|
+
return Math.round(retryDelay * (0.5 + Math.random()));
|
|
8710
8731
|
}
|
|
8711
8732
|
}/******************************************************************************
|
|
8712
8733
|
Copyright (c) Microsoft Corporation.
|
|
@@ -12279,7 +12300,7 @@ function getMatch(exp, ua) {
|
|
|
12279
12300
|
}
|
|
12280
12301
|
function getOSVersion(ua) {
|
|
12281
12302
|
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
|
12282
|
-
}var version$1 = "2.
|
|
12303
|
+
}var version$1 = "2.23.0";const version = version$1;
|
|
12283
12304
|
const protocolVersion = 17;
|
|
12284
12305
|
/** Initial client protocol. */
|
|
12285
12306
|
const CLIENT_PROTOCOL_DEFAULT = 0;
|
|
@@ -12360,6 +12381,32 @@ class ConnectionError extends LivekitReasonedError {
|
|
|
12360
12381
|
return new ConnectionError(message, ConnectionErrorReason.ServiceNotFound, undefined, serviceName);
|
|
12361
12382
|
}
|
|
12362
12383
|
}
|
|
12384
|
+
/**
|
|
12385
|
+
* Whether a failed connection attempt may be retried against a different LiveKit Cloud region.
|
|
12386
|
+
*
|
|
12387
|
+
* LiveKit Cloud signals project-level region pinning by returning 403 on the RTC paths when the
|
|
12388
|
+
* project is not allowed in the region the client geo-routed to. `/settings/regions` is
|
|
12389
|
+
* deliberately left reachable so the client can discover its allowed regions and connect there, so
|
|
12390
|
+
* a 403 must not be treated as terminal.
|
|
12391
|
+
*
|
|
12392
|
+
* A 401 stays terminal: no other region will accept a token this one rejected. So does the 404
|
|
12393
|
+
* "requested room does not exist" case, which is also reported as `NotAllowed`.
|
|
12394
|
+
*
|
|
12395
|
+
* We key on the status rather than the server's error message because that message is an
|
|
12396
|
+
* unversioned human-readable string; matching it would let a copy edit break already-shipped
|
|
12397
|
+
* clients. If a 403 really was a permissions failure rather than region pinning, every region
|
|
12398
|
+
* attempt fails the same way and the original error still surfaces — at the cost of one extra
|
|
12399
|
+
* region lookup.
|
|
12400
|
+
*/
|
|
12401
|
+
function canFailOverToAnotherRegion(error) {
|
|
12402
|
+
if (error.reason === ConnectionErrorReason.Cancelled) {
|
|
12403
|
+
return false;
|
|
12404
|
+
}
|
|
12405
|
+
if (error.reason === ConnectionErrorReason.NotAllowed) {
|
|
12406
|
+
return error.status === 403;
|
|
12407
|
+
}
|
|
12408
|
+
return true;
|
|
12409
|
+
}
|
|
12363
12410
|
class DeviceUnsupportedError extends LivekitError {
|
|
12364
12411
|
constructor(message) {
|
|
12365
12412
|
super(21, message !== null && message !== void 0 ? message : 'device is unsupported');
|
|
@@ -12693,7 +12740,7 @@ var RoomEvent;
|
|
|
12693
12740
|
* Data packets provides the ability to use LiveKit to send/receive arbitrary payloads.
|
|
12694
12741
|
* All participants in the room will receive the messages sent to the room.
|
|
12695
12742
|
*
|
|
12696
|
-
* args: (payload: NonSharedUint8Array, participant: [[Participant]], kind: [[DataPacket_Kind]], topic?: string)
|
|
12743
|
+
* args: (payload: NonSharedUint8Array, participant: [[Participant]] | undefined, kind: [[DataPacket_Kind]], topic?: string, encryptionType?: Encryption_Type, participantIdentity?: string)
|
|
12697
12744
|
*/
|
|
12698
12745
|
RoomEvent["DataReceived"] = "dataReceived";
|
|
12699
12746
|
/**
|
|
@@ -13051,6 +13098,7 @@ var EngineEvent;
|
|
|
13051
13098
|
EngineEvent["Joined"] = "joined";
|
|
13052
13099
|
EngineEvent["TokenRefreshed"] = "tokenRefreshed";
|
|
13053
13100
|
EngineEvent["ServerRegionsReported"] = "serverRegionsReported";
|
|
13101
|
+
EngineEvent["RequestSubscribedCodecRefresh"] = "requestSubscribedCodecRefresh";
|
|
13054
13102
|
})(EngineEvent || (EngineEvent = {}));
|
|
13055
13103
|
var TrackEvent;
|
|
13056
13104
|
(function (TrackEvent) {
|
|
@@ -13520,20 +13568,44 @@ function detectSilence(track_1) {
|
|
|
13520
13568
|
let timeOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200;
|
|
13521
13569
|
return function* () {
|
|
13522
13570
|
const ctx = getNewAudioContext();
|
|
13523
|
-
if (ctx) {
|
|
13524
|
-
|
|
13571
|
+
if (!ctx) {
|
|
13572
|
+
return false;
|
|
13573
|
+
}
|
|
13574
|
+
let source;
|
|
13575
|
+
let analyser;
|
|
13576
|
+
try {
|
|
13577
|
+
analyser = ctx.createAnalyser();
|
|
13525
13578
|
analyser.fftSize = 2048;
|
|
13526
13579
|
const bufferLength = analyser.frequencyBinCount;
|
|
13527
13580
|
const dataArray = new Uint8Array(bufferLength);
|
|
13528
|
-
|
|
13581
|
+
source = ctx.createMediaStreamSource(new MediaStream([track.mediaStreamTrack]));
|
|
13529
13582
|
source.connect(analyser);
|
|
13583
|
+
// read through a function so that `state` isn't narrowed to its value from before `resume`
|
|
13584
|
+
const isRunning = () => ctx.state === 'running';
|
|
13585
|
+
if (!isRunning()) {
|
|
13586
|
+
try {
|
|
13587
|
+
// `resume` can stay pending indefinitely on a blocked page rather than rejecting, which
|
|
13588
|
+
// would keep the context and its nodes alive past the `finally` below
|
|
13589
|
+
yield Promise.race([ctx.resume(), sleep(200)]);
|
|
13590
|
+
} catch (_a) {
|
|
13591
|
+
// autoplay policies can keep us from resuming the context
|
|
13592
|
+
}
|
|
13593
|
+
if (!isRunning()) {
|
|
13594
|
+
// a context that isn't running doesn't process any audio, so the analyser would only ever
|
|
13595
|
+
// read silence. Report the track as not silent rather than raising a false positive.
|
|
13596
|
+
return false;
|
|
13597
|
+
}
|
|
13598
|
+
}
|
|
13530
13599
|
yield sleep(timeOffset);
|
|
13531
13600
|
analyser.getByteTimeDomainData(dataArray);
|
|
13532
13601
|
const someNoise = dataArray.some(sample => sample !== 128 && sample !== 0);
|
|
13533
|
-
ctx.close();
|
|
13534
13602
|
return !someNoise;
|
|
13603
|
+
} finally {
|
|
13604
|
+
source === null || source === void 0 ? void 0 : source.disconnect();
|
|
13605
|
+
analyser === null || analyser === void 0 ? void 0 : analyser.disconnect();
|
|
13606
|
+
// don't let a failing teardown mask the detection result
|
|
13607
|
+
yield ctx.close().catch(() => {});
|
|
13535
13608
|
}
|
|
13536
|
-
return false;
|
|
13537
13609
|
}();
|
|
13538
13610
|
});
|
|
13539
13611
|
}
|
|
@@ -13745,6 +13817,85 @@ function areDimensionsSmaller(a, b) {
|
|
|
13745
13817
|
function layerDimensionsFor(trackInfo, quality) {
|
|
13746
13818
|
var _a;
|
|
13747
13819
|
return (_a = trackInfo.layers) === null || _a === void 0 ? void 0 : _a.find(l => l.quality === quality);
|
|
13820
|
+
}
|
|
13821
|
+
/**
|
|
13822
|
+
* Waits until `track` has actually produced a video frame.
|
|
13823
|
+
*
|
|
13824
|
+
* iOS keeps reporting the camera's sensor frame from `getSettings()` until the camera has
|
|
13825
|
+
* delivered its first picture, so a portrait capture initially reads as landscape. The arrival
|
|
13826
|
+
* of that first frame is the only reliable signal that the reported settings describe reality.
|
|
13827
|
+
*
|
|
13828
|
+
* Best effort, never throws. Resolves `true` when a frame was observed, `false` when no frame
|
|
13829
|
+
* can be observed (the track is already ended, muted or disabled) or `timeoutMs` elapsed first.
|
|
13830
|
+
*
|
|
13831
|
+
* @internal
|
|
13832
|
+
*/
|
|
13833
|
+
function waitForFirstVideoFrame(track_1, timeoutMs_1) {
|
|
13834
|
+
return __awaiter(this, arguments, void 0, function (track, timeoutMs) {
|
|
13835
|
+
let candidateElements = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
|
|
13836
|
+
return function* () {
|
|
13837
|
+
var _a;
|
|
13838
|
+
if (typeof document === 'undefined') {
|
|
13839
|
+
return false;
|
|
13840
|
+
}
|
|
13841
|
+
if (track.readyState === 'ended' || track.muted || !track.enabled) {
|
|
13842
|
+
// no frame is ever going to arrive, don't hold the caller up for the full timeout
|
|
13843
|
+
return false;
|
|
13844
|
+
}
|
|
13845
|
+
// requestVideoFrameCallback (Safari 15.4 and up) is a platform capability, so the answer is the
|
|
13846
|
+
// same for every element
|
|
13847
|
+
const supportsFrameCallback = typeof HTMLVideoElement.prototype.requestVideoFrameCallback === 'function';
|
|
13848
|
+
// prefer an element that is already rendering this track (a local preview, typically) over
|
|
13849
|
+
// spinning up a second decode just to observe a frame
|
|
13850
|
+
const existingElement = candidateElements.find(el => el instanceof HTMLVideoElement && el.srcObject instanceof MediaStream && el.srcObject.getVideoTracks().includes(track) && (
|
|
13851
|
+
// without requestVideoFrameCallback we depend on `loadeddata`, which is one-shot: an element
|
|
13852
|
+
// that already decoded a frame (readyState >= HAVE_CURRENT_DATA) never fires it again.
|
|
13853
|
+
// Leave those alone and set up our own element, which starts out empty.
|
|
13854
|
+
supportsFrameCallback || el.readyState < 2));
|
|
13855
|
+
const element = existingElement !== null && existingElement !== void 0 ? existingElement : document.createElement('video');
|
|
13856
|
+
if (!existingElement) {
|
|
13857
|
+
element.playsInline = true;
|
|
13858
|
+
element.muted = true;
|
|
13859
|
+
element.srcObject = new MediaStream([track]);
|
|
13860
|
+
// the element is deliberately never added to the DOM, it only exists to pull frames.
|
|
13861
|
+
// WebKit delivers frames to a paused element too, so a rejected play() (Safari's low power
|
|
13862
|
+
// mode, for instance) is not a reason to stop waiting
|
|
13863
|
+
element.play().catch(error => {
|
|
13864
|
+
livekitLogger.debug('could not play element while waiting for the first video frame', {
|
|
13865
|
+
error
|
|
13866
|
+
});
|
|
13867
|
+
});
|
|
13868
|
+
}
|
|
13869
|
+
let timer;
|
|
13870
|
+
let frameHandle;
|
|
13871
|
+
let onLoadedData;
|
|
13872
|
+
try {
|
|
13873
|
+
return yield new Promise(resolve => {
|
|
13874
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
13875
|
+
if (supportsFrameCallback) {
|
|
13876
|
+
frameHandle = element.requestVideoFrameCallback(() => resolve(true));
|
|
13877
|
+
} else {
|
|
13878
|
+
// Safari below 15.4 has no requestVideoFrameCallback. `loadeddata` fires once the first
|
|
13879
|
+
// frame has been decoded, which is the same signal a few milliseconds earlier.
|
|
13880
|
+
onLoadedData = () => resolve(true);
|
|
13881
|
+
element.addEventListener('loadeddata', onLoadedData);
|
|
13882
|
+
}
|
|
13883
|
+
});
|
|
13884
|
+
} finally {
|
|
13885
|
+
clearTimeout(timer);
|
|
13886
|
+
if (frameHandle !== undefined) {
|
|
13887
|
+
(_a = element.cancelVideoFrameCallback) === null || _a === void 0 ? void 0 : _a.call(element, frameHandle);
|
|
13888
|
+
}
|
|
13889
|
+
if (onLoadedData) {
|
|
13890
|
+
element.removeEventListener('loadeddata', onLoadedData);
|
|
13891
|
+
}
|
|
13892
|
+
if (!existingElement) {
|
|
13893
|
+
element.pause();
|
|
13894
|
+
element.srcObject = null;
|
|
13895
|
+
}
|
|
13896
|
+
}
|
|
13897
|
+
}();
|
|
13898
|
+
});
|
|
13748
13899
|
}const BACKGROUND_REACTION_DELAY = 5000;
|
|
13749
13900
|
// keep old audio elements when detached, we would re-use them since on iOS
|
|
13750
13901
|
// Safari tracks which audio elements have been "blessed" by the user.
|
|
@@ -14384,6 +14535,32 @@ function isSafariBased() {
|
|
|
14384
14535
|
const b = getBrowser();
|
|
14385
14536
|
return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' || (b === null || b === void 0 ? void 0 : b.os) === 'iOS';
|
|
14386
14537
|
}
|
|
14538
|
+
/**
|
|
14539
|
+
* iPadOS has sent the desktop Macintosh user agent by default since iPadOS 13, so `getBrowser()`
|
|
14540
|
+
* reports `os: 'macOS'` there and every `os === 'iOS'` check misses it. macOS Safari implements no
|
|
14541
|
+
* touch events at all (`TouchEvent` undefined, `maxTouchPoints` 0) regardless of any touchscreen
|
|
14542
|
+
* attached, so touch capability separates the two. Measured on an iPad 6 / iPadOS 17
|
|
14543
|
+
* (`maxTouchPoints` 5) against macOS Safari 26.4 (`maxTouchPoints` 0).
|
|
14544
|
+
*
|
|
14545
|
+
* Deliberately a separate helper rather than a fix in `browserParser`: iPadOS spoofs the Mac
|
|
14546
|
+
* version too, so reporting `os: 'iOS'` there would leave `osVersion` at `10.15.7` and silently
|
|
14547
|
+
* break {@link isSafari17Based} and {@link isSafariSvcApi}, which compare it.
|
|
14548
|
+
*/
|
|
14549
|
+
function isIPadOS() {
|
|
14550
|
+
if (!isWeb()) {
|
|
14551
|
+
return false;
|
|
14552
|
+
}
|
|
14553
|
+
const b = getBrowser();
|
|
14554
|
+
return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && (b === null || b === void 0 ? void 0 : b.os) === 'macOS' && navigator.maxTouchPoints > 1;
|
|
14555
|
+
}
|
|
14556
|
+
/**
|
|
14557
|
+
* iPhone, iPad and iOS-hosted browsers — everything running the Apple camera capture pipeline,
|
|
14558
|
+
* including iPads that present themselves as a Mac.
|
|
14559
|
+
*/
|
|
14560
|
+
function isAppleMobile() {
|
|
14561
|
+
var _a;
|
|
14562
|
+
return ((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) === 'iOS' || isIPadOS();
|
|
14563
|
+
}
|
|
14387
14564
|
function isSafari17Based() {
|
|
14388
14565
|
const b = getBrowser();
|
|
14389
14566
|
return (b === null || b === void 0 ? void 0 : b.name) === 'Safari' && b.version.startsWith('17.') || (b === null || b === void 0 ? void 0 : b.os) === 'iOS' && !!(b === null || b === void 0 ? void 0 : b.osVersion) && compareVersions(b.osVersion, '17') >= 0;
|
|
@@ -14567,6 +14744,15 @@ function createDummyVideoStreamTrack() {
|
|
|
14567
14744
|
return dummyTrack;
|
|
14568
14745
|
}
|
|
14569
14746
|
let emptyAudioStreamTrack;
|
|
14747
|
+
let emptyAudioStreamContext;
|
|
14748
|
+
/** tracks handed out by `getEmptyAudioStreamTrack` that haven't been released yet */
|
|
14749
|
+
const outstandingEmptyAudioStreamTracks = new Set();
|
|
14750
|
+
/**
|
|
14751
|
+
* Returns a silent audio track, cloned off a shared `AudioContext` created on first use. Hand the
|
|
14752
|
+
* clone back to `releaseEmptyAudioStreamTrack` rather than stopping it, so the context can close.
|
|
14753
|
+
* A clone that is only stopped stays counted as outstanding, which holds the shared context open
|
|
14754
|
+
* for the lifetime of the page.
|
|
14755
|
+
*/
|
|
14570
14756
|
function getEmptyAudioStreamTrack() {
|
|
14571
14757
|
if (!emptyAudioStreamTrack) {
|
|
14572
14758
|
// implementation adapted from https://blog.mozilla.org/webrtc/warm-up-with-replacetrack/
|
|
@@ -14582,11 +14768,38 @@ function getEmptyAudioStreamTrack() {
|
|
|
14582
14768
|
var _dst$stream$getAudioT2 = _slicedToArray(_dst$stream$getAudioT, 1);
|
|
14583
14769
|
emptyAudioStreamTrack = _dst$stream$getAudioT2[0];
|
|
14584
14770
|
if (!emptyAudioStreamTrack) {
|
|
14771
|
+
// don't hold on to a context we can't get a track out of
|
|
14772
|
+
ctx.close().catch(() => {});
|
|
14585
14773
|
throw Error('Could not get empty media stream audio track');
|
|
14586
14774
|
}
|
|
14587
14775
|
emptyAudioStreamTrack.enabled = false;
|
|
14776
|
+
emptyAudioStreamContext = ctx;
|
|
14588
14777
|
}
|
|
14589
|
-
|
|
14778
|
+
const track = emptyAudioStreamTrack.clone();
|
|
14779
|
+
outstandingEmptyAudioStreamTracks.add(track);
|
|
14780
|
+
return track;
|
|
14781
|
+
}
|
|
14782
|
+
/**
|
|
14783
|
+
* Stops a track obtained from `getEmptyAudioStreamTrack` and closes the shared `AudioContext`
|
|
14784
|
+
* backing it once every track it handed out has been released. Does nothing for a track this
|
|
14785
|
+
* module never handed out, or one that has been released already.
|
|
14786
|
+
*/
|
|
14787
|
+
function releaseEmptyAudioStreamTrack(track) {
|
|
14788
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
14789
|
+
if (!outstandingEmptyAudioStreamTracks.delete(track)) {
|
|
14790
|
+
// the track wasn't obtained from `getEmptyAudioStreamTrack`, or has been released before
|
|
14791
|
+
return;
|
|
14792
|
+
}
|
|
14793
|
+
track.stop();
|
|
14794
|
+
if (outstandingEmptyAudioStreamTracks.size > 0) {
|
|
14795
|
+
return;
|
|
14796
|
+
}
|
|
14797
|
+
const context = emptyAudioStreamContext;
|
|
14798
|
+
emptyAudioStreamTrack === null || emptyAudioStreamTrack === void 0 ? void 0 : emptyAudioStreamTrack.stop();
|
|
14799
|
+
emptyAudioStreamTrack = undefined;
|
|
14800
|
+
emptyAudioStreamContext = undefined;
|
|
14801
|
+
yield context === null || context === void 0 ? void 0 : context.close();
|
|
14802
|
+
});
|
|
14590
14803
|
}
|
|
14591
14804
|
/** An object that represents a serialized version of a `new Promise((resolve, reject) => {})`
|
|
14592
14805
|
* constructor. Wait for a promise resolution with `await future.promise` and explicitly resolve or
|
|
@@ -14650,11 +14863,19 @@ function createAudioAnalyser(track, options) {
|
|
|
14650
14863
|
const volume = Math.sqrt(sum / dataArray.length);
|
|
14651
14864
|
return volume;
|
|
14652
14865
|
};
|
|
14866
|
+
let isCleanedUp = false;
|
|
14653
14867
|
const cleanup = () => __awaiter(this, void 0, void 0, function* () {
|
|
14654
|
-
|
|
14868
|
+
if (isCleanedUp) {
|
|
14869
|
+
// closing an already closed context would reject, make repeated cleanups a no-op instead
|
|
14870
|
+
return;
|
|
14871
|
+
}
|
|
14872
|
+
isCleanedUp = true;
|
|
14873
|
+
mediaStreamSource.disconnect();
|
|
14874
|
+
analyser.disconnect();
|
|
14655
14875
|
if (opts.cloneTrack) {
|
|
14656
14876
|
streamTrack.stop();
|
|
14657
14877
|
}
|
|
14878
|
+
yield audioContext.close();
|
|
14658
14879
|
});
|
|
14659
14880
|
return {
|
|
14660
14881
|
calculateVolume,
|
|
@@ -14689,6 +14910,31 @@ function unwrapConstraint(constraint) {
|
|
|
14689
14910
|
}
|
|
14690
14911
|
throw Error('could not unwrap constraint');
|
|
14691
14912
|
}
|
|
14913
|
+
/**
|
|
14914
|
+
* The getUserMedia rejections that no softer constraint can recover from, because the user or the
|
|
14915
|
+
* document refused capture outright. Retrying one of these would put a second prompt in front of
|
|
14916
|
+
* someone who already said no. The last two names are legacy spellings still reported by older
|
|
14917
|
+
* engines. Matched by `name` rather than with `instanceof`, because these are not reliably distinct
|
|
14918
|
+
* constructors across environments. happy-dom, which the tests run under, aliases them to `Event`.
|
|
14919
|
+
*/
|
|
14920
|
+
function isPermissionDeniedError(e) {
|
|
14921
|
+
return typeof e === 'object' && e !== null && 'name' in e && (e.name === 'NotAllowedError' || e.name === 'SecurityError' || e.name === 'PermissionDeniedError' || e.name === 'PermissionDismissedError');
|
|
14922
|
+
}
|
|
14923
|
+
const deviceAcquisitionFailures = new WeakSet();
|
|
14924
|
+
/** Records that `e` is the rejection getUserMedia itself produced. */
|
|
14925
|
+
function markDeviceAcquisitionFailure(e) {
|
|
14926
|
+
if (typeof e === 'object' && e !== null) {
|
|
14927
|
+
deviceAcquisitionFailures.add(e);
|
|
14928
|
+
}
|
|
14929
|
+
}
|
|
14930
|
+
/**
|
|
14931
|
+
* Whether a track restart failed at getUserMedia rather than in the work that adopts the new track.
|
|
14932
|
+
* Recorded on the side rather than by wrapping, because callers of `restartTrack` match on the
|
|
14933
|
+
* rejection's own name.
|
|
14934
|
+
*/
|
|
14935
|
+
function isDeviceAcquisitionFailure(e) {
|
|
14936
|
+
return typeof e === 'object' && e !== null && deviceAcquisitionFailures.has(e);
|
|
14937
|
+
}
|
|
14692
14938
|
function toWebsocketUrl(url) {
|
|
14693
14939
|
if (url.startsWith('http')) {
|
|
14694
14940
|
return url.replace(/^(http)/, 'ws');
|
|
@@ -14875,6 +15121,35 @@ function extractMaxAgeFromRequestHeaders(headers) {
|
|
|
14875
15121
|
function isCompressionStreamSupported() {
|
|
14876
15122
|
return typeof CompressionStream !== 'undefined';
|
|
14877
15123
|
}
|
|
15124
|
+
let deflateRawCompressionSupported = null;
|
|
15125
|
+
/**
|
|
15126
|
+
* Whether this runtime can compress AND decompress the `deflate-raw` format, which is a stricter
|
|
15127
|
+
* requirement than {@link isCompressionStreamSupported}: Chromium 80-102 included
|
|
15128
|
+
* `CompressionStream` supporting only `gzip` / `deflate`, and `deflate-raw` was added in Chromium
|
|
15129
|
+
* 103 (Firefox 113, Safari 16.4).
|
|
15130
|
+
*/
|
|
15131
|
+
function isDeflateRawCompressionSupported() {
|
|
15132
|
+
if (typeof deflateRawCompressionSupported !== 'boolean') {
|
|
15133
|
+
deflateRawCompressionSupported = probeDeflateRawCompressionSupport();
|
|
15134
|
+
}
|
|
15135
|
+
return deflateRawCompressionSupported;
|
|
15136
|
+
}
|
|
15137
|
+
function probeDeflateRawCompressionSupport() {
|
|
15138
|
+
if (typeof CompressionStream === 'undefined' || typeof DecompressionStream === 'undefined') {
|
|
15139
|
+
return false;
|
|
15140
|
+
}
|
|
15141
|
+
try {
|
|
15142
|
+
// There is no api to query format support, and constructing with an unsupported format throws
|
|
15143
|
+
// synchronously, so constructing one of each is the probe. Both are checked because the
|
|
15144
|
+
// deflate-raw capability advertised to peers is as much about decompressing what they send us
|
|
15145
|
+
// as it is about compressing what we send them.
|
|
15146
|
+
new CompressionStream('deflate-raw');
|
|
15147
|
+
new DecompressionStream('deflate-raw');
|
|
15148
|
+
return true;
|
|
15149
|
+
} catch (_a) {
|
|
15150
|
+
return false;
|
|
15151
|
+
}
|
|
15152
|
+
}
|
|
14878
15153
|
function isPublisherOfferWithJoinSupported() {
|
|
14879
15154
|
// we have connectivity issue about publisher offer with join on firefox #1919
|
|
14880
15155
|
return isCompressionStreamSupported() && !isFireFox();
|
|
@@ -15877,11 +16152,38 @@ class HTMLElementInfo {
|
|
|
15877
16152
|
}
|
|
15878
16153
|
}
|
|
15879
16154
|
function isElementInPiP(el) {
|
|
15880
|
-
var _a
|
|
16155
|
+
var _a;
|
|
15881
16156
|
// Simple video PiP
|
|
15882
16157
|
if (document.pictureInPictureElement === el) return true;
|
|
15883
|
-
// Document PiP
|
|
15884
|
-
|
|
16158
|
+
// Document PiP: the element has to actually live in the PiP window, otherwise an
|
|
16159
|
+
// opener-document element whose coordinates happen to fall inside the PiP window's
|
|
16160
|
+
// bounds is reported as being in PiP.
|
|
16161
|
+
const pipWindow = (_a = window.documentPictureInPicture) === null || _a === void 0 ? void 0 : _a.window;
|
|
16162
|
+
if (pipWindow) {
|
|
16163
|
+
return isDocumentInPiPWindow(el.ownerDocument, pipWindow) && isElementInViewport(el, pipWindow);
|
|
16164
|
+
}
|
|
16165
|
+
return false;
|
|
16166
|
+
}
|
|
16167
|
+
// Whether the document is the Document PiP window's own document, or the document of a frame
|
|
16168
|
+
// nested inside it. The spec allows frames in a PiP window, so an element in one is in PiP:
|
|
16169
|
+
// https://wicg.github.io/document-picture-in-picture/#iframes
|
|
16170
|
+
function isDocumentInPiPWindow(doc, pipWindow) {
|
|
16171
|
+
var _a, _b, _c;
|
|
16172
|
+
// A cross-origin frame anywhere between the element and the PiP window hides frameElement
|
|
16173
|
+
// from the walk below, but top stays readable across origins and identifies the top-level
|
|
16174
|
+
// window the element belongs to. For an opener element that is the opener, not the PiP window.
|
|
16175
|
+
if (((_a = doc.defaultView) === null || _a === void 0 ? void 0 : _a.top) === pipWindow) return true;
|
|
16176
|
+
let current = doc;
|
|
16177
|
+
while (current) {
|
|
16178
|
+
if (current === pipWindow.document) return true;
|
|
16179
|
+
try {
|
|
16180
|
+
// Reading frameElement across a cross-origin boundary throws, which means we cannot
|
|
16181
|
+
// establish the ancestry and must not claim the element is in PiP.
|
|
16182
|
+
current = (_c = (_b = current.defaultView) === null || _b === void 0 ? void 0 : _b.frameElement) === null || _c === void 0 ? void 0 : _c.ownerDocument;
|
|
16183
|
+
} catch (_d) {
|
|
16184
|
+
return false;
|
|
16185
|
+
}
|
|
16186
|
+
}
|
|
15885
16187
|
return false;
|
|
15886
16188
|
}
|
|
15887
16189
|
// does not account for occlusion by other elements or opacity property
|
|
@@ -16346,12 +16648,16 @@ class E2EEManager extends eventsExports.EventEmitter {
|
|
|
16346
16648
|
this.handleReceiver(track.receiver, track.mediaStreamID, remoteId, track.kind === 'video' ? mimeTypeToVideoCodecString(trackInfo.mimeType) : undefined, hasPacketTrailer);
|
|
16347
16649
|
}
|
|
16348
16650
|
setupE2EESender(track, sender) {
|
|
16349
|
-
var _a, _b, _c;
|
|
16651
|
+
var _a, _b, _c, _d;
|
|
16350
16652
|
if (!isLocalTrack(track) || !sender) {
|
|
16351
16653
|
if (!sender) this.log.warn('early return because sender is not ready');
|
|
16352
16654
|
return;
|
|
16353
16655
|
}
|
|
16354
|
-
|
|
16656
|
+
const isVideo = isVideoTrack(track);
|
|
16657
|
+
// Safari exposes no per-frame codec info until trackInfo arrives. Seed only h264/h265:
|
|
16658
|
+
// a wrong vp8 seed would skip the NALU path, a wrong NALU seed only costs a logged fallback.
|
|
16659
|
+
const publishedCodec = isVideo ? (_a = track.publishOptions) === null || _a === void 0 ? void 0 : _a.videoCodec : undefined;
|
|
16660
|
+
this.handleSender(sender, track.mediaStreamID, publishedCodec === 'h264' || publishedCodec === 'h265' ? publishedCodec : undefined, isVideo ? (_c = (_b = track.publishOptions) === null || _b === void 0 ? void 0 : _b.frameMetadata) !== null && _c !== void 0 ? _c : (_d = track.publishOptions) === null || _d === void 0 ? void 0 : _d.packetTrailer : undefined);
|
|
16355
16661
|
}
|
|
16356
16662
|
/**
|
|
16357
16663
|
* Handles the given {@code RTCRtpReceiver} by creating a {@code TransformStream} which will inject
|
|
@@ -17121,6 +17427,58 @@ class AsyncQueue {
|
|
|
17121
17427
|
snapshot() {
|
|
17122
17428
|
return Array.from(this.pendingTasks.values());
|
|
17123
17429
|
}
|
|
17430
|
+
}/**
|
|
17431
|
+
* Implementation of AbortSignal.any
|
|
17432
|
+
* Creates a signal that will be aborted when any of the given signals is aborted.
|
|
17433
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any
|
|
17434
|
+
*/
|
|
17435
|
+
function abortSignalAny(signals) {
|
|
17436
|
+
// Handle empty signals array
|
|
17437
|
+
if (signals.length === 0) {
|
|
17438
|
+
const controller = new AbortController();
|
|
17439
|
+
return controller.signal;
|
|
17440
|
+
}
|
|
17441
|
+
// Fast path for single signal
|
|
17442
|
+
if (signals.length === 1) {
|
|
17443
|
+
return signals[0];
|
|
17444
|
+
}
|
|
17445
|
+
// Check if any signal is already aborted
|
|
17446
|
+
for (const signal of signals) {
|
|
17447
|
+
if (signal.aborted) {
|
|
17448
|
+
return signal;
|
|
17449
|
+
}
|
|
17450
|
+
}
|
|
17451
|
+
// Create a new controller for the combined signal
|
|
17452
|
+
const controller = new AbortController();
|
|
17453
|
+
const unlisteners = Array(signals.length);
|
|
17454
|
+
// Function to clean up all event listeners
|
|
17455
|
+
const cleanup = () => {
|
|
17456
|
+
for (const unsubscribe of unlisteners) {
|
|
17457
|
+
unsubscribe();
|
|
17458
|
+
}
|
|
17459
|
+
};
|
|
17460
|
+
// Add event listeners to each signal
|
|
17461
|
+
signals.forEach((signal, index) => {
|
|
17462
|
+
const handler = () => {
|
|
17463
|
+
controller.abort(signal.reason);
|
|
17464
|
+
cleanup();
|
|
17465
|
+
};
|
|
17466
|
+
signal.addEventListener('abort', handler);
|
|
17467
|
+
unlisteners[index] = () => signal.removeEventListener('abort', handler);
|
|
17468
|
+
});
|
|
17469
|
+
return controller.signal;
|
|
17470
|
+
}
|
|
17471
|
+
/**
|
|
17472
|
+
* Implementation of AbortSignal.timeout
|
|
17473
|
+
* Creates a signal that will be aborted after the specified timeout.
|
|
17474
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout
|
|
17475
|
+
*/
|
|
17476
|
+
function abortSignalTimeout(ms) {
|
|
17477
|
+
const controller = new AbortController();
|
|
17478
|
+
setTimeout(() => {
|
|
17479
|
+
controller.abort(new DOMException("signal timed out after ".concat(ms, " ms"), 'TimeoutError'));
|
|
17480
|
+
}, ms);
|
|
17481
|
+
return controller.signal;
|
|
17124
17482
|
}/**
|
|
17125
17483
|
* Opt-in registry that lets a development tool observe the state machines driving the connection
|
|
17126
17484
|
* layer.
|
|
@@ -18278,7 +18636,7 @@ class WebSocketStream {
|
|
|
18278
18636
|
let _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
18279
18637
|
code = _ref.closeCode,
|
|
18280
18638
|
reason = _ref.reason;
|
|
18281
|
-
return ws.close(code, reason);
|
|
18639
|
+
return ws.close(code, reason === undefined ? undefined : truncateCloseReason(reason));
|
|
18282
18640
|
};
|
|
18283
18641
|
this.opened = new TypedPromise((resolve, reject) => {
|
|
18284
18642
|
const rejectHandler = () => {
|
|
@@ -18354,6 +18712,25 @@ class WebSocketStream {
|
|
|
18354
18712
|
}
|
|
18355
18713
|
this.close = closeWithInfo;
|
|
18356
18714
|
}
|
|
18715
|
+
}
|
|
18716
|
+
/** `WebSocket.close` throws a `SyntaxError` for a reason longer than this many UTF-8 bytes. */
|
|
18717
|
+
const MAX_CLOSE_REASON_BYTES = 123;
|
|
18718
|
+
function truncateCloseReason(reason) {
|
|
18719
|
+
const encoder = new TextEncoder();
|
|
18720
|
+
if (encoder.encode(reason).length <= MAX_CLOSE_REASON_BYTES) {
|
|
18721
|
+
return reason;
|
|
18722
|
+
}
|
|
18723
|
+
// cut on code point boundaries, so a multi-byte character is never split
|
|
18724
|
+
let truncated = '';
|
|
18725
|
+
let bytes = 0;
|
|
18726
|
+
for (const char of reason) {
|
|
18727
|
+
bytes += encoder.encode(char).length;
|
|
18728
|
+
if (bytes > MAX_CLOSE_REASON_BYTES) {
|
|
18729
|
+
break;
|
|
18730
|
+
}
|
|
18731
|
+
truncated += char;
|
|
18732
|
+
}
|
|
18733
|
+
return truncated;
|
|
18357
18734
|
}const passThroughQueueSignals = ['syncState', 'trickle', 'offer', 'answer', 'simulate', 'leave'];
|
|
18358
18735
|
/**
|
|
18359
18736
|
* Whether a failed resume may be followed by another one. Mirrors how `RTCEngine` classifies these
|
|
@@ -18569,14 +18946,27 @@ class SignalClient {
|
|
|
18569
18946
|
let publisherOffer = arguments.length > 5 ? arguments[5] : undefined;
|
|
18570
18947
|
return function* () {
|
|
18571
18948
|
const unlock = yield _this2.connectionLock.lock();
|
|
18572
|
-
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18949
|
+
let rtcUrl;
|
|
18950
|
+
let validateUrl;
|
|
18951
|
+
try {
|
|
18952
|
+
_this2.connectOptions = opts;
|
|
18953
|
+
_this2.useV0SignalPath = useV0Path;
|
|
18954
|
+
const clientInfo = getClientInfo(opts.clientInfoCapabilities);
|
|
18955
|
+
const params = useV0Path ? createConnectionParams(token, clientInfo, opts) : yield createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer);
|
|
18956
|
+
rtcUrl = createRtcUrl(url, params, useV0Path).toString();
|
|
18957
|
+
validateUrl = createValidateUrl(rtcUrl).toString();
|
|
18958
|
+
} catch (e) {
|
|
18959
|
+
// the executor below owns the lock from here on
|
|
18960
|
+
unlock();
|
|
18961
|
+
throw e;
|
|
18962
|
+
}
|
|
18963
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
18964
|
+
unlock();
|
|
18965
|
+
_this2.log.debug('signal connect abandoned before opening a transport');
|
|
18966
|
+
throw ConnectionError.cancelled('Connection aborted');
|
|
18967
|
+
}
|
|
18578
18968
|
return new Promise((resolve, reject) => __awaiter(_this2, void 0, void 0, function* () {
|
|
18579
|
-
var _a, _b;
|
|
18969
|
+
var _a, _b, _c, _d, _e;
|
|
18580
18970
|
try {
|
|
18581
18971
|
let alreadyAborted = false;
|
|
18582
18972
|
const abortHandler = eventOrError => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -18588,12 +18978,12 @@ class SignalClient {
|
|
|
18588
18978
|
const reason = getAbortReasonAsString(target, 'Abort handler called');
|
|
18589
18979
|
// send leave if we have an active stream writer (connection is open)
|
|
18590
18980
|
if (this.streamWriter && !this.isDisconnected) {
|
|
18591
|
-
this.sendLeave().then(() => this.close(reason)).catch(e => {
|
|
18981
|
+
this.sendLeave().then(() => this.close(true, reason)).catch(e => {
|
|
18592
18982
|
this.log.error(e);
|
|
18593
|
-
this.close();
|
|
18983
|
+
this.close(true, reason);
|
|
18594
18984
|
});
|
|
18595
18985
|
} else {
|
|
18596
|
-
this.close();
|
|
18986
|
+
this.close(true, reason);
|
|
18597
18987
|
}
|
|
18598
18988
|
cleanupAbortHandlers();
|
|
18599
18989
|
// The caller may already have classified this failure: the connect timeout hands us a
|
|
@@ -18619,6 +19009,10 @@ class SignalClient {
|
|
|
18619
19009
|
yield this.teardownTransport('replaced by a new connection attempt');
|
|
18620
19010
|
this.log.debug("closed previous ws connection in ".concat(performance.now() - startClose, "ms"));
|
|
18621
19011
|
}
|
|
19012
|
+
if (alreadyAborted) {
|
|
19013
|
+
this.log.debug('signal connect abandoned after tearing down the previous transport');
|
|
19014
|
+
return;
|
|
19015
|
+
}
|
|
18622
19016
|
// the transport created below belongs to this attempt; events arriving from it after a
|
|
18623
19017
|
// newer attempt has started are dropped by the machine
|
|
18624
19018
|
const attemptId = this.attemptId;
|
|
@@ -18663,6 +19057,12 @@ class SignalClient {
|
|
|
18663
19057
|
}
|
|
18664
19058
|
});
|
|
18665
19059
|
const connection = yield this.ws.opened.catch(reason => __awaiter(this, void 0, void 0, function* () {
|
|
19060
|
+
if (alreadyAborted) {
|
|
19061
|
+
// the abort handler has already rejected this attempt; don't probe the validate
|
|
19062
|
+
// endpoint for a failure nobody is waiting on
|
|
19063
|
+
this.log.debug('signal connect abandoned while the websocket was opening');
|
|
19064
|
+
return;
|
|
19065
|
+
}
|
|
18666
19066
|
if (this.lifecycleState !== 'connected') {
|
|
18667
19067
|
// claimed synchronously, before the await below: the socket's close event is already
|
|
18668
19068
|
// on its way and would otherwise reject first with a less useful error
|
|
@@ -18683,38 +19083,63 @@ class SignalClient {
|
|
|
18683
19083
|
const signalReader = connection.readable.getReader();
|
|
18684
19084
|
this.streamWriter = connection.writable.getWriter();
|
|
18685
19085
|
// wsTimeout only guarded the upgrade; guard the first-message read with
|
|
18686
|
-
// its own
|
|
18687
|
-
|
|
18688
|
-
|
|
19086
|
+
// its own deadline so a silent server can't hang join() forever.
|
|
19087
|
+
// During reconnect, drop any stray messages that arrive before the
|
|
19088
|
+
// ReconnectResponse so we do not declare the channel reconnected on the
|
|
19089
|
+
// wrong signal.
|
|
19090
|
+
let firstSignalResponse;
|
|
18689
19091
|
try {
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
|
|
19092
|
+
const deadline = Date.now() + JOIN_RESPONSE_TIMEOUT;
|
|
19093
|
+
// eslint-disable-next-line no-constant-condition
|
|
19094
|
+
while (true) {
|
|
19095
|
+
const remaining = deadline - Date.now();
|
|
19096
|
+
if (remaining <= 0) {
|
|
19097
|
+
throw ConnectionError.timeout('signal connection timed out while waiting for the first message');
|
|
19098
|
+
}
|
|
19099
|
+
let readTimeout;
|
|
19100
|
+
const readResult = yield Promise.race([signalReader.read(), new Promise((_, rejectRead) => {
|
|
19101
|
+
readTimeout = setTimeout(() => {
|
|
19102
|
+
rejectRead(ConnectionError.timeout('signal connection timed out while waiting for the first message'));
|
|
19103
|
+
}, remaining);
|
|
19104
|
+
})]);
|
|
19105
|
+
clearTimeout(readTimeout);
|
|
19106
|
+
if (!readResult.value) {
|
|
19107
|
+
throw ConnectionError.internal('no message received as first message');
|
|
19108
|
+
}
|
|
19109
|
+
const parsed = parseSignalResponse(readResult.value);
|
|
19110
|
+
if (this.lifecycleState === 'reconnecting' && ((_a = parsed.message) === null || _a === void 0 ? void 0 : _a.case) !== 'reconnect' && ((_b = parsed.message) === null || _b === void 0 ? void 0 : _b.case) !== 'leave') {
|
|
19111
|
+
this.log.warn('dropping signal message while awaiting reconnect response', {
|
|
19112
|
+
messageCase: (_c = parsed.message) === null || _c === void 0 ? void 0 : _c.case
|
|
19113
|
+
});
|
|
19114
|
+
continue;
|
|
19115
|
+
}
|
|
19116
|
+
firstSignalResponse = parsed;
|
|
19117
|
+
break;
|
|
19118
|
+
}
|
|
18695
19119
|
} catch (e) {
|
|
18696
|
-
// No first message in time: release the reader and tear down the ws
|
|
19120
|
+
// No usable first message in time: release the reader and tear down the ws
|
|
18697
19121
|
// so we surface the timeout instead of leaking an open connection.
|
|
18698
19122
|
signalReader.releaseLock();
|
|
18699
19123
|
reject(e);
|
|
18700
19124
|
this.close();
|
|
18701
19125
|
return;
|
|
18702
|
-
} finally {
|
|
18703
|
-
clearTimeout(firstMessageTimeout);
|
|
18704
19126
|
}
|
|
18705
19127
|
signalReader.releaseLock();
|
|
18706
|
-
if (
|
|
18707
|
-
|
|
19128
|
+
if (alreadyAborted) {
|
|
19129
|
+
// The abort handler has already rejected this attempt and is closing the transport.
|
|
19130
|
+
// Don't act on a join that raced it: `onJoined` would publish an abandoned session's
|
|
19131
|
+
// state to the room.
|
|
19132
|
+
this.log.debug('signal connect abandoned while waiting for the first message');
|
|
19133
|
+
return;
|
|
18708
19134
|
}
|
|
18709
|
-
const firstSignalResponse = parseSignalResponse(firstMessage.value);
|
|
18710
19135
|
// Validate the first message
|
|
18711
|
-
const validation = this.validateFirstMessage(firstSignalResponse, (
|
|
19136
|
+
const validation = this.validateFirstMessage(firstSignalResponse, (_d = opts.reconnect) !== null && _d !== void 0 ? _d : false);
|
|
18712
19137
|
if (!validation.isValid) {
|
|
18713
19138
|
reject(validation.error);
|
|
18714
19139
|
return;
|
|
18715
19140
|
}
|
|
18716
19141
|
// Handle join response
|
|
18717
|
-
if (((
|
|
19142
|
+
if (((_e = firstSignalResponse.message) === null || _e === void 0 ? void 0 : _e.case) === 'join') {
|
|
18718
19143
|
// Set up ping configuration
|
|
18719
19144
|
this.pingTimeoutDuration = firstSignalResponse.message.value.pingTimeout;
|
|
18720
19145
|
this.pingIntervalDuration = firstSignalResponse.message.value.pingInterval;
|
|
@@ -18728,9 +19153,7 @@ class SignalClient {
|
|
|
18728
19153
|
this.onJoined(firstSignalResponse.message.value);
|
|
18729
19154
|
}
|
|
18730
19155
|
}
|
|
18731
|
-
|
|
18732
|
-
const firstMessageToProcess = validation.shouldProcessFirstMessage ? firstSignalResponse : undefined;
|
|
18733
|
-
this.handleSignalConnected(connection, wsTimeout, attemptId, firstMessageToProcess);
|
|
19156
|
+
this.handleSignalConnected(connection, wsTimeout, attemptId);
|
|
18734
19157
|
resolve(validation.response);
|
|
18735
19158
|
} catch (e) {
|
|
18736
19159
|
reject(e);
|
|
@@ -18744,11 +19167,8 @@ class SignalClient {
|
|
|
18744
19167
|
}();
|
|
18745
19168
|
});
|
|
18746
19169
|
}
|
|
18747
|
-
startReadingLoop(signalReader
|
|
19170
|
+
startReadingLoop(signalReader) {
|
|
18748
19171
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18749
|
-
if (firstMessage) {
|
|
18750
|
-
this.handleSignalResponse(firstMessage);
|
|
18751
|
-
}
|
|
18752
19172
|
const attemptId = this.attemptId;
|
|
18753
19173
|
while (true) {
|
|
18754
19174
|
if (this.signalLatency) {
|
|
@@ -18808,16 +19228,17 @@ class SignalClient {
|
|
|
18808
19228
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18809
19229
|
try {
|
|
18810
19230
|
this.clearPingInterval();
|
|
18811
|
-
|
|
18812
|
-
|
|
19231
|
+
const ws = this.ws;
|
|
19232
|
+
if (ws) {
|
|
19233
|
+
// released before closing, so a close that throws cannot leave the transport attached
|
|
19234
|
+
this.ws = undefined;
|
|
19235
|
+
this.streamWriter = undefined;
|
|
19236
|
+
ws.close({
|
|
18813
19237
|
closeCode: 1000,
|
|
18814
19238
|
reason
|
|
18815
19239
|
});
|
|
18816
19240
|
// calling `ws.close()` only starts the closing handshake (CLOSING state), prefer to wait until state is actually CLOSED
|
|
18817
|
-
|
|
18818
|
-
this.ws = undefined;
|
|
18819
|
-
this.streamWriter = undefined;
|
|
18820
|
-
yield Promise.race([closePromise, sleep(MAX_WS_CLOSE_TIME)]);
|
|
19241
|
+
yield Promise.race([ws.closed, sleep(MAX_WS_CLOSE_TIME)]);
|
|
18821
19242
|
}
|
|
18822
19243
|
} catch (e) {
|
|
18823
19244
|
this.log.debug('websocket error while closing', {
|
|
@@ -19184,6 +19605,7 @@ class SignalClient {
|
|
|
19184
19605
|
return __awaiter(this, arguments, void 0, function (reason) {
|
|
19185
19606
|
var _this6 = this;
|
|
19186
19607
|
let attemptId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.attemptId;
|
|
19608
|
+
let simulateReconnectDelay = arguments.length > 2 ? arguments[2] : undefined;
|
|
19187
19609
|
return function* () {
|
|
19188
19610
|
const onCloseCallback = _this6.onClose;
|
|
19189
19611
|
if (!_this6.sendLifecycleInput({
|
|
@@ -19205,6 +19627,10 @@ class SignalClient {
|
|
|
19205
19627
|
_this6.log.info("websocket connection closed: ".concat(reason), {
|
|
19206
19628
|
reason
|
|
19207
19629
|
});
|
|
19630
|
+
if (simulateReconnectDelay) {
|
|
19631
|
+
_this6.log.info("waiting for ".concat(simulateReconnectDelay, "ms until reconnecting signal"));
|
|
19632
|
+
yield sleep(simulateReconnectDelay);
|
|
19633
|
+
}
|
|
19208
19634
|
if (onCloseCallback) {
|
|
19209
19635
|
onCloseCallback(reason);
|
|
19210
19636
|
}
|
|
@@ -19265,7 +19691,7 @@ class SignalClient {
|
|
|
19265
19691
|
* @param firstMessage Optional first message to process
|
|
19266
19692
|
* @internal
|
|
19267
19693
|
*/
|
|
19268
|
-
handleSignalConnected(connection, timeoutHandle, attemptId
|
|
19694
|
+
handleSignalConnected(connection, timeoutHandle, attemptId) {
|
|
19269
19695
|
clearTimeout(timeoutHandle);
|
|
19270
19696
|
const established = this.sendLifecycleInput(this.lifecycleState === 'reconnecting' ? {
|
|
19271
19697
|
type: 'reconnectComplete',
|
|
@@ -19287,7 +19713,7 @@ class SignalClient {
|
|
|
19287
19713
|
}
|
|
19288
19714
|
this.log.info('signal connected');
|
|
19289
19715
|
this.startPingInterval();
|
|
19290
|
-
this.startReadingLoop(connection.readable.getReader()
|
|
19716
|
+
this.startReadingLoop(connection.readable.getReader());
|
|
19291
19717
|
}
|
|
19292
19718
|
/**
|
|
19293
19719
|
* Validates the first message received from the signal server
|
|
@@ -19297,28 +19723,18 @@ class SignalClient {
|
|
|
19297
19723
|
* @internal
|
|
19298
19724
|
*/
|
|
19299
19725
|
validateFirstMessage(firstSignalResponse, isReconnect) {
|
|
19300
|
-
var _a, _b, _c, _d
|
|
19726
|
+
var _a, _b, _c, _d;
|
|
19301
19727
|
if (((_a = firstSignalResponse.message) === null || _a === void 0 ? void 0 : _a.case) === 'join') {
|
|
19302
19728
|
return {
|
|
19303
19729
|
isValid: true,
|
|
19304
19730
|
response: firstSignalResponse.message.value
|
|
19305
19731
|
};
|
|
19306
|
-
} else if (this.lifecycleState === 'reconnecting' && ((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case)
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
} else {
|
|
19313
|
-
// in reconnecting, any message received means signal reconnected and we still need to process it
|
|
19314
|
-
this.log.debug('declaring signal reconnected without reconnect response received');
|
|
19315
|
-
return {
|
|
19316
|
-
isValid: true,
|
|
19317
|
-
response: undefined,
|
|
19318
|
-
shouldProcessFirstMessage: true
|
|
19319
|
-
};
|
|
19320
|
-
}
|
|
19321
|
-
} else if (this.isEstablishingConnection && ((_d = firstSignalResponse.message) === null || _d === void 0 ? void 0 : _d.case) === 'leave') {
|
|
19732
|
+
} else if (this.lifecycleState === 'reconnecting' && ((_b = firstSignalResponse.message) === null || _b === void 0 ? void 0 : _b.case) === 'reconnect') {
|
|
19733
|
+
return {
|
|
19734
|
+
isValid: true,
|
|
19735
|
+
response: firstSignalResponse.message.value
|
|
19736
|
+
};
|
|
19737
|
+
} else if (this.isEstablishingConnection && ((_c = firstSignalResponse.message) === null || _c === void 0 ? void 0 : _c.case) === 'leave') {
|
|
19322
19738
|
return {
|
|
19323
19739
|
isValid: false,
|
|
19324
19740
|
error: ConnectionError.leaveRequest('Received leave request while trying to (re)connect', firstSignalResponse.message.value.reason)
|
|
@@ -19327,7 +19743,7 @@ class SignalClient {
|
|
|
19327
19743
|
// non-reconnect case, should receive join response first
|
|
19328
19744
|
return {
|
|
19329
19745
|
isValid: false,
|
|
19330
|
-
error: ConnectionError.internal("did not receive join response, got ".concat((
|
|
19746
|
+
error: ConnectionError.internal("did not receive join response, got ".concat((_d = firstSignalResponse.message) === null || _d === void 0 ? void 0 : _d.case, " instead"))
|
|
19331
19747
|
};
|
|
19332
19748
|
}
|
|
19333
19749
|
return {
|
|
@@ -19345,7 +19761,9 @@ class SignalClient {
|
|
|
19345
19761
|
handleConnectionError(reason, validateUrl) {
|
|
19346
19762
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19347
19763
|
try {
|
|
19348
|
-
const resp = yield fetch(validateUrl
|
|
19764
|
+
const resp = yield fetch(validateUrl, {
|
|
19765
|
+
signal: abortSignalTimeout(3000)
|
|
19766
|
+
});
|
|
19349
19767
|
switch (resp.status) {
|
|
19350
19768
|
case 404:
|
|
19351
19769
|
const errorMsg = yield resp.text();
|
|
@@ -19369,7 +19787,11 @@ class SignalClient {
|
|
|
19369
19787
|
});
|
|
19370
19788
|
}
|
|
19371
19789
|
} catch (e) {
|
|
19372
|
-
|
|
19790
|
+
if (e instanceof ConnectionError) {
|
|
19791
|
+
return e;
|
|
19792
|
+
} else {
|
|
19793
|
+
return ConnectionError.serverUnreachable(e instanceof Error ? e.message : 'server was not reachable');
|
|
19794
|
+
}
|
|
19373
19795
|
}
|
|
19374
19796
|
});
|
|
19375
19797
|
}
|
|
@@ -19432,6 +19854,12 @@ function createConnectionParams(token, info, opts) {
|
|
|
19432
19854
|
if (opts.adaptiveStream) {
|
|
19433
19855
|
params.set('adaptive_stream', '1');
|
|
19434
19856
|
}
|
|
19857
|
+
if (opts.disableIceLite) {
|
|
19858
|
+
params.set('disable_ice_lite', '1');
|
|
19859
|
+
}
|
|
19860
|
+
if (typeof opts.subscriberAllowPause !== 'undefined') {
|
|
19861
|
+
params.set('subscriber_allow_pause', opts.subscriberAllowPause ? '1' : '0');
|
|
19862
|
+
}
|
|
19435
19863
|
if (opts.reconnectReason) {
|
|
19436
19864
|
params.set('reconnect_reason', opts.reconnectReason.toString());
|
|
19437
19865
|
}
|
|
@@ -19450,7 +19878,9 @@ function createJoinRequestConnectionParams(token, info, opts, publisherOffer) {
|
|
|
19450
19878
|
clientInfo: info,
|
|
19451
19879
|
connectionSettings: new ConnectionSettings({
|
|
19452
19880
|
autoSubscribe: !!opts.autoSubscribe,
|
|
19453
|
-
adaptiveStream: !!opts.adaptiveStream
|
|
19881
|
+
adaptiveStream: !!opts.adaptiveStream,
|
|
19882
|
+
disableIceLite: !!opts.disableIceLite,
|
|
19883
|
+
subscriberAllowPause: opts.subscriberAllowPause
|
|
19454
19884
|
}),
|
|
19455
19885
|
reconnect: !!opts.reconnect,
|
|
19456
19886
|
participantSid: opts.sid ? opts.sid : undefined,
|
|
@@ -20287,68 +20717,206 @@ function requireLib() {
|
|
|
20287
20717
|
* Why same for all codecs: Target bitrate already accounts for codec efficiency
|
|
20288
20718
|
* (e.g., users set lower targets for VP9/AV1 knowing they're more efficient).
|
|
20289
20719
|
* Why cap at 1 Mbps: Prevents BWE from starting too aggressively on high bitrate tracks.
|
|
20720
|
+
* The cap is lowered further for connections that were slow to set up, see
|
|
20721
|
+
* `computeStartBitrateCap`, or that the browser reports as slow, see
|
|
20722
|
+
* `connectionEffectiveTypeStartBitrateCap`.
|
|
20290
20723
|
*/
|
|
20291
20724
|
const startBitrateMultiplier = 0.9;
|
|
20292
20725
|
/** Maximum x-google-start-bitrate in kbps. 1 Mbps prevents BWE from starting too aggressively. */
|
|
20293
20726
|
const maxStartBitrateKbps = 1000;
|
|
20727
|
+
/**
|
|
20728
|
+
* Minimum x-google-start-bitrate in kbps: libwebrtc's own default starting estimate. A target
|
|
20729
|
+
* below this gets no hint, since seeding above the real capacity costs more than the ramp it
|
|
20730
|
+
* saves, and a slow connection is seeded no higher than this.
|
|
20731
|
+
*/
|
|
20732
|
+
const minTargetBitrateKbps = 300;
|
|
20733
|
+
/**
|
|
20734
|
+
* Connection setup times (ms) bounding the ramp in `computeStartBitrateCap`: at or below the
|
|
20735
|
+
* first the hint is capped at `maxStartBitrateKbps`, at or above the second at
|
|
20736
|
+
* `minTargetBitrateKbps`. Measured on shaped links (CLT-3380): healthy links set up in under
|
|
20737
|
+
* 860 ms, a 1 Mbps link with 150 ms RTT took 1.1–1.7 s, a 500 kbps link ~2.3 s at the median,
|
|
20738
|
+
* and a 300 kbps link never under 3 s. The first sits above the 1 Mbps link, which a lower seed
|
|
20739
|
+
* only slows down, and the second above the 500 kbps link's median, so only links that cannot
|
|
20740
|
+
* carry more land at the floor.
|
|
20741
|
+
*/
|
|
20742
|
+
const setupTimeForMaxStartBitrateMs = 1500;
|
|
20743
|
+
const setupTimeForMinStartBitrateMs = 3500;
|
|
20744
|
+
/** Cap on the start bitrate hint, in kbps, when the browser reports a `3g` connection. */
|
|
20745
|
+
const effectiveType3gStartBitrateKbps = 500;
|
|
20294
20746
|
const debounceInterval = 20;
|
|
20295
20747
|
/**
|
|
20296
|
-
*
|
|
20297
|
-
* This SDP munging is used for a bitrate setting that cannot be applied through
|
|
20298
|
-
* `RTCRtpEncodingParameters`.
|
|
20748
|
+
* Codec payload for `codec` in this media section, when the section carries `cid`.
|
|
20299
20749
|
*
|
|
20300
|
-
* Returns `undefined` when the section does not belong to the track, `0` when
|
|
20301
|
-
*
|
|
20302
|
-
* requested codec is present (whether the bitrate was added or already set).
|
|
20750
|
+
* Returns `undefined` when the section does not belong to the track, `0` when it does but
|
|
20751
|
+
* does not offer the requested codec, and the codec payload otherwise.
|
|
20303
20752
|
*
|
|
20304
20753
|
* @internal
|
|
20305
20754
|
*/
|
|
20306
|
-
function
|
|
20307
|
-
let isScreenShare = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
20755
|
+
function findTrackCodecPayload(media, cid, codec) {
|
|
20308
20756
|
var _a, _b, _c;
|
|
20309
20757
|
if (!((_a = media.msid) === null || _a === void 0 ? void 0 : _a.includes(cid))) {
|
|
20310
20758
|
return undefined;
|
|
20311
20759
|
}
|
|
20312
|
-
|
|
20313
|
-
|
|
20314
|
-
|
|
20760
|
+
return (_c = (_b = media.rtp.find(rtp => rtp.codec.toUpperCase() === codec.toUpperCase())) === null || _b === void 0 ? void 0 : _b.payload) !== null && _c !== void 0 ? _c : 0;
|
|
20761
|
+
}
|
|
20762
|
+
/**
|
|
20763
|
+
* Start bitrate hinted for a single track, or `undefined` when its target is too low to
|
|
20764
|
+
* be worth seeding.
|
|
20765
|
+
*
|
|
20766
|
+
* 90% of the target leaves ~10% headroom for the estimator to settle. The same multiplier
|
|
20767
|
+
* is used for every codec because the target already reflects the codec's efficiency.
|
|
20768
|
+
* Camera is capped at 1 Mbps so the estimator does not open too aggressively on a
|
|
20769
|
+
* high-bitrate track; screen share is exempt, because its content needs the bitrate
|
|
20770
|
+
* immediately to stay legible.
|
|
20771
|
+
*
|
|
20772
|
+
* The cap is lowered for a connection that was slow to set up (`computeStartBitrateCap`), and
|
|
20773
|
+
* for one the browser reports as slow (`connectionEffectiveTypeStartBitrateCap`), whichever is
|
|
20774
|
+
* lower. Screen share is exempt from both, however slow the connection.
|
|
20775
|
+
*
|
|
20776
|
+
* @internal
|
|
20777
|
+
*/
|
|
20778
|
+
function computeTrackStartBitrate(trackbr, connectionSetupTimeMs) {
|
|
20779
|
+
if (trackbr.maxbr < minTargetBitrateKbps) {
|
|
20780
|
+
return undefined;
|
|
20315
20781
|
}
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
// TODO: dynamically adjust start bitrate based on network conditions (e.g., previous BWE estimate)
|
|
20320
|
-
const calculatedStartBitrate = Math.round(maxbr * startBitrateMultiplier);
|
|
20321
|
-
const startBitrate = isScreenShare ? calculatedStartBitrate : Math.min(calculatedStartBitrate, maxStartBitrateKbps);
|
|
20322
|
-
const fmtp = media.fmtp.find(entry => entry.payload === codecPayload);
|
|
20323
|
-
if (fmtp) {
|
|
20324
|
-
// If another track's fmtp already has a start bitrate, it cannot be
|
|
20325
|
-
// overridden here because the payload type is shared across the bundle.
|
|
20326
|
-
// This forces every track sharing that payload to use the initial track's
|
|
20327
|
-
// start bitrate.
|
|
20328
|
-
if (!fmtp.config.includes('x-google-start-bitrate')) {
|
|
20329
|
-
fmtp.config += ";x-google-start-bitrate=".concat(startBitrate);
|
|
20330
|
-
}
|
|
20331
|
-
} else {
|
|
20332
|
-
// VP8 and some codecs may not have an existing fmtp line.
|
|
20333
|
-
media.fmtp.push({
|
|
20334
|
-
payload: codecPayload,
|
|
20335
|
-
config: "x-google-start-bitrate=".concat(startBitrate)
|
|
20336
|
-
});
|
|
20782
|
+
const calculated = Math.min(Math.round(trackbr.maxbr * startBitrateMultiplier), trackbr.maxbr);
|
|
20783
|
+
if (trackbr.isScreenShare) {
|
|
20784
|
+
return calculated;
|
|
20337
20785
|
}
|
|
20338
|
-
|
|
20786
|
+
const cap = Math.min(computeStartBitrateCap(connectionSetupTimeMs), connectionEffectiveTypeStartBitrateCap());
|
|
20787
|
+
return Math.min(calculated, cap);
|
|
20339
20788
|
}
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
|
|
20345
|
-
|
|
20346
|
-
|
|
20347
|
-
|
|
20348
|
-
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20789
|
+
/**
|
|
20790
|
+
* Cap on the start bitrate hint, in kbps, from the browser's effective connection type
|
|
20791
|
+
* (`navigator.connection.effectiveType`, Chromium only): `slow-2g` and `2g` cap at
|
|
20792
|
+
* `minTargetBitrateKbps`, `3g` at `effectiveType3gStartBitrateKbps`.
|
|
20793
|
+
*
|
|
20794
|
+
* It catches links that have low bandwidth but low latency, which set up quickly and so slip
|
|
20795
|
+
* past the setup time ramp. `4g`, and browsers without the API, give no guidance and stay at the
|
|
20796
|
+
* `maxStartBitrateKbps` ceiling, as `computeStartBitrateCap` does without a setup time. The
|
|
20797
|
+
* connection RTT the API also reports is not used: it lags behind network changes, and the setup
|
|
20798
|
+
* time already reflects latency.
|
|
20799
|
+
*/
|
|
20800
|
+
function connectionEffectiveTypeStartBitrateCap() {
|
|
20801
|
+
// A `typeof` guard, since `globalThis` is missing from some supported browsers (Chrome < 71,
|
|
20802
|
+
// Firefox < 65, Safari < 12.1) and the build does not polyfill it.
|
|
20803
|
+
const connection = typeof navigator !== 'undefined' ? navigator.connection : undefined;
|
|
20804
|
+
switch (connection === null || connection === void 0 ? void 0 : connection.effectiveType) {
|
|
20805
|
+
case 'slow-2g':
|
|
20806
|
+
case '2g':
|
|
20807
|
+
return minTargetBitrateKbps;
|
|
20808
|
+
case '3g':
|
|
20809
|
+
return effectiveType3gStartBitrateKbps;
|
|
20810
|
+
default:
|
|
20811
|
+
return maxStartBitrateKbps;
|
|
20812
|
+
}
|
|
20813
|
+
}
|
|
20814
|
+
/**
|
|
20815
|
+
* Cap on the start bitrate hint, in kbps, from how long the connection took to set up.
|
|
20816
|
+
*
|
|
20817
|
+
* Connection setup time (signaling join plus ICE/DTLS) is the network signal every platform has
|
|
20818
|
+
* before the first video offer, since libwebrtc cannot probe the path until a video sender
|
|
20819
|
+
* exists. It grows with round-trip time and loss, which also mark the links where a 1 Mbps
|
|
20820
|
+
* seed overshoots, so the cap ramps linearly from `maxStartBitrateKbps` at
|
|
20821
|
+
* `setupTimeForMaxStartBitrateMs` down to `minTargetBitrateKbps` at
|
|
20822
|
+
* `setupTimeForMinStartBitrateMs`. A video offer built while the connection is still being set
|
|
20823
|
+
* up (publishing during connect) passes the time elapsed so far, a lower bound on the setup time.
|
|
20824
|
+
* Without any timing the cap stays at the 1 Mbps ceiling.
|
|
20825
|
+
*/
|
|
20826
|
+
function computeStartBitrateCap(connectionSetupTimeMs) {
|
|
20827
|
+
if (connectionSetupTimeMs === undefined) {
|
|
20828
|
+
return maxStartBitrateKbps;
|
|
20829
|
+
}
|
|
20830
|
+
const ramp = Math.min(Math.max((connectionSetupTimeMs - setupTimeForMaxStartBitrateMs) / (setupTimeForMinStartBitrateMs - setupTimeForMaxStartBitrateMs), 0), 1);
|
|
20831
|
+
return Math.round(maxStartBitrateKbps - ramp * (maxStartBitrateKbps - minTargetBitrateKbps));
|
|
20832
|
+
}
|
|
20833
|
+
/**
|
|
20834
|
+
* The single start bitrate for this peer connection: the largest hint among the video
|
|
20835
|
+
* m-sections of `media` that map to a published track.
|
|
20836
|
+
*
|
|
20837
|
+
* libwebrtc reads `x-google-start-bitrate` per m-section but applies it to the shared
|
|
20838
|
+
* `Call` (`WebRtcVideoSendChannel::ApplyChangedParams` -> `SetSdpBitrateParameters`), where
|
|
20839
|
+
* `RtpBitrateConfigurator` holds one config for the whole connection. Differing per-section
|
|
20840
|
+
* values are therefore last-writer-wins, decided by m-section order, so every video section
|
|
20841
|
+
* gets the same number instead.
|
|
20842
|
+
*
|
|
20843
|
+
* Only sections that can send local media are considered. `trackBitrates` is append-only and an
|
|
20844
|
+
* unpublished section keeps its `a=msid`, so matching on msid alone would still pair a stale
|
|
20845
|
+
* entry with its old section — letting an uncapped screen-share target seed a connection that
|
|
20846
|
+
* now carries only a camera, or consuming the one-shot hint on a section that sends nothing.
|
|
20847
|
+
*
|
|
20848
|
+
* The direction is the discriminator, as an exclusion rather than a match: `recvonly` and
|
|
20849
|
+
* `inactive` are the only directions that cannot carry local media, and they are exactly the
|
|
20850
|
+
* two a dead section lands on — unpublish sets `inactive` (explicitly in
|
|
20851
|
+
* `LocalParticipant.unpublishTrack`, and via `removeTrack`'s sendonly -> inactive transition for
|
|
20852
|
+
* the simulcast senders), `removeTrack` on a `sendrecv` transceiver leaves `recvonly`, and the
|
|
20853
|
+
* pre-populated placeholders are `recvonly`. Everything else sends: `sendonly` from the
|
|
20854
|
+
* `addTransceiver` path, `sendrecv` from the legacy `addTrack` fallback (which reuses a
|
|
20855
|
+
* transceiver rather than creating a sendonly one), and a section with no direction attribute,
|
|
20856
|
+
* which SDP defaults to `sendrecv`.
|
|
20857
|
+
*
|
|
20858
|
+
* @internal
|
|
20859
|
+
*/
|
|
20860
|
+
function computeConnectionStartBitrate(media, trackBitrates, connectionSetupTimeMs) {
|
|
20861
|
+
let connectionStartBitrate;
|
|
20862
|
+
for (const m of media) {
|
|
20863
|
+
if (m.type !== 'video' || m.direction === 'recvonly' || m.direction === 'inactive') {
|
|
20864
|
+
continue;
|
|
20865
|
+
}
|
|
20866
|
+
for (const trackbr of trackBitrates) {
|
|
20867
|
+
if (!trackbr.cid) {
|
|
20868
|
+
continue;
|
|
20869
|
+
}
|
|
20870
|
+
const codecPayload = findTrackCodecPayload(m, trackbr.cid, trackbr.codec);
|
|
20871
|
+
if (codecPayload === undefined) {
|
|
20872
|
+
continue;
|
|
20873
|
+
}
|
|
20874
|
+
const startBitrate = codecPayload > 0 ? computeTrackStartBitrate(trackbr, connectionSetupTimeMs) : undefined;
|
|
20875
|
+
if (startBitrate !== undefined && (connectionStartBitrate === undefined || startBitrate > connectionStartBitrate)) {
|
|
20876
|
+
connectionStartBitrate = startBitrate;
|
|
20877
|
+
}
|
|
20878
|
+
break;
|
|
20879
|
+
}
|
|
20880
|
+
}
|
|
20881
|
+
return connectionStartBitrate;
|
|
20882
|
+
}
|
|
20883
|
+
/**
|
|
20884
|
+
* Declares `x-google-start-bitrate` on `codecPayload`'s fmtp. This SDP munging is used for
|
|
20885
|
+
* a bitrate setting that cannot be applied through `RTCRtpEncodingParameters`.
|
|
20886
|
+
*
|
|
20887
|
+
* Returns whether the section now carries the hint.
|
|
20888
|
+
*
|
|
20889
|
+
* @internal
|
|
20890
|
+
*/
|
|
20891
|
+
function applyVideoStartBitrate(media, codecPayload, startBitrate) {
|
|
20892
|
+
const fmtp = media.fmtp.find(entry => entry.payload === codecPayload);
|
|
20893
|
+
if (fmtp) {
|
|
20894
|
+
// A payload type is shared across the bundle, so a value written for one section is
|
|
20895
|
+
// already the connection-level one; leave it rather than rewrite it.
|
|
20896
|
+
if (!fmtp.config.includes('x-google-start-bitrate')) {
|
|
20897
|
+
fmtp.config += ";x-google-start-bitrate=".concat(startBitrate);
|
|
20898
|
+
}
|
|
20899
|
+
} else {
|
|
20900
|
+
// VP8 and some codecs may not have an existing fmtp line.
|
|
20901
|
+
media.fmtp.push({
|
|
20902
|
+
payload: codecPayload,
|
|
20903
|
+
config: "x-google-start-bitrate=".concat(startBitrate)
|
|
20904
|
+
});
|
|
20905
|
+
}
|
|
20906
|
+
return true;
|
|
20907
|
+
}
|
|
20908
|
+
const PCEvents = {
|
|
20909
|
+
NegotiationStarted: 'negotiationStarted',
|
|
20910
|
+
NegotiationComplete: 'negotiationComplete',
|
|
20911
|
+
// Fired with the offerId for every successful publisher answer application,
|
|
20912
|
+
// including answers that immediately recurse into another offer via
|
|
20913
|
+
// `renegotiate`. Use this rather than NegotiationComplete to know that a
|
|
20914
|
+
// specific offer has been negotiated end-to-end.
|
|
20915
|
+
OfferAnswered: 'offerAnswered',
|
|
20916
|
+
RTPVideoPayloadTypes: 'rtpVideoPayloadTypes'
|
|
20917
|
+
};
|
|
20918
|
+
/** @internal */
|
|
20919
|
+
class PCTransport extends eventsExports.EventEmitter {
|
|
20352
20920
|
get pc() {
|
|
20353
20921
|
if (!this._pc) {
|
|
20354
20922
|
this._pc = this.createPC();
|
|
@@ -20363,11 +20931,20 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20363
20931
|
this.iceLog = livekitLogger;
|
|
20364
20932
|
this.ddExtID = 0;
|
|
20365
20933
|
this.latestOfferId = 0;
|
|
20934
|
+
this.answeredOfferId = 0;
|
|
20366
20935
|
this.latestAcknowledgedOfferId = 0;
|
|
20367
20936
|
this.pendingCandidates = [];
|
|
20368
20937
|
this.restartingIce = false;
|
|
20369
20938
|
this.renegotiate = false;
|
|
20370
20939
|
this.trackBitrates = [];
|
|
20940
|
+
/**
|
|
20941
|
+
* Whether an offer carrying the connection-level `x-google-start-bitrate` has been
|
|
20942
|
+
* accepted locally. The hint is written once per peer connection: libwebrtc retains
|
|
20943
|
+
* `start_bitrate_bps` in `RtpBitrateConfigurator` and re-applies it on network route
|
|
20944
|
+
* changes, so a later rewrite is at best a no-op and at worst restarts a converged
|
|
20945
|
+
* bandwidth estimator. A new peer connection (full reconnect) seeds a new estimator.
|
|
20946
|
+
*/
|
|
20947
|
+
this.hasAppliedVideoStartBitrate = false;
|
|
20371
20948
|
this.remoteStereoMids = [];
|
|
20372
20949
|
this.remoteNackMids = [];
|
|
20373
20950
|
// debounced negotiate interface
|
|
@@ -20476,7 +21053,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20476
21053
|
}
|
|
20477
21054
|
setRemoteDescription(sd, offerId) {
|
|
20478
21055
|
return __awaiter(this, void 0, void 0, function* () {
|
|
20479
|
-
var _a
|
|
21056
|
+
var _a;
|
|
20480
21057
|
if (sd.type === 'answer' && this.latestOfferId > 0 && offerId > 0 && offerId !== this.latestOfferId) {
|
|
20481
21058
|
this.log.warn('ignoring answer for old offer', {
|
|
20482
21059
|
offerId,
|
|
@@ -20484,6 +21061,29 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20484
21061
|
});
|
|
20485
21062
|
return false;
|
|
20486
21063
|
}
|
|
21064
|
+
if (sd.type === 'answer') {
|
|
21065
|
+
if (isDuplicateAnswer(offerId, this.answerInFlight, this.answeredOfferId, (_a = this._pc) === null || _a === void 0 ? void 0 : _a.signalingState, this.pendingInitialOffer !== undefined)) {
|
|
21066
|
+
// a repeat of an answer already applied or being applied (e.g.
|
|
21067
|
+
// duplicated by a proxy): there is no local offer left to answer
|
|
21068
|
+
this.log.warn('ignoring duplicate answer', {
|
|
21069
|
+
offerId
|
|
21070
|
+
});
|
|
21071
|
+
return false;
|
|
21072
|
+
}
|
|
21073
|
+
// claimed before any await, so a copy arriving mid-apply is caught too;
|
|
21074
|
+
// released however the apply ends, so a genuine retry is still possible
|
|
21075
|
+
this.answerInFlight = offerId;
|
|
21076
|
+
}
|
|
21077
|
+
try {
|
|
21078
|
+
return yield this.applyRemoteDescription(sd, offerId);
|
|
21079
|
+
} finally {
|
|
21080
|
+
if (sd.type === 'answer') this.answerInFlight = undefined;
|
|
21081
|
+
}
|
|
21082
|
+
});
|
|
21083
|
+
}
|
|
21084
|
+
applyRemoteDescription(sd, offerId) {
|
|
21085
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21086
|
+
var _a, _b;
|
|
20487
21087
|
let mungedSDP = undefined;
|
|
20488
21088
|
if (sd.type === 'offer') {
|
|
20489
21089
|
let _extractStereoAndNack = extractStereoAndNackAudioFromOffer(sd),
|
|
@@ -20557,6 +21157,8 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20557
21157
|
mungedSDP = libExports.write(sdpParsed);
|
|
20558
21158
|
}
|
|
20559
21159
|
yield this.setMungedSDP(sd, mungedSDP, true);
|
|
21160
|
+
// only a successfully applied answer makes later copies duplicates
|
|
21161
|
+
if (sd.type === 'answer' && offerId > 0) this.answeredOfferId = offerId;
|
|
20560
21162
|
if (this.pendingCandidates.length > 0) {
|
|
20561
21163
|
this.iceLog.debug('flushing queued ICE candidates', {
|
|
20562
21164
|
count: this.pendingCandidates.length
|
|
@@ -20664,6 +21266,12 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20664
21266
|
sdp: offer.sdp
|
|
20665
21267
|
});
|
|
20666
21268
|
const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
|
|
21269
|
+
// One value for every video m-section, written only on the first offer that carries
|
|
21270
|
+
// local video: the hint is connection-level in libwebrtc, so differing per-section
|
|
21271
|
+
// values would be last-writer-wins on m-section order. Offers before any video is
|
|
21272
|
+
// published (data channel or audio only) find no target and leave the latch unset.
|
|
21273
|
+
const connectionStartBitrate = this.hasAppliedVideoStartBitrate ? undefined : computeConnectionStartBitrate(sdpParsed.media, this.trackBitrates, this.connectionSetupTimeEstimateMs());
|
|
21274
|
+
let appliedVideoStartBitrate = false;
|
|
20667
21275
|
sdpParsed.media.forEach(media => {
|
|
20668
21276
|
ensureIPAddrMatchVersion(media);
|
|
20669
21277
|
if (media.type === 'audio') {
|
|
@@ -20673,12 +21281,17 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20673
21281
|
if (!trackbr.cid) {
|
|
20674
21282
|
return false;
|
|
20675
21283
|
}
|
|
20676
|
-
const codecPayload =
|
|
21284
|
+
const codecPayload = findTrackCodecPayload(media, trackbr.cid, trackbr.codec);
|
|
20677
21285
|
if (codecPayload === undefined) {
|
|
20678
21286
|
return false;
|
|
20679
21287
|
}
|
|
20680
|
-
if (codecPayload > 0
|
|
20681
|
-
|
|
21288
|
+
if (codecPayload > 0) {
|
|
21289
|
+
if (connectionStartBitrate !== undefined) {
|
|
21290
|
+
appliedVideoStartBitrate = applyVideoStartBitrate(media, codecPayload, connectionStartBitrate) || appliedVideoStartBitrate;
|
|
21291
|
+
}
|
|
21292
|
+
if (isSVCCodec(trackbr.codec) && !isSafari()) {
|
|
21293
|
+
this.ddExtID = ensureVideoDDExtension(media, sdpParsed, this.ddExtID);
|
|
21294
|
+
}
|
|
20682
21295
|
}
|
|
20683
21296
|
return true;
|
|
20684
21297
|
});
|
|
@@ -20700,7 +21313,17 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20700
21313
|
});
|
|
20701
21314
|
return;
|
|
20702
21315
|
}
|
|
20703
|
-
|
|
21316
|
+
const mungedSdp = libExports.write(sdpParsed);
|
|
21317
|
+
yield this.setMungedSDP(offer, mungedSdp);
|
|
21318
|
+
// setMungedSDP falls back to the unmunged SDP on rejection. Only consume the
|
|
21319
|
+
// one-shot hint once the SDP carrying it has been accepted locally.
|
|
21320
|
+
if (appliedVideoStartBitrate && offer.sdp === mungedSdp) {
|
|
21321
|
+
this.hasAppliedVideoStartBitrate = true;
|
|
21322
|
+
this.log.info('applied x-google-start-bitrate', {
|
|
21323
|
+
startBitrateKbps: connectionStartBitrate,
|
|
21324
|
+
connectionSetupTimeMs: this.connectionSetupTimeEstimateMs()
|
|
21325
|
+
});
|
|
21326
|
+
}
|
|
20704
21327
|
this.onOffer(offer, this.latestOfferId);
|
|
20705
21328
|
} finally {
|
|
20706
21329
|
unlock();
|
|
@@ -20751,6 +21374,27 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
20751
21374
|
setTrackCodecBitrate(info) {
|
|
20752
21375
|
this.trackBitrates.push(info);
|
|
20753
21376
|
}
|
|
21377
|
+
/**
|
|
21378
|
+
* Record how long this peer connection took to set up. Called once, when the initial connect
|
|
21379
|
+
* succeeds; resumes and ICE restarts keep the estimator they have and never call this.
|
|
21380
|
+
*/
|
|
21381
|
+
setConnectionSetupTime(ms) {
|
|
21382
|
+
this.connectionSetupTimeMs = ms;
|
|
21383
|
+
}
|
|
21384
|
+
/** Record when the connection attempt that built this peer connection began. */
|
|
21385
|
+
setConnectionStartedAt(startedAt) {
|
|
21386
|
+
this.connectionStartedAt = startedAt;
|
|
21387
|
+
}
|
|
21388
|
+
/** The measured setup time, or while still connecting, the time elapsed so far. */
|
|
21389
|
+
connectionSetupTimeEstimateMs() {
|
|
21390
|
+
if (this.connectionSetupTimeMs !== undefined) {
|
|
21391
|
+
return this.connectionSetupTimeMs;
|
|
21392
|
+
}
|
|
21393
|
+
if (this.connectionStartedAt === undefined) {
|
|
21394
|
+
return undefined;
|
|
21395
|
+
}
|
|
21396
|
+
return Math.round(performance.now() - this.connectionStartedAt);
|
|
21397
|
+
}
|
|
20754
21398
|
setConfiguration(rtcConfig) {
|
|
20755
21399
|
var _a;
|
|
20756
21400
|
if (!this._pc) {
|
|
@@ -21096,6 +21740,19 @@ function conformBundledCodecFmtp(media, isPlaceholder) {
|
|
|
21096
21740
|
}
|
|
21097
21741
|
}
|
|
21098
21742
|
/** @internal */
|
|
21743
|
+
/**
|
|
21744
|
+
* Whether an incoming answer repeats one already applied or being applied.
|
|
21745
|
+
* Any answer for the offer whose answer is in flight is a duplicate, numbered
|
|
21746
|
+
* or not. With an offerId (current servers) an answer is also a duplicate if
|
|
21747
|
+
* that offer's answer was already applied. Without one, fall back to the
|
|
21748
|
+
* signaling state: in `stable` no local offer is pending, unless the initial
|
|
21749
|
+
* offer is still deferred (it is set locally only once its answer arrives).
|
|
21750
|
+
*/
|
|
21751
|
+
function isDuplicateAnswer(offerId, answerInFlight, answeredOfferId, signalingState, hasPendingInitialOffer) {
|
|
21752
|
+
if (answerInFlight === offerId) return true;
|
|
21753
|
+
if (offerId > 0) return offerId === answeredOfferId;
|
|
21754
|
+
return signalingState === 'stable' && !hasPendingInitialOffer;
|
|
21755
|
+
}
|
|
21099
21756
|
function extractStereoAndNackAudioFromOffer(offer) {
|
|
21100
21757
|
var _a;
|
|
21101
21758
|
const stereoMids = [];
|
|
@@ -21187,6 +21844,7 @@ const roomOptionDefaults = {
|
|
|
21187
21844
|
};
|
|
21188
21845
|
const roomConnectOptionDefaults = {
|
|
21189
21846
|
autoSubscribe: true,
|
|
21847
|
+
disableIceLite: false,
|
|
21190
21848
|
maxRetries: 1,
|
|
21191
21849
|
peerConnectionTimeout: 15000,
|
|
21192
21850
|
websocketTimeout: 15000
|
|
@@ -21400,7 +22058,9 @@ class PCTransportManager {
|
|
|
21400
22058
|
try {
|
|
21401
22059
|
if (this.isPublisherConnectionRequired && this.publisher.getConnectionState() !== 'connected' && this.publisher.getConnectionState() !== 'connecting') {
|
|
21402
22060
|
this.log.debug('negotiation required, start negotiating');
|
|
21403
|
-
this.publisher.negotiate()
|
|
22061
|
+
this.publisher.negotiate().catch(err => {
|
|
22062
|
+
this.log.error(err);
|
|
22063
|
+
});
|
|
21404
22064
|
}
|
|
21405
22065
|
yield Promise.all((_a = this.requiredTransports) === null || _a === void 0 ? void 0 : _a.map(transport => this.ensureTransportConnected(transport, abortController, timeout)));
|
|
21406
22066
|
} finally {
|
|
@@ -21551,7 +22211,8 @@ class PCTransportManager {
|
|
|
21551
22211
|
* Waiters are parked on the channel object captured at wait entry. If that object stops being
|
|
21552
22212
|
* current — replaced or torn down — its events may never fire again, so the owner must call
|
|
21553
22213
|
* {@link invalidateWaiters}, which aborts parked waiters (releasing the gate); the next waiter
|
|
21554
|
-
* gets a fresh controller.
|
|
22214
|
+
* gets a fresh controller. {@link attach} and {@link detach} do this, and close the released
|
|
22215
|
+
* handle, as part of handle turnover.
|
|
21555
22216
|
*/
|
|
21556
22217
|
class FlowControlledDataChannel {
|
|
21557
22218
|
constructor(opts) {
|
|
@@ -21571,25 +22232,38 @@ class FlowControlledDataChannel {
|
|
|
21571
22232
|
return this.handle;
|
|
21572
22233
|
}
|
|
21573
22234
|
/**
|
|
21574
|
-
* Attaches the channel handle this wrapper controls. Replacing an existing handle
|
|
21575
|
-
* parked waiters — their events would never fire again on the abandoned object — and
|
|
21576
|
-
* fresh controller
|
|
21577
|
-
*
|
|
21578
|
-
* waiter structurally impossible.
|
|
22235
|
+
* Attaches the channel handle this wrapper controls. Replacing an existing handle closes it and
|
|
22236
|
+
* rejects parked waiters — their events would never fire again on the abandoned object — and
|
|
22237
|
+
* installs a fresh controller. Wrappers outlive their handles: this is the one place handle
|
|
22238
|
+
* turnover happens, so a handle the wrapper lets go of is never left open, and stranding a
|
|
22239
|
+
* waiter is structurally impossible.
|
|
21579
22240
|
*/
|
|
21580
22241
|
attach(dc) {
|
|
21581
|
-
|
|
21582
|
-
this.invalidateWaiters('data channel replaced');
|
|
21583
|
-
}
|
|
22242
|
+
const old = this.handle;
|
|
21584
22243
|
this.handle = dc;
|
|
22244
|
+
if (old && old !== dc) {
|
|
22245
|
+
this.release(old, 'data channel replaced');
|
|
22246
|
+
}
|
|
21585
22247
|
}
|
|
21586
|
-
/** Detaches the handle on teardown, rejecting parked waiters. */
|
|
22248
|
+
/** Detaches and closes the handle on teardown, rejecting parked waiters. */
|
|
21587
22249
|
detach() {
|
|
21588
22250
|
let reason = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'data channel torn down';
|
|
21589
|
-
|
|
21590
|
-
this.invalidateWaiters(reason);
|
|
21591
|
-
}
|
|
22251
|
+
const old = this.handle;
|
|
21592
22252
|
this.handle = undefined;
|
|
22253
|
+
if (old) {
|
|
22254
|
+
this.release(old, reason);
|
|
22255
|
+
}
|
|
22256
|
+
}
|
|
22257
|
+
/**
|
|
22258
|
+
* Closes a handle the wrapper no longer controls. `close()` moves it to `closing` synchronously,
|
|
22259
|
+
* so senders still holding it (queued on the headroom lock) fail the open check once they get
|
|
22260
|
+
* the lock. Parked waiters are aborted directly instead of relying on the `close` event, which
|
|
22261
|
+
* fires asynchronously, if at all (the spec lets `pc.close()` skip it). Strip any handlers that
|
|
22262
|
+
* must not observe the close before calling this.
|
|
22263
|
+
*/
|
|
22264
|
+
release(dc, reason) {
|
|
22265
|
+
this.invalidateWaiters(reason);
|
|
22266
|
+
dc.close();
|
|
21593
22267
|
}
|
|
21594
22268
|
getChannel() {
|
|
21595
22269
|
return this.handle;
|
|
@@ -21620,32 +22294,35 @@ class FlowControlledDataChannel {
|
|
|
21620
22294
|
return this.headroomLock.lock();
|
|
21621
22295
|
}
|
|
21622
22296
|
/**
|
|
21623
|
-
* Resolves once the caller may send on
|
|
21624
|
-
*
|
|
22297
|
+
* Resolves once the caller may send on `dc`: immediately while its send buffer is at or below
|
|
22298
|
+
* the high-water mark, otherwise once the buffer has drained to the low-water mark (the
|
|
21625
22299
|
* `bufferedamountlow` event). Callers are serialized through the headroom lock so that, when
|
|
21626
22300
|
* the buffer drains, they refill it one at a time (up to the high-water mark) rather than all
|
|
21627
22301
|
* sending at once and overflowing the SCTP send buffer (see livekit/client-sdk-js#1995). The
|
|
21628
|
-
*
|
|
22302
|
+
* checks run inside the lock so queued callers proceed in FIFO order.
|
|
22303
|
+
*
|
|
22304
|
+
* The caller passes the handle it resolved before queueing, and the wait is against that one
|
|
22305
|
+
* handle throughout: if it was replaced meanwhile, it has been closed, so the wait rejects
|
|
22306
|
+
* rather than letting the caller send on the replacement out of turn.
|
|
21629
22307
|
*/
|
|
21630
|
-
waitForHeadroomWithLock() {
|
|
22308
|
+
waitForHeadroomWithLock(dc) {
|
|
21631
22309
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21632
22310
|
const unlock = yield this.lockHeadroom();
|
|
21633
22311
|
try {
|
|
21634
|
-
yield this.waitForHeadroomWithoutLock();
|
|
22312
|
+
yield this.waitForHeadroomWithoutLock(dc);
|
|
21635
22313
|
} finally {
|
|
21636
22314
|
unlock();
|
|
21637
22315
|
}
|
|
21638
22316
|
});
|
|
21639
22317
|
}
|
|
21640
22318
|
/** Core wait of {@link waitForHeadroomWithLock}. The caller must hold the headroom lock. */
|
|
21641
|
-
waitForHeadroomWithoutLock() {
|
|
22319
|
+
waitForHeadroomWithoutLock(dc) {
|
|
21642
22320
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21643
22321
|
if (this.isEngineClosed()) {
|
|
21644
22322
|
throw new UnexpectedConnectionState('engine closed');
|
|
21645
22323
|
}
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
throw new UnexpectedConnectionState("DataChannel not found, kind: ".concat(this.kind));
|
|
22324
|
+
if (dc.readyState !== 'open') {
|
|
22325
|
+
throw new UnexpectedConnectionState("DataChannel ".concat(this.kind, " is ").concat(dc.readyState));
|
|
21649
22326
|
}
|
|
21650
22327
|
if (this.isBelowHighWaterMark(dc)) {
|
|
21651
22328
|
return;
|
|
@@ -21735,7 +22412,16 @@ class LossyDataChannel extends FlowControlledDataChannel {
|
|
|
21735
22412
|
switch (this.bufferFullBehavior) {
|
|
21736
22413
|
case 'wait':
|
|
21737
22414
|
if (!this.isBelowHighWaterMark(dc)) {
|
|
21738
|
-
|
|
22415
|
+
try {
|
|
22416
|
+
yield this.waitForHeadroomWithLock(dc);
|
|
22417
|
+
} catch (error) {
|
|
22418
|
+
if (this.isEngineClosed()) {
|
|
22419
|
+
throw error;
|
|
22420
|
+
}
|
|
22421
|
+
// The channel closed or was replaced while this send waited. The replacement may
|
|
22422
|
+
// still be connecting, so drop the packet, as sends during a reconnect are.
|
|
22423
|
+
return;
|
|
22424
|
+
}
|
|
21739
22425
|
}
|
|
21740
22426
|
break;
|
|
21741
22427
|
case 'drop':
|
|
@@ -21917,7 +22603,7 @@ class ReliableDataChannel extends FlowControlledDataChannel {
|
|
|
21917
22603
|
return;
|
|
21918
22604
|
}
|
|
21919
22605
|
try {
|
|
21920
|
-
yield this.waitForHeadroomWithLock();
|
|
22606
|
+
yield this.waitForHeadroomWithLock(dc);
|
|
21921
22607
|
} catch (error) {
|
|
21922
22608
|
if (this.isEngineClosed()) {
|
|
21923
22609
|
// No replay is coming after an engine close — surface the failure.
|
|
@@ -21925,7 +22611,9 @@ class ReliableDataChannel extends FlowControlledDataChannel {
|
|
|
21925
22611
|
}
|
|
21926
22612
|
// Transient teardown (the channel closed or was replaced while we waited): the reliable
|
|
21927
22613
|
// channel promises delivery across resume, so queue the packet for the replay instead of
|
|
21928
|
-
// rejecting a send the app can't meaningfully retry.
|
|
22614
|
+
// rejecting a send the app can't meaningfully retry. This also keeps the order: a send
|
|
22615
|
+
// queued behind one aborted by a replacement fails the same way (the old handle is closed),
|
|
22616
|
+
// so it can't reach the replacement ahead of the lower sequence the replay will deliver.
|
|
21929
22617
|
this.messageBuffer.push({
|
|
21930
22618
|
data: msg,
|
|
21931
22619
|
sequence,
|
|
@@ -21960,13 +22648,18 @@ class ReliableDataChannel extends FlowControlledDataChannel {
|
|
|
21960
22648
|
*/
|
|
21961
22649
|
replay(lastMessageSeq) {
|
|
21962
22650
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21963
|
-
|
|
21964
|
-
if (!dc) {
|
|
22651
|
+
if (!this.getChannel()) {
|
|
21965
22652
|
return;
|
|
21966
22653
|
}
|
|
21967
22654
|
this.messageBuffer.popToSequence(lastMessageSeq);
|
|
21968
22655
|
const unlock = yield this.lockHeadroom();
|
|
21969
22656
|
try {
|
|
22657
|
+
// Resolved only once the lock is held: the handle can be replaced while the replay waits
|
|
22658
|
+
// for it.
|
|
22659
|
+
const dc = this.getChannel();
|
|
22660
|
+
if (!dc) {
|
|
22661
|
+
throw new UnexpectedConnectionState("DataChannel not found, kind: ".concat(this.kind));
|
|
22662
|
+
}
|
|
21970
22663
|
// Everything left after the ack cutoff must be re-handed to the current channel.
|
|
21971
22664
|
this.messageBuffer.markAllUnsent();
|
|
21972
22665
|
// Drain in passes, re-scanning the live buffer each time: a send that arrives (deferred,
|
|
@@ -21978,7 +22671,10 @@ class ReliableDataChannel extends FlowControlledDataChannel {
|
|
|
21978
22671
|
for (let batch = this.messageBuffer.getUnsent(); batch.length > 0; batch = this.messageBuffer.getUnsent()) {
|
|
21979
22672
|
for (const item of batch) {
|
|
21980
22673
|
// Respect flow control on resume too, so a large resend doesn't overflow the buffer.
|
|
21981
|
-
|
|
22674
|
+
// Against the handle resolved under the lock: if it is replaced mid-replay (another
|
|
22675
|
+
// resume), it has been closed, so this throws and that resume's replay picks up the
|
|
22676
|
+
// entries still flagged unsent.
|
|
22677
|
+
yield this.waitForHeadroomWithoutLock(dc);
|
|
21982
22678
|
dc.send(item.data);
|
|
21983
22679
|
this.messageBuffer.markSent(item);
|
|
21984
22680
|
}
|
|
@@ -22033,13 +22729,23 @@ function dataChannelHighWaterMark(kind) {
|
|
|
22033
22729
|
}const lossyDataChannelLabel = '_lossy';
|
|
22034
22730
|
const reliableDataChannelLabel = '_reliable';
|
|
22035
22731
|
const dataTrackDataChannelLabel = '_data_track';
|
|
22732
|
+
/** Unhooks every handler the manager may have wired onto `dc`, ahead of closing it. */
|
|
22733
|
+
function clearHandlers(dc) {
|
|
22734
|
+
dc.onbufferedamountlow = null;
|
|
22735
|
+
dc.onclose = null;
|
|
22736
|
+
dc.onclosing = null;
|
|
22737
|
+
dc.onerror = null;
|
|
22738
|
+
dc.onmessage = null;
|
|
22739
|
+
dc.onopen = null;
|
|
22740
|
+
}
|
|
22036
22741
|
/**
|
|
22037
22742
|
* Owns the engine's data channels: the three flow-controlled publisher channel wrappers (which
|
|
22038
22743
|
* live for the engine's lifetime and have RTCDataChannel handles attached/detached as peer
|
|
22039
22744
|
* connections come and go) plus the subscriber-side receive handles adopted by label.
|
|
22040
22745
|
*
|
|
22041
|
-
* Handle turnover goes through {@link FlowControlledDataChannel.attach}/`detach`, which
|
|
22042
|
-
* parked headroom waiters as a built-in — there is no separate
|
|
22746
|
+
* Handle turnover goes through {@link FlowControlledDataChannel.attach}/`detach`, which close the
|
|
22747
|
+
* released handle and reject parked headroom waiters as a built-in — there is no separate
|
|
22748
|
+
* invalidation step to forget.
|
|
22043
22749
|
*/
|
|
22044
22750
|
class DataChannelManager {
|
|
22045
22751
|
constructor(opts) {
|
|
@@ -22097,17 +22803,16 @@ class DataChannelManager {
|
|
|
22097
22803
|
}
|
|
22098
22804
|
/**
|
|
22099
22805
|
* Creates the three publisher data channels on the given transport, wires their handlers, and
|
|
22100
|
-
* attaches them to the wrappers — attaching
|
|
22101
|
-
*
|
|
22806
|
+
* attaches them to the wrappers — attaching closes any replaced channel objects and rejects the
|
|
22807
|
+
* waiters still parked on them.
|
|
22102
22808
|
*/
|
|
22103
22809
|
createPublisherChannels(pcManager) {
|
|
22104
|
-
//
|
|
22810
|
+
// Recreating (the Safari null-id path): attaching the new handles below closes the old ones,
|
|
22811
|
+
// so unhook them first — the old channel's close must not report an unexpected close.
|
|
22105
22812
|
for (const channel of [this.lossy, this.reliable, this.dataTrack]) {
|
|
22106
22813
|
const old = channel.channelHandle;
|
|
22107
22814
|
if (old) {
|
|
22108
|
-
old
|
|
22109
|
-
old.onerror = null;
|
|
22110
|
-
old.onclose = null;
|
|
22815
|
+
clearHandlers(old);
|
|
22111
22816
|
}
|
|
22112
22817
|
}
|
|
22113
22818
|
const wire = (channel, dc, onMessage) => {
|
|
@@ -22158,38 +22863,35 @@ class DataChannelManager {
|
|
|
22158
22863
|
return true;
|
|
22159
22864
|
}
|
|
22160
22865
|
/**
|
|
22161
|
-
* Tears down all channels for a peer-connection cleanup:
|
|
22162
|
-
*
|
|
22163
|
-
*
|
|
22164
|
-
*
|
|
22866
|
+
* Tears down all channels for a peer-connection cleanup: strips handlers, closes the handles,
|
|
22867
|
+
* and resets the reliable session state. Detaching the publisher handles closes them and
|
|
22868
|
+
* rejects parked waiters (the spec allows `pc.close()` to transition channels to 'closed'
|
|
22869
|
+
* without firing events, so waiting for browser close events is not an option).
|
|
22165
22870
|
*/
|
|
22166
22871
|
teardown() {
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
dc.onbufferedamountlow = null;
|
|
22178
|
-
dc.onclose = null;
|
|
22179
|
-
dc.onclosing = null;
|
|
22180
|
-
dc.onerror = null;
|
|
22181
|
-
dc.onmessage = null;
|
|
22182
|
-
dc.onopen = null;
|
|
22183
|
-
dc.close();
|
|
22184
|
-
};
|
|
22185
|
-
for (const channel of [this.lossy, this.reliable, this.dataTrack]) {
|
|
22872
|
+
// Detach the data channel handlers before closing anything. Closing a peer connection tears
|
|
22873
|
+
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
|
|
22874
|
+
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
|
|
22875
|
+
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
|
|
22876
|
+
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
|
|
22877
|
+
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
|
|
22878
|
+
const publisherDataChannels = [this.lossy, this.reliable, this.dataTrack];
|
|
22879
|
+
const subscriberDataChannels = [this.lossySub, this.reliableSub, this.dataTrackSub];
|
|
22880
|
+
// Publisher handles are closed by detach, which also rejects parked waiters.
|
|
22881
|
+
for (const channel of publisherDataChannels) {
|
|
22186
22882
|
const dc = channel.channelHandle;
|
|
22883
|
+
if (dc) {
|
|
22884
|
+
clearHandlers(dc);
|
|
22885
|
+
}
|
|
22187
22886
|
channel.detach('peer connections cleaned up');
|
|
22188
|
-
dcCleanup(dc);
|
|
22189
22887
|
}
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
|
|
22888
|
+
// Subscriber handles have no wrapper, so close them directly.
|
|
22889
|
+
for (const dc of subscriberDataChannels) {
|
|
22890
|
+
if (dc) {
|
|
22891
|
+
clearHandlers(dc);
|
|
22892
|
+
dc.close();
|
|
22893
|
+
}
|
|
22894
|
+
}
|
|
22193
22895
|
this.lossySub = undefined;
|
|
22194
22896
|
this.reliableSub = undefined;
|
|
22195
22897
|
this.dataTrackSub = undefined;
|
|
@@ -22351,6 +23053,9 @@ class LocalTrack extends Track {
|
|
|
22351
23053
|
get isUserProvided() {
|
|
22352
23054
|
return this.providedByUser;
|
|
22353
23055
|
}
|
|
23056
|
+
get isStopped() {
|
|
23057
|
+
return this.manuallyStopped;
|
|
23058
|
+
}
|
|
22354
23059
|
get mediaStreamTrack() {
|
|
22355
23060
|
var _a, _b;
|
|
22356
23061
|
return (_b = (_a = this.processor) === null || _a === void 0 ? void 0 : _a.processedTrack) !== null && _b !== void 0 ? _b : this._mediaStreamTrack;
|
|
@@ -22408,6 +23113,9 @@ class LocalTrack extends Track {
|
|
|
22408
23113
|
track: newTrack,
|
|
22409
23114
|
kind: this.kind,
|
|
22410
23115
|
element: this.processorElement,
|
|
23116
|
+
// audio processors need the context they were initialised with in order to rebuild
|
|
23117
|
+
// their graph, omitting it here would leave them without one
|
|
23118
|
+
audioContext: this.audioContext,
|
|
22411
23119
|
localTrack: this
|
|
22412
23120
|
});
|
|
22413
23121
|
processedTrack = this.processor.processedTrack;
|
|
@@ -22431,6 +23139,18 @@ class LocalTrack extends Track {
|
|
|
22431
23139
|
attachToElement(processedTrack !== null && processedTrack !== void 0 ? processedTrack : newTrack, el);
|
|
22432
23140
|
});
|
|
22433
23141
|
}
|
|
23142
|
+
yield this.onMediaStreamTrackChanged();
|
|
23143
|
+
});
|
|
23144
|
+
}
|
|
23145
|
+
/**
|
|
23146
|
+
* Hook invoked after the underlying MediaStreamTrack has been swapped and the processor has
|
|
23147
|
+
* been restarted around it, so `processor.processedTrack` already holds the new track.
|
|
23148
|
+
* Unlike `onSenderTrackSwapped` this also fires on the restart and unmute paths.
|
|
23149
|
+
*/
|
|
23150
|
+
onMediaStreamTrackChanged() {
|
|
23151
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23152
|
+
// base implementation is a no-op; LocalAudioTrack overrides this to move the krisp
|
|
23153
|
+
// feature listeners onto the new processed track.
|
|
22434
23154
|
});
|
|
22435
23155
|
}
|
|
22436
23156
|
waitForDimensions() {
|
|
@@ -22438,21 +23158,29 @@ class LocalTrack extends Track {
|
|
|
22438
23158
|
var _this = this;
|
|
22439
23159
|
let timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_DIMENSIONS_TIMEOUT;
|
|
22440
23160
|
return function* () {
|
|
22441
|
-
var _a;
|
|
22442
23161
|
if (_this.kind === Track.Kind.Audio) {
|
|
22443
23162
|
throw new Error('cannot get dimensions for audio tracks');
|
|
22444
23163
|
}
|
|
22445
|
-
if (((_a = getBrowser()) === null || _a === void 0 ? void 0 : _a.os) === 'iOS') {
|
|
22446
|
-
// browsers report wrong initial resolution on iOS.
|
|
22447
|
-
// when slightly delaying the call to .getSettings(), the correct resolution is being reported
|
|
22448
|
-
yield sleep(10);
|
|
22449
|
-
}
|
|
22450
23164
|
const started = Date.now();
|
|
22451
|
-
|
|
23165
|
+
if (isAppleMobile()) {
|
|
23166
|
+
// iOS and iPadOS keep reporting the camera's sensor frame from getSettings() until the
|
|
23167
|
+
// camera has delivered its first picture, so a portrait capture initially reads as
|
|
23168
|
+
// landscape. Wait for that first frame instead of guessing at a delay.
|
|
23169
|
+
// Gated to the Apple capture pipeline rather than run everywhere: elsewhere getSettings() is
|
|
23170
|
+
// already right and the first frame can be hundreds of milliseconds out (measured ~500ms on
|
|
23171
|
+
// macOS Safari), which would be pure added publish latency.
|
|
23172
|
+
// https://github.com/livekit/client-sdk-js/issues/2099
|
|
23173
|
+
// https://github.com/livekit/client-sdk-js/issues/2107
|
|
23174
|
+
yield waitForFirstVideoFrame(_this._mediaStreamTrack, timeout, _this.attachedElements);
|
|
23175
|
+
}
|
|
23176
|
+
while (true) {
|
|
22452
23177
|
const dims = _this.dimensions;
|
|
22453
23178
|
if (dims) {
|
|
22454
23179
|
return dims;
|
|
22455
23180
|
}
|
|
23181
|
+
if (Date.now() - started >= timeout) {
|
|
23182
|
+
break;
|
|
23183
|
+
}
|
|
22456
23184
|
yield sleep(50);
|
|
22457
23185
|
}
|
|
22458
23186
|
throw new TrackInvalidError('unable to get track dimensions after timeout');
|
|
@@ -22461,7 +23189,7 @@ class LocalTrack extends Track {
|
|
|
22461
23189
|
}
|
|
22462
23190
|
setDeviceId(deviceId) {
|
|
22463
23191
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22464
|
-
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId)) {
|
|
23192
|
+
if (this._constraints.deviceId === deviceId && this._mediaStreamTrack.getSettings().deviceId === unwrapConstraint(deviceId) && this._mediaStreamTrack.readyState === 'live') {
|
|
22465
23193
|
return true;
|
|
22466
23194
|
}
|
|
22467
23195
|
this._constraints.deviceId = deviceId;
|
|
@@ -22589,24 +23317,40 @@ class LocalTrack extends Track {
|
|
|
22589
23317
|
// 'A MediaStreamTrack ended due to a capture failure`
|
|
22590
23318
|
this._mediaStreamTrack.stop();
|
|
22591
23319
|
// create new track and attach
|
|
22592
|
-
|
|
23320
|
+
let mediaStream;
|
|
23321
|
+
try {
|
|
23322
|
+
mediaStream = yield navigator.mediaDevices.getUserMedia(streamConstraints);
|
|
23323
|
+
} catch (e) {
|
|
23324
|
+
markDeviceAcquisitionFailure(e);
|
|
23325
|
+
throw e;
|
|
23326
|
+
}
|
|
22593
23327
|
const newTrack = mediaStream.getTracks()[0];
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
23328
|
+
try {
|
|
23329
|
+
if (this.kind === Track.Kind.Video) {
|
|
23330
|
+
// we already captured the audio track with the constraints, so we only need to apply the video constraints
|
|
23331
|
+
yield newTrack.applyConstraints(otherConstraints);
|
|
23332
|
+
}
|
|
23333
|
+
newTrack.addEventListener('ended', this.handleEnded);
|
|
23334
|
+
this.log.debug('re-acquired MediaStreamTrack', this.logContext);
|
|
23335
|
+
yield this.setMediaStreamTrack(newTrack, false, isUnmuting);
|
|
23336
|
+
} catch (e) {
|
|
23337
|
+
// setMediaStreamTrack can throw after acquisition but before it adopts the new track,
|
|
23338
|
+
// which would leave the capture device live with nothing referencing it
|
|
23339
|
+
if (this._mediaStreamTrack !== newTrack) {
|
|
23340
|
+
newTrack.stop();
|
|
23341
|
+
}
|
|
23342
|
+
throw e;
|
|
22597
23343
|
}
|
|
22598
|
-
newTrack.addEventListener('ended', this.handleEnded);
|
|
22599
|
-
this.log.debug('re-acquired MediaStreamTrack', this.logContext);
|
|
22600
|
-
yield this.setMediaStreamTrack(newTrack, false, isUnmuting);
|
|
22601
23344
|
this._constraints = constraints;
|
|
22602
23345
|
this.pendingDeviceChange = false;
|
|
22603
23346
|
this.emit(TrackEvent.Restarted, this);
|
|
23347
|
+
return this;
|
|
23348
|
+
} finally {
|
|
23349
|
+
// in the finally because a throw can also leave the new track adopted, and so still live
|
|
22604
23350
|
if (this.manuallyStopped) {
|
|
22605
23351
|
this.log.warn('track was stopped during a restart, stopping restarted track', this.logContext);
|
|
22606
23352
|
this.stop();
|
|
22607
23353
|
}
|
|
22608
|
-
return this;
|
|
22609
|
-
} finally {
|
|
22610
23354
|
unlock();
|
|
22611
23355
|
}
|
|
22612
23356
|
});
|
|
@@ -22647,7 +23391,13 @@ class LocalTrack extends Track {
|
|
|
22647
23391
|
this._mediaStreamTrack.removeEventListener('ended', this.handleEnded);
|
|
22648
23392
|
this._mediaStreamTrack.removeEventListener('mute', this.handleTrackMuteEvent);
|
|
22649
23393
|
this._mediaStreamTrack.removeEventListener('unmute', this.handleTrackUnmuteEvent);
|
|
22650
|
-
|
|
23394
|
+
// `stop` is synchronous, so we can't await the teardown of the processor here.
|
|
23395
|
+
// make sure a failing teardown doesn't surface as an unhandled rejection.
|
|
23396
|
+
(_a = this.processor) === null || _a === void 0 ? void 0 : _a.destroy().catch(error => {
|
|
23397
|
+
this.log.error('failed to destroy processor', Object.assign(Object.assign({}, this.logContext), {
|
|
23398
|
+
error
|
|
23399
|
+
}));
|
|
23400
|
+
});
|
|
22651
23401
|
this.processor = undefined;
|
|
22652
23402
|
}
|
|
22653
23403
|
/**
|
|
@@ -22726,6 +23476,11 @@ class LocalTrack extends Track {
|
|
|
22726
23476
|
* Sets a processor on this track.
|
|
22727
23477
|
* See https://github.com/livekit/track-processors-js for example usage
|
|
22728
23478
|
*
|
|
23479
|
+
* The processor lives on the track, so stopping the track destroys it. Unpublishing and
|
|
23480
|
+
* disconnecting both stop the track by default, which means a processor has to be set again on
|
|
23481
|
+
* the track published after a reconnect. See {@link TrackProcessor} for the full lifecycle,
|
|
23482
|
+
* including what `stopLocalTrackOnUnpublish: false` changes.
|
|
23483
|
+
*
|
|
22729
23484
|
* @param processor
|
|
22730
23485
|
* @param showProcessedStreamLocally
|
|
22731
23486
|
* @returns
|
|
@@ -22795,6 +23550,10 @@ class LocalTrack extends Track {
|
|
|
22795
23550
|
}();
|
|
22796
23551
|
});
|
|
22797
23552
|
}
|
|
23553
|
+
/**
|
|
23554
|
+
* Returns the processor currently set on this track, or `undefined` if there is none. A track
|
|
23555
|
+
* that was stopped by an unpublish or a disconnect has none. See {@link TrackProcessor}.
|
|
23556
|
+
*/
|
|
22798
23557
|
getProcessor() {
|
|
22799
23558
|
return this.processor;
|
|
22800
23559
|
}
|
|
@@ -22802,6 +23561,8 @@ class LocalTrack extends Track {
|
|
|
22802
23561
|
* Stops the track processor
|
|
22803
23562
|
* See https://github.com/livekit/track-processors-js for example usage
|
|
22804
23563
|
*
|
|
23564
|
+
* Only needed to remove a processor from a track that stays published. Unpublishing or
|
|
23565
|
+
* disconnecting already destroys it. See {@link TrackProcessor}.
|
|
22805
23566
|
*/
|
|
22806
23567
|
stopProcessor() {
|
|
22807
23568
|
return __awaiter(this, arguments, void 0, function () {
|
|
@@ -23042,6 +23803,38 @@ class LocalTrack extends Track {
|
|
|
23042
23803
|
this.monitorSender();
|
|
23043
23804
|
}, monitorFrequency);
|
|
23044
23805
|
}
|
|
23806
|
+
/**
|
|
23807
|
+
* registers the krisp feature listeners on the given processed track, moving them over from
|
|
23808
|
+
* a previously observed one. Passing `undefined` just removes the existing listeners.
|
|
23809
|
+
*/
|
|
23810
|
+
observeProcessedTrack(processedTrack) {
|
|
23811
|
+
if (this.observedProcessedTrack === processedTrack) {
|
|
23812
|
+
return;
|
|
23813
|
+
}
|
|
23814
|
+
if (this.observedProcessedTrack) {
|
|
23815
|
+
this.observedProcessedTrack.removeEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
|
|
23816
|
+
this.observedProcessedTrack.removeEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
|
|
23817
|
+
this.observedProcessedTrack = undefined;
|
|
23818
|
+
if (this.isKrispNoiseFilterEnabled) {
|
|
23819
|
+
// we're no longer observing the track that had the filter enabled, so the feature
|
|
23820
|
+
// can't be active anymore. Make sure the state doesn't get stuck on `true`.
|
|
23821
|
+
this.handleKrispNoiseFilterDisable();
|
|
23822
|
+
}
|
|
23823
|
+
}
|
|
23824
|
+
if (!processedTrack) {
|
|
23825
|
+
return;
|
|
23826
|
+
}
|
|
23827
|
+
processedTrack.addEventListener('enable-lk-krisp-noise-filter', this.handleKrispNoiseFilterEnable);
|
|
23828
|
+
processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
|
|
23829
|
+
this.observedProcessedTrack = processedTrack;
|
|
23830
|
+
}
|
|
23831
|
+
/**
|
|
23832
|
+
* Sets a processor on this track.
|
|
23833
|
+
*
|
|
23834
|
+
* The processor is destroyed when the track is stopped, and also when the audio context goes
|
|
23835
|
+
* away, so a disconnect ends it on both counts and it has to be set again after reconnecting.
|
|
23836
|
+
* See {@link TrackProcessor} for the full lifecycle.
|
|
23837
|
+
*/
|
|
23045
23838
|
setProcessor(processor) {
|
|
23046
23839
|
return __awaiter(this, void 0, void 0, function* () {
|
|
23047
23840
|
var _a;
|
|
@@ -23065,8 +23858,7 @@ class LocalTrack extends Track {
|
|
|
23065
23858
|
this.processor = processor;
|
|
23066
23859
|
if (this.processor.processedTrack) {
|
|
23067
23860
|
yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
|
|
23068
|
-
this.processor.processedTrack
|
|
23069
|
-
this.processor.processedTrack.addEventListener('disable-lk-krisp-noise-filter', this.handleKrispNoiseFilterDisable);
|
|
23861
|
+
this.observeProcessedTrack(this.processor.processedTrack);
|
|
23070
23862
|
}
|
|
23071
23863
|
this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
|
|
23072
23864
|
} finally {
|
|
@@ -23074,12 +23866,73 @@ class LocalTrack extends Track {
|
|
|
23074
23866
|
}
|
|
23075
23867
|
});
|
|
23076
23868
|
}
|
|
23869
|
+
onMediaStreamTrackChanged() {
|
|
23870
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23871
|
+
var _a;
|
|
23872
|
+
// the processor rebuilds its graph around the new track, so the krisp listeners have to
|
|
23873
|
+
// follow it onto whatever it hands back
|
|
23874
|
+
this.observeProcessedTrack((_a = this.processor) === null || _a === void 0 ? void 0 : _a.processedTrack);
|
|
23875
|
+
});
|
|
23876
|
+
}
|
|
23877
|
+
internalStopProcessor() {
|
|
23878
|
+
const _super = Object.create(null, {
|
|
23879
|
+
internalStopProcessor: {
|
|
23880
|
+
get: () => super.internalStopProcessor
|
|
23881
|
+
}
|
|
23882
|
+
});
|
|
23883
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
23884
|
+
var _this = this;
|
|
23885
|
+
let keepElement = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
23886
|
+
return function* () {
|
|
23887
|
+
_this.observeProcessedTrack(undefined);
|
|
23888
|
+
yield _super.internalStopProcessor.call(_this, keepElement);
|
|
23889
|
+
}();
|
|
23890
|
+
});
|
|
23891
|
+
}
|
|
23892
|
+
stop() {
|
|
23893
|
+
this.observeProcessedTrack(undefined);
|
|
23894
|
+
super.stop();
|
|
23895
|
+
}
|
|
23077
23896
|
/**
|
|
23078
23897
|
* @internal
|
|
23079
23898
|
* @experimental
|
|
23899
|
+
*
|
|
23900
|
+
* A processor's nodes (`AudioWorkletNode`, ...) can't move between contexts, so the processor
|
|
23901
|
+
* is rebuilt whenever the context changes and torn down when there is none left to run in.
|
|
23080
23902
|
*/
|
|
23081
23903
|
setAudioContext(audioContext) {
|
|
23082
|
-
this
|
|
23904
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23905
|
+
var _a;
|
|
23906
|
+
if (this.audioContext === audioContext) {
|
|
23907
|
+
return;
|
|
23908
|
+
}
|
|
23909
|
+
const unlock = yield this.trackChangeLock.lock();
|
|
23910
|
+
try {
|
|
23911
|
+
this.audioContext = audioContext;
|
|
23912
|
+
if (!this.processor || isReactNative()) {
|
|
23913
|
+
return;
|
|
23914
|
+
}
|
|
23915
|
+
if (!audioContext) {
|
|
23916
|
+
this.log.debug('stopping audio processor, audio context has been removed', this.logContext);
|
|
23917
|
+
yield this.internalStopProcessor();
|
|
23918
|
+
return;
|
|
23919
|
+
}
|
|
23920
|
+
this.log.debug('restarting audio processor on new audio context', this.logContext);
|
|
23921
|
+
yield this.processor.restart({
|
|
23922
|
+
kind: this.kind,
|
|
23923
|
+
track: this._mediaStreamTrack,
|
|
23924
|
+
audioContext,
|
|
23925
|
+
localTrack: this
|
|
23926
|
+
});
|
|
23927
|
+
if (this.processor.processedTrack) {
|
|
23928
|
+
yield (_a = this.sender) === null || _a === void 0 ? void 0 : _a.replaceTrack(this.processor.processedTrack);
|
|
23929
|
+
this.observeProcessedTrack(this.processor.processedTrack);
|
|
23930
|
+
}
|
|
23931
|
+
this.emit(TrackEvent.TrackProcessorUpdate, this.processor);
|
|
23932
|
+
} finally {
|
|
23933
|
+
unlock();
|
|
23934
|
+
}
|
|
23935
|
+
});
|
|
23083
23936
|
}
|
|
23084
23937
|
getSenderStats() {
|
|
23085
23938
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -23155,24 +24008,6 @@ const computeDefaultScreenShareSimulcastPresets = fromPreset => {
|
|
|
23155
24008
|
return new VideoPreset(Math.floor(fromPreset.width / t.scaleResolutionDownBy), Math.floor(fromPreset.height / t.scaleResolutionDownBy), Math.max(150000, Math.floor(fromPreset.encoding.maxBitrate / (Math.pow(t.scaleResolutionDownBy, 2) * (((_a = fromPreset.encoding.maxFramerate) !== null && _a !== void 0 ? _a : 30) / ((_b = t.fps) !== null && _b !== void 0 ? _b : 30))))), t.fps, fromPreset.encoding.priority);
|
|
23156
24009
|
});
|
|
23157
24010
|
};
|
|
23158
|
-
// /**
|
|
23159
|
-
// *
|
|
23160
|
-
// * @internal
|
|
23161
|
-
// * @experimental
|
|
23162
|
-
// */
|
|
23163
|
-
// const computeDefaultMultiCodecSimulcastEncodings = (width: number, height: number) => {
|
|
23164
|
-
// // use vp8 as a default
|
|
23165
|
-
// const vp8 = determineAppropriateEncoding(false, width, height);
|
|
23166
|
-
// const vp9 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.9 };
|
|
23167
|
-
// const h264 = { ...vp8, maxBitrate: vp8.maxBitrate * 1.1 };
|
|
23168
|
-
// const av1 = { ...vp8, maxBitrate: vp8.maxBitrate * 0.7 };
|
|
23169
|
-
// return {
|
|
23170
|
-
// vp8,
|
|
23171
|
-
// vp9,
|
|
23172
|
-
// h264,
|
|
23173
|
-
// av1,
|
|
23174
|
-
// };
|
|
23175
|
-
// };
|
|
23176
24011
|
const videoRids = ['q', 'h', 'f'];
|
|
23177
24012
|
/* @internal */
|
|
23178
24013
|
function computeVideoEncodings(isScreenShare, width, height, options) {
|
|
@@ -23927,6 +24762,13 @@ class LocalVideoTrack extends LocalTrack {
|
|
|
23927
24762
|
yield sender.setParameters(params);
|
|
23928
24763
|
});
|
|
23929
24764
|
}
|
|
24765
|
+
/**
|
|
24766
|
+
* Sets a processor on this track, and wires its processed track into the simulcast codecs.
|
|
24767
|
+
*
|
|
24768
|
+
* The processor is destroyed when the track is stopped, which unpublishing and disconnecting
|
|
24769
|
+
* both do by default, so it has to be set again after reconnecting. See {@link TrackProcessor}
|
|
24770
|
+
* for the full lifecycle.
|
|
24771
|
+
*/
|
|
23930
24772
|
setProcessor(processor_1) {
|
|
23931
24773
|
const _super = Object.create(null, {
|
|
23932
24774
|
setProcessor: {
|
|
@@ -24027,12 +24869,20 @@ class LocalVideoTrack extends LocalTrack {
|
|
|
24027
24869
|
// browser will reenable disabled codec/layers after new codec has been published,
|
|
24028
24870
|
// so refresh subscribedCodecs after publish a new codec
|
|
24029
24871
|
setTimeout(() => {
|
|
24030
|
-
|
|
24031
|
-
this.setPublishingCodecs(this.subscribedCodecs);
|
|
24032
|
-
}
|
|
24872
|
+
this.refreshSubscribedCodecs();
|
|
24033
24873
|
}, refreshSubscribedCodecAfterNewCodec);
|
|
24034
24874
|
});
|
|
24035
24875
|
}
|
|
24876
|
+
/** @interal */
|
|
24877
|
+
refreshSubscribedCodecs() {
|
|
24878
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24879
|
+
if (this.subscribedCodecs) {
|
|
24880
|
+
return this.setPublishingCodecs(this.subscribedCodecs).catch(e => this.log.warn("failed to refresh codec for track", Object.assign(Object.assign({}, this.logContext), {
|
|
24881
|
+
error: e
|
|
24882
|
+
})));
|
|
24883
|
+
}
|
|
24884
|
+
});
|
|
24885
|
+
}
|
|
24036
24886
|
/**
|
|
24037
24887
|
* @internal
|
|
24038
24888
|
* Sets codecs that should be publishing, returns new codecs that have not yet
|
|
@@ -24377,6 +25227,13 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24377
25227
|
this.midToTrackId = {};
|
|
24378
25228
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
24379
25229
|
this.isWaitingForNetworkReconnect = false;
|
|
25230
|
+
/**
|
|
25231
|
+
* Abort handlers for the in-flight `negotiate()` calls, which a single central
|
|
25232
|
+
* Closing/Restarting listener pair fans out to. Registering a listener pair per call instead
|
|
25233
|
+
* accumulates them on the engine emitter and trips its max-listener warning under
|
|
25234
|
+
* renegotiation bursts.
|
|
25235
|
+
*/
|
|
25236
|
+
this.pendingNegotiationAborts = new Set();
|
|
24380
25237
|
this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) {
|
|
24381
25238
|
var _this = this;
|
|
24382
25239
|
let channel = _ref.channel;
|
|
@@ -24513,7 +25370,17 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24513
25370
|
return;
|
|
24514
25371
|
}
|
|
24515
25372
|
if (connection === leaveReconnect) {
|
|
24516
|
-
|
|
25373
|
+
// The server asked for a reconnect, so skip any remaining backoff. Use the policy's first
|
|
25374
|
+
// retry delay instead of 0 so that clients told to reconnect together don't all reconnect
|
|
25375
|
+
// at the same instant.
|
|
25376
|
+
delay = this.getNextRetryDelay({
|
|
25377
|
+
elapsedMs: duration,
|
|
25378
|
+
retryCount: 0
|
|
25379
|
+
});
|
|
25380
|
+
if (delay === null) {
|
|
25381
|
+
disconnect(duration);
|
|
25382
|
+
return;
|
|
25383
|
+
}
|
|
24517
25384
|
}
|
|
24518
25385
|
this.log.debug("reconnecting in ".concat(delay, "ms"));
|
|
24519
25386
|
this.clearReconnectTimeout();
|
|
@@ -24622,6 +25489,14 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24622
25489
|
this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response);
|
|
24623
25490
|
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
24624
25491
|
this.client.onJoined = joinResponse => this.emit(EngineEvent.Joined, joinResponse);
|
|
25492
|
+
const abortPendingNegotiations = () => {
|
|
25493
|
+
// Iterate a copy: each handler removes itself from the set as its negotiation settles.
|
|
25494
|
+
for (const abort of Array.from(this.pendingNegotiationAborts)) {
|
|
25495
|
+
abort();
|
|
25496
|
+
}
|
|
25497
|
+
};
|
|
25498
|
+
this.on(EngineEvent.Closing, abortPendingNegotiations);
|
|
25499
|
+
this.on(EngineEvent.Restarting, abortPendingNegotiations);
|
|
24625
25500
|
}
|
|
24626
25501
|
/** @internal */
|
|
24627
25502
|
get logContext() {
|
|
@@ -24638,7 +25513,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24638
25513
|
var _this2 = this;
|
|
24639
25514
|
let useV0Path = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
|
|
24640
25515
|
return function* () {
|
|
24641
|
-
var _a, _b, _c;
|
|
25516
|
+
var _a, _b, _c, _d;
|
|
24642
25517
|
_this2._isNewlyCreated = false;
|
|
24643
25518
|
_this2.url = url;
|
|
24644
25519
|
_this2.token = token;
|
|
@@ -24646,6 +25521,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24646
25521
|
_this2.maxJoinAttempts = opts.maxRetries;
|
|
24647
25522
|
try {
|
|
24648
25523
|
_this2.joinAttempts += 1;
|
|
25524
|
+
_this2.joinStartedAt = performance.now();
|
|
24649
25525
|
_this2.setupSignalClientCallbacks();
|
|
24650
25526
|
// Whether the initial publisher offer is bundled with the join request. Computed once and
|
|
24651
25527
|
// reused after the join below. Only the (non-Firefox) offer-with-join path does this.
|
|
@@ -24675,6 +25551,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24675
25551
|
throw ConnectionError.serviceNotFound('Simulated v1 path failure', 'v0-rtc');
|
|
24676
25552
|
}
|
|
24677
25553
|
const joinResponse = yield _this2.client.join(url, token, opts, abortSignal, useV0Path, offerProto);
|
|
25554
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
25555
|
+
_this2.log.info('join cancelled after the signal join response');
|
|
25556
|
+
throw ConnectionError.cancelled('Connection aborted');
|
|
25557
|
+
}
|
|
24678
25558
|
_this2._isClosed = false;
|
|
24679
25559
|
_this2.latestJoinResponse = joinResponse;
|
|
24680
25560
|
_this2.participantSid = (_b = joinResponse.participant) === null || _b === void 0 ? void 0 : _b.sid;
|
|
@@ -24687,6 +25567,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24687
25567
|
// the server's ICE servers and topology, then negotiate separately rather than bundling
|
|
24688
25568
|
// the offer with the join.
|
|
24689
25569
|
yield _this2.configure(joinResponse, !useV0Path);
|
|
25570
|
+
if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) {
|
|
25571
|
+
_this2.log.info('join cancelled after configuring peer connections');
|
|
25572
|
+
throw ConnectionError.cancelled('Connection aborted');
|
|
25573
|
+
}
|
|
24690
25574
|
if (!useV0Path) {
|
|
24691
25575
|
// The V1 first offer must carry the media layout so Firefox binds receive decoders for
|
|
24692
25576
|
// subscribed tracks — without it, subscribed audio/video arrive as RTP but
|
|
@@ -24701,6 +25585,11 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24701
25585
|
});
|
|
24702
25586
|
}
|
|
24703
25587
|
}
|
|
25588
|
+
if (_this2.joinStartedAt !== undefined) {
|
|
25589
|
+
// Lets a video published before the transports connect be seeded from the time elapsed
|
|
25590
|
+
// so far, rather than delaying its offer until the setup time is measured.
|
|
25591
|
+
(_d = _this2.pcManager) === null || _d === void 0 ? void 0 : _d.publisher.setConnectionStartedAt(_this2.joinStartedAt);
|
|
25592
|
+
}
|
|
24704
25593
|
_this2.registerOnLineListener();
|
|
24705
25594
|
_this2.clientConfiguration = joinResponse.clientConfiguration;
|
|
24706
25595
|
_this2.emit(EngineEvent.SignalConnected, joinResponse);
|
|
@@ -24749,6 +25638,9 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24749
25638
|
*/
|
|
24750
25639
|
close(reason) {
|
|
24751
25640
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25641
|
+
var _a;
|
|
25642
|
+
(_a = this.reconnectAbortController) === null || _a === void 0 ? void 0 : _a.abort();
|
|
25643
|
+
this.reconnectAbortController = undefined;
|
|
24752
25644
|
const unlock = yield this.closingLock.lock();
|
|
24753
25645
|
if (this.isClosed) {
|
|
24754
25646
|
unlock();
|
|
@@ -24757,6 +25649,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24757
25649
|
try {
|
|
24758
25650
|
this._isClosed = true;
|
|
24759
25651
|
this.joinAttempts = 0;
|
|
25652
|
+
this.joinStartedAt = undefined;
|
|
24760
25653
|
this.emit(EngineEvent.Closing);
|
|
24761
25654
|
this.removeAllListeners();
|
|
24762
25655
|
this.deregisterOnLineListener();
|
|
@@ -24885,6 +25778,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24885
25778
|
};
|
|
24886
25779
|
this.pcManager.onDataChannel = this.handleDataChannel;
|
|
24887
25780
|
this.pcManager.onStateChange = (connectionState, publisherState, subscriberState) => __awaiter(this, void 0, void 0, function* () {
|
|
25781
|
+
var _a;
|
|
24888
25782
|
this.log.debug("primary PC state changed ".concat(connectionState));
|
|
24889
25783
|
// Record when the primary transport actually entered CONNECTING so
|
|
24890
25784
|
// verifyTransport() can bound how long we tolerate it. Deriving it from the
|
|
@@ -24900,6 +25794,16 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24900
25794
|
this.publisherConnectionPromise = undefined;
|
|
24901
25795
|
}
|
|
24902
25796
|
if (connectionState === PCTransportState.CONNECTED) {
|
|
25797
|
+
if (this.joinStartedAt !== undefined) {
|
|
25798
|
+
// Connection setup time is the only network signal there is before the first video
|
|
25799
|
+
// offer. Set synchronously, so it is in place before `room.connect()` resolves and the
|
|
25800
|
+
// app can publish. A full restart joins again and builds new transports, so it is
|
|
25801
|
+
// measured again; a resume keeps its estimator and never restarts the timer.
|
|
25802
|
+
const setupTimeMs = Math.round(performance.now() - this.joinStartedAt);
|
|
25803
|
+
this.joinStartedAt = undefined;
|
|
25804
|
+
this.log.info("connection setup took ".concat(setupTimeMs, " ms"));
|
|
25805
|
+
(_a = this.pcManager) === null || _a === void 0 ? void 0 : _a.publisher.setConnectionSetupTime(setupTimeMs);
|
|
25806
|
+
}
|
|
24903
25807
|
const shouldEmit = this.pcState === PCState.New;
|
|
24904
25808
|
this.pcState = PCState.Connected;
|
|
24905
25809
|
if (shouldEmit) {
|
|
@@ -24944,6 +25848,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
24944
25848
|
this.midToTrackId = midToTrackId;
|
|
24945
25849
|
}
|
|
24946
25850
|
yield this.pcManager.setPublisherAnswer(sd, offerId);
|
|
25851
|
+
this.emit(EngineEvent.RequestSubscribedCodecRefresh);
|
|
24947
25852
|
});
|
|
24948
25853
|
// add candidate on trickle
|
|
24949
25854
|
this.client.onTrickle = (candidate, target) => {
|
|
@@ -25010,7 +25915,9 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25010
25915
|
};
|
|
25011
25916
|
this.client.onMediaSectionsRequirement = requirement => {
|
|
25012
25917
|
this.addMediaSections(requirement.numAudios, requirement.numVideos);
|
|
25013
|
-
this.negotiate()
|
|
25918
|
+
this.negotiate().catch(err => {
|
|
25919
|
+
this.log.error(err);
|
|
25920
|
+
});
|
|
25014
25921
|
};
|
|
25015
25922
|
this.client.onPublishDataTrackResponse = event => {
|
|
25016
25923
|
this.emit(EngineEvent.PublishDataTrackResponse, event);
|
|
@@ -25131,6 +26038,12 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25131
26038
|
if (!this.pcManager) {
|
|
25132
26039
|
return;
|
|
25133
26040
|
}
|
|
26041
|
+
if (this.dataChannels.hasPublisherChannels) {
|
|
26042
|
+
// Replacing live channels (the Safari null-id path in resumeConnection): the new ones start
|
|
26043
|
+
// out connecting, so drop the memoized readiness check and make the next sender wait for
|
|
26044
|
+
// them to open.
|
|
26045
|
+
this.publisherConnectionPromise = undefined;
|
|
26046
|
+
}
|
|
25134
26047
|
this.dataChannels.createPublisherChannels(this.pcManager);
|
|
25135
26048
|
}
|
|
25136
26049
|
/** Normalizes an incoming data-channel message into bytes, or logs and returns undefined. */
|
|
@@ -25363,16 +26276,28 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25363
26276
|
const fullReconnect = this.fullReconnectOnNext;
|
|
25364
26277
|
this.fullReconnectOnNext = false;
|
|
25365
26278
|
let succeeded = false;
|
|
26279
|
+
const abortController = new AbortController();
|
|
26280
|
+
this.reconnectAbortController = abortController;
|
|
25366
26281
|
try {
|
|
25367
26282
|
this.attemptingReconnect = true;
|
|
25368
26283
|
if (fullReconnect) {
|
|
25369
|
-
yield this.restartConnection();
|
|
26284
|
+
yield this.restartConnection(abortController.signal);
|
|
25370
26285
|
} else {
|
|
25371
26286
|
yield this.resumeConnection(reason);
|
|
25372
26287
|
}
|
|
26288
|
+
if (abortController.signal.aborted) {
|
|
26289
|
+
this.log.info('reconnect attempt cancelled, not marking it as succeeded');
|
|
26290
|
+
return;
|
|
26291
|
+
}
|
|
25373
26292
|
this.clearPendingReconnect();
|
|
25374
26293
|
succeeded = true;
|
|
25375
26294
|
} catch (e) {
|
|
26295
|
+
if (abortController.signal.aborted) {
|
|
26296
|
+
this.log.info('reconnect attempt cancelled, not retrying', {
|
|
26297
|
+
error: e
|
|
26298
|
+
});
|
|
26299
|
+
return;
|
|
26300
|
+
}
|
|
25376
26301
|
this.reconnectAttempts += 1;
|
|
25377
26302
|
let recoverable = true;
|
|
25378
26303
|
if (e instanceof UnexpectedConnectionState) {
|
|
@@ -25394,6 +26319,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25394
26319
|
yield this.close("gave up reconnecting after ".concat(this.reconnectAttempts, " attempts, ").concat(Date.now() - this.reconnectStart, "ms"));
|
|
25395
26320
|
}
|
|
25396
26321
|
} finally {
|
|
26322
|
+
// close() may already have cleared this (or a later attempt replaced it); only release our own
|
|
26323
|
+
if (this.reconnectAbortController === abortController) {
|
|
26324
|
+
this.reconnectAbortController = undefined;
|
|
26325
|
+
}
|
|
25397
26326
|
this.attemptingReconnect = false;
|
|
25398
26327
|
// A full reconnect requested while this attempt was running (e.g. a `RECONNECT` leave
|
|
25399
26328
|
// during a resume or a restart) that a successful attempt didn't act on; dispatch it now
|
|
@@ -25416,10 +26345,16 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25416
26345
|
// error in user code with provided reconnect policy, stop reconnecting
|
|
25417
26346
|
return null;
|
|
25418
26347
|
}
|
|
25419
|
-
restartConnection(regionUrl) {
|
|
26348
|
+
restartConnection(abortSignal, regionUrl) {
|
|
25420
26349
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25421
26350
|
var _a, _b, _c;
|
|
25422
26351
|
try {
|
|
26352
|
+
if (abortSignal.aborted) {
|
|
26353
|
+
this.log.info('full reconnect cancelled before starting', {
|
|
26354
|
+
regionUrl
|
|
26355
|
+
});
|
|
26356
|
+
return;
|
|
26357
|
+
}
|
|
25423
26358
|
if (!this.url || !this.token) {
|
|
25424
26359
|
// permanent failure, don't attempt reconnection
|
|
25425
26360
|
throw new UnexpectedConnectionState('could not reconnect, url or token not saved');
|
|
@@ -25431,6 +26366,12 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25431
26366
|
}
|
|
25432
26367
|
yield this.cleanupPeerConnections();
|
|
25433
26368
|
yield this.cleanupClient();
|
|
26369
|
+
if (abortSignal.aborted) {
|
|
26370
|
+
this.log.info('full reconnect cancelled after cleaning up the previous session', {
|
|
26371
|
+
regionUrl
|
|
26372
|
+
});
|
|
26373
|
+
return;
|
|
26374
|
+
}
|
|
25434
26375
|
let joinResponse;
|
|
25435
26376
|
try {
|
|
25436
26377
|
if (!this.signalOpts) {
|
|
@@ -25438,13 +26379,19 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25438
26379
|
throw new SignalReconnectError();
|
|
25439
26380
|
}
|
|
25440
26381
|
// in case a regionUrl is passed, the region URL takes precedence
|
|
25441
|
-
joinResponse = (yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts,
|
|
26382
|
+
joinResponse = (yield this.join(regionUrl !== null && regionUrl !== void 0 ? regionUrl : this.url, this.token, this.signalOpts, abortSignal, !this.options.singlePeerConnection)).joinResponse;
|
|
25442
26383
|
} catch (e) {
|
|
25443
26384
|
if (e instanceof ConnectionError && e.reason === ConnectionErrorReason.NotAllowed) {
|
|
25444
26385
|
throw new UnexpectedConnectionState('could not reconnect, token might be expired');
|
|
25445
26386
|
}
|
|
25446
26387
|
throw new SignalReconnectError();
|
|
25447
26388
|
}
|
|
26389
|
+
if (abortSignal.aborted) {
|
|
26390
|
+
this.log.info('full reconnect cancelled after the signal join response', {
|
|
26391
|
+
regionUrl
|
|
26392
|
+
});
|
|
26393
|
+
return;
|
|
26394
|
+
}
|
|
25448
26395
|
if (this.shouldFailNext) {
|
|
25449
26396
|
this.shouldFailNext = false;
|
|
25450
26397
|
throw new Error('simulated failure');
|
|
@@ -25452,6 +26399,12 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25452
26399
|
this.client.setReconnected();
|
|
25453
26400
|
this.emit(EngineEvent.SignalRestarted, joinResponse);
|
|
25454
26401
|
yield this.waitForPCReconnected();
|
|
26402
|
+
if (abortSignal.aborted) {
|
|
26403
|
+
this.log.info('full reconnect cancelled after peer connections reconnected', {
|
|
26404
|
+
regionUrl
|
|
26405
|
+
});
|
|
26406
|
+
return;
|
|
26407
|
+
}
|
|
25455
26408
|
// re-check signal connection state before setting engine as resumed
|
|
25456
26409
|
if (this.client.currentState !== SignalConnectionState.CONNECTED) {
|
|
25457
26410
|
throw new SignalReconnectError('Signal connection got severed during reconnect');
|
|
@@ -25460,9 +26413,21 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25460
26413
|
// reconnect success
|
|
25461
26414
|
this.emit(EngineEvent.Restarted);
|
|
25462
26415
|
} catch (error) {
|
|
25463
|
-
|
|
26416
|
+
if (abortSignal.aborted) {
|
|
26417
|
+
this.log.info('full reconnect cancelled after a failed attempt', {
|
|
26418
|
+
regionUrl
|
|
26419
|
+
});
|
|
26420
|
+
return;
|
|
26421
|
+
}
|
|
26422
|
+
const nextRegionUrl = yield (_b = this.regionStrategy) === null || _b === void 0 ? void 0 : _b.getNextUrl(abortSignal);
|
|
26423
|
+
if (abortSignal.aborted) {
|
|
26424
|
+
this.log.info('full reconnect cancelled during region selection', {
|
|
26425
|
+
regionUrl
|
|
26426
|
+
});
|
|
26427
|
+
return;
|
|
26428
|
+
}
|
|
25464
26429
|
if (nextRegionUrl) {
|
|
25465
|
-
yield this.restartConnection(nextRegionUrl);
|
|
26430
|
+
yield this.restartConnection(abortSignal, nextRegionUrl);
|
|
25466
26431
|
return;
|
|
25467
26432
|
} else {
|
|
25468
26433
|
// no more regions to try (or we're not on cloud)
|
|
@@ -25657,7 +26622,11 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25657
26622
|
*/
|
|
25658
26623
|
waitForBufferHeadroom(kind) {
|
|
25659
26624
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25660
|
-
|
|
26625
|
+
const dc = this.dataChannelForKind(kind);
|
|
26626
|
+
if (!dc) {
|
|
26627
|
+
throw new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind));
|
|
26628
|
+
}
|
|
26629
|
+
return this.flowControlFor(kind).waitForHeadroomWithLock(dc);
|
|
25661
26630
|
});
|
|
25662
26631
|
}
|
|
25663
26632
|
/**
|
|
@@ -25763,8 +26732,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25763
26732
|
if (this.isClosed) {
|
|
25764
26733
|
reject(new NegotiationError('cannot negotiate on closed engine'));
|
|
25765
26734
|
}
|
|
25766
|
-
this.
|
|
25767
|
-
this.on(EngineEvent.Restarting, handleClosed);
|
|
26735
|
+
this.pendingNegotiationAborts.add(handleClosed);
|
|
25768
26736
|
this.pcManager.publisher.off(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable);
|
|
25769
26737
|
this.pcManager.publisher.once(PCEvents.RTPVideoPayloadTypes, this.onRtpMapAvailable);
|
|
25770
26738
|
try {
|
|
@@ -25787,8 +26755,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
25787
26755
|
reject(new Error(String(e)));
|
|
25788
26756
|
}
|
|
25789
26757
|
} finally {
|
|
25790
|
-
this.
|
|
25791
|
-
this.off(EngineEvent.Restarting, handleClosed);
|
|
26758
|
+
this.pendingNegotiationAborts.delete(handleClosed);
|
|
25792
26759
|
}
|
|
25793
26760
|
}));
|
|
25794
26761
|
});
|
|
@@ -26519,6 +27486,16 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
26519
27486
|
this.byteStreamHandlers.delete(topic);
|
|
26520
27487
|
}
|
|
26521
27488
|
clearControllers() {
|
|
27489
|
+
// Error any in flight streams so readers do not wait forever for chunks that can no longer arrive.
|
|
27490
|
+
const abnormalEndError = new DataStreamError('Room disconnected in the middle of receiving data', DataStreamErrorReason.AbnormalEnd);
|
|
27491
|
+
for (const _ref2 of this.byteStreamControllers.values()) {
|
|
27492
|
+
const controller = _ref2.controller;
|
|
27493
|
+
controller.error(abnormalEndError);
|
|
27494
|
+
}
|
|
27495
|
+
for (const _ref3 of this.textStreamControllers.values()) {
|
|
27496
|
+
const controller = _ref3.controller;
|
|
27497
|
+
controller.error(abnormalEndError);
|
|
27498
|
+
}
|
|
26522
27499
|
this.byteStreamControllers.clear();
|
|
26523
27500
|
this.textStreamControllers.clear();
|
|
26524
27501
|
this.bufferedPackets = [];
|
|
@@ -26527,20 +27504,20 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
26527
27504
|
// Terminate any in flight data stream receives from the given participant
|
|
26528
27505
|
const textStreamsBeingSentByDisconnectingParticipant = Array.from(this.textStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity);
|
|
26529
27506
|
const byteStreamsBeingSentByDisconnectingParticipant = Array.from(this.byteStreamControllers.entries()).filter(entry => entry[1].sendingParticipantIdentity === participantIdentity);
|
|
26530
|
-
if (textStreamsBeingSentByDisconnectingParticipant.length > 0 || byteStreamsBeingSentByDisconnectingParticipant.length > 0) {
|
|
26531
|
-
const abnormalEndError = new DataStreamError("Participant ".concat(participantIdentity, " unexpectedly disconnected in the middle of sending data"), DataStreamErrorReason.AbnormalEnd);
|
|
26532
|
-
for (const
|
|
26533
|
-
var _ref3 = _slicedToArray(_ref2, 2);
|
|
26534
|
-
const id = _ref3[0];
|
|
26535
|
-
const controller = _ref3[1];
|
|
26536
|
-
controller.controller.error(abnormalEndError);
|
|
26537
|
-
this.byteStreamControllers.delete(id);
|
|
26538
|
-
}
|
|
26539
|
-
for (const _ref4 of textStreamsBeingSentByDisconnectingParticipant) {
|
|
27507
|
+
if (textStreamsBeingSentByDisconnectingParticipant.length > 0 || byteStreamsBeingSentByDisconnectingParticipant.length > 0) {
|
|
27508
|
+
const abnormalEndError = new DataStreamError("Participant ".concat(participantIdentity, " unexpectedly disconnected in the middle of sending data"), DataStreamErrorReason.AbnormalEnd);
|
|
27509
|
+
for (const _ref4 of byteStreamsBeingSentByDisconnectingParticipant) {
|
|
26540
27510
|
var _ref5 = _slicedToArray(_ref4, 2);
|
|
26541
27511
|
const id = _ref5[0];
|
|
26542
27512
|
const controller = _ref5[1];
|
|
26543
27513
|
controller.controller.error(abnormalEndError);
|
|
27514
|
+
this.byteStreamControllers.delete(id);
|
|
27515
|
+
}
|
|
27516
|
+
for (const _ref6 of textStreamsBeingSentByDisconnectingParticipant) {
|
|
27517
|
+
var _ref7 = _slicedToArray(_ref6, 2);
|
|
27518
|
+
const id = _ref7[0];
|
|
27519
|
+
const controller = _ref7[1];
|
|
27520
|
+
controller.controller.error(abnormalEndError);
|
|
26544
27521
|
this.textStreamControllers.delete(id);
|
|
26545
27522
|
}
|
|
26546
27523
|
}
|
|
@@ -26589,10 +27566,10 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
26589
27566
|
let compressed;
|
|
26590
27567
|
switch (streamHeader.compression) {
|
|
26591
27568
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
26592
|
-
if (!
|
|
27569
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
26593
27570
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
26594
27571
|
// isn't properly abiding by the data streams v2 protocol.
|
|
26595
|
-
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not
|
|
27572
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping..."));
|
|
26596
27573
|
return;
|
|
26597
27574
|
}
|
|
26598
27575
|
compressed = true;
|
|
@@ -26660,10 +27637,10 @@ class TextStreamReader extends BaseStreamReader {
|
|
|
26660
27637
|
let compressed;
|
|
26661
27638
|
switch (streamHeader.compression) {
|
|
26662
27639
|
case DataStream_CompressionType.DEFLATE_RAW:
|
|
26663
|
-
if (!
|
|
27640
|
+
if (!isDeflateRawCompressionSupported()) {
|
|
26664
27641
|
// NOTE: this shouldn't really ever happen, if this warning is logged then the sender
|
|
26665
27642
|
// isn't properly abiding by the data streams v2 protocol.
|
|
26666
|
-
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not
|
|
27643
|
+
livekitLogger.warn("Data stream ".concat(streamHeader.streamId, " received with deflate-raw compression, but this browser does not support deflate-raw decompression. Dropping..."));
|
|
26667
27644
|
return;
|
|
26668
27645
|
}
|
|
26669
27646
|
compressed = true;
|
|
@@ -27028,7 +28005,7 @@ class OutgoingDataStreamManager {
|
|
|
27028
28005
|
attributes: options === null || options === void 0 ? void 0 : options.attributes,
|
|
27029
28006
|
encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
27030
28007
|
};
|
|
27031
|
-
const compressEligible = compress &&
|
|
28008
|
+
const compressEligible = compress && isDeflateRawCompressionSupported() && this.allRecipientsSupportV2(options === null || options === void 0 ? void 0 : options.destinationIdentities) && this.allRecipientsSupportCompression(options === null || options === void 0 ? void 0 : options.destinationIdentities);
|
|
27032
28009
|
let compressedStream = compressEligible ? MaybeCollectedStream.fromStream(readableFromBytes(textInBytes).pipeThrough(deflateRawTransform())) : null;
|
|
27033
28010
|
// Phase 1: Try to send as a single packet data stream
|
|
27034
28011
|
const noAttachments = !(options === null || options === void 0 ? void 0 : options.attachments) || options.attachments.length === 0;
|
|
@@ -27137,7 +28114,7 @@ class OutgoingDataStreamManager {
|
|
|
27137
28114
|
encryptionType: ((_e = this.engine.e2eeManager) === null || _e === void 0 ? void 0 : _e.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
|
|
27138
28115
|
};
|
|
27139
28116
|
const progressMonitorTap = progressReportingStream(bytes.length, options === null || options === void 0 ? void 0 : options.onProgress);
|
|
27140
|
-
const compressEligible = compress &&
|
|
28117
|
+
const compressEligible = compress && isDeflateRawCompressionSupported() && this.allRecipientsSupportV2(destinationIdentities) && this.allRecipientsSupportCompression(destinationIdentities);
|
|
27141
28118
|
let compressedStream = compressEligible ? MaybeCollectedStream.fromStream(readableFromBytes(bytes).pipeThrough(progressMonitorTap).pipeThrough(deflateRawTransform())) : null;
|
|
27142
28119
|
// Phase 1: Try to send as a single packet data stream
|
|
27143
28120
|
if (this.allRecipientsSupportV2(destinationIdentities)) {
|
|
@@ -27334,7 +28311,7 @@ class OutgoingDataStreamManager {
|
|
|
27334
28311
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27335
28312
|
var _a, _b, _c, _d, _e;
|
|
27336
28313
|
const destinationIdentities = options === null || options === void 0 ? void 0 : options.destinationIdentities;
|
|
27337
|
-
const compress = ((_a = options === null || options === void 0 ? void 0 : options.compress) !== null && _a !== void 0 ? _a : true) &&
|
|
28314
|
+
const compress = ((_a = options === null || options === void 0 ? void 0 : options.compress) !== null && _a !== void 0 ? _a : true) && isDeflateRawCompressionSupported() && this.allRecipientsSupportV2(destinationIdentities) && this.allRecipientsSupportCompression(destinationIdentities);
|
|
27338
28315
|
const info = {
|
|
27339
28316
|
id: streamId,
|
|
27340
28317
|
name: file.name,
|
|
@@ -27537,58 +28514,6 @@ function sendStreamTrailer(streamId, destinationIdentities, engine) {
|
|
|
27537
28514
|
});
|
|
27538
28515
|
yield engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
|
|
27539
28516
|
});
|
|
27540
|
-
}/**
|
|
27541
|
-
* Implementation of AbortSignal.any
|
|
27542
|
-
* Creates a signal that will be aborted when any of the given signals is aborted.
|
|
27543
|
-
* @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any
|
|
27544
|
-
*/
|
|
27545
|
-
function abortSignalAny(signals) {
|
|
27546
|
-
// Handle empty signals array
|
|
27547
|
-
if (signals.length === 0) {
|
|
27548
|
-
const controller = new AbortController();
|
|
27549
|
-
return controller.signal;
|
|
27550
|
-
}
|
|
27551
|
-
// Fast path for single signal
|
|
27552
|
-
if (signals.length === 1) {
|
|
27553
|
-
return signals[0];
|
|
27554
|
-
}
|
|
27555
|
-
// Check if any signal is already aborted
|
|
27556
|
-
for (const signal of signals) {
|
|
27557
|
-
if (signal.aborted) {
|
|
27558
|
-
return signal;
|
|
27559
|
-
}
|
|
27560
|
-
}
|
|
27561
|
-
// Create a new controller for the combined signal
|
|
27562
|
-
const controller = new AbortController();
|
|
27563
|
-
const unlisteners = Array(signals.length);
|
|
27564
|
-
// Function to clean up all event listeners
|
|
27565
|
-
const cleanup = () => {
|
|
27566
|
-
for (const unsubscribe of unlisteners) {
|
|
27567
|
-
unsubscribe();
|
|
27568
|
-
}
|
|
27569
|
-
};
|
|
27570
|
-
// Add event listeners to each signal
|
|
27571
|
-
signals.forEach((signal, index) => {
|
|
27572
|
-
const handler = () => {
|
|
27573
|
-
controller.abort(signal.reason);
|
|
27574
|
-
cleanup();
|
|
27575
|
-
};
|
|
27576
|
-
signal.addEventListener('abort', handler);
|
|
27577
|
-
unlisteners[index] = () => signal.removeEventListener('abort', handler);
|
|
27578
|
-
});
|
|
27579
|
-
return controller.signal;
|
|
27580
|
-
}
|
|
27581
|
-
/**
|
|
27582
|
-
* Implementation of AbortSignal.timeout
|
|
27583
|
-
* Creates a signal that will be aborted after the specified timeout.
|
|
27584
|
-
* @link https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout
|
|
27585
|
-
*/
|
|
27586
|
-
function abortSignalTimeout(ms) {
|
|
27587
|
-
const controller = new AbortController();
|
|
27588
|
-
setTimeout(() => {
|
|
27589
|
-
controller.abort(new DOMException("signal timed out after ".concat(ms, " ms"), 'TimeoutError'));
|
|
27590
|
-
}, ms);
|
|
27591
|
-
return controller.signal;
|
|
27592
28517
|
}// Number type sizes
|
|
27593
28518
|
const U8_LENGTH_BYTES = 1;
|
|
27594
28519
|
const U16_LENGTH_BYTES = 2;
|
|
@@ -29945,6 +30870,12 @@ function truncateBytes(str, maxBytes) {
|
|
|
29945
30870
|
high = mid - 1;
|
|
29946
30871
|
}
|
|
29947
30872
|
}
|
|
30873
|
+
// Never end on the first half of a surrogate pair: it would be encoded as a replacement
|
|
30874
|
+
// character, and the truncated string would no longer round trip.
|
|
30875
|
+
const lastCode = low > 0 ? str.charCodeAt(low - 1) : 0;
|
|
30876
|
+
if (lastCode >= 0xd800 && lastCode <= 0xdbff) {
|
|
30877
|
+
low -= 1;
|
|
30878
|
+
}
|
|
29948
30879
|
return str.slice(0, low);
|
|
29949
30880
|
}/**
|
|
29950
30881
|
* Manages the client (caller) side of RPC: sending requests, tracking pending
|
|
@@ -30005,20 +30936,37 @@ class RpcClientManager extends eventsExports.EventEmitter {
|
|
|
30005
30936
|
completionFuture,
|
|
30006
30937
|
participantIdentity: destinationIdentity
|
|
30007
30938
|
});
|
|
30008
|
-
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
_this.pendingResponses.delete(id);
|
|
30012
|
-
(_a = completionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(completionFuture, RpcError.builtIn('RESPONSE_TIMEOUT'));
|
|
30013
|
-
}, responseTimeoutMs);
|
|
30939
|
+
// Observe the future before publishing: a slow send can outlast the ack timer.
|
|
30940
|
+
// Keep the returned promise rejected for callers, while marking its rejection
|
|
30941
|
+
// handled until performRpc has finished publishing and can return it.
|
|
30014
30942
|
const completionPromise = completionFuture.promise.finally(() => {
|
|
30015
|
-
|
|
30943
|
+
if (responseTimeoutId !== null) {
|
|
30944
|
+
clearTimeout(responseTimeoutId);
|
|
30945
|
+
}
|
|
30016
30946
|
if (_this.pendingAcks.has(id)) {
|
|
30017
30947
|
_this.log.warn('RPC response received before ack', id);
|
|
30018
30948
|
_this.pendingAcks.delete(id);
|
|
30019
30949
|
clearTimeout(ackTimeoutId);
|
|
30020
30950
|
}
|
|
30021
30951
|
});
|
|
30952
|
+
completionPromise.catch(() => {});
|
|
30953
|
+
try {
|
|
30954
|
+
yield _this.publishRpcRequest(destinationIdentity, id, method, payload, effectiveTimeoutMs, remoteClientProtocol);
|
|
30955
|
+
} catch (e) {
|
|
30956
|
+
// The caller receives the publish error. Do not leave an ack timer that
|
|
30957
|
+
// later rejects a future the caller never received.
|
|
30958
|
+
clearTimeout(ackTimeoutId);
|
|
30959
|
+
_this.pendingAcks.delete(id);
|
|
30960
|
+
_this.pendingResponses.delete(id);
|
|
30961
|
+
throw e;
|
|
30962
|
+
}
|
|
30963
|
+
if (_this.pendingResponses.has(id)) {
|
|
30964
|
+
responseTimeoutId = setTimeout(() => {
|
|
30965
|
+
var _a;
|
|
30966
|
+
_this.pendingResponses.delete(id);
|
|
30967
|
+
(_a = completionFuture.reject) === null || _a === void 0 ? void 0 : _a.call(completionFuture, RpcError.builtIn('RESPONSE_TIMEOUT'));
|
|
30968
|
+
}, responseTimeoutMs);
|
|
30969
|
+
}
|
|
30022
30970
|
return [id, completionPromise];
|
|
30023
30971
|
}();
|
|
30024
30972
|
});
|
|
@@ -30332,6 +31280,12 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30332
31280
|
}class RemoteAudioTrack extends RemoteTrack {
|
|
30333
31281
|
constructor(mediaTrack, sid, receiver, audioContext, audioOutput, loggerOptions) {
|
|
30334
31282
|
super(mediaTrack, sid, Track.Kind.Audio, receiver, loggerOptions);
|
|
31283
|
+
/**
|
|
31284
|
+
* the nodes of the graph currently wired up, in order, so teardown can undo exactly the edges
|
|
31285
|
+
* `connectWebAudio` created. Reading `webAudioPluginNodes` instead would miss the old plugins
|
|
31286
|
+
* once `setWebAudioPlugins` has replaced them.
|
|
31287
|
+
*/
|
|
31288
|
+
this.connectedNodes = [];
|
|
30335
31289
|
this.monitorReceiver = () => __awaiter(this, void 0, void 0, function* () {
|
|
30336
31290
|
if (!this.receiver) {
|
|
30337
31291
|
this._currentBitrate = 0;
|
|
@@ -30354,10 +31308,11 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30354
31308
|
*/
|
|
30355
31309
|
setVolume(volume) {
|
|
30356
31310
|
var _a;
|
|
30357
|
-
|
|
30358
|
-
|
|
30359
|
-
|
|
30360
|
-
|
|
31311
|
+
if (this.audioContext) {
|
|
31312
|
+
// playback goes through the web audio graph, the attached elements themselves stay silent
|
|
31313
|
+
(_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.gain.setTargetAtTime(volume, 0, 0.1);
|
|
31314
|
+
} else {
|
|
31315
|
+
for (const el of this.attachedElements) {
|
|
30361
31316
|
el.volume = volume;
|
|
30362
31317
|
}
|
|
30363
31318
|
}
|
|
@@ -30371,7 +31326,7 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30371
31326
|
* gets the volume of attached audio elements (loudest)
|
|
30372
31327
|
*/
|
|
30373
31328
|
getVolume() {
|
|
30374
|
-
if (this.elementVolume) {
|
|
31329
|
+
if (this.elementVolume !== undefined) {
|
|
30375
31330
|
return this.elementVolume;
|
|
30376
31331
|
}
|
|
30377
31332
|
if (isReactNative()) {
|
|
@@ -30403,7 +31358,9 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30403
31358
|
});
|
|
30404
31359
|
}
|
|
30405
31360
|
attach(element) {
|
|
30406
|
-
|
|
31361
|
+
// the graph is fed by the track rather than by a specific element, so one connection covers
|
|
31362
|
+
// all attached elements
|
|
31363
|
+
const needsNewWebAudioConnection = !this.sourceNode;
|
|
30407
31364
|
if (!element) {
|
|
30408
31365
|
element = super.attach();
|
|
30409
31366
|
} else {
|
|
@@ -30414,13 +31371,17 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30414
31371
|
this.log.error('Failed to set sink id on remote audio track', e, this.logContext);
|
|
30415
31372
|
});
|
|
30416
31373
|
}
|
|
30417
|
-
if (this.audioContext
|
|
30418
|
-
|
|
30419
|
-
|
|
31374
|
+
if (this.audioContext) {
|
|
31375
|
+
// audio is routed through the web audio graph, so every attached element has to stay
|
|
31376
|
+
// silent, no matter whether it is the one the graph was built from
|
|
30420
31377
|
element.volume = 0;
|
|
30421
31378
|
element.muted = true;
|
|
31379
|
+
if (needsNewWebAudioConnection) {
|
|
31380
|
+
this.log.debug('using audio context mapping', this.logContext);
|
|
31381
|
+
this.connectWebAudio(this.audioContext, element);
|
|
31382
|
+
}
|
|
30422
31383
|
}
|
|
30423
|
-
if (this.elementVolume) {
|
|
31384
|
+
if (this.elementVolume !== undefined) {
|
|
30424
31385
|
// make sure volume setting is being applied to the newly attached element
|
|
30425
31386
|
this.setVolume(this.elementVolume);
|
|
30426
31387
|
}
|
|
@@ -30450,11 +31411,27 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30450
31411
|
* @experimental
|
|
30451
31412
|
*/
|
|
30452
31413
|
setAudioContext(audioContext) {
|
|
31414
|
+
var _a;
|
|
31415
|
+
if (this.audioContext === audioContext) {
|
|
31416
|
+
return;
|
|
31417
|
+
}
|
|
30453
31418
|
this.audioContext = audioContext;
|
|
30454
|
-
if (audioContext
|
|
30455
|
-
|
|
30456
|
-
|
|
31419
|
+
if (audioContext) {
|
|
31420
|
+
// playback moves into the web audio graph, mute the elements to avoid double playback
|
|
31421
|
+
for (const element of this.attachedElements) {
|
|
31422
|
+
element.volume = 0;
|
|
31423
|
+
element.muted = true;
|
|
31424
|
+
}
|
|
31425
|
+
if (this.attachedElements.length > 0) {
|
|
31426
|
+
this.connectWebAudio(audioContext, this.attachedElements[0]);
|
|
31427
|
+
}
|
|
31428
|
+
} else {
|
|
30457
31429
|
this.disconnectWebAudio();
|
|
31430
|
+
// playback moves back into the elements, which `attach` had muted in favour of the graph
|
|
31431
|
+
for (const element of this.attachedElements) {
|
|
31432
|
+
element.muted = false;
|
|
31433
|
+
element.volume = (_a = this.elementVolume) !== null && _a !== void 0 ? _a : 1;
|
|
31434
|
+
}
|
|
30458
31435
|
}
|
|
30459
31436
|
}
|
|
30460
31437
|
/**
|
|
@@ -30473,14 +31450,17 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30473
31450
|
// @ts-ignore attached elements always have a srcObject set
|
|
30474
31451
|
this.sourceNode = context.createMediaStreamSource(element.srcObject);
|
|
30475
31452
|
let lastNode = this.sourceNode;
|
|
31453
|
+
this.connectedNodes = [this.sourceNode];
|
|
30476
31454
|
this.webAudioPluginNodes.forEach(node => {
|
|
30477
31455
|
lastNode.connect(node);
|
|
30478
31456
|
lastNode = node;
|
|
31457
|
+
this.connectedNodes.push(node);
|
|
30479
31458
|
});
|
|
30480
31459
|
this.gainNode = context.createGain();
|
|
30481
31460
|
lastNode.connect(this.gainNode);
|
|
31461
|
+
this.connectedNodes.push(this.gainNode);
|
|
30482
31462
|
this.gainNode.connect(context.destination);
|
|
30483
|
-
if (this.elementVolume) {
|
|
31463
|
+
if (this.elementVolume !== undefined) {
|
|
30484
31464
|
this.gainNode.gain.setTargetAtTime(this.elementVolume, 0, 0.1);
|
|
30485
31465
|
}
|
|
30486
31466
|
// try to resume the context if it isn't running already
|
|
@@ -30496,6 +31476,19 @@ class RpcServerManager extends eventsExports.EventEmitter {
|
|
|
30496
31476
|
}
|
|
30497
31477
|
disconnectWebAudio() {
|
|
30498
31478
|
var _a, _b;
|
|
31479
|
+
// the plugin nodes belong to whoever passed them in and may feed graphs of their own, so only
|
|
31480
|
+
// the edges this track created come down. A bare `node.disconnect()` would drop the
|
|
31481
|
+
// application's own routing with them
|
|
31482
|
+
for (let i = 0; i < this.connectedNodes.length - 1; i += 1) {
|
|
31483
|
+
try {
|
|
31484
|
+
this.connectedNodes[i].disconnect(this.connectedNodes[i + 1]);
|
|
31485
|
+
} catch (_c) {
|
|
31486
|
+
// a plugin node the application already disconnected itself, the rest of the chain still
|
|
31487
|
+
// has to come apart
|
|
31488
|
+
}
|
|
31489
|
+
}
|
|
31490
|
+
this.connectedNodes = [];
|
|
31491
|
+
// the source and gain nodes are ours alone, so every remaining edge of theirs is ours to drop
|
|
30499
31492
|
(_a = this.gainNode) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
30500
31493
|
(_b = this.sourceNode) === null || _b === void 0 ? void 0 : _b.disconnect();
|
|
30501
31494
|
this.gainNode = undefined;
|
|
@@ -31157,8 +32150,10 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
31157
32150
|
* @internal
|
|
31158
32151
|
*/
|
|
31159
32152
|
setAudioContext(ctx) {
|
|
31160
|
-
this
|
|
31161
|
-
|
|
32153
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32154
|
+
this.audioContext = ctx;
|
|
32155
|
+
yield Promise.all(Array.from(this.audioTrackPublications.values()).map(publication => publication.track).filter(isAudioTrack).map(track => track.setAudioContext(ctx)));
|
|
32156
|
+
});
|
|
31162
32157
|
}
|
|
31163
32158
|
addTrackPublication(publication) {
|
|
31164
32159
|
this.log.debug("adding track publication", {
|
|
@@ -31417,10 +32412,7 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
31417
32412
|
if (!track.isMuted) {
|
|
31418
32413
|
this.log.debug('track ended, attempting to use a different device', getLogContextFromTrack(track));
|
|
31419
32414
|
if (isLocalAudioTrack(track)) {
|
|
31420
|
-
|
|
31421
|
-
yield track.restartTrack({
|
|
31422
|
-
deviceId: 'default'
|
|
31423
|
-
});
|
|
32415
|
+
yield this.restartOnDefaultAudioDevice(track);
|
|
31424
32416
|
} else {
|
|
31425
32417
|
yield track.restartTrack();
|
|
31426
32418
|
}
|
|
@@ -31767,10 +32759,10 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
31767
32759
|
loggerName: this.roomOptions.loggerName,
|
|
31768
32760
|
loggerContextCb: () => this.logContext
|
|
31769
32761
|
});
|
|
31770
|
-
const localTracks = tracks.map(track => {
|
|
32762
|
+
const localTracks = yield Promise.all(tracks.map(track => __awaiter(this, void 0, void 0, function* () {
|
|
31771
32763
|
if (isAudioTrack(track)) {
|
|
31772
32764
|
this.microphoneError = undefined;
|
|
31773
|
-
track.setAudioContext(this.audioContext);
|
|
32765
|
+
yield track.setAudioContext(this.audioContext);
|
|
31774
32766
|
track.source = Track.Source.Microphone;
|
|
31775
32767
|
this.emit(ParticipantEvent.AudioStreamAcquired);
|
|
31776
32768
|
}
|
|
@@ -31779,7 +32771,7 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
31779
32771
|
track.source = Track.Source.Camera;
|
|
31780
32772
|
}
|
|
31781
32773
|
return track;
|
|
31782
|
-
});
|
|
32774
|
+
})));
|
|
31783
32775
|
return localTracks;
|
|
31784
32776
|
} catch (err) {
|
|
31785
32777
|
if (err instanceof Error) {
|
|
@@ -31889,7 +32881,7 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
31889
32881
|
return function* () {
|
|
31890
32882
|
var _a, _b, _c, _d;
|
|
31891
32883
|
if (isLocalAudioTrack(track)) {
|
|
31892
|
-
track.setAudioContext(_this2.audioContext);
|
|
32884
|
+
yield track.setAudioContext(_this2.audioContext);
|
|
31893
32885
|
}
|
|
31894
32886
|
yield (_a = _this2.reconnectFuture) === null || _a === void 0 ? void 0 : _a.promise;
|
|
31895
32887
|
if (_this2.republishPromise && !isRepublish) {
|
|
@@ -32489,19 +33481,36 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
32489
33481
|
return;
|
|
32490
33482
|
}
|
|
32491
33483
|
const packetTrailerFeatures = this.normalizeRequestedFrameMetadataOptions(track, opts);
|
|
33484
|
+
// the backup sender encodes the processed track, not the raw capture track.dimensions reports
|
|
33485
|
+
const _track$mediaStreamTra = track.mediaStreamTrack.getSettings(),
|
|
33486
|
+
width = _track$mediaStreamTra.width,
|
|
33487
|
+
height = _track$mediaStreamTra.height;
|
|
33488
|
+
const dims = width && height ? {
|
|
33489
|
+
width,
|
|
33490
|
+
height
|
|
33491
|
+
} : undefined;
|
|
33492
|
+
if (!dims) {
|
|
33493
|
+
this.log.warn('could not determine track dimensions, publishing backup codec without layers', getLogContextFromTrack(track));
|
|
33494
|
+
}
|
|
33495
|
+
// the server reads the layers off the codec entry for a request that carries a sid, and
|
|
33496
|
+
// falls back to the top level ones when it predates per codec layers
|
|
33497
|
+
const layers = dims ? videoLayersFromEncodings(dims.width, dims.height, encodings) : [];
|
|
32492
33498
|
const req = new AddTrackRequest({
|
|
32493
33499
|
cid: simulcastTrack.mediaStreamTrack.id,
|
|
32494
33500
|
type: Track.kindToProto(track.kind),
|
|
32495
33501
|
muted: track.isMuted,
|
|
32496
33502
|
source: Track.sourceToProto(track.source),
|
|
32497
33503
|
sid: track.sid,
|
|
33504
|
+
width: dims === null || dims === void 0 ? void 0 : dims.width,
|
|
33505
|
+
height: dims === null || dims === void 0 ? void 0 : dims.height,
|
|
32498
33506
|
packetTrailerFeatures,
|
|
33507
|
+
layers,
|
|
32499
33508
|
simulcastCodecs: [{
|
|
32500
33509
|
codec: opts.videoCodec,
|
|
32501
|
-
cid: simulcastTrack.mediaStreamTrack.id
|
|
33510
|
+
cid: simulcastTrack.mediaStreamTrack.id,
|
|
33511
|
+
layers
|
|
32502
33512
|
}]
|
|
32503
33513
|
});
|
|
32504
|
-
req.layers = videoLayersFromEncodings(req.width, req.height, encodings);
|
|
32505
33514
|
if (!this.engine || this.engine.isClosed) {
|
|
32506
33515
|
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
|
32507
33516
|
}
|
|
@@ -32921,6 +33930,35 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
32921
33930
|
}
|
|
32922
33931
|
return this.activeAgentFuture.promise;
|
|
32923
33932
|
}
|
|
33933
|
+
/**
|
|
33934
|
+
* @internal
|
|
33935
|
+
*/
|
|
33936
|
+
restartOnDefaultAudioDevice(track) {
|
|
33937
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33938
|
+
var _a, _b;
|
|
33939
|
+
try {
|
|
33940
|
+
if (isChromiumBased()) {
|
|
33941
|
+
// only chrome has the notion of a literal `default` device
|
|
33942
|
+
yield track.setDeviceId({
|
|
33943
|
+
exact: 'default'
|
|
33944
|
+
});
|
|
33945
|
+
} else {
|
|
33946
|
+
// just try to restart for other browsers and try to change the device id as a fallback in the catch clause
|
|
33947
|
+
yield track.restartTrack();
|
|
33948
|
+
}
|
|
33949
|
+
} catch (e) {
|
|
33950
|
+
// restart() clears the manually-stopped flag on entry, so retrying a track the user stopped
|
|
33951
|
+
// while the first attempt was in flight would re-open the capture device behind their back
|
|
33952
|
+
if (!isDeviceAcquisitionFailure(e) || isPermissionDeniedError(e) || track.isStopped) {
|
|
33953
|
+
throw e;
|
|
33954
|
+
}
|
|
33955
|
+
this.log.debug('could not reacquire the audio device, retrying with an enumerated device', getLogContextFromTrack(track));
|
|
33956
|
+
// permission is already granted at this point, so skip the probe that would re-acquire media
|
|
33957
|
+
const allDevices = yield DeviceManager.getInstance().getDevices('audioinput', false);
|
|
33958
|
+
yield track.setDeviceId((_b = (_a = allDevices === null || allDevices === void 0 ? void 0 : allDevices[0]) === null || _a === void 0 ? void 0 : _a.deviceId) !== null && _b !== void 0 ? _b : 'default');
|
|
33959
|
+
}
|
|
33960
|
+
});
|
|
33961
|
+
}
|
|
32924
33962
|
getPublicationForTrack(track) {
|
|
32925
33963
|
let publication;
|
|
32926
33964
|
this.trackPublications.forEach(pub => {
|
|
@@ -33631,6 +34669,30 @@ class DeferrableMap extends Map {
|
|
|
33631
34669
|
})(ConnectionState || (ConnectionState = {}));
|
|
33632
34670
|
const CONNECTION_RECONCILE_FREQUENCY_MS = 4 * 1000;
|
|
33633
34671
|
const STATS_LOG_FREQUENCY_MS = 30 * 1000;
|
|
34672
|
+
const DUMMY_AUDIO_ELEMENT_ID = 'livekit-dummy-audio-el';
|
|
34673
|
+
/**
|
|
34674
|
+
* One silent audio element serves every room on the page, so it is reference counted: a room
|
|
34675
|
+
* disconnecting must not pull it out from under the rooms still playing through it. The element
|
|
34676
|
+
* and its track go away once the last room hands its reference back.
|
|
34677
|
+
*/
|
|
34678
|
+
let sharedDummyAudioElement;
|
|
34679
|
+
function createSharedDummyAudioElement() {
|
|
34680
|
+
const element = document.createElement('audio');
|
|
34681
|
+
element.id = DUMMY_AUDIO_ELEMENT_ID;
|
|
34682
|
+
element.autoplay = true;
|
|
34683
|
+
element.hidden = true;
|
|
34684
|
+
const track = getEmptyAudioStreamTrack();
|
|
34685
|
+
track.enabled = true;
|
|
34686
|
+
const stream = new MediaStream([track]);
|
|
34687
|
+
element.srcObject = stream;
|
|
34688
|
+
document.body.append(element);
|
|
34689
|
+
return {
|
|
34690
|
+
element,
|
|
34691
|
+
stream,
|
|
34692
|
+
track,
|
|
34693
|
+
refCount: 0
|
|
34694
|
+
};
|
|
34695
|
+
}
|
|
33634
34696
|
/**
|
|
33635
34697
|
* In LiveKit, a room is the logical grouping for a list of participants.
|
|
33636
34698
|
* Participants in a room can publish tracks, and subscribe to others' tracks.
|
|
@@ -33661,6 +34723,24 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33661
34723
|
/** reflects the sender encryption status of the local participant */
|
|
33662
34724
|
this.isE2EEEnabled = false;
|
|
33663
34725
|
this.audioEnabled = true;
|
|
34726
|
+
/**
|
|
34727
|
+
* whether `audioContext` was created by the SDK and therefore has to be closed by it.
|
|
34728
|
+
* A context handed in through `webAudioMix` is owned by the application instead.
|
|
34729
|
+
*/
|
|
34730
|
+
this.ownsAudioContext = false;
|
|
34731
|
+
/**
|
|
34732
|
+
* set once the audio context has been released as part of a disconnect. Guards against
|
|
34733
|
+
* creating a new context outside of a connection lifecycle, where nothing would close it again.
|
|
34734
|
+
*/
|
|
34735
|
+
this.audioContextReleased = false;
|
|
34736
|
+
/**
|
|
34737
|
+
* serialises `acquireAudioContext` against `releaseAudioContext`. Both await per-track graph
|
|
34738
|
+
* rebuilds, so without it a reconnect landing inside a disconnect's teardown leaves the two
|
|
34739
|
+
* racing to set the participants' context, and the loser's value is the one that sticks.
|
|
34740
|
+
*/
|
|
34741
|
+
this.audioContextMutex = new _();
|
|
34742
|
+
/** silent audio tracks this room acquired, handed back to the shared source on disconnect */
|
|
34743
|
+
this.acquiredEmptyAudioTracks = new Set();
|
|
33664
34744
|
this.e2eeStateMutex = new _();
|
|
33665
34745
|
this.isVideoPlaybackBlocked = false;
|
|
33666
34746
|
this.log = livekitLogger;
|
|
@@ -33731,7 +34811,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33731
34811
|
this.abortController = undefined;
|
|
33732
34812
|
resolve();
|
|
33733
34813
|
} catch (error) {
|
|
33734
|
-
if (this.regionUrlProvider && error instanceof ConnectionError && error
|
|
34814
|
+
if (this.regionUrlProvider && error instanceof ConnectionError && canFailOverToAnotherRegion(error)) {
|
|
33735
34815
|
let nextUrl = null;
|
|
33736
34816
|
try {
|
|
33737
34817
|
this.log.debug('Fetching next region');
|
|
@@ -33780,6 +34860,8 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33780
34860
|
const _yield$engine$join = yield engine.join(url, token, {
|
|
33781
34861
|
autoSubscribe: connectOptions.autoSubscribe,
|
|
33782
34862
|
adaptiveStream: typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
|
|
34863
|
+
disableIceLite: connectOptions.disableIceLite,
|
|
34864
|
+
subscriberAllowPause: connectOptions.subscriberAllowPause,
|
|
33783
34865
|
clientInfoCapabilities: this.getClientInfoCapabilities(roomOptions),
|
|
33784
34866
|
maxRetries: connectOptions.maxRetries,
|
|
33785
34867
|
e2eeEnabled: !!this.e2eeManager,
|
|
@@ -33831,7 +34913,9 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33831
34913
|
if ((_b = this.regionUrlProvider) === null || _b === void 0 ? void 0 : _b.isCloud()) {
|
|
33832
34914
|
this.engine.setRegionStrategy(this.createRegionStrategy());
|
|
33833
34915
|
}
|
|
33834
|
-
this.acquireAudioContext()
|
|
34916
|
+
this.acquireAudioContext(true).catch(error => this.log.warn('Could not acquire audio context', Object.assign(Object.assign({}, this.logContext), {
|
|
34917
|
+
error
|
|
34918
|
+
})));
|
|
33835
34919
|
this.connOptions = Object.assign(Object.assign({}, roomConnectOptionDefaults), opts);
|
|
33836
34920
|
if (this.connOptions.rtcConfig) {
|
|
33837
34921
|
this.engine.rtcConfig = this.connOptions.rtcConfig;
|
|
@@ -33950,44 +35034,8 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33950
35034
|
this.startAudio = () => __awaiter(this, void 0, void 0, function* () {
|
|
33951
35035
|
const elements = [];
|
|
33952
35036
|
const browser = getBrowser();
|
|
33953
|
-
if (browser && browser.os === 'iOS') {
|
|
33954
|
-
|
|
33955
|
-
* iOS blocks audio element playback if
|
|
33956
|
-
* - user is not publishing audio themselves and
|
|
33957
|
-
* - no other audio source is playing
|
|
33958
|
-
*
|
|
33959
|
-
* as a workaround, we create an audio element with an empty track, so that
|
|
33960
|
-
* silent audio is always playing
|
|
33961
|
-
*/
|
|
33962
|
-
const audioId = 'livekit-dummy-audio-el';
|
|
33963
|
-
let dummyAudioEl = document.getElementById(audioId);
|
|
33964
|
-
if (!dummyAudioEl) {
|
|
33965
|
-
dummyAudioEl = document.createElement('audio');
|
|
33966
|
-
dummyAudioEl.id = audioId;
|
|
33967
|
-
dummyAudioEl.autoplay = true;
|
|
33968
|
-
dummyAudioEl.hidden = true;
|
|
33969
|
-
const track = getEmptyAudioStreamTrack();
|
|
33970
|
-
track.enabled = true;
|
|
33971
|
-
const stream = new MediaStream([track]);
|
|
33972
|
-
dummyAudioEl.srcObject = stream;
|
|
33973
|
-
document.addEventListener('visibilitychange', () => {
|
|
33974
|
-
if (!dummyAudioEl) {
|
|
33975
|
-
return;
|
|
33976
|
-
}
|
|
33977
|
-
// set the srcObject to null on page hide in order to prevent lock screen controls to show up for it
|
|
33978
|
-
dummyAudioEl.srcObject = document.hidden ? null : stream;
|
|
33979
|
-
if (!document.hidden) {
|
|
33980
|
-
this.log.debug('page visible again, triggering startAudio to resume playback and update playback status');
|
|
33981
|
-
this.startAudio();
|
|
33982
|
-
}
|
|
33983
|
-
});
|
|
33984
|
-
document.body.append(dummyAudioEl);
|
|
33985
|
-
this.once(RoomEvent.Disconnected, () => {
|
|
33986
|
-
dummyAudioEl === null || dummyAudioEl === void 0 ? void 0 : dummyAudioEl.remove();
|
|
33987
|
-
dummyAudioEl = null;
|
|
33988
|
-
});
|
|
33989
|
-
}
|
|
33990
|
-
elements.push(dummyAudioEl);
|
|
35037
|
+
if (browser && browser.os === 'iOS' && !this.audioContextReleased) {
|
|
35038
|
+
elements.push(this.acquireDummyAudioElement());
|
|
33991
35039
|
}
|
|
33992
35040
|
this.remoteParticipants.forEach(p => {
|
|
33993
35041
|
p.audioTrackPublications.forEach(t => {
|
|
@@ -33999,7 +35047,10 @@ class Room extends eventsExports.EventEmitter {
|
|
|
33999
35047
|
});
|
|
34000
35048
|
});
|
|
34001
35049
|
try {
|
|
34002
|
-
yield Promise.all([
|
|
35050
|
+
yield Promise.all([
|
|
35051
|
+
// once the audio context has been released as part of a disconnect there is nothing left
|
|
35052
|
+
// to unlock, and creating a new context here would leak it as no disconnect will follow
|
|
35053
|
+
...(this.audioContextReleased ? [] : [this.acquireAudioContext()]), ...elements.map(e => {
|
|
34003
35054
|
// when webAudioMix is enabled, attached elements are deliberately kept muted by
|
|
34004
35055
|
// RemoteAudioTrack.attach() and audio is routed through the web audio graph instead.
|
|
34005
35056
|
// unmuting them here would cause double playback on platforms where element.volume
|
|
@@ -34218,7 +35269,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
34218
35269
|
// find the participant
|
|
34219
35270
|
const participant = this.remoteParticipants.get(packet.participantIdentity);
|
|
34220
35271
|
if (packet.value.case === 'user') {
|
|
34221
|
-
this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType);
|
|
35272
|
+
this.handleUserPacket(participant, packet.value.value, packet.kind, encryptionType, packet.participantIdentity);
|
|
34222
35273
|
} else if (packet.value.case === 'transcription') {
|
|
34223
35274
|
this.handleTranscription(participant, packet.value.value);
|
|
34224
35275
|
} else if (packet.value.case === 'sipDtmf') {
|
|
@@ -34249,8 +35300,8 @@ class Room extends eventsExports.EventEmitter {
|
|
|
34249
35300
|
this.rpcClientManager.handleIncomingRpcAck(packet.value.value.requestId);
|
|
34250
35301
|
}
|
|
34251
35302
|
};
|
|
34252
|
-
this.handleUserPacket = (participant, userPacket, kind, encryptionType) => {
|
|
34253
|
-
this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic, encryptionType);
|
|
35303
|
+
this.handleUserPacket = (participant, userPacket, kind, encryptionType, participantIdentity) => {
|
|
35304
|
+
this.emit(RoomEvent.DataReceived, userPacket.payload, participant, kind, userPacket.topic, encryptionType, participantIdentity);
|
|
34254
35305
|
// also emit on the participant
|
|
34255
35306
|
participant === null || participant === void 0 ? void 0 : participant.emit(ParticipantEvent.DataReceived, userPacket.payload, kind, encryptionType);
|
|
34256
35307
|
};
|
|
@@ -34825,6 +35876,12 @@ class Room extends eventsExports.EventEmitter {
|
|
|
34825
35876
|
updatedAtInMs: Date.now(),
|
|
34826
35877
|
maxAgeInMs: DEFAULT_MAX_AGE_MS
|
|
34827
35878
|
});
|
|
35879
|
+
}).on(EngineEvent.RequestSubscribedCodecRefresh, () => {
|
|
35880
|
+
for (const videoPub of this.localParticipant.videoTrackPublications.values()) {
|
|
35881
|
+
if (isLocalVideoTrack(videoPub.track)) {
|
|
35882
|
+
videoPub.track.refreshSubscribedCodecs();
|
|
35883
|
+
}
|
|
35884
|
+
}
|
|
34828
35885
|
});
|
|
34829
35886
|
if (this.localParticipant) {
|
|
34830
35887
|
this.localParticipant.setupEngine(this.engine);
|
|
@@ -34921,8 +35978,9 @@ class Room extends eventsExports.EventEmitter {
|
|
|
34921
35978
|
let req;
|
|
34922
35979
|
switch (scenario) {
|
|
34923
35980
|
case 'signal-reconnect':
|
|
35981
|
+
const reconnectDelay = typeof arg === 'number' ? arg : 0;
|
|
34924
35982
|
// @ts-expect-error function is private
|
|
34925
|
-
yield this.engine.client.handleOnClose('simulate disconnect');
|
|
35983
|
+
yield this.engine.client.handleOnClose('simulate disconnect', undefined, reconnectDelay);
|
|
34926
35984
|
break;
|
|
34927
35985
|
case 'fail-on-v1-path':
|
|
34928
35986
|
this.engine.failNextV1Path();
|
|
@@ -35036,6 +36094,78 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35036
36094
|
}
|
|
35037
36095
|
});
|
|
35038
36096
|
}
|
|
36097
|
+
/**
|
|
36098
|
+
* iOS blocks audio element playback if
|
|
36099
|
+
* - user is not publishing audio themselves and
|
|
36100
|
+
* - no other audio source is playing
|
|
36101
|
+
*
|
|
36102
|
+
* as a workaround, we create an audio element with an empty track, so that
|
|
36103
|
+
* silent audio is always playing
|
|
36104
|
+
*
|
|
36105
|
+
* The element is shared with any other room on the page and handed back on disconnect, so it
|
|
36106
|
+
* outlives whichever room happened to create it.
|
|
36107
|
+
*/
|
|
36108
|
+
acquireDummyAudioElement() {
|
|
36109
|
+
if (!sharedDummyAudioElement) {
|
|
36110
|
+
sharedDummyAudioElement = createSharedDummyAudioElement();
|
|
36111
|
+
}
|
|
36112
|
+
const shared = sharedDummyAudioElement;
|
|
36113
|
+
if (this.releaseDummyAudioElement) {
|
|
36114
|
+
// this room already holds a reference, a second one would never be handed back
|
|
36115
|
+
return shared.element;
|
|
36116
|
+
}
|
|
36117
|
+
shared.refCount += 1;
|
|
36118
|
+
// the listener is per room rather than shared, so that the room it resumes playback for is
|
|
36119
|
+
// still connected, and so that it goes away with the room that installed it
|
|
36120
|
+
const handleVisibilityChange = () => {
|
|
36121
|
+
// set the srcObject to null on page hide in order to prevent lock screen controls to show up for it
|
|
36122
|
+
shared.element.srcObject = document.hidden ? null : shared.stream;
|
|
36123
|
+
if (!document.hidden) {
|
|
36124
|
+
this.log.debug('page visible again, triggering startAudio to resume playback and update playback status');
|
|
36125
|
+
this.startAudio().catch(error => this.log.warn('Could not restart audio on page becoming visible', Object.assign(Object.assign({}, this.logContext), {
|
|
36126
|
+
error
|
|
36127
|
+
})));
|
|
36128
|
+
}
|
|
36129
|
+
};
|
|
36130
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
36131
|
+
this.releaseDummyAudioElement = () => {
|
|
36132
|
+
this.releaseDummyAudioElement = undefined;
|
|
36133
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
36134
|
+
shared.refCount -= 1;
|
|
36135
|
+
if (shared.refCount > 0) {
|
|
36136
|
+
return;
|
|
36137
|
+
}
|
|
36138
|
+
shared.element.srcObject = null;
|
|
36139
|
+
shared.element.remove();
|
|
36140
|
+
sharedDummyAudioElement = undefined;
|
|
36141
|
+
releaseEmptyAudioStreamTrack(shared.track).catch(error => this.log.warn('Could not release the dummy audio element track', Object.assign(Object.assign({}, this.logContext), {
|
|
36142
|
+
error
|
|
36143
|
+
})));
|
|
36144
|
+
};
|
|
36145
|
+
return shared.element;
|
|
36146
|
+
}
|
|
36147
|
+
/**
|
|
36148
|
+
* Obtains a silent audio track from the shared source and keeps track of it, so that it can be
|
|
36149
|
+
* handed back on disconnect and the shared `AudioContext` behind it can be closed again.
|
|
36150
|
+
*/
|
|
36151
|
+
acquireEmptyAudioStreamTrack() {
|
|
36152
|
+
const track = getEmptyAudioStreamTrack();
|
|
36153
|
+
this.acquiredEmptyAudioTracks.add(track);
|
|
36154
|
+
return track;
|
|
36155
|
+
}
|
|
36156
|
+
releaseAcquiredEmptyAudioTrack(track) {
|
|
36157
|
+
if (!this.acquiredEmptyAudioTracks.delete(track)) {
|
|
36158
|
+
return;
|
|
36159
|
+
}
|
|
36160
|
+
releaseEmptyAudioStreamTrack(track).catch(error => this.log.warn('Could not release empty audio stream track', Object.assign(Object.assign({}, this.logContext), {
|
|
36161
|
+
error
|
|
36162
|
+
})));
|
|
36163
|
+
}
|
|
36164
|
+
releaseAcquiredEmptyAudioTracks() {
|
|
36165
|
+
for (const track of this.acquiredEmptyAudioTracks) {
|
|
36166
|
+
this.releaseAcquiredEmptyAudioTrack(track);
|
|
36167
|
+
}
|
|
36168
|
+
}
|
|
35039
36169
|
/**
|
|
35040
36170
|
* Returns true if audio playback is enabled
|
|
35041
36171
|
*/
|
|
@@ -35318,7 +36448,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35318
36448
|
handleDisconnect() {
|
|
35319
36449
|
let shouldStopTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
|
35320
36450
|
let reason = arguments.length > 1 ? arguments[1] : undefined;
|
|
35321
|
-
var _a, _b;
|
|
36451
|
+
var _a, _b, _c;
|
|
35322
36452
|
this.clearConnectionReconcile();
|
|
35323
36453
|
this.isResuming = false;
|
|
35324
36454
|
this.bufferedEvents = [];
|
|
@@ -35335,6 +36465,9 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35335
36465
|
this.regionUrlProvider.notifyDisconnected();
|
|
35336
36466
|
}
|
|
35337
36467
|
try {
|
|
36468
|
+
// audio tracks that outlive this room are about to become unreachable through the local
|
|
36469
|
+
// participant, collect them while we can so they can be detached from the context below
|
|
36470
|
+
const retainedAudioTracks = shouldStopTracks ? [] : Array.from(this.localParticipant.audioTrackPublications.values()).map(pub => pub.track).filter(isLocalAudioTrack);
|
|
35338
36471
|
this.remoteParticipants.forEach(p => {
|
|
35339
36472
|
p.trackPublications.forEach(pub => {
|
|
35340
36473
|
p.unpublishTrack(pub.trackSid);
|
|
@@ -35359,15 +36492,18 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35359
36492
|
this.remoteParticipants.clear();
|
|
35360
36493
|
this.sidToIdentity.clear();
|
|
35361
36494
|
this.activeSpeakers = [];
|
|
35362
|
-
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
36495
|
+
(_a = this.releaseDummyAudioElement) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
36496
|
+
this.releaseAcquiredEmptyAudioTracks();
|
|
36497
|
+
// teardown is asynchronous (processors need to release their web audio nodes), but
|
|
36498
|
+
// `handleDisconnect` is not, so let it finish in the background
|
|
36499
|
+
this.releaseAudioContext(retainedAudioTracks).catch(error => this.log.warn('Could not release audio context', Object.assign(Object.assign({}, this.logContext), {
|
|
36500
|
+
error
|
|
36501
|
+
})));
|
|
35366
36502
|
if (isWeb()) {
|
|
35367
36503
|
window.removeEventListener('beforeunload', this.onPageLeave);
|
|
35368
36504
|
window.removeEventListener('pagehide', this.onPageLeave);
|
|
35369
36505
|
window.removeEventListener('freeze', this.onPageLeave);
|
|
35370
|
-
(
|
|
36506
|
+
(_c = (_b = navigator.mediaDevices) === null || _b === void 0 ? void 0 : _b.removeEventListener) === null || _c === void 0 ? void 0 : _c.call(_b, 'devicechange', this.handleDeviceChange);
|
|
35371
36507
|
}
|
|
35372
36508
|
} finally {
|
|
35373
36509
|
this.setAndEmitConnectionState(ConnectionState.Disconnected);
|
|
@@ -35442,39 +36578,139 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35442
36578
|
}
|
|
35443
36579
|
});
|
|
35444
36580
|
}
|
|
36581
|
+
/**
|
|
36582
|
+
* @param forConnect whether this acquisition belongs to a connect, which is the only caller
|
|
36583
|
+
* allowed to put a released room back on an audio context.
|
|
36584
|
+
*/
|
|
35445
36585
|
acquireAudioContext() {
|
|
35446
|
-
return __awaiter(this,
|
|
35447
|
-
var
|
|
35448
|
-
|
|
35449
|
-
|
|
35450
|
-
|
|
35451
|
-
|
|
35452
|
-
// by using an AudioContext, it reduces lag on audio elements
|
|
35453
|
-
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
|
|
35454
|
-
this.audioContext = (_a = getNewAudioContext()) !== null && _a !== void 0 ? _a : undefined;
|
|
35455
|
-
}
|
|
35456
|
-
if (this.options.webAudioMix) {
|
|
35457
|
-
this.remoteParticipants.forEach(participant => participant.setAudioContext(this.audioContext));
|
|
35458
|
-
}
|
|
35459
|
-
this.localParticipant.setAudioContext(this.audioContext);
|
|
35460
|
-
if (this.audioContext && this.audioContext.state === 'suspended') {
|
|
35461
|
-
// for iOS a newly created AudioContext is always in `suspended` state.
|
|
35462
|
-
// we try our best to resume the context here, if that doesn't work, we just continue with regular processing
|
|
36586
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
36587
|
+
var _this4 = this;
|
|
36588
|
+
let forConnect = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
36589
|
+
return function* () {
|
|
36590
|
+
var _a, _b;
|
|
36591
|
+
const unlock = yield _this4.audioContextMutex.lock();
|
|
35463
36592
|
try {
|
|
35464
|
-
|
|
35465
|
-
|
|
35466
|
-
|
|
35467
|
-
|
|
35468
|
-
}
|
|
36593
|
+
if (_this4.audioContextReleased && !forConnect) {
|
|
36594
|
+
// a disconnect set the flag and took the lock while this call was queued behind it.
|
|
36595
|
+
// Creating a context now would leave one open on a room nothing tears down again
|
|
36596
|
+
return;
|
|
36597
|
+
}
|
|
36598
|
+
_this4.audioContextReleased = false;
|
|
36599
|
+
const providedAudioContext = typeof _this4.options.webAudioMix !== 'boolean' ? _this4.options.webAudioMix.audioContext : undefined;
|
|
36600
|
+
if (providedAudioContext) {
|
|
36601
|
+
// override audio context with custom audio context if supplied by user
|
|
36602
|
+
yield _this4.setRoomAudioContext(providedAudioContext, false);
|
|
36603
|
+
} else if (!_this4.audioContext || _this4.audioContext.state === 'closed') {
|
|
36604
|
+
// by using an AudioContext, it reduces lag on audio elements
|
|
36605
|
+
// https://stackoverflow.com/questions/9811429/html5-audio-tag-on-safari-has-a-delay/54119854#54119854
|
|
36606
|
+
yield _this4.setRoomAudioContext((_a = getNewAudioContext()) !== null && _a !== void 0 ? _a : undefined, true);
|
|
36607
|
+
}
|
|
36608
|
+
if (_this4.audioContext && _this4.audioContext.state === 'suspended') {
|
|
36609
|
+
// for iOS a newly created AudioContext is always in `suspended` state.
|
|
36610
|
+
// we try our best to resume the context here, if that doesn't work, we just continue with regular processing
|
|
36611
|
+
try {
|
|
36612
|
+
yield Promise.race([_this4.audioContext.resume(), sleep(200)]);
|
|
36613
|
+
} catch (e) {
|
|
36614
|
+
_this4.log.warn('Could not resume audio context', {
|
|
36615
|
+
error: e
|
|
36616
|
+
});
|
|
36617
|
+
}
|
|
36618
|
+
}
|
|
36619
|
+
const newContextIsRunning = ((_b = _this4.audioContext) === null || _b === void 0 ? void 0 : _b.state) === 'running';
|
|
36620
|
+
if (newContextIsRunning !== _this4.canPlaybackAudio) {
|
|
36621
|
+
_this4.audioEnabled = newContextIsRunning;
|
|
36622
|
+
_this4.emit(RoomEvent.AudioPlaybackStatusChanged, newContextIsRunning);
|
|
36623
|
+
}
|
|
36624
|
+
} finally {
|
|
36625
|
+
unlock();
|
|
35469
36626
|
}
|
|
36627
|
+
}();
|
|
36628
|
+
});
|
|
36629
|
+
}
|
|
36630
|
+
/**
|
|
36631
|
+
* Points the room and its participants at `audioContext`, rebuilding the web audio graph of
|
|
36632
|
+
* every audio track in it. Closes the previous context if the SDK `owned` it.
|
|
36633
|
+
*/
|
|
36634
|
+
setRoomAudioContext(audioContext, owned) {
|
|
36635
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36636
|
+
const ownsAudioContext = !!audioContext && owned;
|
|
36637
|
+
if (this.audioContext === audioContext) {
|
|
36638
|
+
this.ownsAudioContext = ownsAudioContext;
|
|
36639
|
+
return;
|
|
35470
36640
|
}
|
|
35471
|
-
const
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
36641
|
+
const contextToClose = this.ownsAudioContext ? this.audioContext : undefined;
|
|
36642
|
+
this.audioContext = audioContext;
|
|
36643
|
+
this.ownsAudioContext = ownsAudioContext;
|
|
36644
|
+
try {
|
|
36645
|
+
// every audio track rebuilds its nodes in the new context, which has to happen before the
|
|
36646
|
+
// previous one is closed so that processors can move off it while it is still alive
|
|
36647
|
+
if (this.options.webAudioMix) {
|
|
36648
|
+
yield Promise.all(Array.from(this.remoteParticipants.values()).map(participant => participant.setAudioContext(audioContext)));
|
|
36649
|
+
}
|
|
36650
|
+
yield this.localParticipant.setAudioContext(audioContext);
|
|
36651
|
+
} catch (error) {
|
|
36652
|
+
// the context is being abandoned either way, so a track that failed to move off it must
|
|
36653
|
+
// not stop us closing it — that would leak the context this whole path exists to release
|
|
36654
|
+
this.log.warn('Could not move audio tracks to the new audio context', Object.assign(Object.assign({}, this.logContext), {
|
|
36655
|
+
error
|
|
36656
|
+
}));
|
|
36657
|
+
} finally {
|
|
36658
|
+
if (contextToClose && contextToClose.state !== 'closed') {
|
|
36659
|
+
try {
|
|
36660
|
+
yield contextToClose.close();
|
|
36661
|
+
} catch (error) {
|
|
36662
|
+
this.log.warn('Could not close audio context', Object.assign(Object.assign({}, this.logContext), {
|
|
36663
|
+
error
|
|
36664
|
+
}));
|
|
36665
|
+
}
|
|
36666
|
+
}
|
|
35475
36667
|
}
|
|
35476
36668
|
});
|
|
35477
36669
|
}
|
|
36670
|
+
/**
|
|
36671
|
+
* Detaches the web audio graph the SDK built from the current audio context and closes that
|
|
36672
|
+
* context if the SDK created it. A context provided through `webAudioMix` is left untouched.
|
|
36673
|
+
*/
|
|
36674
|
+
releaseAudioContext() {
|
|
36675
|
+
return __awaiter(this, arguments, void 0, function () {
|
|
36676
|
+
var _this5 = this;
|
|
36677
|
+
let retainedAudioTracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
36678
|
+
return function* () {
|
|
36679
|
+
// both reads happen before the first await, so they describe the room as it was when the
|
|
36680
|
+
// disconnect started rather than after a reconnect has had a chance to run
|
|
36681
|
+
_this5.audioContextReleased = true;
|
|
36682
|
+
const contextToRelease = _this5.audioContext;
|
|
36683
|
+
const unlock = yield _this5.audioContextMutex.lock();
|
|
36684
|
+
try {
|
|
36685
|
+
if (!_this5.audioContextReleased || _this5.audioContext !== contextToRelease) {
|
|
36686
|
+
// a reconnect acquired a new context while we were waiting, closing it would leave the
|
|
36687
|
+
// reconnected room without one
|
|
36688
|
+
return;
|
|
36689
|
+
}
|
|
36690
|
+
if (!_this5.ownsAudioContext) {
|
|
36691
|
+
// a provided context stays open, so tracks that outlive the room stay functional on it.
|
|
36692
|
+
// The tracks this room detached have released their nodes during track teardown already.
|
|
36693
|
+
// read under the lock: an acquire queued behind an in-flight release flips this flag
|
|
36694
|
+
return;
|
|
36695
|
+
}
|
|
36696
|
+
try {
|
|
36697
|
+
// tracks that outlive the room aren't reachable through the local participant anymore,
|
|
36698
|
+
// but still hold web audio nodes in the context we're about to close
|
|
36699
|
+
yield Promise.all(retainedAudioTracks.map(track => track.setAudioContext(undefined)));
|
|
36700
|
+
} catch (error) {
|
|
36701
|
+
_this5.log.warn('Could not detach retained audio tracks from the audio context', Object.assign(Object.assign({}, _this5.logContext), {
|
|
36702
|
+
error
|
|
36703
|
+
}));
|
|
36704
|
+
}
|
|
36705
|
+
// the participant maps have already been cleared by `handleDisconnect`, so this reaches no
|
|
36706
|
+
// tracks of its own — `retainedAudioTracks` above is what detaches them
|
|
36707
|
+
yield _this5.setRoomAudioContext(undefined, false);
|
|
36708
|
+
} finally {
|
|
36709
|
+
unlock();
|
|
36710
|
+
}
|
|
36711
|
+
}();
|
|
36712
|
+
});
|
|
36713
|
+
}
|
|
35478
36714
|
createParticipant(identity, info) {
|
|
35479
36715
|
var _a;
|
|
35480
36716
|
let participant;
|
|
@@ -35490,7 +36726,9 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35490
36726
|
});
|
|
35491
36727
|
}
|
|
35492
36728
|
if (this.options.webAudioMix) {
|
|
35493
|
-
participant.setAudioContext(this.audioContext)
|
|
36729
|
+
participant.setAudioContext(this.audioContext).catch(error => this.log.warn('Could not set audio context', Object.assign(Object.assign({}, this.logContext), {
|
|
36730
|
+
error
|
|
36731
|
+
})));
|
|
35494
36732
|
}
|
|
35495
36733
|
if ((_a = this.options.audioOutput) === null || _a === void 0 ? void 0 : _a.deviceId) {
|
|
35496
36734
|
participant.setAudioOutput(this.options.audioOutput).catch(e => this.log.warn("Could not set audio output: ".concat(e.message)));
|
|
@@ -35603,28 +36841,28 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35603
36841
|
}
|
|
35604
36842
|
// Advertise deflate-raw decompression support so peers know they can send us compressed data
|
|
35605
36843
|
// streams (gated separately from clientProtocol — see the data streams v2 spec).
|
|
35606
|
-
if (
|
|
36844
|
+
if (isDeflateRawCompressionSupported()) {
|
|
35607
36845
|
capabilities.push(ClientInfo_Capability.CAP_COMPRESSION_DEFLATE_RAW);
|
|
35608
36846
|
}
|
|
35609
36847
|
return capabilities;
|
|
35610
36848
|
}
|
|
35611
36849
|
registerRpcDataStreamHandler() {
|
|
35612
36850
|
this.incomingDataStreamManager.registerTextStreamHandler(RPC_REQUEST_DATA_STREAM_TOPIC, (reader_1, _a) => __awaiter(this, [reader_1, _a], void 0, function (reader, _ref7) {
|
|
35613
|
-
var
|
|
36851
|
+
var _this6 = this;
|
|
35614
36852
|
let identity = _ref7.identity;
|
|
35615
36853
|
return function* () {
|
|
35616
36854
|
var _b;
|
|
35617
36855
|
const attributes = (_b = reader.info.attributes) !== null && _b !== void 0 ? _b : {};
|
|
35618
|
-
yield
|
|
36856
|
+
yield _this6.rpcServerManager.handleIncomingDataStream(reader, identity, attributes);
|
|
35619
36857
|
}();
|
|
35620
36858
|
}));
|
|
35621
36859
|
this.incomingDataStreamManager.registerTextStreamHandler(RPC_RESPONSE_DATA_STREAM_TOPIC, (reader_1, _a) => __awaiter(this, [reader_1, _a], void 0, function (reader, _ref8) {
|
|
35622
|
-
var
|
|
36860
|
+
var _this7 = this;
|
|
35623
36861
|
let identity = _ref8.identity;
|
|
35624
36862
|
return function* () {
|
|
35625
36863
|
var _b;
|
|
35626
36864
|
const attributes = (_b = reader.info.attributes) !== null && _b !== void 0 ? _b : {};
|
|
35627
|
-
yield
|
|
36865
|
+
yield _this7.rpcClientManager.handleIncomingDataStream(reader, identity, attributes);
|
|
35628
36866
|
}();
|
|
35629
36867
|
}));
|
|
35630
36868
|
}
|
|
@@ -35783,7 +37021,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35783
37021
|
type: TrackType.AUDIO
|
|
35784
37022
|
}), new LocalAudioTrack(publishOptions.useRealTracks && ((_c = navigator.mediaDevices) === null || _c === void 0 ? void 0 : _c.getUserMedia) ? (yield navigator.mediaDevices.getUserMedia({
|
|
35785
37023
|
audio: true
|
|
35786
|
-
})).getAudioTracks()[0] :
|
|
37024
|
+
})).getAudioTracks()[0] : this.acquireEmptyAudioStreamTrack(), undefined, false, this.audioContext, {
|
|
35787
37025
|
loggerName: this.options.loggerName,
|
|
35788
37026
|
loggerContextCb: () => this.logContext
|
|
35789
37027
|
}), {
|
|
@@ -35814,7 +37052,7 @@ class Room extends eventsExports.EventEmitter {
|
|
|
35814
37052
|
info.tracks = [...info.tracks, videoTrack];
|
|
35815
37053
|
}
|
|
35816
37054
|
if (participantOptions.audio) {
|
|
35817
|
-
const dummyTrack =
|
|
37055
|
+
const dummyTrack = this.acquireEmptyAudioStreamTrack();
|
|
35818
37056
|
const audioTrack = new TrackInfo({
|
|
35819
37057
|
source: TrackSource.MICROPHONE,
|
|
35820
37058
|
sid: Math.floor(Math.random() * 10000).toString(),
|
|
@@ -36653,225 +37891,186 @@ class TokenSourceFixed {}
|
|
|
36653
37891
|
* and {@link TokenSourceCustom}.
|
|
36654
37892
|
*/
|
|
36655
37893
|
class TokenSourceConfigurable {}new TextEncoder();
|
|
36656
|
-
const decoder = new TextDecoder()
|
|
36657
|
-
|
|
36658
|
-
|
|
36659
|
-
});
|
|
37894
|
+
const decoder = new TextDecoder(),
|
|
37895
|
+
strictDecoder = new TextDecoder("utf-8", {
|
|
37896
|
+
fatal: true
|
|
37897
|
+
});
|
|
37898
|
+
function decodeBase64(encoded) {
|
|
37899
|
+
let url = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
37900
|
+
if (Uint8Array.fromBase64) return Uint8Array.fromBase64(encoded, {
|
|
37901
|
+
alphabet: url ? "base64url" : "base64"
|
|
37902
|
+
});
|
|
37903
|
+
if (url) {
|
|
37904
|
+
if (encoded.includes("+") || encoded.includes("/")) throw new TypeError("Invalid base64url");
|
|
37905
|
+
encoded = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
37906
|
+
}
|
|
37907
|
+
const binary = atob(encoded),
|
|
37908
|
+
bytes = new Uint8Array(binary.length);
|
|
37909
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
37910
|
+
return bytes;
|
|
37911
|
+
}class JOSEError extends Error {
|
|
36660
37912
|
constructor(message, options) {
|
|
36661
37913
|
var _Error$captureStackTr;
|
|
36662
|
-
super(message, options);
|
|
36663
|
-
_defineProperty(this, "code", 'ERR_JOSE_GENERIC');
|
|
36664
|
-
this.name = this.constructor.name;
|
|
36665
|
-
(_Error$captureStackTr = Error.captureStackTrace) === null || _Error$captureStackTr === void 0 || _Error$captureStackTr.call(Error, this, this.constructor);
|
|
37914
|
+
super(message, options), _defineProperty(this, "code", "ERR_JOSE_GENERIC"), this.name = this.constructor.name, (_Error$captureStackTr = Error.captureStackTrace) === null || _Error$captureStackTr === void 0 ? void 0 : _Error$captureStackTr.call(Error, this, this.constructor);
|
|
36666
37915
|
}
|
|
36667
37916
|
}
|
|
36668
|
-
_defineProperty(JOSEError, "code",
|
|
37917
|
+
_defineProperty(JOSEError, "code", "ERR_JOSE_GENERIC");
|
|
36669
37918
|
class JWTClaimValidationFailed extends JOSEError {
|
|
36670
37919
|
constructor(message, payload) {
|
|
36671
|
-
let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] :
|
|
36672
|
-
let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] :
|
|
37920
|
+
let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "unspecified";
|
|
37921
|
+
let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "unspecified";
|
|
36673
37922
|
super(message, {
|
|
36674
37923
|
cause: {
|
|
36675
37924
|
claim,
|
|
36676
37925
|
reason,
|
|
36677
37926
|
payload
|
|
36678
37927
|
}
|
|
36679
|
-
});
|
|
36680
|
-
_defineProperty(this, "code", 'ERR_JWT_CLAIM_VALIDATION_FAILED');
|
|
36681
|
-
_defineProperty(this, "claim", void 0);
|
|
36682
|
-
_defineProperty(this, "reason", void 0);
|
|
36683
|
-
_defineProperty(this, "payload", void 0);
|
|
36684
|
-
this.claim = claim;
|
|
36685
|
-
this.reason = reason;
|
|
36686
|
-
this.payload = payload;
|
|
37928
|
+
}), _defineProperty(this, "code", "ERR_JWT_CLAIM_VALIDATION_FAILED"), _defineProperty(this, "claim", void 0), _defineProperty(this, "reason", void 0), _defineProperty(this, "payload", void 0), this.claim = claim, this.reason = reason, this.payload = payload;
|
|
36687
37929
|
}
|
|
36688
37930
|
}
|
|
36689
|
-
_defineProperty(JWTClaimValidationFailed, "code",
|
|
37931
|
+
_defineProperty(JWTClaimValidationFailed, "code", "ERR_JWT_CLAIM_VALIDATION_FAILED");
|
|
36690
37932
|
class JWTExpired extends JOSEError {
|
|
36691
37933
|
constructor(message, payload) {
|
|
36692
|
-
let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] :
|
|
36693
|
-
let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] :
|
|
37934
|
+
let claim = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "unspecified";
|
|
37935
|
+
let reason = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "unspecified";
|
|
36694
37936
|
super(message, {
|
|
36695
37937
|
cause: {
|
|
36696
37938
|
claim,
|
|
36697
37939
|
reason,
|
|
36698
37940
|
payload
|
|
36699
37941
|
}
|
|
36700
|
-
});
|
|
36701
|
-
_defineProperty(this, "code", 'ERR_JWT_EXPIRED');
|
|
36702
|
-
_defineProperty(this, "claim", void 0);
|
|
36703
|
-
_defineProperty(this, "reason", void 0);
|
|
36704
|
-
_defineProperty(this, "payload", void 0);
|
|
36705
|
-
this.claim = claim;
|
|
36706
|
-
this.reason = reason;
|
|
36707
|
-
this.payload = payload;
|
|
37942
|
+
}), _defineProperty(this, "code", "ERR_JWT_EXPIRED"), _defineProperty(this, "claim", void 0), _defineProperty(this, "reason", void 0), _defineProperty(this, "payload", void 0), this.claim = claim, this.reason = reason, this.payload = payload;
|
|
36708
37943
|
}
|
|
36709
37944
|
}
|
|
36710
|
-
_defineProperty(JWTExpired, "code",
|
|
37945
|
+
_defineProperty(JWTExpired, "code", "ERR_JWT_EXPIRED");
|
|
36711
37946
|
class JOSEAlgNotAllowed extends JOSEError {
|
|
36712
37947
|
constructor() {
|
|
36713
37948
|
super(...arguments);
|
|
36714
|
-
_defineProperty(this, "code",
|
|
37949
|
+
_defineProperty(this, "code", "ERR_JOSE_ALG_NOT_ALLOWED");
|
|
36715
37950
|
}
|
|
36716
37951
|
}
|
|
36717
|
-
_defineProperty(JOSEAlgNotAllowed, "code",
|
|
37952
|
+
_defineProperty(JOSEAlgNotAllowed, "code", "ERR_JOSE_ALG_NOT_ALLOWED");
|
|
36718
37953
|
class JOSENotSupported extends JOSEError {
|
|
36719
37954
|
constructor() {
|
|
36720
37955
|
super(...arguments);
|
|
36721
|
-
_defineProperty(this, "code",
|
|
37956
|
+
_defineProperty(this, "code", "ERR_JOSE_NOT_SUPPORTED");
|
|
36722
37957
|
}
|
|
36723
37958
|
}
|
|
36724
|
-
_defineProperty(JOSENotSupported, "code",
|
|
37959
|
+
_defineProperty(JOSENotSupported, "code", "ERR_JOSE_NOT_SUPPORTED");
|
|
36725
37960
|
class JWEDecryptionFailed extends JOSEError {
|
|
36726
37961
|
constructor() {
|
|
36727
|
-
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
37962
|
+
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "decryption operation failed";
|
|
36728
37963
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
36729
37964
|
super(message, options);
|
|
36730
|
-
_defineProperty(this, "code",
|
|
37965
|
+
_defineProperty(this, "code", "ERR_JWE_DECRYPTION_FAILED");
|
|
36731
37966
|
}
|
|
36732
37967
|
}
|
|
36733
|
-
_defineProperty(JWEDecryptionFailed, "code",
|
|
37968
|
+
_defineProperty(JWEDecryptionFailed, "code", "ERR_JWE_DECRYPTION_FAILED");
|
|
36734
37969
|
class JWEInvalid extends JOSEError {
|
|
36735
37970
|
constructor() {
|
|
36736
37971
|
super(...arguments);
|
|
36737
|
-
_defineProperty(this, "code",
|
|
37972
|
+
_defineProperty(this, "code", "ERR_JWE_INVALID");
|
|
36738
37973
|
}
|
|
36739
37974
|
}
|
|
36740
|
-
_defineProperty(JWEInvalid, "code",
|
|
37975
|
+
_defineProperty(JWEInvalid, "code", "ERR_JWE_INVALID");
|
|
36741
37976
|
class JWSInvalid extends JOSEError {
|
|
36742
37977
|
constructor() {
|
|
36743
37978
|
super(...arguments);
|
|
36744
|
-
_defineProperty(this, "code",
|
|
37979
|
+
_defineProperty(this, "code", "ERR_JWS_INVALID");
|
|
36745
37980
|
}
|
|
36746
37981
|
}
|
|
36747
|
-
_defineProperty(JWSInvalid, "code",
|
|
37982
|
+
_defineProperty(JWSInvalid, "code", "ERR_JWS_INVALID");
|
|
36748
37983
|
class JWTInvalid extends JOSEError {
|
|
36749
37984
|
constructor() {
|
|
36750
37985
|
super(...arguments);
|
|
36751
|
-
_defineProperty(this, "code",
|
|
37986
|
+
_defineProperty(this, "code", "ERR_JWT_INVALID");
|
|
36752
37987
|
}
|
|
36753
37988
|
}
|
|
36754
|
-
_defineProperty(JWTInvalid, "code",
|
|
37989
|
+
_defineProperty(JWTInvalid, "code", "ERR_JWT_INVALID");
|
|
36755
37990
|
class JWKInvalid extends JOSEError {
|
|
36756
37991
|
constructor() {
|
|
36757
37992
|
super(...arguments);
|
|
36758
|
-
_defineProperty(this, "code",
|
|
37993
|
+
_defineProperty(this, "code", "ERR_JWK_INVALID");
|
|
36759
37994
|
}
|
|
36760
37995
|
}
|
|
36761
|
-
_defineProperty(JWKInvalid, "code",
|
|
37996
|
+
_defineProperty(JWKInvalid, "code", "ERR_JWK_INVALID");
|
|
36762
37997
|
class JWKSInvalid extends JOSEError {
|
|
36763
37998
|
constructor() {
|
|
36764
37999
|
super(...arguments);
|
|
36765
|
-
_defineProperty(this, "code",
|
|
38000
|
+
_defineProperty(this, "code", "ERR_JWKS_INVALID");
|
|
36766
38001
|
}
|
|
36767
38002
|
}
|
|
36768
|
-
_defineProperty(JWKSInvalid, "code",
|
|
38003
|
+
_defineProperty(JWKSInvalid, "code", "ERR_JWKS_INVALID");
|
|
36769
38004
|
class JWKSNoMatchingKey extends JOSEError {
|
|
36770
38005
|
constructor() {
|
|
36771
|
-
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
38006
|
+
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "no applicable key found in the JSON Web Key Set";
|
|
36772
38007
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
36773
38008
|
super(message, options);
|
|
36774
|
-
_defineProperty(this, "code",
|
|
38009
|
+
_defineProperty(this, "code", "ERR_JWKS_NO_MATCHING_KEY");
|
|
36775
38010
|
}
|
|
36776
38011
|
}
|
|
36777
|
-
_defineProperty(JWKSNoMatchingKey, "code",
|
|
38012
|
+
_defineProperty(JWKSNoMatchingKey, "code", "ERR_JWKS_NO_MATCHING_KEY");
|
|
36778
38013
|
class JWKSMultipleMatchingKeys extends JOSEError {
|
|
36779
38014
|
constructor() {
|
|
36780
|
-
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
38015
|
+
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "multiple matching keys found in the JSON Web Key Set";
|
|
36781
38016
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
36782
38017
|
super(message, options);
|
|
36783
38018
|
_defineProperty(this, Symbol.asyncIterator, _wrapAsyncGenerator(function* () {}));
|
|
36784
|
-
_defineProperty(this, "code",
|
|
38019
|
+
_defineProperty(this, "code", "ERR_JWKS_MULTIPLE_MATCHING_KEYS");
|
|
36785
38020
|
}
|
|
36786
38021
|
}
|
|
36787
|
-
_defineProperty(JWKSMultipleMatchingKeys, "code",
|
|
38022
|
+
_defineProperty(JWKSMultipleMatchingKeys, "code", "ERR_JWKS_MULTIPLE_MATCHING_KEYS");
|
|
36788
38023
|
class JWKSTimeout extends JOSEError {
|
|
36789
38024
|
constructor() {
|
|
36790
|
-
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
38025
|
+
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "request timed out";
|
|
36791
38026
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
36792
38027
|
super(message, options);
|
|
36793
|
-
_defineProperty(this, "code",
|
|
38028
|
+
_defineProperty(this, "code", "ERR_JWKS_TIMEOUT");
|
|
36794
38029
|
}
|
|
36795
38030
|
}
|
|
36796
|
-
_defineProperty(JWKSTimeout, "code",
|
|
38031
|
+
_defineProperty(JWKSTimeout, "code", "ERR_JWKS_TIMEOUT");
|
|
36797
38032
|
class JWSSignatureVerificationFailed extends JOSEError {
|
|
36798
38033
|
constructor() {
|
|
36799
|
-
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] :
|
|
38034
|
+
let message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "signature verification failed";
|
|
36800
38035
|
let options = arguments.length > 1 ? arguments[1] : undefined;
|
|
36801
38036
|
super(message, options);
|
|
36802
|
-
_defineProperty(this, "code",
|
|
38037
|
+
_defineProperty(this, "code", "ERR_JWS_SIGNATURE_VERIFICATION_FAILED");
|
|
36803
38038
|
}
|
|
36804
38039
|
}
|
|
36805
|
-
_defineProperty(JWSSignatureVerificationFailed, "code",
|
|
36806
|
-
if (Uint8Array.fromBase64) {
|
|
36807
|
-
return Uint8Array.fromBase64(encoded);
|
|
36808
|
-
}
|
|
36809
|
-
const binary = atob(encoded);
|
|
36810
|
-
const bytes = new Uint8Array(binary.length);
|
|
36811
|
-
for (let i = 0; i < binary.length; i++) {
|
|
36812
|
-
bytes[i] = binary.charCodeAt(i);
|
|
36813
|
-
}
|
|
36814
|
-
return bytes;
|
|
36815
|
-
}const invalid = 'The input to be decoded is not correctly encoded.';
|
|
38040
|
+
_defineProperty(JWSSignatureVerificationFailed, "code", "ERR_JWS_SIGNATURE_VERIFICATION_FAILED");const invalid = "The input to be decoded is not correctly encoded.";
|
|
36816
38041
|
function decode(input) {
|
|
36817
|
-
if (Uint8Array.fromBase64) {
|
|
36818
|
-
try {
|
|
36819
|
-
return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
|
|
36820
|
-
alphabet: 'base64url'
|
|
36821
|
-
});
|
|
36822
|
-
} catch (cause) {
|
|
36823
|
-
throw new TypeError(invalid, {
|
|
36824
|
-
cause
|
|
36825
|
-
});
|
|
36826
|
-
}
|
|
36827
|
-
}
|
|
36828
|
-
let encoded = input;
|
|
36829
|
-
if (encoded instanceof Uint8Array) {
|
|
36830
|
-
encoded = decoder.decode(encoded);
|
|
36831
|
-
}
|
|
36832
|
-
if (encoded.includes('+') || encoded.includes('/')) {
|
|
36833
|
-
throw new TypeError(invalid);
|
|
36834
|
-
}
|
|
36835
|
-
encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
36836
38042
|
try {
|
|
36837
|
-
return decodeBase64(
|
|
36838
|
-
} catch (
|
|
36839
|
-
throw new TypeError(invalid
|
|
38043
|
+
return decodeBase64(typeof input == "string" ? input : decoder.decode(input), !0);
|
|
38044
|
+
} catch (cause) {
|
|
38045
|
+
throw new TypeError(invalid, {
|
|
38046
|
+
cause
|
|
38047
|
+
});
|
|
36840
38048
|
}
|
|
36841
38049
|
}function isObject(input) {
|
|
36842
|
-
if (typeof input
|
|
36843
|
-
return false;
|
|
36844
|
-
}
|
|
38050
|
+
if (typeof input != "object" || input === null || Object.prototype.toString.call(input) !== "[object Object]") return false;
|
|
36845
38051
|
const prototype = Object.getPrototypeOf(input);
|
|
36846
|
-
|
|
36847
|
-
return true;
|
|
36848
|
-
}
|
|
36849
|
-
let proto = prototype;
|
|
36850
|
-
while (Object.getPrototypeOf(proto) !== null) {
|
|
36851
|
-
proto = Object.getPrototypeOf(proto);
|
|
36852
|
-
}
|
|
36853
|
-
return prototype === proto;
|
|
38052
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
36854
38053
|
}function decodeJwt(jwt) {
|
|
36855
|
-
if (typeof jwt
|
|
36856
|
-
const _jwt$split = jwt.split(
|
|
38054
|
+
if (typeof jwt != "string") throw new JWTInvalid("JWTs must use Compact JWS serialization, JWT must be a string");
|
|
38055
|
+
const _jwt$split = jwt.split("."),
|
|
36857
38056
|
payload = _jwt$split[1],
|
|
36858
38057
|
length = _jwt$split.length;
|
|
36859
|
-
if (length === 5) throw new JWTInvalid(
|
|
36860
|
-
if (length !== 3) throw new JWTInvalid(
|
|
36861
|
-
if (!payload) throw new JWTInvalid(
|
|
38058
|
+
if (length === 5) throw new JWTInvalid("Only JWTs using Compact JWS serialization can be decoded");
|
|
38059
|
+
if (length !== 3) throw new JWTInvalid("Invalid JWT");
|
|
38060
|
+
if (!payload) throw new JWTInvalid("JWTs must contain a payload");
|
|
36862
38061
|
let decoded;
|
|
36863
38062
|
try {
|
|
36864
38063
|
decoded = decode(payload);
|
|
36865
38064
|
} catch (_unused) {
|
|
36866
|
-
throw new JWTInvalid(
|
|
38065
|
+
throw new JWTInvalid("Failed to base64url decode the payload");
|
|
36867
38066
|
}
|
|
36868
38067
|
let result;
|
|
36869
38068
|
try {
|
|
36870
38069
|
result = JSON.parse(strictDecoder.decode(decoded));
|
|
36871
38070
|
} catch (_unused2) {
|
|
36872
|
-
throw new JWTInvalid(
|
|
38071
|
+
throw new JWTInvalid("Failed to parse the decoded payload as JSON");
|
|
36873
38072
|
}
|
|
36874
|
-
if (!isObject(result)) throw new JWTInvalid(
|
|
38073
|
+
if (!isObject(result)) throw new JWTInvalid("Invalid JWT Claims Set");
|
|
36875
38074
|
return result;
|
|
36876
38075
|
}const ONE_SECOND_IN_MILLISECONDS = 1000;
|
|
36877
38076
|
const ONE_MINUTE_IN_MILLISECONDS = 60 * ONE_SECOND_IN_MILLISECONDS;
|
|
@@ -37279,4 +38478,4 @@ const serializers = {
|
|
|
37279
38478
|
json,
|
|
37280
38479
|
raw,
|
|
37281
38480
|
custom
|
|
37282
|
-
};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,isSVCCodec,isScriptTransformSupported,isSerializer,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,serializers,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsH265,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map
|
|
38481
|
+
};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,canFailOverToAnotherRegion,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,isSVCCodec,isScriptTransformSupported,isSerializer,isVideoCodec,isVideoFrame,isVideoTrack,needsRbspUnescaping,parseRbsp,protocolVersion,ratchet,releaseEmptyAudioStreamTrack,serializers,setLogExtension,setLogLevel,supportsAV1,supportsAdaptiveStream,supportsAudioOutputSelection,supportsDynacast,supportsH265,supportsVP9,version,videoCodecs,writeRbsp};//# sourceMappingURL=livekit-client.esm.mjs.map
|