livekit-client 2.20.0 → 2.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +41 -11
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +744 -178
- 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 +41 -7
- package/dist/livekit-client.fm.worker.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +1 -0
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +1 -0
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +1 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
- package/dist/src/frameMetadata/frameMetadata.d.ts +1 -0
- package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -1
- package/dist/src/frameMetadata/types.d.ts +1 -0
- package/dist/src/frameMetadata/types.d.ts.map +1 -1
- package/dist/src/logger.d.ts +3 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +42 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/PCTransportManager.d.ts +1 -0
- package/dist/src/room/PCTransportManager.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +46 -4
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/RegionUrlProvider.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +1 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
- package/dist/src/room/data-track/frame.d.ts +1 -0
- package/dist/src/room/data-track/frame.d.ts.map +1 -1
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
- package/dist/src/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/index.d.ts +1 -0
- package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
- package/dist/src/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/Participant.d.ts +1 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/publishUtils.d.ts +7 -0
- package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/Track.d.ts +1 -0
- package/dist/src/room/track/Track.d.ts.map +1 -1
- package/dist/src/room/track/options.d.ts +11 -1
- package/dist/src/room/track/options.d.ts.map +1 -1
- package/dist/src/room/track/record.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +1 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/test/signalToken.d.ts +26 -0
- package/dist/src/test/signalToken.d.ts.map +1 -0
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts.map +1 -0
- package/dist/src/utils/dataPacketBuffer.d.ts +19 -0
- package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
- package/dist/ts4.2/e2ee/E2eeManager.d.ts +1 -0
- package/dist/ts4.2/e2ee/types.d.ts +1 -0
- package/dist/ts4.2/e2ee/utils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -0
- package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +1 -0
- package/dist/ts4.2/frameMetadata/frameMetadata.d.ts +1 -0
- package/dist/ts4.2/frameMetadata/types.d.ts +1 -0
- package/dist/ts4.2/logger.d.ts +3 -1
- package/dist/ts4.2/room/PCTransport.d.ts +42 -1
- package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
- package/dist/ts4.2/room/RTCEngine.d.ts +46 -4
- package/dist/ts4.2/room/Room.d.ts +1 -0
- package/dist/ts4.2/room/data-track/frame.d.ts +1 -0
- package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +7 -0
- package/dist/ts4.2/room/data-track/outgoing/types.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/extensions.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/index.d.ts +1 -0
- package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -0
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/room/participant/Participant.d.ts +1 -0
- package/dist/ts4.2/room/participant/publishUtils.d.ts +7 -0
- package/dist/ts4.2/room/track/LocalAudioTrack.d.ts +1 -0
- package/dist/ts4.2/room/track/Track.d.ts +1 -0
- package/dist/ts4.2/room/track/options.d.ts +11 -1
- package/dist/ts4.2/room/utils.d.ts +1 -0
- package/dist/ts4.2/test/signalToken.d.ts +26 -0
- package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
- package/dist/ts4.2/utils/dataPacketBuffer.d.ts +19 -0
- package/package.json +8 -4
- package/src/api/SignalClient.e2e.test.ts +264 -0
- package/src/api/SignalClient.ts +35 -2
- package/src/e2ee/E2eeManager.ts +3 -1
- package/src/e2ee/types.ts +1 -0
- package/src/e2ee/utils.ts +1 -0
- package/src/e2ee/worker/DataCryptor.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.test.ts +1 -0
- package/src/e2ee/worker/FrameCryptor.ts +1 -0
- package/src/e2ee/worker/ParticipantKeyHandler.test.ts +1 -0
- package/src/e2ee/worker/e2ee.worker.ts +1 -0
- package/src/e2ee/worker/naluUtils.ts +1 -0
- package/src/e2ee/worker/sifPayload.ts +1 -0
- package/src/frameMetadata/frameMetadata.test.ts +83 -0
- package/src/frameMetadata/frameMetadata.ts +8 -0
- package/src/frameMetadata/types.ts +1 -0
- package/src/logger.ts +2 -0
- package/src/room/PCTransport.test.ts +150 -0
- package/src/room/PCTransport.ts +179 -31
- package/src/room/PCTransportManager.ts +10 -6
- package/src/room/RTCEngine.test.ts +275 -2
- package/src/room/RTCEngine.ts +277 -71
- package/src/room/RegionUrlProvider.ts +4 -2
- package/src/room/Room.ts +8 -1
- package/src/room/data-track/depacketizer.ts +1 -0
- package/src/room/data-track/frame.ts +1 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +303 -0
- package/src/room/data-track/incoming/IncomingDataTrackManager.ts +70 -8
- package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +2 -0
- package/src/room/data-track/outgoing/types.ts +1 -0
- package/src/room/data-track/packet/extensions.ts +1 -0
- package/src/room/data-track/packet/index.ts +1 -0
- package/src/room/data-track/packet/serializable.ts +1 -0
- package/src/room/errors.ts +4 -4
- package/src/room/participant/LocalParticipant.ts +17 -6
- package/src/room/participant/Participant.ts +6 -0
- package/src/room/participant/publishUtils.ts +14 -11
- package/src/room/track/LocalAudioTrack.ts +16 -0
- package/src/room/track/Track.ts +11 -6
- package/src/room/track/options.ts +11 -1
- package/src/room/track/record.ts +1 -0
- package/src/room/utils.ts +1 -0
- package/src/test/signalServerSetup.ts +151 -0
- package/src/test/signalToken.ts +59 -0
- package/src/test/vitest-context.d.ts +10 -0
- package/src/type-polyfills/{non-shared-typed-arrays.d.ts → non-shared-typed-arrays.ts} +6 -1
- package/src/utils/dataPacketBuffer.test.ts +100 -0
- package/src/utils/dataPacketBuffer.ts +48 -1
|
@@ -8226,6 +8226,8 @@ var LoggerNames;
|
|
|
8226
8226
|
LoggerNames["PCTransport"] = "livekit-pc-transport";
|
|
8227
8227
|
LoggerNames["E2EE"] = "lk-e2ee";
|
|
8228
8228
|
LoggerNames["DataTracks"] = "livekit-data-tracks";
|
|
8229
|
+
LoggerNames["Region"] = "livekit-region";
|
|
8230
|
+
LoggerNames["ICE"] = "livekit-ice";
|
|
8229
8231
|
})(LoggerNames || (LoggerNames = {}));
|
|
8230
8232
|
let livekitLogger = loglevelExports.getLogger(LoggerNames.Default);
|
|
8231
8233
|
const livekitLoggers = Object.values(LoggerNames).map(name => loglevelExports.getLogger(name));
|
|
@@ -8810,7 +8812,7 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
|
|
|
8810
8812
|
}
|
|
8811
8813
|
const addEventListener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener');
|
|
8812
8814
|
if (!addEventListener.writable) {
|
|
8813
|
-
log$
|
|
8815
|
+
log$5('Unable to polyfill events');
|
|
8814
8816
|
return;
|
|
8815
8817
|
}
|
|
8816
8818
|
const proto = window.RTCPeerConnection.prototype;
|
|
@@ -8890,7 +8892,7 @@ function disableWarnings(bool) {
|
|
|
8890
8892
|
deprecationWarnings_ = !bool;
|
|
8891
8893
|
return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
|
|
8892
8894
|
}
|
|
8893
|
-
function log$
|
|
8895
|
+
function log$5() {
|
|
8894
8896
|
if (typeof window === 'object') {
|
|
8895
8897
|
if (logDisabled_) {
|
|
8896
8898
|
return;
|
|
@@ -8937,10 +8939,15 @@ function detectBrowser(window) {
|
|
|
8937
8939
|
return brand.brand === 'Chromium';
|
|
8938
8940
|
});
|
|
8939
8941
|
if (chromium) {
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8942
|
+
const version = parseInt(chromium.version, 10);
|
|
8943
|
+
// navigator.userAgentData was introduced in Chromium 90
|
|
8944
|
+
// so any Chromium brand reporting a version below 90 is invalid
|
|
8945
|
+
if (version >= 90) {
|
|
8946
|
+
return {
|
|
8947
|
+
browser: 'chrome',
|
|
8948
|
+
version
|
|
8949
|
+
};
|
|
8950
|
+
}
|
|
8944
8951
|
}
|
|
8945
8952
|
}
|
|
8946
8953
|
if (navigator.mozGetUserMedia) {
|
|
@@ -9047,8 +9054,11 @@ function filterStats(result, track, outbound) {
|
|
|
9047
9054
|
* tree.
|
|
9048
9055
|
*/
|
|
9049
9056
|
/* eslint-env node */
|
|
9050
|
-
const logging = log$
|
|
9057
|
+
const logging = log$5;
|
|
9051
9058
|
function shimGetUserMedia$2(window, browserDetails) {
|
|
9059
|
+
if (browserDetails.version >= 64) {
|
|
9060
|
+
return;
|
|
9061
|
+
}
|
|
9052
9062
|
const navigator = window && window.navigator;
|
|
9053
9063
|
if (!navigator.mediaDevices) {
|
|
9054
9064
|
return;
|
|
@@ -9202,6 +9212,8 @@ function shimGetUserMedia$2(window, browserDetails) {
|
|
|
9202
9212
|
// Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
|
|
9203
9213
|
// function which returns a Promise, it does not accept spec-style
|
|
9204
9214
|
// constraints.
|
|
9215
|
+
// Fixed in M64 too, see
|
|
9216
|
+
// https://groups.google.com/g/discuss-webrtc/c/DMPTKcXVUQ4/m/85FaASoFBwAJ?
|
|
9205
9217
|
if (navigator.mediaDevices.getUserMedia) {
|
|
9206
9218
|
const origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
9207
9219
|
navigator.mediaDevices.getUserMedia = function (cs) {
|
|
@@ -9787,6 +9799,9 @@ function fixNegotiationNeeded(window, browserDetails) {
|
|
|
9787
9799
|
/* eslint-env node */
|
|
9788
9800
|
function shimGetUserMedia$1(window, browserDetails) {
|
|
9789
9801
|
const navigator = window && window.navigator;
|
|
9802
|
+
if (!navigator.mediaDevices) {
|
|
9803
|
+
return;
|
|
9804
|
+
}
|
|
9790
9805
|
const MediaStreamTrack = window && window.MediaStreamTrack;
|
|
9791
9806
|
navigator.getUserMedia = function (constraints, onSuccess, onError) {
|
|
9792
9807
|
// Replace Firefox 44+'s deprecation warning with unprefixed version.
|
|
@@ -9840,10 +9855,10 @@ function shimGetUserMedia$1(window, browserDetails) {
|
|
|
9840
9855
|
/* eslint-env node */
|
|
9841
9856
|
|
|
9842
9857
|
function shimGetDisplayMedia(window, preferredMediaSource) {
|
|
9843
|
-
if (window.navigator.mediaDevices
|
|
9858
|
+
if (!window.navigator.mediaDevices) {
|
|
9844
9859
|
return;
|
|
9845
9860
|
}
|
|
9846
|
-
if (
|
|
9861
|
+
if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) {
|
|
9847
9862
|
return;
|
|
9848
9863
|
}
|
|
9849
9864
|
window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) {
|
|
@@ -10023,13 +10038,17 @@ function shimRTCDataChannel(window) {
|
|
|
10023
10038
|
window.RTCDataChannel = window.DataChannel;
|
|
10024
10039
|
}
|
|
10025
10040
|
}
|
|
10026
|
-
function shimAddTransceiver(window) {
|
|
10041
|
+
function shimAddTransceiver(window, browserDetails) {
|
|
10027
10042
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
10028
10043
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
10029
10044
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
10030
10045
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
10031
10046
|
return;
|
|
10032
10047
|
}
|
|
10048
|
+
// Fixed by https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 in FF110.
|
|
10049
|
+
if (browserDetails.version >= 110) {
|
|
10050
|
+
return;
|
|
10051
|
+
}
|
|
10033
10052
|
const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;
|
|
10034
10053
|
if (origAddTransceiver) {
|
|
10035
10054
|
window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() {
|
|
@@ -10089,10 +10108,14 @@ function shimAddTransceiver(window) {
|
|
|
10089
10108
|
};
|
|
10090
10109
|
}
|
|
10091
10110
|
}
|
|
10092
|
-
function shimGetParameters(window) {
|
|
10111
|
+
function shimGetParameters(window, browserDetails) {
|
|
10093
10112
|
if (!(typeof window === 'object' && window.RTCRtpSender)) {
|
|
10094
10113
|
return;
|
|
10095
10114
|
}
|
|
10115
|
+
// Fixed by https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 in FF110.
|
|
10116
|
+
if (browserDetails.version >= 110) {
|
|
10117
|
+
return;
|
|
10118
|
+
}
|
|
10096
10119
|
const origGetParameters = window.RTCRtpSender.prototype.getParameters;
|
|
10097
10120
|
if (origGetParameters) {
|
|
10098
10121
|
window.RTCRtpSender.prototype.getParameters = function getParameters() {
|
|
@@ -10104,13 +10127,17 @@ function shimGetParameters(window) {
|
|
|
10104
10127
|
};
|
|
10105
10128
|
}
|
|
10106
10129
|
}
|
|
10107
|
-
function shimCreateOffer(window) {
|
|
10130
|
+
function shimCreateOffer(window, browserDetails) {
|
|
10108
10131
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
10109
10132
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
10110
10133
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
10111
10134
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
10112
10135
|
return;
|
|
10113
10136
|
}
|
|
10137
|
+
// Fixed by https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 in FF110.
|
|
10138
|
+
if (browserDetails.version >= 110) {
|
|
10139
|
+
return;
|
|
10140
|
+
}
|
|
10114
10141
|
const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
|
|
10115
10142
|
window.RTCPeerConnection.prototype.createOffer = function createOffer() {
|
|
10116
10143
|
if (this.setParametersPromises && this.setParametersPromises.length) {
|
|
@@ -10123,13 +10150,17 @@ function shimCreateOffer(window) {
|
|
|
10123
10150
|
return origCreateOffer.apply(this, arguments);
|
|
10124
10151
|
};
|
|
10125
10152
|
}
|
|
10126
|
-
function shimCreateAnswer(window) {
|
|
10153
|
+
function shimCreateAnswer(window, browserDetails) {
|
|
10127
10154
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
10128
10155
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
10129
10156
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
10130
10157
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
10131
10158
|
return;
|
|
10132
10159
|
}
|
|
10160
|
+
// Fixed by https://bugzilla.mozilla.org/show_bug.cgi?id=1401592 in FF110.
|
|
10161
|
+
if (browserDetails.version >= 110) {
|
|
10162
|
+
return;
|
|
10163
|
+
}
|
|
10133
10164
|
const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;
|
|
10134
10165
|
window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
|
|
10135
10166
|
if (this.setParametersPromises && this.setParametersPromises.length) {
|
|
@@ -11291,6 +11322,15 @@ function shimMaxMessageSize(window, browserDetails) {
|
|
|
11291
11322
|
if (!window.RTCPeerConnection) {
|
|
11292
11323
|
return;
|
|
11293
11324
|
}
|
|
11325
|
+
if (browserDetails.browser === 'chrome' && browserDetails.version > 102) {
|
|
11326
|
+
// Unified plan is supported so no need to do anything.
|
|
11327
|
+
return;
|
|
11328
|
+
}
|
|
11329
|
+
if (browserDetails.browser === 'firefox' && browserDetails.version >= 113) {
|
|
11330
|
+
// Native RTCSctpTransport, see
|
|
11331
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1278299
|
|
11332
|
+
return;
|
|
11333
|
+
}
|
|
11294
11334
|
if (!('sctp' in window.RTCPeerConnection.prototype)) {
|
|
11295
11335
|
Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {
|
|
11296
11336
|
get() {
|
|
@@ -11427,7 +11467,7 @@ function shimSendThrowTypeError(window, browserDetails) {
|
|
|
11427
11467
|
if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) {
|
|
11428
11468
|
return;
|
|
11429
11469
|
}
|
|
11430
|
-
if (browserDetails.browser === 'chrome' && browserDetails.version
|
|
11470
|
+
if (browserDetails.browser === 'chrome' && browserDetails.version >= 149) {
|
|
11431
11471
|
// Fixed by https://issues.chromium.org/issues/490588131
|
|
11432
11472
|
return;
|
|
11433
11473
|
}
|
|
@@ -11641,7 +11681,7 @@ function adapterFactory() {
|
|
|
11641
11681
|
shimSafari: true
|
|
11642
11682
|
};
|
|
11643
11683
|
// Utils.
|
|
11644
|
-
const logging = log$
|
|
11684
|
+
const logging = log$5;
|
|
11645
11685
|
const browserDetails = detectBrowser(window);
|
|
11646
11686
|
const adapter = {
|
|
11647
11687
|
browserDetails,
|
|
@@ -11706,10 +11746,10 @@ function adapterFactory() {
|
|
|
11706
11746
|
shimSenderGetStats(window);
|
|
11707
11747
|
shimReceiverGetStats(window);
|
|
11708
11748
|
shimRTCDataChannel(window);
|
|
11709
|
-
shimAddTransceiver(window);
|
|
11710
|
-
shimGetParameters(window);
|
|
11711
|
-
shimCreateOffer(window);
|
|
11712
|
-
shimCreateAnswer(window);
|
|
11749
|
+
shimAddTransceiver(window, browserDetails);
|
|
11750
|
+
shimGetParameters(window, browserDetails);
|
|
11751
|
+
shimCreateOffer(window, browserDetails);
|
|
11752
|
+
shimCreateAnswer(window, browserDetails);
|
|
11713
11753
|
shimRTCIceCandidate(window);
|
|
11714
11754
|
shimConnectionState(window);
|
|
11715
11755
|
shimMaxMessageSize(window, browserDetails);
|
|
@@ -11843,7 +11883,7 @@ function getMatch(exp, ua) {
|
|
|
11843
11883
|
}
|
|
11844
11884
|
function getOSVersion(ua) {
|
|
11845
11885
|
return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
|
|
11846
|
-
}var version$1 = "2.20.
|
|
11886
|
+
}var version$1 = "2.20.2";const version = version$1;
|
|
11847
11887
|
const protocolVersion = 17;
|
|
11848
11888
|
/** Initial client protocol. */
|
|
11849
11889
|
const CLIENT_PROTOCOL_DEFAULT = 0;
|
|
@@ -11934,25 +11974,25 @@ class TrackInvalidError extends LivekitError {
|
|
|
11934
11974
|
}
|
|
11935
11975
|
class UnsupportedServer extends LivekitError {
|
|
11936
11976
|
constructor(message) {
|
|
11937
|
-
super(10, message
|
|
11977
|
+
super(10, message || 'unsupported server');
|
|
11938
11978
|
this.name = 'UnsupportedServer';
|
|
11939
11979
|
}
|
|
11940
11980
|
}
|
|
11941
11981
|
class UnexpectedConnectionState extends LivekitError {
|
|
11942
11982
|
constructor(message) {
|
|
11943
|
-
super(12, message
|
|
11983
|
+
super(12, message || 'unexpected connection state');
|
|
11944
11984
|
this.name = 'UnexpectedConnectionState';
|
|
11945
11985
|
}
|
|
11946
11986
|
}
|
|
11947
11987
|
class NegotiationError extends LivekitError {
|
|
11948
11988
|
constructor(message) {
|
|
11949
|
-
super(13, message
|
|
11989
|
+
super(13, message || 'unable to negotiate');
|
|
11950
11990
|
this.name = 'NegotiationError';
|
|
11951
11991
|
}
|
|
11952
11992
|
}
|
|
11953
11993
|
class PublishDataError extends LivekitError {
|
|
11954
11994
|
constructor(message) {
|
|
11955
|
-
super(14, message
|
|
11995
|
+
super(14, message || 'unable to publish data');
|
|
11956
11996
|
this.name = 'PublishDataError';
|
|
11957
11997
|
}
|
|
11958
11998
|
}
|
|
@@ -13157,6 +13197,9 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13157
13197
|
}
|
|
13158
13198
|
/** @internal */
|
|
13159
13199
|
setStreamState(value) {
|
|
13200
|
+
if (this._streamState !== value) {
|
|
13201
|
+
this.log.debug("stream state changed: ".concat(this._streamState, " -> ").concat(value));
|
|
13202
|
+
}
|
|
13160
13203
|
this._streamState = value;
|
|
13161
13204
|
}
|
|
13162
13205
|
constructor(mediaTrack, kind) {
|
|
@@ -13181,8 +13224,8 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13181
13224
|
this.handleAppVisibilityChanged();
|
|
13182
13225
|
}
|
|
13183
13226
|
};
|
|
13184
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Track);
|
|
13185
13227
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
|
13228
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.Track, () => this.logContext);
|
|
13186
13229
|
this.setMaxListeners(100);
|
|
13187
13230
|
this.kind = kind;
|
|
13188
13231
|
this._mediaStreamTrack = mediaTrack;
|
|
@@ -13250,9 +13293,11 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13250
13293
|
this.emit(hasAudio ? TrackEvent.AudioPlaybackFailed : TrackEvent.VideoPlaybackFailed, e);
|
|
13251
13294
|
} else if (e.name === 'AbortError') {
|
|
13252
13295
|
// commonly triggered by another `play` request, only log for debugging purposes
|
|
13253
|
-
|
|
13296
|
+
this.log.debug("".concat(hasAudio ? 'audio' : 'video', " playback aborted, likely due to new play request"));
|
|
13254
13297
|
} else {
|
|
13255
|
-
|
|
13298
|
+
this.log.warn("could not playback ".concat(hasAudio ? 'audio' : 'video'), {
|
|
13299
|
+
error: e
|
|
13300
|
+
});
|
|
13256
13301
|
}
|
|
13257
13302
|
// If audio playback isn't allowed make sure we still play back the video
|
|
13258
13303
|
if (hasAudio && element && allMediaStreamTracks.some(tr => tr.kind === 'video') && e.name === 'NotAllowedError') {
|
|
@@ -13295,6 +13340,7 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13295
13340
|
}
|
|
13296
13341
|
}
|
|
13297
13342
|
stop() {
|
|
13343
|
+
this.log.debug('stopping track');
|
|
13298
13344
|
this.stopMonitor();
|
|
13299
13345
|
this._mediaStreamTrack.stop();
|
|
13300
13346
|
}
|
|
@@ -13315,12 +13361,12 @@ class Track extends eventsExports.EventEmitter {
|
|
|
13315
13361
|
}
|
|
13316
13362
|
/** @internal */
|
|
13317
13363
|
updateLoggerOptions(loggerOptions) {
|
|
13318
|
-
if (loggerOptions.loggerName) {
|
|
13319
|
-
this.log = getLogger(loggerOptions.loggerName);
|
|
13320
|
-
}
|
|
13321
13364
|
if (loggerOptions.loggerContextCb) {
|
|
13322
13365
|
this.loggerContextCb = loggerOptions.loggerContextCb;
|
|
13323
13366
|
}
|
|
13367
|
+
if (loggerOptions.loggerName) {
|
|
13368
|
+
this.log = getLogger(loggerOptions.loggerName, () => this.logContext);
|
|
13369
|
+
}
|
|
13324
13370
|
}
|
|
13325
13371
|
recycleElement(element) {
|
|
13326
13372
|
if (element instanceof HTMLAudioElement) {
|
|
@@ -16279,6 +16325,10 @@ var SignalConnectionState;
|
|
|
16279
16325
|
})(SignalConnectionState || (SignalConnectionState = {}));
|
|
16280
16326
|
/** specifies how much time (in ms) we allow for the ws to close its connection gracefully before continuing */
|
|
16281
16327
|
const MAX_WS_CLOSE_TIME = 250;
|
|
16328
|
+
/**
|
|
16329
|
+
* How long (in ms) to wait for the first message after the WebSocket upgrade.
|
|
16330
|
+
*/
|
|
16331
|
+
const JOIN_RESPONSE_TIMEOUT = 5000;
|
|
16282
16332
|
/** @internal */
|
|
16283
16333
|
class SignalClient {
|
|
16284
16334
|
get currentState() {
|
|
@@ -16427,7 +16477,6 @@ class SignalClient {
|
|
|
16427
16477
|
this.ws = new WebSocketStream(rtcUrl);
|
|
16428
16478
|
try {
|
|
16429
16479
|
this.ws.closed.then(closeInfo => {
|
|
16430
|
-
var _a;
|
|
16431
16480
|
if (this.isEstablishingConnection) {
|
|
16432
16481
|
reject(ConnectionError.internal("Websocket got closed during a (re)connection attempt: ".concat(closeInfo.reason)));
|
|
16433
16482
|
}
|
|
@@ -16439,7 +16488,7 @@ class SignalClient {
|
|
|
16439
16488
|
state: this.state
|
|
16440
16489
|
});
|
|
16441
16490
|
if (this.state === SignalConnectionState.CONNECTED) {
|
|
16442
|
-
this.handleOnClose(
|
|
16491
|
+
this.handleOnClose(closeInfo.reason || 'Unexpected WS error');
|
|
16443
16492
|
}
|
|
16444
16493
|
}
|
|
16445
16494
|
return;
|
|
@@ -16467,7 +16516,26 @@ class SignalClient {
|
|
|
16467
16516
|
}
|
|
16468
16517
|
const signalReader = connection.readable.getReader();
|
|
16469
16518
|
this.streamWriter = connection.writable.getWriter();
|
|
16470
|
-
|
|
16519
|
+
// wsTimeout only guarded the upgrade; guard the first-message read with
|
|
16520
|
+
// its own timeout so a silent server can't hang join() forever.
|
|
16521
|
+
let firstMessage;
|
|
16522
|
+
let firstMessageTimeout;
|
|
16523
|
+
try {
|
|
16524
|
+
firstMessage = yield Promise.race([signalReader.read(), new Promise((_, rejectRead) => {
|
|
16525
|
+
firstMessageTimeout = setTimeout(() => {
|
|
16526
|
+
rejectRead(ConnectionError.timeout('signal connection timed out while waiting for the first message'));
|
|
16527
|
+
}, JOIN_RESPONSE_TIMEOUT);
|
|
16528
|
+
})]);
|
|
16529
|
+
} catch (e) {
|
|
16530
|
+
// No first message in time: release the reader and tear down the ws
|
|
16531
|
+
// so we surface the timeout instead of leaking an open connection.
|
|
16532
|
+
signalReader.releaseLock();
|
|
16533
|
+
reject(e);
|
|
16534
|
+
this.close();
|
|
16535
|
+
return;
|
|
16536
|
+
} finally {
|
|
16537
|
+
clearTimeout(firstMessageTimeout);
|
|
16538
|
+
}
|
|
16471
16539
|
signalReader.releaseLock();
|
|
16472
16540
|
if (!firstMessage.value) {
|
|
16473
16541
|
throw ConnectionError.internal('no message received as first message');
|
|
@@ -17205,21 +17273,50 @@ function createJoinRequestConnectionParams(token, info, opts, publisherOffer) {
|
|
|
17205
17273
|
constructor() {
|
|
17206
17274
|
this.buffer = [];
|
|
17207
17275
|
this._totalSize = 0;
|
|
17276
|
+
this._sentSize = 0;
|
|
17208
17277
|
}
|
|
17209
17278
|
push(item) {
|
|
17210
17279
|
this.buffer.push(item);
|
|
17211
17280
|
this._totalSize += item.data.byteLength;
|
|
17281
|
+
if (item.sent) {
|
|
17282
|
+
this._sentSize += item.data.byteLength;
|
|
17283
|
+
}
|
|
17212
17284
|
}
|
|
17213
17285
|
pop() {
|
|
17214
17286
|
const item = this.buffer.shift();
|
|
17215
17287
|
if (item) {
|
|
17216
17288
|
this._totalSize -= item.data.byteLength;
|
|
17289
|
+
if (item.sent) {
|
|
17290
|
+
this._sentSize -= item.data.byteLength;
|
|
17291
|
+
}
|
|
17217
17292
|
}
|
|
17218
17293
|
return item;
|
|
17219
17294
|
}
|
|
17220
17295
|
getAll() {
|
|
17221
17296
|
return this.buffer.slice();
|
|
17222
17297
|
}
|
|
17298
|
+
/** Every queued packet not yet handed to the channel, in sequence order. */
|
|
17299
|
+
getUnsent() {
|
|
17300
|
+
return this.buffer.filter(item => !item.sent);
|
|
17301
|
+
}
|
|
17302
|
+
/** Marks a single queued packet as handed to the channel. */
|
|
17303
|
+
markSent(item) {
|
|
17304
|
+
if (!item.sent) {
|
|
17305
|
+
item.sent = true;
|
|
17306
|
+
this._sentSize += item.data.byteLength;
|
|
17307
|
+
}
|
|
17308
|
+
}
|
|
17309
|
+
/**
|
|
17310
|
+
* Marks every queued packet as not-yet-sent. Used at the start of a resume replay: whatever is
|
|
17311
|
+
* still buffered was sent on the previous channel (or deferred) and must be re-handed to the
|
|
17312
|
+
* current one, so none of it counts as sent until the replay actually transmits it.
|
|
17313
|
+
*/
|
|
17314
|
+
markAllUnsent() {
|
|
17315
|
+
for (const item of this.buffer) {
|
|
17316
|
+
item.sent = false;
|
|
17317
|
+
}
|
|
17318
|
+
this._sentSize = 0;
|
|
17319
|
+
}
|
|
17223
17320
|
popToSequence(sequence) {
|
|
17224
17321
|
while (this.buffer.length > 0) {
|
|
17225
17322
|
const first = this.buffer[0];
|
|
@@ -17233,7 +17330,12 @@ function createJoinRequestConnectionParams(token, info, opts, publisherOffer) {
|
|
|
17233
17330
|
alignBufferedAmount(bufferedAmount) {
|
|
17234
17331
|
while (this.buffer.length > 0) {
|
|
17235
17332
|
const first = this.buffer[0];
|
|
17236
|
-
|
|
17333
|
+
// Unsent packets aren't part of the channel's bufferedAmount and are still awaiting the
|
|
17334
|
+
// resume replay — trimming them would silently lose them.
|
|
17335
|
+
if (!first.sent) {
|
|
17336
|
+
break;
|
|
17337
|
+
}
|
|
17338
|
+
if (this._sentSize - first.data.byteLength <= bufferedAmount) {
|
|
17237
17339
|
break;
|
|
17238
17340
|
}
|
|
17239
17341
|
this.pop();
|
|
@@ -18018,13 +18120,20 @@ function requireLib() {
|
|
|
18018
18120
|
lib.parseImageAttributes = parser.parseImageAttributes;
|
|
18019
18121
|
lib.parseSimulcastStreamList = parser.parseSimulcastStreamList;
|
|
18020
18122
|
return lib;
|
|
18021
|
-
}var libExports = requireLib();/*
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
18123
|
+
}var libExports = requireLib();/*
|
|
18124
|
+
* Video codecs use a very low bitrate at the beginning and increase slowly by
|
|
18125
|
+
* the bandwidth estimator until they reach the target bitrate. The process commonly
|
|
18126
|
+
* costs more than 10 seconds causing subscribers to get blurry video at the first
|
|
18127
|
+
* few seconds. We use x-google-start-bitrate to hint the BWE to start higher.
|
|
18128
|
+
*
|
|
18129
|
+
* Why 90%: Gives ~10% headroom for bandwidth estimation while starting close to target.
|
|
18130
|
+
* Why same for all codecs: Target bitrate already accounts for codec efficiency
|
|
18131
|
+
* (e.g., users set lower targets for VP9/AV1 knowing they're more efficient).
|
|
18132
|
+
* Why cap at 1 Mbps: Prevents BWE from starting too aggressively on high bitrate tracks.
|
|
18133
|
+
*/
|
|
18134
|
+
const startBitrateMultiplier = 0.9;
|
|
18135
|
+
/** Maximum x-google-start-bitrate in kbps. 1 Mbps prevents BWE from starting too aggressively. */
|
|
18136
|
+
const maxStartBitrateKbps = 1000;
|
|
18028
18137
|
const debounceInterval = 20;
|
|
18029
18138
|
const PCEvents = {
|
|
18030
18139
|
NegotiationStarted: 'negotiationStarted',
|
|
@@ -18049,6 +18158,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18049
18158
|
var _a;
|
|
18050
18159
|
super();
|
|
18051
18160
|
this.log = livekitLogger;
|
|
18161
|
+
this.iceLog = livekitLogger;
|
|
18052
18162
|
this.ddExtID = 0;
|
|
18053
18163
|
this.latestOfferId = 0;
|
|
18054
18164
|
this.latestAcknowledgedOfferId = 0;
|
|
@@ -18075,6 +18185,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18075
18185
|
if (!this._pc) {
|
|
18076
18186
|
return;
|
|
18077
18187
|
}
|
|
18188
|
+
this.log.debug('closing peer connection');
|
|
18078
18189
|
this.pendingInitialOffer = undefined;
|
|
18079
18190
|
this._pc.close();
|
|
18080
18191
|
this._pc.onconnectionstatechange = null;
|
|
@@ -18090,8 +18201,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18090
18201
|
this._pc.oniceconnectionstatechange = null;
|
|
18091
18202
|
this._pc = null;
|
|
18092
18203
|
};
|
|
18093
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport);
|
|
18094
18204
|
this.loggerOptions = loggerOptions;
|
|
18205
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCTransport, () => this.logContext);
|
|
18206
|
+
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
|
|
18095
18207
|
this.config = config;
|
|
18096
18208
|
this._pc = this.createPC();
|
|
18097
18209
|
this.offerLock = new _();
|
|
@@ -18101,26 +18213,39 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18101
18213
|
pc.onicecandidate = ev => {
|
|
18102
18214
|
var _a;
|
|
18103
18215
|
if (!ev.candidate) return;
|
|
18216
|
+
this.iceLog.debug('local ICE candidate gathered', {
|
|
18217
|
+
candidate: ev.candidate.candidate
|
|
18218
|
+
});
|
|
18104
18219
|
(_a = this.onIceCandidate) === null || _a === void 0 ? void 0 : _a.call(this, ev.candidate);
|
|
18105
18220
|
};
|
|
18106
18221
|
pc.onicecandidateerror = ev => {
|
|
18107
18222
|
var _a;
|
|
18223
|
+
this.iceLog.debug('ICE candidate error', {
|
|
18224
|
+
event: ev
|
|
18225
|
+
});
|
|
18108
18226
|
(_a = this.onIceCandidateError) === null || _a === void 0 ? void 0 : _a.call(this, ev);
|
|
18109
18227
|
};
|
|
18110
18228
|
pc.oniceconnectionstatechange = () => {
|
|
18111
18229
|
var _a;
|
|
18230
|
+
this.iceLog.debug("ICE connection state: ".concat(pc.iceConnectionState));
|
|
18112
18231
|
(_a = this.onIceConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.iceConnectionState);
|
|
18113
18232
|
};
|
|
18114
18233
|
pc.onsignalingstatechange = () => {
|
|
18115
18234
|
var _a;
|
|
18235
|
+
this.log.debug("signaling state: ".concat(pc.signalingState));
|
|
18116
18236
|
(_a = this.onSignalingStatechange) === null || _a === void 0 ? void 0 : _a.call(this, pc.signalingState);
|
|
18117
18237
|
};
|
|
18118
18238
|
pc.onconnectionstatechange = () => {
|
|
18119
18239
|
var _a;
|
|
18240
|
+
this.log.debug("connection state: ".concat(pc.connectionState));
|
|
18120
18241
|
(_a = this.onConnectionStateChange) === null || _a === void 0 ? void 0 : _a.call(this, pc.connectionState);
|
|
18121
18242
|
};
|
|
18122
18243
|
pc.ondatachannel = ev => {
|
|
18123
18244
|
var _a;
|
|
18245
|
+
this.log.debug('data channel opened by peer', {
|
|
18246
|
+
label: ev.channel.label,
|
|
18247
|
+
id: ev.channel.id
|
|
18248
|
+
});
|
|
18124
18249
|
(_a = this.onDataChannel) === null || _a === void 0 ? void 0 : _a.call(this, ev);
|
|
18125
18250
|
};
|
|
18126
18251
|
pc.ontrack = ev => {
|
|
@@ -18141,6 +18266,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18141
18266
|
if (this.pc.remoteDescription && !this.restartingIce) {
|
|
18142
18267
|
return this.pc.addIceCandidate(candidate);
|
|
18143
18268
|
}
|
|
18269
|
+
this.iceLog.debug('queuing remote ICE candidate until remote description applied', {
|
|
18270
|
+
pendingCount: this.pendingCandidates.length + 1
|
|
18271
|
+
});
|
|
18144
18272
|
this.pendingCandidates.push(candidate);
|
|
18145
18273
|
});
|
|
18146
18274
|
}
|
|
@@ -18148,10 +18276,10 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18148
18276
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18149
18277
|
var _a, _b;
|
|
18150
18278
|
if (sd.type === 'answer' && this.latestOfferId > 0 && offerId > 0 && offerId !== this.latestOfferId) {
|
|
18151
|
-
this.log.warn('ignoring answer for old offer',
|
|
18279
|
+
this.log.warn('ignoring answer for old offer', {
|
|
18152
18280
|
offerId,
|
|
18153
18281
|
latestOfferId: this.latestOfferId
|
|
18154
|
-
})
|
|
18282
|
+
});
|
|
18155
18283
|
return false;
|
|
18156
18284
|
}
|
|
18157
18285
|
let mungedSDP = undefined;
|
|
@@ -18169,7 +18297,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18169
18297
|
sdpParsed.media.forEach(media => {
|
|
18170
18298
|
ensureIPAddrMatchVersion(media);
|
|
18171
18299
|
});
|
|
18172
|
-
this.log.debug('setting pending initial offer before processing answer'
|
|
18300
|
+
this.log.debug('setting pending initial offer before processing answer');
|
|
18173
18301
|
yield this.setMungedSDP(initialOffer, libExports.write(sdpParsed));
|
|
18174
18302
|
}
|
|
18175
18303
|
const sdpParsed = libExports.parse((_b = sd.sdp) !== null && _b !== void 0 ? _b : '');
|
|
@@ -18215,9 +18343,23 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18215
18343
|
});
|
|
18216
18344
|
}
|
|
18217
18345
|
});
|
|
18346
|
+
// The server's answer sets per-track codec params (e.g. opus `usedtx`) on
|
|
18347
|
+
// the sections mapped to a published track, but leaves the pre-populated
|
|
18348
|
+
// recvonly placeholder sections with defaults. Conform the placeholders so
|
|
18349
|
+
// each shared payload type is consistent across the bundle, otherwise
|
|
18350
|
+
// libwebrtc flags a "bundled payload type collision".
|
|
18351
|
+
const placeholderMids = this.getPlaceholderMids();
|
|
18352
|
+
if (placeholderMids.size > 0) {
|
|
18353
|
+
conformBundledCodecFmtp(sdpParsed.media, media => placeholderMids.has(getMidString(media.mid)));
|
|
18354
|
+
}
|
|
18218
18355
|
mungedSDP = libExports.write(sdpParsed);
|
|
18219
18356
|
}
|
|
18220
18357
|
yield this.setMungedSDP(sd, mungedSDP, true);
|
|
18358
|
+
if (this.pendingCandidates.length > 0) {
|
|
18359
|
+
this.iceLog.debug('flushing queued ICE candidates', {
|
|
18360
|
+
count: this.pendingCandidates.length
|
|
18361
|
+
});
|
|
18362
|
+
}
|
|
18221
18363
|
this.pendingCandidates.forEach(candidate => {
|
|
18222
18364
|
this.pc.addIceCandidate(candidate);
|
|
18223
18365
|
});
|
|
@@ -18253,7 +18395,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18253
18395
|
const unlock = yield this.offerLock.lock();
|
|
18254
18396
|
try {
|
|
18255
18397
|
if (this.pc.signalingState !== 'stable') {
|
|
18256
|
-
this.log.warn('signaling state is not stable, cannot create initial offer'
|
|
18398
|
+
this.log.warn('signaling state is not stable, cannot create initial offer');
|
|
18257
18399
|
return;
|
|
18258
18400
|
}
|
|
18259
18401
|
const offerId = this.latestOfferId + 1;
|
|
@@ -18286,7 +18428,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18286
18428
|
return;
|
|
18287
18429
|
}
|
|
18288
18430
|
if (options === null || options === void 0 ? void 0 : options.iceRestart) {
|
|
18289
|
-
this.
|
|
18431
|
+
this.iceLog.debug('restarting ICE');
|
|
18290
18432
|
this.restartingIce = true;
|
|
18291
18433
|
}
|
|
18292
18434
|
if (this._pc && (this._pc.signalingState === 'have-local-offer' || this.pendingInitialOffer)) {
|
|
@@ -18299,22 +18441,22 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18299
18441
|
yield this._pc.setRemoteDescription(currentSD);
|
|
18300
18442
|
} else {
|
|
18301
18443
|
this.renegotiate = true;
|
|
18302
|
-
this.log.debug('requesting renegotiation'
|
|
18444
|
+
this.log.debug('requesting renegotiation');
|
|
18303
18445
|
return;
|
|
18304
18446
|
}
|
|
18305
18447
|
} else if (!this._pc || this._pc.signalingState === 'closed') {
|
|
18306
|
-
this.log.warn('could not createOffer with closed peer connection'
|
|
18448
|
+
this.log.warn('could not createOffer with closed peer connection');
|
|
18307
18449
|
return;
|
|
18308
18450
|
}
|
|
18309
18451
|
// actually negotiate
|
|
18310
|
-
this.log.debug('starting to negotiate'
|
|
18452
|
+
this.log.debug('starting to negotiate');
|
|
18311
18453
|
// increase the offer id at the start to ensure the offer is always > 0 so that we can use 0 as a default value for legacy behavior
|
|
18312
18454
|
const offerId = this.latestOfferId + 1;
|
|
18313
18455
|
this.latestOfferId = offerId;
|
|
18314
18456
|
const offer = yield this.pc.createOffer(options);
|
|
18315
|
-
this.log.debug('original offer',
|
|
18457
|
+
this.log.debug('original offer', {
|
|
18316
18458
|
sdp: offer.sdp
|
|
18317
|
-
}
|
|
18459
|
+
});
|
|
18318
18460
|
const sdpParsed = libExports.parse((_a = offer.sdp) !== null && _a !== void 0 ? _a : '');
|
|
18319
18461
|
sdpParsed.media.forEach(media => {
|
|
18320
18462
|
ensureIPAddrMatchVersion(media);
|
|
@@ -18340,12 +18482,15 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18340
18482
|
this.ensureVideoDDExtensionForSVC(media, sdpParsed);
|
|
18341
18483
|
}
|
|
18342
18484
|
// mung sdp for bitrate setting that can't apply by sendEncoding
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
const
|
|
18485
|
+
// Use 90% of target bitrate, capped at 1 Mbps for camera to prevent BWE from starting too aggressively
|
|
18486
|
+
// Screen share is not capped since text/UI clarity requires high bitrate from the start
|
|
18487
|
+
// TODO: dynamically adjust start bitrate based on network conditions (e.g., use previous BWE estimate)
|
|
18488
|
+
const calculatedStartBitrate = Math.round(trackbr.maxbr * startBitrateMultiplier);
|
|
18489
|
+
const startBitrate = trackbr.isScreenShare ? calculatedStartBitrate : Math.min(calculatedStartBitrate, maxStartBitrateKbps);
|
|
18490
|
+
let fmtpFound = false;
|
|
18347
18491
|
for (const fmtp of media.fmtp) {
|
|
18348
18492
|
if (fmtp.payload === codecPayload) {
|
|
18493
|
+
fmtpFound = true;
|
|
18349
18494
|
// if another track's fmtp already is set, we cannot override the bitrate
|
|
18350
18495
|
// this has the unfortunate consequence of being forced to use the
|
|
18351
18496
|
// initial track's bitrate for all tracks
|
|
@@ -18355,15 +18500,31 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18355
18500
|
break;
|
|
18356
18501
|
}
|
|
18357
18502
|
}
|
|
18503
|
+
// VP8 and some codecs may not have an existing fmtp line - create one
|
|
18504
|
+
if (!fmtpFound) {
|
|
18505
|
+
media.fmtp.push({
|
|
18506
|
+
payload: codecPayload,
|
|
18507
|
+
config: "x-google-start-bitrate=".concat(startBitrate)
|
|
18508
|
+
});
|
|
18509
|
+
}
|
|
18358
18510
|
return true;
|
|
18359
18511
|
});
|
|
18360
18512
|
}
|
|
18361
18513
|
});
|
|
18514
|
+
// Conform the placeholder sections (pre-populated, or reverted after an
|
|
18515
|
+
// unpublish) so every shared payload type carries identical fmtp across the
|
|
18516
|
+
// bundle, otherwise libwebrtc flags a "bundled payload type collision".
|
|
18517
|
+
// Detection is by transceiver (mids are stable across renegotiations) since
|
|
18518
|
+
// an unpublished section keeps its `a=msid`.
|
|
18519
|
+
const placeholderMids = this.getPlaceholderMids();
|
|
18520
|
+
if (placeholderMids.size > 0) {
|
|
18521
|
+
conformBundledCodecFmtp(sdpParsed.media, media => placeholderMids.has(getMidString(media.mid)));
|
|
18522
|
+
}
|
|
18362
18523
|
if (this.latestOfferId > offerId) {
|
|
18363
|
-
this.log.warn('latestOfferId mismatch',
|
|
18524
|
+
this.log.warn('latestOfferId mismatch', {
|
|
18364
18525
|
latestOfferId: this.latestOfferId,
|
|
18365
18526
|
offerId
|
|
18366
|
-
})
|
|
18527
|
+
});
|
|
18367
18528
|
return;
|
|
18368
18529
|
}
|
|
18369
18530
|
yield this.setMungedSDP(offer, libExports.write(sdpParsed));
|
|
@@ -18388,6 +18549,17 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18388
18549
|
return answer;
|
|
18389
18550
|
});
|
|
18390
18551
|
}
|
|
18552
|
+
/**
|
|
18553
|
+
* Returns the mids of transceivers that carry no outgoing track on this
|
|
18554
|
+
* (publisher) connection: the pre-populated placeholders added by
|
|
18555
|
+
* `RTCEngine.applyInitialPublisherLayout`, plus any transceiver that was used
|
|
18556
|
+
* for a track and reverted on unpublish. Their codec fmtp is conformed to the
|
|
18557
|
+
* published tracks so a shared payload type stays consistent across the bundle.
|
|
18558
|
+
*/
|
|
18559
|
+
getPlaceholderMids() {
|
|
18560
|
+
var _a, _b;
|
|
18561
|
+
return placeholderMidsFromTransceivers((_b = (_a = this._pc) === null || _a === void 0 ? void 0 : _a.getTransceivers()) !== null && _b !== void 0 ? _b : []);
|
|
18562
|
+
}
|
|
18391
18563
|
createDataChannel(label, dataChannelDict) {
|
|
18392
18564
|
return this.pc.createDataChannel(label, dataChannelDict);
|
|
18393
18565
|
}
|
|
@@ -18500,7 +18672,7 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18500
18672
|
if (munged) {
|
|
18501
18673
|
sd.sdp = munged;
|
|
18502
18674
|
try {
|
|
18503
|
-
this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description")
|
|
18675
|
+
this.log.debug("setting munged ".concat(remote ? 'remote' : 'local', " description"));
|
|
18504
18676
|
if (remote) {
|
|
18505
18677
|
yield this.pc.setRemoteDescription(sd);
|
|
18506
18678
|
} else {
|
|
@@ -18508,11 +18680,11 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18508
18680
|
}
|
|
18509
18681
|
return;
|
|
18510
18682
|
} catch (e) {
|
|
18511
|
-
this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"),
|
|
18683
|
+
this.log.warn("not able to set ".concat(sd.type, ", falling back to unmodified sdp"), {
|
|
18512
18684
|
error: e,
|
|
18513
18685
|
mungedSdp: munged,
|
|
18514
18686
|
originalSdp
|
|
18515
|
-
})
|
|
18687
|
+
});
|
|
18516
18688
|
sd.sdp = originalSdp;
|
|
18517
18689
|
}
|
|
18518
18690
|
}
|
|
@@ -18539,9 +18711,9 @@ class PCTransport extends eventsExports.EventEmitter {
|
|
|
18539
18711
|
if (!remote && this.pc.remoteDescription) {
|
|
18540
18712
|
fields.remoteSdp = this.pc.remoteDescription;
|
|
18541
18713
|
}
|
|
18542
|
-
this.log.error("unable to set ".concat(sd.type),
|
|
18714
|
+
this.log.error("unable to set ".concat(sd.type), {
|
|
18543
18715
|
fields
|
|
18544
|
-
})
|
|
18716
|
+
});
|
|
18545
18717
|
throw new NegotiationError(msg);
|
|
18546
18718
|
}
|
|
18547
18719
|
});
|
|
@@ -18610,6 +18782,79 @@ function ensureAudioNackAndStereo(media, stereoMids, nackMids) {
|
|
|
18610
18782
|
}
|
|
18611
18783
|
}
|
|
18612
18784
|
}
|
|
18785
|
+
/**
|
|
18786
|
+
* Returns the mids of transceivers that carry no outgoing track: the
|
|
18787
|
+
* pre-populated placeholders added by `RTCEngine.applyInitialPublisherLayout`,
|
|
18788
|
+
* plus any transceiver that was used for a track and reverted on unpublish. The
|
|
18789
|
+
* `sender.track` check is the reliable signal — an unpublished section keeps its
|
|
18790
|
+
* `a=msid` (and its stale send-derived fmtp), so it can't be told apart from a
|
|
18791
|
+
* real send by SDP alone. Transceiver mids are stable across renegotiations, so
|
|
18792
|
+
* this works for every offer/answer after the first.
|
|
18793
|
+
* @internal
|
|
18794
|
+
*/
|
|
18795
|
+
function placeholderMidsFromTransceivers(transceivers) {
|
|
18796
|
+
const mids = new Set();
|
|
18797
|
+
for (const transceiver of transceivers) {
|
|
18798
|
+
if (transceiver.mid && !transceiver.sender.track) {
|
|
18799
|
+
mids.add(transceiver.mid);
|
|
18800
|
+
}
|
|
18801
|
+
}
|
|
18802
|
+
return mids;
|
|
18803
|
+
}
|
|
18804
|
+
/**
|
|
18805
|
+
* Within a BUNDLE group a payload type must map to identical codec parameters
|
|
18806
|
+
* across every m-line. When the same payload type carries different fmtp between
|
|
18807
|
+
* sections — e.g. opus `usedtx=1` on the published microphone but not on the
|
|
18808
|
+
* pre-populated recvonly placeholders, or H.265 with different `level-id` between
|
|
18809
|
+
* a published video track and a placeholder — libwebrtc flags a "bundled payload
|
|
18810
|
+
* type collision".
|
|
18811
|
+
*
|
|
18812
|
+
* Rewrite the placeholder sections so every shared payload type carries the
|
|
18813
|
+
* same fmtp. Real (non-placeholder) sections always win the canonical value, so
|
|
18814
|
+
* a published track's encoder parameters are never altered. When no real
|
|
18815
|
+
* section declares a payload type — e.g. a placeholder that was reused for a
|
|
18816
|
+
* track and then reverted to recvonly keeps its send-derived `level-id` while
|
|
18817
|
+
* fresh placeholders use the default — the placeholders still converge on the
|
|
18818
|
+
* first value seen, so two placeholders can't disagree either. Only placeholder
|
|
18819
|
+
* sections are ever rewritten, and it is codec-agnostic (opus, H.265, ...).
|
|
18820
|
+
* `isPlaceholder` identifies the sections to conform.
|
|
18821
|
+
* @internal
|
|
18822
|
+
*/
|
|
18823
|
+
function conformBundledCodecFmtp(media, isPlaceholder) {
|
|
18824
|
+
var _a, _b;
|
|
18825
|
+
// Canonical fmtp per payload type. Payload types are unique within a BUNDLE
|
|
18826
|
+
// group, so keying by payload alone (across audio and video) is safe. A real
|
|
18827
|
+
// section's value always takes precedence; otherwise the first placeholder
|
|
18828
|
+
// value seen is used so divergent placeholders still converge.
|
|
18829
|
+
const canonicalByPayload = new Map();
|
|
18830
|
+
const fromRealSection = new Set();
|
|
18831
|
+
for (const m of media) {
|
|
18832
|
+
const placeholder = isPlaceholder(m);
|
|
18833
|
+
for (const fmtp of (_a = m.fmtp) !== null && _a !== void 0 ? _a : []) {
|
|
18834
|
+
if (!placeholder) {
|
|
18835
|
+
canonicalByPayload.set(fmtp.payload, fmtp.config);
|
|
18836
|
+
fromRealSection.add(fmtp.payload);
|
|
18837
|
+
} else if (!canonicalByPayload.has(fmtp.payload)) {
|
|
18838
|
+
canonicalByPayload.set(fmtp.payload, fmtp.config);
|
|
18839
|
+
}
|
|
18840
|
+
}
|
|
18841
|
+
}
|
|
18842
|
+
if (canonicalByPayload.size === 0) {
|
|
18843
|
+
return;
|
|
18844
|
+
}
|
|
18845
|
+
// Conform placeholder sections to the canonical fmtp for each shared payload.
|
|
18846
|
+
for (const m of media) {
|
|
18847
|
+
if (!isPlaceholder(m)) {
|
|
18848
|
+
continue;
|
|
18849
|
+
}
|
|
18850
|
+
for (const fmtp of (_b = m.fmtp) !== null && _b !== void 0 ? _b : []) {
|
|
18851
|
+
const config = canonicalByPayload.get(fmtp.payload);
|
|
18852
|
+
if (config !== undefined && fmtp.config !== config) {
|
|
18853
|
+
fmtp.config = config;
|
|
18854
|
+
}
|
|
18855
|
+
}
|
|
18856
|
+
}
|
|
18857
|
+
}
|
|
18613
18858
|
function extractStereoAndNackAudioFromOffer(offer) {
|
|
18614
18859
|
var _a;
|
|
18615
18860
|
const stereoMids = [];
|
|
@@ -18729,6 +18974,7 @@ class PCTransportManager {
|
|
|
18729
18974
|
var _a;
|
|
18730
18975
|
this.peerConnectionTimeout = roomConnectOptionDefaults.peerConnectionTimeout;
|
|
18731
18976
|
this.log = livekitLogger;
|
|
18977
|
+
this.iceLog = livekitLogger;
|
|
18732
18978
|
this.updateState = () => {
|
|
18733
18979
|
var _a, _b;
|
|
18734
18980
|
const previousState = this.state;
|
|
@@ -18747,12 +18993,13 @@ class PCTransportManager {
|
|
|
18747
18993
|
this.state = PCTransportState.NEW;
|
|
18748
18994
|
}
|
|
18749
18995
|
if (previousState !== this.state) {
|
|
18750
|
-
this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state])
|
|
18996
|
+
this.log.debug("pc state change: from ".concat(PCTransportState[previousState], " to ").concat(PCTransportState[this.state]));
|
|
18751
18997
|
(_a = this.onStateChange) === null || _a === void 0 ? void 0 : _a.call(this, this.state, this.publisher.getConnectionState(), (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.getConnectionState());
|
|
18752
18998
|
}
|
|
18753
18999
|
};
|
|
18754
|
-
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager);
|
|
18755
19000
|
this.loggerOptions = loggerOptions;
|
|
19001
|
+
this.log = getLogger((_a = loggerOptions.loggerName) !== null && _a !== void 0 ? _a : LoggerNames.PCManager, () => this.logContext);
|
|
19002
|
+
this.iceLog = getLogger(LoggerNames.ICE, () => this.logContext);
|
|
18756
19003
|
this.isPublisherConnectionRequired = mode !== 'subscriber-primary';
|
|
18757
19004
|
this.isSubscriberConnectionRequired = mode === 'subscriber-primary';
|
|
18758
19005
|
this.publisher = new PCTransport(rtcConfig, loggerOptions);
|
|
@@ -18825,9 +19072,9 @@ class PCTransportManager {
|
|
|
18825
19072
|
publisher.removeTrack(sender);
|
|
18826
19073
|
}
|
|
18827
19074
|
} catch (e) {
|
|
18828
|
-
this.log.warn('could not removeTrack',
|
|
19075
|
+
this.log.warn('could not removeTrack', {
|
|
18829
19076
|
error: e
|
|
18830
|
-
})
|
|
19077
|
+
});
|
|
18831
19078
|
}
|
|
18832
19079
|
}
|
|
18833
19080
|
}
|
|
@@ -18837,6 +19084,7 @@ class PCTransportManager {
|
|
|
18837
19084
|
}
|
|
18838
19085
|
triggerIceRestart() {
|
|
18839
19086
|
return __awaiter(this, void 0, void 0, function* () {
|
|
19087
|
+
this.iceLog.warn('triggering ICE restart');
|
|
18840
19088
|
if (this.subscriber) {
|
|
18841
19089
|
this.subscriber.restartingIce = true;
|
|
18842
19090
|
}
|
|
@@ -18851,6 +19099,10 @@ class PCTransportManager {
|
|
|
18851
19099
|
addIceCandidate(candidate, target) {
|
|
18852
19100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18853
19101
|
var _a;
|
|
19102
|
+
this.iceLog.debug('adding remote ICE candidate', {
|
|
19103
|
+
target,
|
|
19104
|
+
candidate
|
|
19105
|
+
});
|
|
18854
19106
|
if (target === SignalTarget.PUBLISHER) {
|
|
18855
19107
|
yield this.publisher.addIceCandidate(candidate);
|
|
18856
19108
|
} else {
|
|
@@ -18861,11 +19113,11 @@ class PCTransportManager {
|
|
|
18861
19113
|
createSubscriberAnswerFromOffer(sd, offerId) {
|
|
18862
19114
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18863
19115
|
var _a, _b, _c;
|
|
18864
|
-
this.log.debug('received server offer',
|
|
19116
|
+
this.log.debug('received server offer', {
|
|
18865
19117
|
RTCSdpType: sd.type,
|
|
18866
19118
|
sdp: sd.sdp,
|
|
18867
19119
|
signalingState: (_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.getSignallingState().toString()
|
|
18868
|
-
})
|
|
19120
|
+
});
|
|
18869
19121
|
const unlock = yield this.remoteOfferLock.lock();
|
|
18870
19122
|
try {
|
|
18871
19123
|
const success = yield (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.setRemoteDescription(sd, offerId);
|
|
@@ -18882,6 +19134,9 @@ class PCTransportManager {
|
|
|
18882
19134
|
}
|
|
18883
19135
|
updateConfiguration(config, iceRestart) {
|
|
18884
19136
|
var _a;
|
|
19137
|
+
this.log.debug('updating rtc configuration', {
|
|
19138
|
+
iceRestart
|
|
19139
|
+
});
|
|
18885
19140
|
this.publisher.setConfiguration(config);
|
|
18886
19141
|
(_a = this.subscriber) === null || _a === void 0 ? void 0 : _a.setConfiguration(config);
|
|
18887
19142
|
if (iceRestart) {
|
|
@@ -18894,7 +19149,7 @@ class PCTransportManager {
|
|
|
18894
19149
|
const unlock = yield this.connectionLock.lock();
|
|
18895
19150
|
try {
|
|
18896
19151
|
if (this.isPublisherConnectionRequired && this.publisher.getConnectionState() !== 'connected' && this.publisher.getConnectionState() !== 'connecting') {
|
|
18897
|
-
this.log.debug('negotiation required, start negotiating'
|
|
19152
|
+
this.log.debug('negotiation required, start negotiating');
|
|
18898
19153
|
this.publisher.negotiate();
|
|
18899
19154
|
}
|
|
18900
19155
|
yield Promise.all((_a = this.requiredTransports) === null || _a === void 0 ? void 0 : _a.map(transport => this.ensureTransportConnected(transport, abortController, timeout)));
|
|
@@ -19008,7 +19263,7 @@ class PCTransportManager {
|
|
|
19008
19263
|
}
|
|
19009
19264
|
return new Promise((resolve, reject) => __awaiter(_this, void 0, void 0, function* () {
|
|
19010
19265
|
const abortHandler = () => {
|
|
19011
|
-
this.log.warn('abort transport connection'
|
|
19266
|
+
this.log.warn('abort transport connection');
|
|
19012
19267
|
CriticalTimers.clearTimeout(connectTimeout);
|
|
19013
19268
|
reject(ConnectionError.cancelled('room connection has been cancelled'));
|
|
19014
19269
|
};
|
|
@@ -19844,6 +20099,18 @@ class LocalTrack extends Track {
|
|
|
19844
20099
|
yield this.restart(constraints);
|
|
19845
20100
|
});
|
|
19846
20101
|
}
|
|
20102
|
+
applyConstraints(constraints) {
|
|
20103
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20104
|
+
const unlock = yield this.trackChangeLock.lock();
|
|
20105
|
+
try {
|
|
20106
|
+
const res = yield this._mediaStreamTrack.applyConstraints(constraints);
|
|
20107
|
+
this._constraints = Object.assign(Object.assign({}, this._constraints), constraints);
|
|
20108
|
+
return res;
|
|
20109
|
+
} finally {
|
|
20110
|
+
unlock();
|
|
20111
|
+
}
|
|
20112
|
+
});
|
|
20113
|
+
}
|
|
19847
20114
|
restart(constraints, isUnmuting) {
|
|
19848
20115
|
const _super = Object.create(null, {
|
|
19849
20116
|
restart: {
|
|
@@ -20280,14 +20547,21 @@ class ScalabilityMode {
|
|
|
20280
20547
|
return "L".concat(this.spatial, "T").concat(this.temporal).concat((_a = this.suffix) !== null && _a !== void 0 ? _a : '');
|
|
20281
20548
|
}
|
|
20282
20549
|
}
|
|
20550
|
+
/**
|
|
20551
|
+
* Returns the appropriate degradation preference for a video track based on its source.
|
|
20552
|
+
*
|
|
20553
|
+
* - Camera: 'maintain-framerate' (smoother video for real-time communication)
|
|
20554
|
+
* - Screen share: 'maintain-resolution' (clarity is critical for reading text/UI)
|
|
20555
|
+
* - Other/unknown: 'balanced'
|
|
20556
|
+
*/
|
|
20283
20557
|
function getDefaultDegradationPreference(track) {
|
|
20284
|
-
|
|
20285
|
-
|
|
20286
|
-
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20290
|
-
|
|
20558
|
+
switch (track.source) {
|
|
20559
|
+
case Track.Source.Camera:
|
|
20560
|
+
return 'maintain-framerate';
|
|
20561
|
+
case Track.Source.ScreenShare:
|
|
20562
|
+
return 'maintain-resolution';
|
|
20563
|
+
default:
|
|
20564
|
+
return 'balanced';
|
|
20291
20565
|
}
|
|
20292
20566
|
}const refreshSubscribedCodecAfterNewCodec = 5000;
|
|
20293
20567
|
class LocalVideoTrack extends LocalTrack {
|
|
@@ -21055,8 +21329,16 @@ const dataTrackDataChannel = '_data_track';
|
|
|
21055
21329
|
const minReconnectWait = 2 * 1000;
|
|
21056
21330
|
const leaveReconnect = 'leave-reconnect';
|
|
21057
21331
|
const reliabeReceiveStateTTL = 30000;
|
|
21058
|
-
|
|
21059
|
-
|
|
21332
|
+
// Two-watermark flow control for the reliable and data-track channels. Senders fill the buffer
|
|
21333
|
+
// freely up to the high-water mark; once it's exceeded they block until the browser's
|
|
21334
|
+
// `bufferedamountlow` event (which we arm at the low-water mark) signals the buffer has drained.
|
|
21335
|
+
// The gap between the marks keeps the SCTP send buffer saturated while we refill, so throughput
|
|
21336
|
+
// isn't starved, while the high-water mark bounds the buffer well below the level that would abort
|
|
21337
|
+
// the channel (see livekit/client-sdk-js#1995).
|
|
21338
|
+
const reliableDataChannelWaterMarkLow = 64 * 1024;
|
|
21339
|
+
const reliableDataChannelWaterMarkHigh = 1024 * 1024;
|
|
21340
|
+
const lossyDataChannelWaterMarkLow = 8 * 1024;
|
|
21341
|
+
const lossyDataChannelWaterMarkHigh = 256 * 1024;
|
|
21060
21342
|
const initialMediaSectionsAudio = 3;
|
|
21061
21343
|
const initialMediaSectionsVideo = 3;
|
|
21062
21344
|
var PCState;
|
|
@@ -21123,7 +21405,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21123
21405
|
this.midToTrackId = {};
|
|
21124
21406
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
21125
21407
|
this.isWaitingForNetworkReconnect = false;
|
|
21126
|
-
this.bufferStatusLowClosingFuture = new Future();
|
|
21127
21408
|
this.handleDataChannel = _a => __awaiter(this, [_a], void 0, function (_ref) {
|
|
21128
21409
|
var _this = this;
|
|
21129
21410
|
let channel = _ref.channel;
|
|
@@ -21317,18 +21598,53 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21317
21598
|
this.reliableMessageBuffer.alignBufferedAmount(dc.bufferedAmount);
|
|
21318
21599
|
}
|
|
21319
21600
|
}
|
|
21320
|
-
|
|
21321
|
-
|
|
21322
|
-
this.dcBufferStatus.
|
|
21323
|
-
|
|
21601
|
+
try {
|
|
21602
|
+
const status = this.isBelowLowWaterMark(kind);
|
|
21603
|
+
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
|
21604
|
+
this.dcBufferStatus.set(kind, status);
|
|
21605
|
+
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
|
21606
|
+
}
|
|
21607
|
+
} catch (e) {
|
|
21608
|
+
this.log.warn('could not update buffer status', {
|
|
21609
|
+
error: e
|
|
21610
|
+
});
|
|
21324
21611
|
}
|
|
21325
21612
|
};
|
|
21326
|
-
|
|
21613
|
+
/**
|
|
21614
|
+
* Whether the send buffer has room to accept more data (the send gate). Senders proceed while
|
|
21615
|
+
* this is true and block once it goes false.
|
|
21616
|
+
*/
|
|
21617
|
+
this.isBelowHighWaterMark = kind => {
|
|
21327
21618
|
const dc = this.dataChannelForKind(kind);
|
|
21328
|
-
if (dc) {
|
|
21329
|
-
|
|
21619
|
+
if (!dc) {
|
|
21620
|
+
throw new TypeError("Could not get data channel for kind ".concat(kind));
|
|
21330
21621
|
}
|
|
21622
|
+
// because RTCDatachannel has no high water mark built in we read the statically defined versions as constants
|
|
21623
|
+
const highMark = kind === DataChannelKind.RELIABLE ? reliableDataChannelWaterMarkHigh : lossyDataChannelWaterMarkHigh;
|
|
21624
|
+
return dc.bufferedAmount <= highMark;
|
|
21331
21625
|
};
|
|
21626
|
+
/**
|
|
21627
|
+
* Whether the send buffer has drained to its low-water mark. Drives the public
|
|
21628
|
+
* {@link EngineEvent.DCBufferStatusChanged} event.
|
|
21629
|
+
*/
|
|
21630
|
+
this.isBelowLowWaterMark = kind => {
|
|
21631
|
+
const dc = this.dataChannelForKind(kind);
|
|
21632
|
+
if (!dc) {
|
|
21633
|
+
throw new TypeError("Could not get data channel for kind ".concat(kind));
|
|
21634
|
+
}
|
|
21635
|
+
// because RTCDatachannel has the threshold built in we can read it dynamically to account for changing thresholds over time
|
|
21636
|
+
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
|
21637
|
+
};
|
|
21638
|
+
/** Per-kind lock serializing senders that have to wait for the buffer to drain. */
|
|
21639
|
+
this.waitForBufferHeadroomLocks = new Map();
|
|
21640
|
+
/**
|
|
21641
|
+
* Per-kind AbortController that cancels parked headroom waiters whenever a kind's channel object
|
|
21642
|
+
* stops being current (replaced by createDataChannels or torn down by cleanupPeerConnections). A
|
|
21643
|
+
* waiter is parked on the channel object it captured at wait entry; if that object is abandoned
|
|
21644
|
+
* its events may never fire again, so the abort is what rejects the waiter and releases the
|
|
21645
|
+
* headroom lock instead of stranding every future sender behind it.
|
|
21646
|
+
*/
|
|
21647
|
+
this.waiterAbortControllers = new Map();
|
|
21332
21648
|
this.onRtpMapAvailable = rtpTypes => {
|
|
21333
21649
|
const rtpMap = new Map();
|
|
21334
21650
|
rtpTypes.forEach(rtp => {
|
|
@@ -21399,13 +21715,6 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21399
21715
|
this.client.onRequestResponse = response => this.emit(EngineEvent.SignalRequestResponse, response);
|
|
21400
21716
|
this.client.onParticipantUpdate = updates => this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
21401
21717
|
this.client.onJoined = joinResponse => this.emit(EngineEvent.Joined, joinResponse);
|
|
21402
|
-
this.on(EngineEvent.Closing, () => {
|
|
21403
|
-
var _a, _b;
|
|
21404
|
-
(_b = (_a = this.bufferStatusLowClosingFuture).reject) === null || _b === void 0 ? void 0 : _b.call(_a, new UnexpectedConnectionState('engine closed'));
|
|
21405
|
-
});
|
|
21406
|
-
// Swallow the rejection at the source so it doesn't surface as an unhandled promise rejection
|
|
21407
|
-
// when no waitForBufferStatusLow callers are attached.
|
|
21408
|
-
this.bufferStatusLowClosingFuture.promise.catch(() => {});
|
|
21409
21718
|
}
|
|
21410
21719
|
/** @internal */
|
|
21411
21720
|
get logContext() {
|
|
@@ -21551,6 +21860,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21551
21860
|
cleanupPeerConnections() {
|
|
21552
21861
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21553
21862
|
var _a;
|
|
21863
|
+
// Reject parked headroom waiters up front: closing the peer connection is allowed (per spec)
|
|
21864
|
+
// to transition data channels to 'closed' without firing events, which would otherwise strand
|
|
21865
|
+
// a waiter holding the headroom lock.
|
|
21866
|
+
this.invalidateDataChannelWaiters('peer connections cleaned up');
|
|
21554
21867
|
const dcCleanup = dc => {
|
|
21555
21868
|
if (!dc) {
|
|
21556
21869
|
return;
|
|
@@ -21922,6 +22235,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21922
22235
|
if (!this.pcManager) {
|
|
21923
22236
|
return;
|
|
21924
22237
|
}
|
|
22238
|
+
// Waiters parked on the old channel objects would never see another event from them once they
|
|
22239
|
+
// are replaced below — reject them so the headroom lock is released and queued senders
|
|
22240
|
+
// re-check against the new channels.
|
|
22241
|
+
this.invalidateDataChannelWaiters('data channels recreated');
|
|
21925
22242
|
// clear old data channel callbacks if recreate
|
|
21926
22243
|
if (this.lossyDC) {
|
|
21927
22244
|
this.lossyDC.onmessage = null;
|
|
@@ -21962,10 +22279,10 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21962
22279
|
this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY);
|
|
21963
22280
|
this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE);
|
|
21964
22281
|
this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY);
|
|
21965
|
-
// set up dc buffer threshold
|
|
21966
|
-
this.lossyDC.bufferedAmountLowThreshold =
|
|
21967
|
-
this.reliableDC.bufferedAmountLowThreshold =
|
|
21968
|
-
this.dataTrackDC.bufferedAmountLowThreshold =
|
|
22282
|
+
// set up dc buffer threshold - if this is not set, it will default to 0
|
|
22283
|
+
this.lossyDC.bufferedAmountLowThreshold = lossyDataChannelWaterMarkLow;
|
|
22284
|
+
this.reliableDC.bufferedAmountLowThreshold = reliableDataChannelWaterMarkLow;
|
|
22285
|
+
this.dataTrackDC.bufferedAmountLowThreshold = lossyDataChannelWaterMarkLow;
|
|
21969
22286
|
// handle buffer amount low events
|
|
21970
22287
|
this.lossyDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.LOSSY);
|
|
21971
22288
|
this.reliableDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.RELIABLE);
|
|
@@ -21978,7 +22295,7 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
21978
22295
|
if (dc) {
|
|
21979
22296
|
// control buffered latency to ~100ms
|
|
21980
22297
|
const threshold = this.lossyDataStatByterate / 10;
|
|
21981
|
-
dc.bufferedAmountLowThreshold = Math.min(Math.max(threshold,
|
|
22298
|
+
dc.bufferedAmountLowThreshold = Math.min(Math.max(threshold, lossyDataChannelWaterMarkLow), lossyDataChannelWaterMarkHigh);
|
|
21982
22299
|
}
|
|
21983
22300
|
}, 1000);
|
|
21984
22301
|
}
|
|
@@ -22290,7 +22607,11 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22290
22607
|
this.createDataChannels();
|
|
22291
22608
|
}
|
|
22292
22609
|
if (res === null || res === void 0 ? void 0 : res.lastMessageSeq) {
|
|
22293
|
-
this.resendReliableMessagesForResume(res.lastMessageSeq)
|
|
22610
|
+
this.resendReliableMessagesForResume(res.lastMessageSeq).catch(error => {
|
|
22611
|
+
this.log.warn('failed to resend reliable messages after resume', Object.assign(Object.assign({}, this.logContext), {
|
|
22612
|
+
error
|
|
22613
|
+
}));
|
|
22614
|
+
});
|
|
22294
22615
|
}
|
|
22295
22616
|
// resume success
|
|
22296
22617
|
this.emit(EngineEvent.Resumed);
|
|
@@ -22375,20 +22696,55 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22375
22696
|
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
22376
22697
|
return this.sendLossyBytes(msg, kind);
|
|
22377
22698
|
case DataChannelKind.RELIABLE:
|
|
22378
|
-
|
|
22379
|
-
if (dc) {
|
|
22380
|
-
yield this.waitForBufferStatusLow(kind);
|
|
22381
|
-
this.reliableMessageBuffer.push({
|
|
22382
|
-
data: msg,
|
|
22383
|
-
sequence: packet.sequence
|
|
22384
|
-
});
|
|
22699
|
+
{
|
|
22385
22700
|
if (this.attemptingReconnect) {
|
|
22701
|
+
// A reconnect is already underway — queue for the resume replay instead of parking on a
|
|
22702
|
+
// channel that is being torn down. The send resolves; delivery is deferred to the replay.
|
|
22703
|
+
this.reliableMessageBuffer.push({
|
|
22704
|
+
data: msg,
|
|
22705
|
+
sequence: packet.sequence,
|
|
22706
|
+
sent: false
|
|
22707
|
+
});
|
|
22386
22708
|
return;
|
|
22387
22709
|
}
|
|
22388
|
-
dc.
|
|
22710
|
+
const dc = this.dataChannelForKind(kind);
|
|
22711
|
+
if (dc) {
|
|
22712
|
+
try {
|
|
22713
|
+
yield this.waitForBufferHeadroomWithLock(kind);
|
|
22714
|
+
} catch (error) {
|
|
22715
|
+
if (this.isClosed) {
|
|
22716
|
+
// No replay is coming after an engine close — surface the failure.
|
|
22717
|
+
throw error;
|
|
22718
|
+
}
|
|
22719
|
+
// Transient teardown (the channel closed or was replaced while we waited): the
|
|
22720
|
+
// reliable channel promises delivery across resume, so queue the packet for the
|
|
22721
|
+
// replay instead of rejecting a send the app can't meaningfully retry.
|
|
22722
|
+
this.reliableMessageBuffer.push({
|
|
22723
|
+
data: msg,
|
|
22724
|
+
sequence: packet.sequence,
|
|
22725
|
+
sent: false
|
|
22726
|
+
});
|
|
22727
|
+
return;
|
|
22728
|
+
}
|
|
22729
|
+
if (this.attemptingReconnect) {
|
|
22730
|
+
// A reconnect began while we waited for headroom — same deal as above.
|
|
22731
|
+
this.reliableMessageBuffer.push({
|
|
22732
|
+
data: msg,
|
|
22733
|
+
sequence: packet.sequence,
|
|
22734
|
+
sent: false
|
|
22735
|
+
});
|
|
22736
|
+
return;
|
|
22737
|
+
}
|
|
22738
|
+
this.reliableMessageBuffer.push({
|
|
22739
|
+
data: msg,
|
|
22740
|
+
sequence: packet.sequence,
|
|
22741
|
+
sent: true
|
|
22742
|
+
});
|
|
22743
|
+
dc.send(msg);
|
|
22744
|
+
}
|
|
22745
|
+
this.updateAndEmitDCBufferStatus(kind);
|
|
22746
|
+
break;
|
|
22389
22747
|
}
|
|
22390
|
-
this.updateAndEmitDCBufferStatus(kind);
|
|
22391
|
-
break;
|
|
22392
22748
|
}
|
|
22393
22749
|
});
|
|
22394
22750
|
}
|
|
@@ -22396,36 +22752,56 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22396
22752
|
sendLossyBytes(bytes_1, kind_1) {
|
|
22397
22753
|
return __awaiter(this, arguments, void 0, function (bytes, kind) {
|
|
22398
22754
|
var _this3 = this;
|
|
22399
|
-
let
|
|
22755
|
+
let bufferStatusFullBehavior = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'drop';
|
|
22400
22756
|
return function* () {
|
|
22401
|
-
//
|
|
22757
|
+
// Make sure we do have a data connection. This matters most for the direct data-track path
|
|
22758
|
+
// (Room's packetAvailable handler), which doesn't go through sendDataPacket: it both waits for
|
|
22759
|
+
// the channel to be open and — on lazily negotiated publisher connections — is what kicks the
|
|
22760
|
+
// negotiation off in the first place. The call is memoized, so the steady-state cost is one
|
|
22761
|
+
// await on an already-resolved promise.
|
|
22402
22762
|
yield _this3.ensurePublisherConnected(kind);
|
|
22403
22763
|
const dc = _this3.dataChannelForKind(kind);
|
|
22404
|
-
|
|
22405
|
-
if (
|
|
22764
|
+
try {
|
|
22765
|
+
if (dc) {
|
|
22406
22766
|
// Depending on the exact circumstance that data is being sent, either drop or wait for the
|
|
22407
|
-
// buffer
|
|
22408
|
-
switch (
|
|
22767
|
+
// buffer to drain below the high-water mark before continuing.
|
|
22768
|
+
switch (bufferStatusFullBehavior) {
|
|
22409
22769
|
case 'wait':
|
|
22410
|
-
|
|
22770
|
+
if (!_this3.isBelowHighWaterMark(kind)) {
|
|
22771
|
+
yield _this3.waitForBufferHeadroomWithLock(kind);
|
|
22772
|
+
}
|
|
22411
22773
|
break;
|
|
22412
22774
|
case 'drop':
|
|
22413
|
-
//
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
_this3.
|
|
22775
|
+
// we check against the actual threshold on the DC here, as it is dynamic for the lossy DC
|
|
22776
|
+
if (!_this3.isBelowLowWaterMark(kind)) {
|
|
22777
|
+
// Drop messages to reduce latency
|
|
22778
|
+
_this3.lossyDataDropCount += 1;
|
|
22779
|
+
if (_this3.lossyDataDropCount % 100 === 0) {
|
|
22780
|
+
_this3.log.warn("dropping lossy data channel messages, total dropped: ".concat(_this3.lossyDataDropCount));
|
|
22781
|
+
}
|
|
22782
|
+
return;
|
|
22418
22783
|
}
|
|
22419
|
-
return;
|
|
22420
22784
|
}
|
|
22785
|
+
if (kind === DataChannelKind.LOSSY) {
|
|
22786
|
+
// The byterate stat tunes the LOSSY channel's dynamic drop threshold; counting data-track
|
|
22787
|
+
// bytes here would inflate it and let the lossy channel buffer far more latency than the
|
|
22788
|
+
// ~100ms the tuning targets.
|
|
22789
|
+
_this3.lossyDataStatCurrentBytes += bytes.byteLength;
|
|
22790
|
+
}
|
|
22791
|
+
if (_this3.attemptingReconnect) {
|
|
22792
|
+
return;
|
|
22793
|
+
}
|
|
22794
|
+
dc.send(bytes);
|
|
22421
22795
|
}
|
|
22422
|
-
_this3.
|
|
22423
|
-
|
|
22424
|
-
|
|
22796
|
+
_this3.updateAndEmitDCBufferStatus(kind);
|
|
22797
|
+
} catch (error) {
|
|
22798
|
+
// ensure same surface behaviour as before with missing data channel being silently ignored, just log an error message for clarity
|
|
22799
|
+
if (error instanceof TypeError) {
|
|
22800
|
+
_this3.log.error(error);
|
|
22801
|
+
} else {
|
|
22802
|
+
throw error;
|
|
22425
22803
|
}
|
|
22426
|
-
dc.send(bytes);
|
|
22427
22804
|
}
|
|
22428
|
-
_this3.updateAndEmitDCBufferStatus(kind);
|
|
22429
22805
|
}();
|
|
22430
22806
|
});
|
|
22431
22807
|
}
|
|
@@ -22435,33 +22811,129 @@ class RTCEngine extends eventsExports.EventEmitter {
|
|
|
22435
22811
|
const dc = this.dataChannelForKind(DataChannelKind.RELIABLE);
|
|
22436
22812
|
if (dc) {
|
|
22437
22813
|
this.reliableMessageBuffer.popToSequence(lastMessageSeq);
|
|
22438
|
-
|
|
22439
|
-
|
|
22440
|
-
|
|
22814
|
+
// Hold the headroom lock across the whole replay. Releasing it between messages would let a
|
|
22815
|
+
// concurrent send — whose (newer) sequence was already assigned in sendDataPacket before it
|
|
22816
|
+
// queued on the lock — hit the wire mid-replay, and receivers would then discard the
|
|
22817
|
+
// remaining lower-sequence resent messages as duplicates.
|
|
22818
|
+
const unlock = yield this.getBufferHeadroomLock(DataChannelKind.RELIABLE).lock();
|
|
22819
|
+
try {
|
|
22820
|
+
// Everything left after the ack cutoff must be re-handed to the current channel.
|
|
22821
|
+
this.reliableMessageBuffer.markAllUnsent();
|
|
22822
|
+
// Drain in passes, re-scanning the live buffer each time: a send that arrives (deferred,
|
|
22823
|
+
// sent:false) during our own awaits appends after this pass started, so we pick it up on
|
|
22824
|
+
// the next one. We mark each packet only once we've actually handed it to the channel — a
|
|
22825
|
+
// blanket "mark all sent" would flip such a late arrival to sent without transmitting it,
|
|
22826
|
+
// and a later alignBufferedAmount would then drop it for good. If the loop throws
|
|
22827
|
+
// mid-drain, unsent entries keep their flag and the next replay picks them up.
|
|
22828
|
+
for (let batch = this.reliableMessageBuffer.getUnsent(); batch.length > 0; batch = this.reliableMessageBuffer.getUnsent()) {
|
|
22829
|
+
for (const item of batch) {
|
|
22830
|
+
// Respect flow control on resume too, so a large resend doesn't overflow the buffer.
|
|
22831
|
+
// We already hold the lock across the whole replay, so use the lock-free wait.
|
|
22832
|
+
yield this.waitForBufferHeadroomWithoutLock(DataChannelKind.RELIABLE);
|
|
22833
|
+
dc.send(item.data);
|
|
22834
|
+
this.reliableMessageBuffer.markSent(item);
|
|
22835
|
+
}
|
|
22836
|
+
}
|
|
22837
|
+
} finally {
|
|
22838
|
+
unlock();
|
|
22839
|
+
}
|
|
22441
22840
|
}
|
|
22442
22841
|
this.updateAndEmitDCBufferStatus(DataChannelKind.RELIABLE);
|
|
22443
22842
|
});
|
|
22444
22843
|
}
|
|
22445
|
-
|
|
22844
|
+
waiterAbortSignal(kind) {
|
|
22845
|
+
let controller = this.waiterAbortControllers.get(kind);
|
|
22846
|
+
if (!controller) {
|
|
22847
|
+
controller = new AbortController();
|
|
22848
|
+
this.waiterAbortControllers.set(kind, controller);
|
|
22849
|
+
}
|
|
22850
|
+
return controller.signal;
|
|
22851
|
+
}
|
|
22852
|
+
/** Rejects all parked headroom waiters (all kinds); the next waiter gets a fresh controller. */
|
|
22853
|
+
invalidateDataChannelWaiters(reason) {
|
|
22854
|
+
for (const controller of this.waiterAbortControllers.values()) {
|
|
22855
|
+
controller.abort(reason);
|
|
22856
|
+
}
|
|
22857
|
+
this.waiterAbortControllers.clear();
|
|
22858
|
+
}
|
|
22859
|
+
/** Acquires the per-kind headroom lock, resolving with the unlock function. */
|
|
22860
|
+
getBufferHeadroomLock(kind) {
|
|
22861
|
+
let lock = this.waitForBufferHeadroomLocks.get(kind);
|
|
22862
|
+
if (!lock) {
|
|
22863
|
+
lock = new _();
|
|
22864
|
+
this.waitForBufferHeadroomLocks.set(kind, lock);
|
|
22865
|
+
}
|
|
22866
|
+
return lock;
|
|
22867
|
+
}
|
|
22868
|
+
/**
|
|
22869
|
+
* Acquires the `kind` headroom lock, waits until the send buffer has room, then releases. The
|
|
22870
|
+
* common single-send entry point. Callers that need to hold the lock across several sends (the
|
|
22871
|
+
* resume replay) acquire it via {@link getBufferHeadroomLock} and call
|
|
22872
|
+
* {@link waitForBufferHeadroomWithoutLock} per message instead.
|
|
22873
|
+
*/
|
|
22874
|
+
waitForBufferHeadroomWithLock(kind) {
|
|
22446
22875
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22876
|
+
const unlock = yield this.getBufferHeadroomLock(kind).lock();
|
|
22877
|
+
try {
|
|
22878
|
+
yield this.waitForBufferHeadroomWithoutLock(kind);
|
|
22879
|
+
} finally {
|
|
22880
|
+
unlock();
|
|
22881
|
+
}
|
|
22882
|
+
});
|
|
22883
|
+
}
|
|
22884
|
+
/**
|
|
22885
|
+
* Waits until the send buffer for `kind` is at or below the high-water mark (draining, if
|
|
22886
|
+
* needed, via the `bufferedamountlow` event). Does no locking of its own — the caller must
|
|
22887
|
+
* already hold the kind's headroom lock (via {@link getBufferHeadroomLock}). The resume replay
|
|
22888
|
+
* holds the lock across its whole batch and calls this per message so no other sender can
|
|
22889
|
+
* interleave; the single-send path goes through {@link waitForBufferHeadroomWithLock}.
|
|
22890
|
+
*
|
|
22891
|
+
* Serializing through the per-kind lock means that, once the buffer drains, queued callers
|
|
22892
|
+
* refill it one at a time (up to the high-water mark) rather than all at once and overflowing
|
|
22893
|
+
* the SCTP send buffer (see livekit/client-sdk-js#1995).
|
|
22894
|
+
*/
|
|
22895
|
+
waitForBufferHeadroomWithoutLock(kind) {
|
|
22896
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22897
|
+
if (this.isClosed) {
|
|
22898
|
+
throw new UnexpectedConnectionState('engine closed');
|
|
22899
|
+
}
|
|
22900
|
+
if (this.isBelowHighWaterMark(kind)) {
|
|
22901
|
+
return;
|
|
22902
|
+
}
|
|
22903
|
+
const dc = this.dataChannelForKind(kind);
|
|
22904
|
+
if (!dc) {
|
|
22905
|
+
throw new UnexpectedConnectionState("DataChannel not found, kind: ".concat(kind));
|
|
22906
|
+
}
|
|
22907
|
+
const abortSignal = this.waiterAbortSignal(kind);
|
|
22908
|
+
yield new TypedPromise((resolve, reject) => {
|
|
22909
|
+
const onBufferedAmountLow = () => {
|
|
22910
|
+
cleanup();
|
|
22452
22911
|
resolve();
|
|
22453
|
-
}
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
|
|
22458
|
-
|
|
22459
|
-
|
|
22460
|
-
|
|
22461
|
-
|
|
22462
|
-
|
|
22912
|
+
};
|
|
22913
|
+
const onDCClose = () => {
|
|
22914
|
+
cleanup();
|
|
22915
|
+
reject(new UnexpectedConnectionState("DataChannel ".concat(kind, " closed while draining the buffer")));
|
|
22916
|
+
};
|
|
22917
|
+
const onAbort = () => {
|
|
22918
|
+
cleanup();
|
|
22919
|
+
reject(new UnexpectedConnectionState("DataChannel ".concat(kind, " was replaced or torn down while waiting for headroom")));
|
|
22920
|
+
};
|
|
22921
|
+
const cleanup = () => {
|
|
22922
|
+
dc.removeEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
22923
|
+
dc.removeEventListener('close', onDCClose);
|
|
22924
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
22925
|
+
};
|
|
22926
|
+
if (abortSignal.aborted) {
|
|
22927
|
+
onAbort();
|
|
22928
|
+
return;
|
|
22463
22929
|
}
|
|
22464
|
-
|
|
22930
|
+
dc.addEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
22931
|
+
// Proxy along any error caused by the data channel closing while we wait.
|
|
22932
|
+
dc.addEventListener('close', onDCClose);
|
|
22933
|
+
// The channel object we're parked on can be abandoned without ever firing another event
|
|
22934
|
+
// (e.g. createDataChannels replacing it); the abort is our way out.
|
|
22935
|
+
abortSignal.addEventListener('abort', onAbort);
|
|
22936
|
+
});
|
|
22465
22937
|
});
|
|
22466
22938
|
}
|
|
22467
22939
|
/**
|
|
@@ -22743,7 +23215,8 @@ function applyUserDataCompat(newObj, oldObj) {
|
|
|
22743
23215
|
const destinationIdentities = newObj.destinationIdentities.length !== 0 ? newObj.destinationIdentities : oldObj.destinationIdentities;
|
|
22744
23216
|
newObj.destinationIdentities = destinationIdentities;
|
|
22745
23217
|
oldObj.destinationIdentities = destinationIdentities;
|
|
22746
|
-
}const
|
|
23218
|
+
}const log$4 = getLogger(LoggerNames.Region);
|
|
23219
|
+
const DEFAULT_MAX_AGE_MS = 5000;
|
|
22747
23220
|
const STOP_REFETCH_DELAY_MS = 30000;
|
|
22748
23221
|
class RegionUrlProvider {
|
|
22749
23222
|
static fetchRegionSettings(serverUrl, token, signal) {
|
|
@@ -22797,10 +23270,10 @@ class RegionUrlProvider {
|
|
|
22797
23270
|
RegionUrlProvider.updateCachedRegionSettings(url, token, newSettings);
|
|
22798
23271
|
} catch (error) {
|
|
22799
23272
|
if (error instanceof ConnectionError && error.reason === ConnectionErrorReason.NotAllowed) {
|
|
22800
|
-
|
|
23273
|
+
log$4.debug('token is not valid, cancelling auto region refresh');
|
|
22801
23274
|
return;
|
|
22802
23275
|
}
|
|
22803
|
-
|
|
23276
|
+
log$4.debug('auto refetching of region settings failed', {
|
|
22804
23277
|
error
|
|
22805
23278
|
});
|
|
22806
23279
|
// continue retrying with the same max age
|
|
@@ -22833,7 +23306,7 @@ class RegionUrlProvider {
|
|
|
22833
23306
|
tracker.cleanupTimeout = setTimeout(() => {
|
|
22834
23307
|
const currentTracker = RegionUrlProvider.connectionTrackers.get(hostname);
|
|
22835
23308
|
if (currentTracker && currentTracker.connectionCount === 0) {
|
|
22836
|
-
|
|
23309
|
+
log$4.debug('stopping region refetch after disconnect delay', {
|
|
22837
23310
|
hostname
|
|
22838
23311
|
});
|
|
22839
23312
|
RegionUrlProvider.stopRefetch(hostname);
|
|
@@ -22913,7 +23386,9 @@ class RegionUrlProvider {
|
|
|
22913
23386
|
if (regionsLeft.length > 0) {
|
|
22914
23387
|
const nextRegion = regionsLeft[0];
|
|
22915
23388
|
this.attemptedRegions.push(nextRegion);
|
|
22916
|
-
|
|
23389
|
+
log$4.info("switching to region: ".concat(nextRegion.region), {
|
|
23390
|
+
region: nextRegion.region
|
|
23391
|
+
});
|
|
22917
23392
|
return nextRegion.url;
|
|
22918
23393
|
} else {
|
|
22919
23394
|
return null;
|
|
@@ -24689,6 +25164,9 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24689
25164
|
let bufferSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : READABLE_STREAM_DEFAULT_BUFFER_SIZE;
|
|
24690
25165
|
let streamController = null;
|
|
24691
25166
|
const sfuSubscriptionComplete = new Future();
|
|
25167
|
+
// Hold the descriptor by reference: SID reassignment re-keys the map, so lookups
|
|
25168
|
+
// with the SID captured at subscribe time would fail.
|
|
25169
|
+
const descriptor = this.descriptors.get(sid);
|
|
24692
25170
|
const detachSignal = () => {
|
|
24693
25171
|
signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
|
|
24694
25172
|
};
|
|
@@ -24698,8 +25176,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24698
25176
|
log$1.warn("ReadableStream subscribed to ".concat(sid, " was not started."));
|
|
24699
25177
|
return;
|
|
24700
25178
|
}
|
|
24701
|
-
|
|
24702
|
-
if (!descriptor) {
|
|
25179
|
+
if (!descriptor || this.descriptors.get(descriptor.info.sid) !== descriptor) {
|
|
24703
25180
|
log$1.warn("Unknown track ".concat(sid, ", skipping cancel..."));
|
|
24704
25181
|
return;
|
|
24705
25182
|
}
|
|
@@ -24718,9 +25195,8 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24718
25195
|
if (!streamController) {
|
|
24719
25196
|
return;
|
|
24720
25197
|
}
|
|
24721
|
-
|
|
24722
|
-
|
|
24723
|
-
currentDescriptor.subscription.streamControllers.delete(streamController);
|
|
25198
|
+
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.subscription.type) === 'active') {
|
|
25199
|
+
descriptor.subscription.streamControllers.delete(streamController);
|
|
24724
25200
|
}
|
|
24725
25201
|
streamController.error(DataTrackSubscribeError.cancelled());
|
|
24726
25202
|
(_a = sfuSubscriptionComplete.reject) === null || _a === void 0 ? void 0 : _a.call(sfuSubscriptionComplete, DataTrackSubscribeError.cancelled());
|
|
@@ -24731,8 +25207,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24731
25207
|
streamController = controller;
|
|
24732
25208
|
this.subscribeRequest(sid, signal).then(() => __awaiter(this, void 0, void 0, function* () {
|
|
24733
25209
|
var _a, _b, _c;
|
|
24734
|
-
|
|
24735
|
-
if (!descriptor) {
|
|
25210
|
+
if (!descriptor || this.descriptors.get(descriptor.info.sid) !== descriptor) {
|
|
24736
25211
|
log$1.error("Unknown track ".concat(sid));
|
|
24737
25212
|
const err = DataTrackSubscribeError.disconnected();
|
|
24738
25213
|
controller.error(err);
|
|
@@ -24847,7 +25322,7 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24847
25322
|
};
|
|
24848
25323
|
// Let the SFU know that the subscribe has been cancelled
|
|
24849
25324
|
this.emit('sfuUpdateSubscription', {
|
|
24850
|
-
sid,
|
|
25325
|
+
sid: descriptor.info.sid,
|
|
24851
25326
|
subscribe: false
|
|
24852
25327
|
});
|
|
24853
25328
|
if (previousDescriptorSubscription.type === 'pending') {
|
|
@@ -24961,6 +25436,9 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
24961
25436
|
if (this.descriptors.has(info.sid)) {
|
|
24962
25437
|
continue;
|
|
24963
25438
|
}
|
|
25439
|
+
if (this.handleSidReassigned(publisherIdentity, info)) {
|
|
25440
|
+
continue;
|
|
25441
|
+
}
|
|
24964
25442
|
yield this.handleTrackPublished(publisherIdentity, info);
|
|
24965
25443
|
}
|
|
24966
25444
|
publisherParticipantToSidsInUpdate.set(publisherIdentity, sidsInUpdate);
|
|
@@ -25018,6 +25496,62 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25018
25496
|
});
|
|
25019
25497
|
});
|
|
25020
25498
|
}
|
|
25499
|
+
/**
|
|
25500
|
+
* Detects and handles SID reassignment, which occurs when the publisher
|
|
25501
|
+
* republishes its tracks after a full reconnect.
|
|
25502
|
+
*
|
|
25503
|
+
* Returns `true` if an SID reassignment occurred, `false` otherwise.
|
|
25504
|
+
*/
|
|
25505
|
+
handleSidReassigned(publisherIdentity, info) {
|
|
25506
|
+
// Publisher identity and pub handle are stable across republications.
|
|
25507
|
+
const existingEntry = Array.from(this.descriptors.entries()).find(_ref1 => {
|
|
25508
|
+
let _ref10 = _slicedToArray(_ref1, 2);
|
|
25509
|
+
_ref10[0];
|
|
25510
|
+
let descriptor = _ref10[1];
|
|
25511
|
+
return descriptor.publisherIdentity === publisherIdentity && descriptor.info.pubHandle === info.pubHandle;
|
|
25512
|
+
});
|
|
25513
|
+
if (!existingEntry) {
|
|
25514
|
+
return false;
|
|
25515
|
+
}
|
|
25516
|
+
const _existingEntry = _slicedToArray(existingEntry, 2),
|
|
25517
|
+
oldSid = _existingEntry[0],
|
|
25518
|
+
descriptor = _existingEntry[1];
|
|
25519
|
+
// Invariant: other than SID, info should not have changed.
|
|
25520
|
+
// TODO: consider refactoring to move SID out of info to allow for direct comparison.
|
|
25521
|
+
const _descriptor$info = descriptor.info,
|
|
25522
|
+
name = _descriptor$info.name,
|
|
25523
|
+
usesE2ee = _descriptor$info.usesE2ee;
|
|
25524
|
+
if (name !== info.name || usesE2ee !== info.usesE2ee) {
|
|
25525
|
+
log$1.warn("Info mismatch for ".concat(oldSid, ", treating as new publication"));
|
|
25526
|
+
return false;
|
|
25527
|
+
}
|
|
25528
|
+
const newSid = info.sid;
|
|
25529
|
+
log$1.debug("SID reassigned: ".concat(oldSid, " -> ").concat(newSid));
|
|
25530
|
+
if (!this.descriptors.delete(oldSid)) {
|
|
25531
|
+
return false;
|
|
25532
|
+
}
|
|
25533
|
+
descriptor.info.sid = newSid;
|
|
25534
|
+
switch (descriptor.subscription.type) {
|
|
25535
|
+
case 'none':
|
|
25536
|
+
break;
|
|
25537
|
+
case 'pending':
|
|
25538
|
+
case 'active':
|
|
25539
|
+
// The SFU does not carry subscriptions across a publisher's full
|
|
25540
|
+
// reconnect; re-request the subscription under the new SID.
|
|
25541
|
+
this.emit('sfuUpdateSubscription', {
|
|
25542
|
+
sid: newSid,
|
|
25543
|
+
subscribe: true
|
|
25544
|
+
});
|
|
25545
|
+
break;
|
|
25546
|
+
}
|
|
25547
|
+
if (descriptor.subscription.type === 'active') {
|
|
25548
|
+
// Keep the routing index consistent until the SFU assigns a new handle
|
|
25549
|
+
// (see `registerSubscriberHandle`).
|
|
25550
|
+
this.subscriptionHandles.set(descriptor.subscription.subcriptionHandle, newSid);
|
|
25551
|
+
}
|
|
25552
|
+
this.descriptors.set(newSid, descriptor);
|
|
25553
|
+
return true;
|
|
25554
|
+
}
|
|
25021
25555
|
handleTrackUnpublished(sid) {
|
|
25022
25556
|
const descriptor = this.descriptors.get(sid);
|
|
25023
25557
|
if (!descriptor) {
|
|
@@ -25039,10 +25573,10 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25039
25573
|
/** Mapping between track handles attached to incoming packets to the
|
|
25040
25574
|
* track SIDs they belong to. */
|
|
25041
25575
|
mapping) {
|
|
25042
|
-
for (const
|
|
25043
|
-
var
|
|
25044
|
-
const handle =
|
|
25045
|
-
const sid =
|
|
25576
|
+
for (const _ref11 of mapping.entries()) {
|
|
25577
|
+
var _ref12 = _slicedToArray(_ref11, 2);
|
|
25578
|
+
const handle = _ref12[0];
|
|
25579
|
+
const sid = _ref12[1];
|
|
25046
25580
|
this.registerSubscriberHandle(handle, sid);
|
|
25047
25581
|
}
|
|
25048
25582
|
}
|
|
@@ -25063,12 +25597,17 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25063
25597
|
case 'active':
|
|
25064
25598
|
{
|
|
25065
25599
|
// Update handle for an active subscription. This can occur following a full reconnect.
|
|
25600
|
+
this.subscriptionHandles.delete(descriptor.subscription.subcriptionHandle);
|
|
25066
25601
|
descriptor.subscription.subcriptionHandle = assignedHandle;
|
|
25067
25602
|
this.subscriptionHandles.set(assignedHandle, sid);
|
|
25068
25603
|
return;
|
|
25069
25604
|
}
|
|
25070
25605
|
case 'pending':
|
|
25071
25606
|
{
|
|
25607
|
+
log$1.debug("data track subscription activated", {
|
|
25608
|
+
sid,
|
|
25609
|
+
handle: assignedHandle
|
|
25610
|
+
});
|
|
25072
25611
|
const pipeline = new IncomingDataTrackPipeline({
|
|
25073
25612
|
info: descriptor.info,
|
|
25074
25613
|
publisherIdentity: descriptor.publisherIdentity,
|
|
@@ -25135,10 +25674,10 @@ class IncomingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25135
25674
|
* tracks are subscribed to locally.
|
|
25136
25675
|
*/
|
|
25137
25676
|
resendSubscriptionUpdates() {
|
|
25138
|
-
for (const
|
|
25139
|
-
var
|
|
25140
|
-
const sid =
|
|
25141
|
-
const descriptor =
|
|
25677
|
+
for (const _ref13 of this.descriptors) {
|
|
25678
|
+
var _ref14 = _slicedToArray(_ref13, 2);
|
|
25679
|
+
const sid = _ref14[0];
|
|
25680
|
+
const descriptor = _ref14[1];
|
|
25142
25681
|
if (descriptor.subscription.type === 'none') {
|
|
25143
25682
|
continue;
|
|
25144
25683
|
}
|
|
@@ -25804,10 +26343,16 @@ class OutgoingDataTrackManager extends eventsExports.EventEmitter {
|
|
|
25804
26343
|
{
|
|
25805
26344
|
if (result.type === 'ok') {
|
|
25806
26345
|
const info = result.data;
|
|
26346
|
+
log.debug("SFU accepted publish request for handle ".concat(handle), {
|
|
26347
|
+
sid: info.sid
|
|
26348
|
+
});
|
|
25807
26349
|
const e2eeManager = info.usesE2ee ? this.e2eeManager : null;
|
|
25808
26350
|
this.descriptors.set(info.pubHandle, Descriptor.active(info, e2eeManager));
|
|
25809
26351
|
(_b = (_a = descriptor.completionFuture).resolve) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
25810
26352
|
} else {
|
|
26353
|
+
log.debug("SFU rejected publish request for handle ".concat(handle), {
|
|
26354
|
+
error: result.error
|
|
26355
|
+
});
|
|
25811
26356
|
(_d = (_c = descriptor.completionFuture).reject) === null || _d === void 0 ? void 0 : _d.call(_c, result.error);
|
|
25812
26357
|
}
|
|
25813
26358
|
return;
|
|
@@ -27263,6 +27808,11 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
27263
27808
|
this.audioTrackPublications.forEach(track => isAudioTrack(track.track) && track.track.setAudioContext(ctx));
|
|
27264
27809
|
}
|
|
27265
27810
|
addTrackPublication(publication) {
|
|
27811
|
+
this.log.debug("adding track publication", {
|
|
27812
|
+
trackSid: publication.trackSid,
|
|
27813
|
+
source: publication.source,
|
|
27814
|
+
kind: publication.kind
|
|
27815
|
+
});
|
|
27266
27816
|
// forward publication driven events
|
|
27267
27817
|
publication.on(TrackEvent.Muted, () => {
|
|
27268
27818
|
this.emit(ParticipantEvent.TrackMuted, publication);
|
|
@@ -28307,7 +28857,7 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
28307
28857
|
throw new UnexpectedConnectionState('cannot publish track when not connected');
|
|
28308
28858
|
}
|
|
28309
28859
|
const negotiate = () => __awaiter(this, void 0, void 0, function* () {
|
|
28310
|
-
var _a, _b, _c;
|
|
28860
|
+
var _a, _b, _c, _d;
|
|
28311
28861
|
if (!this.engine.pcManager) {
|
|
28312
28862
|
throw new UnexpectedConnectionState('pcManager is not ready');
|
|
28313
28863
|
}
|
|
@@ -28343,12 +28893,22 @@ class Participant extends eventsExports.EventEmitter {
|
|
|
28343
28893
|
maxbr: ((_b = encodings[0]) === null || _b === void 0 ? void 0 : _b.maxBitrate) ? encodings[0].maxBitrate / 1000 : 0
|
|
28344
28894
|
});
|
|
28345
28895
|
}
|
|
28346
|
-
} else if (track.codec &&
|
|
28347
|
-
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28896
|
+
} else if (track.codec && isVideoCodec(track.codec)) {
|
|
28897
|
+
// Apply start bitrate for all video codecs to prevent initial blurriness.
|
|
28898
|
+
// - SVC codecs: use first encoding's bitrate (single stream with built-in layers)
|
|
28899
|
+
// - Simulcast: sum all encoding bitrates (independent streams, BWE needs total)
|
|
28900
|
+
const targetBitrate = isSVCCodec(track.codec) ? (_d = (_c = encodings[0]) === null || _c === void 0 ? void 0 : _c.maxBitrate) !== null && _d !== void 0 ? _d : 0 : encodings.reduce((sum, enc) => {
|
|
28901
|
+
var _a;
|
|
28902
|
+
return sum + ((_a = enc.maxBitrate) !== null && _a !== void 0 ? _a : 0);
|
|
28903
|
+
}, 0);
|
|
28904
|
+
if (targetBitrate > 0) {
|
|
28905
|
+
this.engine.pcManager.publisher.setTrackCodecBitrate({
|
|
28906
|
+
cid: req.cid,
|
|
28907
|
+
codec: track.codec,
|
|
28908
|
+
maxbr: targetBitrate / 1000,
|
|
28909
|
+
isScreenShare: track.source === Track.Source.ScreenShare
|
|
28910
|
+
});
|
|
28911
|
+
}
|
|
28352
28912
|
}
|
|
28353
28913
|
}
|
|
28354
28914
|
yield this.engine.negotiate();
|
|
@@ -30066,7 +30626,13 @@ class Room extends eventsExports.EventEmitter {
|
|
|
30066
30626
|
});
|
|
30067
30627
|
try {
|
|
30068
30628
|
yield Promise.all([this.acquireAudioContext(), ...elements.map(e => {
|
|
30069
|
-
|
|
30629
|
+
// when webAudioMix is enabled, attached elements are deliberately kept muted by
|
|
30630
|
+
// RemoteAudioTrack.attach() and audio is routed through the web audio graph instead.
|
|
30631
|
+
// unmuting them here would cause double playback on platforms where element.volume
|
|
30632
|
+
// has no effect (e.g. iOS Safari)
|
|
30633
|
+
if (!this.options.webAudioMix) {
|
|
30634
|
+
e.muted = false;
|
|
30635
|
+
}
|
|
30070
30636
|
return e.play();
|
|
30071
30637
|
})]);
|
|
30072
30638
|
this.handleAudioPlaybackStarted();
|