livekit-client 2.20.2 → 2.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +4 -0
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +1993 -791
- 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 +4 -0
- 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/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts +28 -64
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts +4 -0
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/src/room/data-channel/DataChannelManager.d.ts.map +1 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/src/room/data-channel/FlowControlledDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts +40 -0
- package/dist/src/room/data-channel/LossyDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts +56 -0
- package/dist/src/room/data-channel/ReliableDataChannel.d.ts.map +1 -0
- package/dist/src/room/data-channel/types.d.ts +12 -0
- package/dist/src/room/data-channel/types.d.ts.map +1 -0
- package/dist/src/room/data-stream/compression.d.ts +49 -0
- package/dist/src/room/data-stream/compression.d.ts.map +1 -0
- package/dist/src/room/data-stream/constants.d.ts +18 -0
- package/dist/src/room/data-stream/constants.d.ts.map +1 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/src/room/data-stream/incoming/IncomingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/incoming/StreamReader.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/src/room/data-stream/outgoing/header-utils.d.ts.map +1 -0
- package/dist/src/room/errors.d.ts +3 -1
- package/dist/src/room/errors.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +11 -2
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -1
- package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +17 -0
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/src/room/utils.d.ts +13 -0
- package/dist/src/room/utils.d.ts.map +1 -1
- package/dist/src/version.d.ts +5 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/ts4.2/index.d.ts +1 -1
- package/dist/ts4.2/room/RTCEngine.d.ts +28 -64
- package/dist/ts4.2/room/Room.d.ts +4 -0
- package/dist/ts4.2/room/data-channel/DataChannelManager.d.ts +62 -0
- package/dist/ts4.2/room/data-channel/FlowControlledDataChannel.d.ts +95 -0
- package/dist/ts4.2/room/data-channel/LossyDataChannel.d.ts +41 -0
- package/dist/ts4.2/room/data-channel/ReliableDataChannel.d.ts +57 -0
- package/dist/ts4.2/room/data-channel/types.d.ts +12 -0
- package/dist/ts4.2/room/data-stream/compression.d.ts +49 -0
- package/dist/ts4.2/room/data-stream/constants.d.ts +18 -0
- package/dist/ts4.2/room/data-stream/incoming/IncomingDataStreamManager.d.ts +4 -0
- package/dist/ts4.2/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts +49 -2
- package/dist/ts4.2/room/data-stream/outgoing/header-utils.d.ts +18 -0
- package/dist/ts4.2/room/errors.d.ts +3 -1
- package/dist/ts4.2/room/participant/LocalParticipant.d.ts +9 -1
- package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +11 -1
- package/dist/ts4.2/room/types.d.ts +17 -0
- package/dist/ts4.2/room/utils.d.ts +13 -0
- package/dist/ts4.2/version.d.ts +5 -1
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/room/RTCEngine.test.ts +58 -99
- package/src/room/RTCEngine.ts +92 -534
- package/src/room/Room.test.ts +4 -1
- package/src/room/Room.ts +40 -8
- package/src/room/data-channel/DataChannelManager.test.ts +152 -0
- package/src/room/data-channel/DataChannelManager.ts +237 -0
- package/src/room/data-channel/FlowControlledDataChannel.test.ts +154 -0
- package/src/room/data-channel/FlowControlledDataChannel.ts +216 -0
- package/src/room/data-channel/LossyDataChannel.test.ts +118 -0
- package/src/room/data-channel/LossyDataChannel.ts +125 -0
- package/src/room/data-channel/ReliableDataChannel.test.ts +158 -0
- package/src/room/data-channel/ReliableDataChannel.ts +153 -0
- package/src/room/data-channel/types.ts +30 -0
- package/src/room/data-stream/compression.ts +117 -0
- package/src/room/data-stream/constants.ts +18 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.test.ts +1601 -0
- package/src/room/data-stream/incoming/IncomingDataStreamManager.ts +438 -86
- package/src/room/data-stream/incoming/StreamReader.ts +8 -2
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.test.ts +1292 -0
- package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +489 -116
- package/src/room/data-stream/outgoing/header-utils.ts +87 -0
- package/src/room/errors.ts +6 -0
- package/src/room/participant/LocalParticipant.ts +13 -0
- package/src/room/participant/RemoteParticipant.ts +19 -5
- package/src/room/rpc/client/RpcClientManager.test.ts +20 -27
- package/src/room/rpc/client/RpcClientManager.ts +1 -4
- package/src/room/rpc/server/RpcServerManager.test.ts +24 -23
- package/src/room/rpc/server/RpcServerManager.ts +1 -3
- package/src/room/types.ts +18 -0
- package/src/room/utils.ts +67 -0
- package/src/version.ts +5 -1
package/src/room/RTCEngine.ts
CHANGED
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
type UserPacket,
|
|
45
45
|
} from '@livekit/protocol';
|
|
46
46
|
import { EventEmitter } from 'events';
|
|
47
|
-
import type { Throws } from '@livekit/throws-transformer/throws';
|
|
48
47
|
import type { MediaAttributes } from 'sdp-transform';
|
|
49
48
|
import type TypedEventEmitter from 'typed-emitter';
|
|
50
49
|
import type { SignalOptions } from '../api/SignalClient';
|
|
@@ -64,11 +63,15 @@ import log, { LoggerNames, getLogger } from '../logger';
|
|
|
64
63
|
import type { InternalRoomOptions } from '../options';
|
|
65
64
|
import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
|
|
66
65
|
import TypedPromise from '../utils/TypedPromise';
|
|
67
|
-
import { DataPacketBuffer } from '../utils/dataPacketBuffer';
|
|
68
66
|
import { TTLMap } from '../utils/ttlmap';
|
|
69
67
|
import PCTransport, { PCEvents } from './PCTransport';
|
|
70
68
|
import { PCTransportManager, PCTransportState } from './PCTransportManager';
|
|
71
69
|
import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
|
|
70
|
+
import { DataChannelManager } from './data-channel/DataChannelManager';
|
|
71
|
+
import type { FlowControlledDataChannel } from './data-channel/FlowControlledDataChannel';
|
|
72
|
+
import type { LossyDataChannel } from './data-channel/LossyDataChannel';
|
|
73
|
+
import type { ReliableDataChannel } from './data-channel/ReliableDataChannel';
|
|
74
|
+
import { DataChannelKind } from './data-channel/types';
|
|
72
75
|
import { DataTrackInfo } from './data-track/types';
|
|
73
76
|
import { roomConnectOptionDefaults } from './defaults';
|
|
74
77
|
import {
|
|
@@ -103,24 +106,10 @@ import {
|
|
|
103
106
|
toHttpUrl,
|
|
104
107
|
} from './utils';
|
|
105
108
|
|
|
106
|
-
const lossyDataChannel = '_lossy';
|
|
107
|
-
const reliableDataChannel = '_reliable';
|
|
108
|
-
const dataTrackDataChannel = '_data_track';
|
|
109
109
|
const minReconnectWait = 2 * 1000;
|
|
110
110
|
const leaveReconnect = 'leave-reconnect';
|
|
111
111
|
const reliabeReceiveStateTTL = 30_000;
|
|
112
112
|
|
|
113
|
-
// Two-watermark flow control for the reliable and data-track channels. Senders fill the buffer
|
|
114
|
-
// freely up to the high-water mark; once it's exceeded they block until the browser's
|
|
115
|
-
// `bufferedamountlow` event (which we arm at the low-water mark) signals the buffer has drained.
|
|
116
|
-
// The gap between the marks keeps the SCTP send buffer saturated while we refill, so throughput
|
|
117
|
-
// isn't starved, while the high-water mark bounds the buffer well below the level that would abort
|
|
118
|
-
// the channel (see livekit/client-sdk-js#1995).
|
|
119
|
-
const reliableDataChannelWaterMarkLow = 64 * 1024;
|
|
120
|
-
const reliableDataChannelWaterMarkHigh = 1024 * 1024;
|
|
121
|
-
const lossyDataChannelWaterMarkLow = 8 * 1024;
|
|
122
|
-
const lossyDataChannelWaterMarkHigh = 256 * 1024;
|
|
123
|
-
|
|
124
113
|
const initialMediaSectionsAudio = 3;
|
|
125
114
|
const initialMediaSectionsVideo = 3;
|
|
126
115
|
|
|
@@ -132,11 +121,7 @@ enum PCState {
|
|
|
132
121
|
Closed,
|
|
133
122
|
}
|
|
134
123
|
|
|
135
|
-
export
|
|
136
|
-
RELIABLE = DataPacket_Kind.RELIABLE,
|
|
137
|
-
LOSSY = DataPacket_Kind.LOSSY,
|
|
138
|
-
DATA_TRACK_LOSSY = 2,
|
|
139
|
-
}
|
|
124
|
+
export { DataChannelKind };
|
|
140
125
|
|
|
141
126
|
// Default data-channel max message size (bytes), used when the remote SDP
|
|
142
127
|
// answer does not advertise an `a=max-message-size` attribute (RFC 8841).
|
|
@@ -180,22 +165,24 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
180
165
|
return !!this.reconnectTimeout;
|
|
181
166
|
}
|
|
182
167
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
private
|
|
189
|
-
|
|
190
|
-
// @ts-ignore noUnusedLocals
|
|
191
|
-
private reliableDCSub?: RTCDataChannel;
|
|
168
|
+
/**
|
|
169
|
+
* Owns the data channels: the three flow-controlled publisher wrappers (engine-lifetime; the
|
|
170
|
+
* RTCDataChannel handles underneath are attached/detached as peer connections come and go, with
|
|
171
|
+
* waiter invalidation built into the turnover) plus the subscriber-side receive handles.
|
|
172
|
+
*/
|
|
173
|
+
private dataChannels: DataChannelManager;
|
|
192
174
|
|
|
193
|
-
private
|
|
175
|
+
private get reliableChannel(): ReliableDataChannel {
|
|
176
|
+
return this.dataChannels.reliable;
|
|
177
|
+
}
|
|
194
178
|
|
|
195
|
-
|
|
196
|
-
|
|
179
|
+
private get lossyChannel(): LossyDataChannel {
|
|
180
|
+
return this.dataChannels.lossy;
|
|
181
|
+
}
|
|
197
182
|
|
|
198
|
-
private
|
|
183
|
+
private get dataTrackChannel(): LossyDataChannel {
|
|
184
|
+
return this.dataChannels.dataTrack;
|
|
185
|
+
}
|
|
199
186
|
|
|
200
187
|
private subscriberPrimary: boolean = false;
|
|
201
188
|
|
|
@@ -252,20 +239,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
252
239
|
|
|
253
240
|
private publisherConnectionPromise: Promise<void> | undefined;
|
|
254
241
|
|
|
255
|
-
private reliableDataSequence: number = 1;
|
|
256
|
-
|
|
257
|
-
private reliableMessageBuffer = new DataPacketBuffer();
|
|
258
|
-
|
|
259
242
|
private reliableReceivedState: TTLMap<string, number> = new TTLMap(reliabeReceiveStateTTL);
|
|
260
243
|
|
|
261
|
-
private lossyDataStatCurrentBytes: number = 0;
|
|
262
|
-
|
|
263
|
-
private lossyDataStatByterate: number = 0;
|
|
264
|
-
|
|
265
|
-
private lossyDataStatInterval: ReturnType<typeof setInterval> | undefined;
|
|
266
|
-
|
|
267
|
-
private lossyDataDropCount: number = 0;
|
|
268
|
-
|
|
269
244
|
private midToTrackId: { [key: string]: string } = {};
|
|
270
245
|
|
|
271
246
|
/** used to indicate whether the browser is currently waiting to reconnect */
|
|
@@ -283,11 +258,16 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
283
258
|
this.reconnectPolicy = this.options.reconnectPolicy;
|
|
284
259
|
this.closingLock = new Mutex();
|
|
285
260
|
this.dataProcessLock = new Mutex();
|
|
286
|
-
this.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
261
|
+
this.dataChannels = new DataChannelManager({
|
|
262
|
+
isEngineClosed: () => this.isClosed,
|
|
263
|
+
isReconnecting: () => this.attemptingReconnect,
|
|
264
|
+
onDataMessage: (message) => this.handleDataMessage(message),
|
|
265
|
+
onDataTrackMessage: (message) => this.handleDataTrackMessage(message),
|
|
266
|
+
onDataError: (event) => this.handleDataError(event),
|
|
267
|
+
onChannelClose: (kind) => this.handleDataChannelClose(kind)(),
|
|
268
|
+
onBufferStatusChanged: (kind, isLow) =>
|
|
269
|
+
this.emit(EngineEvent.DCBufferStatusChanged, isLow, kind),
|
|
270
|
+
});
|
|
291
271
|
|
|
292
272
|
this.client.onParticipantUpdate = (updates) =>
|
|
293
273
|
this.emit(EngineEvent.ParticipantUpdate, updates);
|
|
@@ -458,60 +438,16 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
458
438
|
}
|
|
459
439
|
|
|
460
440
|
async cleanupPeerConnections() {
|
|
461
|
-
|
|
462
|
-
// to transition data channels to 'closed' without firing events, which would otherwise strand
|
|
463
|
-
// a waiter holding the headroom lock.
|
|
464
|
-
this.invalidateDataChannelWaiters('peer connections cleaned up');
|
|
465
|
-
|
|
466
|
-
const dcCleanup = (dc: RTCDataChannel | undefined) => {
|
|
467
|
-
if (!dc) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Detach the data channel handlers before closing anything. Closing a peer connection tears
|
|
472
|
-
// down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
|
|
473
|
-
// channels; if our handlers are still attached at that point, handleDataError logs a spurious
|
|
474
|
-
// "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
|
|
475
|
-
// before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
|
|
476
|
-
// browser dispatches those teardown events. See livekit/client-sdk-js#1953.
|
|
477
|
-
dc.onbufferedamountlow = null;
|
|
478
|
-
dc.onclose = null;
|
|
479
|
-
dc.onclosing = null;
|
|
480
|
-
dc.onerror = null;
|
|
481
|
-
dc.onmessage = null;
|
|
482
|
-
dc.onopen = null;
|
|
483
|
-
|
|
484
|
-
dc.close();
|
|
485
|
-
};
|
|
486
|
-
dcCleanup(this.lossyDC);
|
|
487
|
-
dcCleanup(this.lossyDCSub);
|
|
488
|
-
dcCleanup(this.reliableDC);
|
|
489
|
-
dcCleanup(this.reliableDCSub);
|
|
490
|
-
dcCleanup(this.dataTrackDC);
|
|
491
|
-
dcCleanup(this.dataTrackDCSub);
|
|
441
|
+
this.dataChannels.teardown();
|
|
492
442
|
|
|
493
443
|
await this.pcManager?.close();
|
|
494
444
|
this.pcManager = undefined;
|
|
495
445
|
|
|
496
|
-
this.lossyDC = undefined;
|
|
497
|
-
this.lossyDCSub = undefined;
|
|
498
|
-
this.reliableDC = undefined;
|
|
499
|
-
this.reliableDCSub = undefined;
|
|
500
|
-
this.dataTrackDC = undefined;
|
|
501
|
-
this.dataTrackDCSub = undefined;
|
|
502
|
-
this.reliableMessageBuffer = new DataPacketBuffer();
|
|
503
|
-
this.reliableDataSequence = 1;
|
|
504
446
|
this.reliableReceivedState.clear();
|
|
505
447
|
}
|
|
506
448
|
|
|
507
449
|
cleanupLossyDataStats() {
|
|
508
|
-
this.
|
|
509
|
-
this.lossyDataStatCurrentBytes = 0;
|
|
510
|
-
if (this.lossyDataStatInterval) {
|
|
511
|
-
clearInterval(this.lossyDataStatInterval);
|
|
512
|
-
this.lossyDataStatInterval = undefined;
|
|
513
|
-
}
|
|
514
|
-
this.lossyDataDropCount = 0;
|
|
450
|
+
this.lossyChannel.stopThresholdTuning();
|
|
515
451
|
}
|
|
516
452
|
|
|
517
453
|
async cleanupClient() {
|
|
@@ -580,7 +516,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
580
516
|
}
|
|
581
517
|
|
|
582
518
|
get dataSubscriberReadyState(): string | undefined {
|
|
583
|
-
return this.
|
|
519
|
+
return this.dataChannelForKind(DataChannelKind.RELIABLE, true)?.readyState;
|
|
584
520
|
}
|
|
585
521
|
|
|
586
522
|
async getConnectedServerAddress(): Promise<string | undefined> {
|
|
@@ -890,121 +826,39 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
890
826
|
return;
|
|
891
827
|
}
|
|
892
828
|
|
|
893
|
-
|
|
894
|
-
// are replaced below — reject them so the headroom lock is released and queued senders
|
|
895
|
-
// re-check against the new channels.
|
|
896
|
-
this.invalidateDataChannelWaiters('data channels recreated');
|
|
897
|
-
|
|
898
|
-
// clear old data channel callbacks if recreate
|
|
899
|
-
if (this.lossyDC) {
|
|
900
|
-
this.lossyDC.onmessage = null;
|
|
901
|
-
this.lossyDC.onerror = null;
|
|
902
|
-
this.lossyDC.onclose = null;
|
|
903
|
-
}
|
|
904
|
-
if (this.reliableDC) {
|
|
905
|
-
this.reliableDC.onmessage = null;
|
|
906
|
-
this.reliableDC.onerror = null;
|
|
907
|
-
this.reliableDC.onclose = null;
|
|
908
|
-
}
|
|
909
|
-
if (this.dataTrackDC) {
|
|
910
|
-
this.dataTrackDC.onmessage = null;
|
|
911
|
-
this.dataTrackDC.onerror = null;
|
|
912
|
-
this.dataTrackDC.onclose = null;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// create data channels
|
|
916
|
-
this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
|
|
917
|
-
ordered: false,
|
|
918
|
-
maxRetransmits: 0,
|
|
919
|
-
});
|
|
920
|
-
this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
|
|
921
|
-
ordered: true,
|
|
922
|
-
});
|
|
923
|
-
this.dataTrackDC = this.pcManager.createPublisherDataChannel(dataTrackDataChannel, {
|
|
924
|
-
ordered: false,
|
|
925
|
-
maxRetransmits: 0,
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
// also handle messages over the pub channel, for backwards compatibility
|
|
929
|
-
this.lossyDC.onmessage = this.handleDataMessage;
|
|
930
|
-
this.reliableDC.onmessage = this.handleDataMessage;
|
|
931
|
-
this.dataTrackDC.onmessage = this.handleDataTrackMessage;
|
|
932
|
-
|
|
933
|
-
// handle datachannel errors
|
|
934
|
-
this.lossyDC.onerror = this.handleDataError;
|
|
935
|
-
this.reliableDC.onerror = this.handleDataError;
|
|
936
|
-
this.dataTrackDC.onerror = this.handleDataError;
|
|
937
|
-
|
|
938
|
-
// detect unexpected publisher data channel closes
|
|
939
|
-
this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY);
|
|
940
|
-
this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE);
|
|
941
|
-
this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY);
|
|
942
|
-
|
|
943
|
-
// set up dc buffer threshold - if this is not set, it will default to 0
|
|
944
|
-
this.lossyDC.bufferedAmountLowThreshold = lossyDataChannelWaterMarkLow;
|
|
945
|
-
this.reliableDC.bufferedAmountLowThreshold = reliableDataChannelWaterMarkLow;
|
|
946
|
-
this.dataTrackDC.bufferedAmountLowThreshold = lossyDataChannelWaterMarkLow;
|
|
947
|
-
|
|
948
|
-
// handle buffer amount low events
|
|
949
|
-
this.lossyDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.LOSSY);
|
|
950
|
-
this.reliableDC.onbufferedamountlow = () =>
|
|
951
|
-
this.handleBufferedAmountLow(DataChannelKind.RELIABLE);
|
|
952
|
-
this.dataTrackDC.onbufferedamountlow = () =>
|
|
953
|
-
this.handleBufferedAmountLow(DataChannelKind.DATA_TRACK_LOSSY);
|
|
954
|
-
|
|
955
|
-
this.cleanupLossyDataStats();
|
|
956
|
-
this.lossyDataStatInterval = setInterval(() => {
|
|
957
|
-
this.lossyDataStatByterate = this.lossyDataStatCurrentBytes;
|
|
958
|
-
this.lossyDataStatCurrentBytes = 0;
|
|
959
|
-
|
|
960
|
-
const dc = this.dataChannelForKind(DataChannelKind.LOSSY);
|
|
961
|
-
if (dc) {
|
|
962
|
-
// control buffered latency to ~100ms
|
|
963
|
-
const threshold = this.lossyDataStatByterate / 10;
|
|
964
|
-
dc.bufferedAmountLowThreshold = Math.min(
|
|
965
|
-
Math.max(threshold, lossyDataChannelWaterMarkLow),
|
|
966
|
-
lossyDataChannelWaterMarkHigh,
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
}, 1000);
|
|
829
|
+
this.dataChannels.createPublisherChannels(this.pcManager);
|
|
970
830
|
}
|
|
971
831
|
|
|
972
832
|
private handleDataChannel = async ({ channel }: RTCDataChannelEvent) => {
|
|
973
833
|
if (!channel) {
|
|
974
834
|
return;
|
|
975
835
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
this.reliableDCSub = channel;
|
|
979
|
-
handler = this.handleDataMessage;
|
|
980
|
-
} else if (channel.label === lossyDataChannel) {
|
|
981
|
-
this.lossyDCSub = channel;
|
|
982
|
-
handler = this.handleDataMessage;
|
|
983
|
-
} else if (channel.label === dataTrackDataChannel) {
|
|
984
|
-
this.dataTrackDCSub = channel;
|
|
985
|
-
handler = this.handleDataTrackMessage;
|
|
986
|
-
} else {
|
|
987
|
-
return;
|
|
836
|
+
if (this.dataChannels.adoptSubscriberChannel(channel)) {
|
|
837
|
+
this.log.debug(`on data channel ${channel.id}, ${channel.label}`);
|
|
988
838
|
}
|
|
989
|
-
this.log.debug(`on data channel ${channel.id}, ${channel.label}`);
|
|
990
|
-
channel.onmessage = handler;
|
|
991
839
|
};
|
|
992
840
|
|
|
841
|
+
/** Normalizes an incoming data-channel message into bytes, or logs and returns undefined. */
|
|
842
|
+
private async decodeDataMessage(message: MessageEvent): Promise<Uint8Array | undefined> {
|
|
843
|
+
if (message.data instanceof ArrayBuffer) {
|
|
844
|
+
return new Uint8Array(message.data);
|
|
845
|
+
}
|
|
846
|
+
if (message.data instanceof Blob) {
|
|
847
|
+
return new Uint8Array(await message.data.arrayBuffer());
|
|
848
|
+
}
|
|
849
|
+
this.log.error('unsupported data type', { data: message.data });
|
|
850
|
+
return undefined;
|
|
851
|
+
}
|
|
852
|
+
|
|
993
853
|
private handleDataMessage = async (message: MessageEvent) => {
|
|
994
854
|
// make sure to respect incoming data message order by processing message events one after the other
|
|
995
855
|
const unlock = await this.dataProcessLock.lock();
|
|
996
856
|
try {
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (message.data instanceof ArrayBuffer) {
|
|
1000
|
-
buffer = message.data;
|
|
1001
|
-
} else if (message.data instanceof Blob) {
|
|
1002
|
-
buffer = await message.data.arrayBuffer();
|
|
1003
|
-
} else {
|
|
1004
|
-
this.log.error('unsupported data type', { data: message.data });
|
|
857
|
+
const bytes = await this.decodeDataMessage(message);
|
|
858
|
+
if (!bytes) {
|
|
1005
859
|
return;
|
|
1006
860
|
}
|
|
1007
|
-
const dp = DataPacket.fromBinary(
|
|
861
|
+
const dp = DataPacket.fromBinary(bytes);
|
|
1008
862
|
|
|
1009
863
|
if (dp.sequence > 0 && dp.participantSid !== '') {
|
|
1010
864
|
const lastSeq = this.reliableReceivedState.get(dp.participantSid);
|
|
@@ -1053,18 +907,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1053
907
|
};
|
|
1054
908
|
|
|
1055
909
|
private handleDataTrackMessage = async (message: MessageEvent) => {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (message.data instanceof ArrayBuffer) {
|
|
1059
|
-
buffer = message.data;
|
|
1060
|
-
} else if (message.data instanceof Blob) {
|
|
1061
|
-
buffer = await message.data.arrayBuffer();
|
|
1062
|
-
} else {
|
|
1063
|
-
this.log.error('unsupported data type', { data: message.data });
|
|
910
|
+
const bytes = await this.decodeDataMessage(message);
|
|
911
|
+
if (!bytes) {
|
|
1064
912
|
return;
|
|
1065
913
|
}
|
|
1066
|
-
|
|
1067
|
-
this.emit('dataTrackPacketReceived', new Uint8Array(buffer));
|
|
914
|
+
this.emit('dataTrackPacketReceived', bytes);
|
|
1068
915
|
};
|
|
1069
916
|
|
|
1070
917
|
private handleDataError = (event: Event) => {
|
|
@@ -1103,10 +950,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1103
950
|
}
|
|
1104
951
|
};
|
|
1105
952
|
|
|
1106
|
-
private handleBufferedAmountLow = (channelKind: DataChannelKind) => {
|
|
1107
|
-
this.updateAndEmitDCBufferStatus(channelKind);
|
|
1108
|
-
};
|
|
1109
|
-
|
|
1110
953
|
async createSender(
|
|
1111
954
|
track: LocalTrack,
|
|
1112
955
|
opts: TrackPublishOptions,
|
|
@@ -1510,7 +1353,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1510
1353
|
|
|
1511
1354
|
// recreate publish datachannel if it's id is null
|
|
1512
1355
|
// (for safari https://bugs.webkit.org/show_bug.cgi?id=184688)
|
|
1513
|
-
|
|
1356
|
+
const reliableDC = this.dataChannelForKind(DataChannelKind.RELIABLE);
|
|
1357
|
+
if (reliableDC?.readyState === 'open' && reliableDC.id === null) {
|
|
1514
1358
|
this.createDataChannels();
|
|
1515
1359
|
}
|
|
1516
1360
|
|
|
@@ -1586,7 +1430,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1586
1430
|
}
|
|
1587
1431
|
|
|
1588
1432
|
/* @internal */
|
|
1589
|
-
async sendDataPacket(
|
|
1433
|
+
async sendDataPacket(
|
|
1434
|
+
packet: DataPacket,
|
|
1435
|
+
/** Data-track frames don't come through here — they're sent pre-serialized via {@link sendDataTrackFrame }. */
|
|
1436
|
+
kind: Exclude<DataChannelKind, DataChannelKind.DATA_TRACK_LOSSY>,
|
|
1437
|
+
) {
|
|
1590
1438
|
// make sure we do have a data connection
|
|
1591
1439
|
await this.ensurePublisherConnected(kind);
|
|
1592
1440
|
|
|
@@ -1608,8 +1456,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1608
1456
|
}
|
|
1609
1457
|
|
|
1610
1458
|
if (kind === DataChannelKind.RELIABLE) {
|
|
1611
|
-
packet.sequence = this.
|
|
1612
|
-
this.reliableDataSequence += 1;
|
|
1459
|
+
packet.sequence = this.reliableChannel.nextSequence();
|
|
1613
1460
|
}
|
|
1614
1461
|
|
|
1615
1462
|
const msg = packet.toBinary() as Uint8Array<ArrayBuffer>;
|
|
@@ -1631,314 +1478,46 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
1631
1478
|
);
|
|
1632
1479
|
}
|
|
1633
1480
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
1637
|
-
return this.sendLossyBytes(msg, kind);
|
|
1638
|
-
|
|
1639
|
-
case DataChannelKind.RELIABLE: {
|
|
1640
|
-
if (this.attemptingReconnect) {
|
|
1641
|
-
// A reconnect is already underway — queue for the resume replay instead of parking on a
|
|
1642
|
-
// channel that is being torn down. The send resolves; delivery is deferred to the replay.
|
|
1643
|
-
this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence, sent: false });
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
const dc = this.dataChannelForKind(kind);
|
|
1648
|
-
if (dc) {
|
|
1649
|
-
try {
|
|
1650
|
-
await this.waitForBufferHeadroomWithLock(kind);
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
if (this.isClosed) {
|
|
1653
|
-
// No replay is coming after an engine close — surface the failure.
|
|
1654
|
-
throw error;
|
|
1655
|
-
}
|
|
1656
|
-
// Transient teardown (the channel closed or was replaced while we waited): the
|
|
1657
|
-
// reliable channel promises delivery across resume, so queue the packet for the
|
|
1658
|
-
// replay instead of rejecting a send the app can't meaningfully retry.
|
|
1659
|
-
this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence, sent: false });
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
if (this.attemptingReconnect) {
|
|
1664
|
-
// A reconnect began while we waited for headroom — same deal as above.
|
|
1665
|
-
this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence, sent: false });
|
|
1666
|
-
return;
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence, sent: true });
|
|
1670
|
-
dc.send(msg);
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
this.updateAndEmitDCBufferStatus(kind);
|
|
1674
|
-
break;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
/* @internal */
|
|
1680
|
-
async sendLossyBytes(
|
|
1681
|
-
bytes: NonSharedUint8Array,
|
|
1682
|
-
kind: Exclude<DataChannelKind, DataChannelKind.RELIABLE>,
|
|
1683
|
-
bufferStatusFullBehavior: 'drop' | 'wait' = 'drop',
|
|
1684
|
-
) {
|
|
1685
|
-
// Make sure we do have a data connection. This matters most for the direct data-track path
|
|
1686
|
-
// (Room's packetAvailable handler), which doesn't go through sendDataPacket: it both waits for
|
|
1687
|
-
// the channel to be open and — on lazily negotiated publisher connections — is what kicks the
|
|
1688
|
-
// negotiation off in the first place. The call is memoized, so the steady-state cost is one
|
|
1689
|
-
// await on an already-resolved promise.
|
|
1690
|
-
await this.ensurePublisherConnected(kind);
|
|
1691
|
-
|
|
1692
|
-
const dc = this.dataChannelForKind(kind);
|
|
1693
|
-
try {
|
|
1694
|
-
if (dc) {
|
|
1695
|
-
// Depending on the exact circumstance that data is being sent, either drop or wait for the
|
|
1696
|
-
// buffer to drain below the high-water mark before continuing.
|
|
1697
|
-
switch (bufferStatusFullBehavior) {
|
|
1698
|
-
case 'wait':
|
|
1699
|
-
if (!this.isBelowHighWaterMark(kind)) {
|
|
1700
|
-
await this.waitForBufferHeadroomWithLock(kind);
|
|
1701
|
-
}
|
|
1702
|
-
break;
|
|
1703
|
-
case 'drop':
|
|
1704
|
-
// we check against the actual threshold on the DC here, as it is dynamic for the lossy DC
|
|
1705
|
-
if (!this.isBelowLowWaterMark(kind)) {
|
|
1706
|
-
// Drop messages to reduce latency
|
|
1707
|
-
this.lossyDataDropCount += 1;
|
|
1708
|
-
if (this.lossyDataDropCount % 100 === 0) {
|
|
1709
|
-
this.log.warn(
|
|
1710
|
-
`dropping lossy data channel messages, total dropped: ${this.lossyDataDropCount}`,
|
|
1711
|
-
);
|
|
1712
|
-
}
|
|
1713
|
-
return;
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
if (kind === DataChannelKind.LOSSY) {
|
|
1717
|
-
// The byterate stat tunes the LOSSY channel's dynamic drop threshold; counting data-track
|
|
1718
|
-
// bytes here would inflate it and let the lossy channel buffer far more latency than the
|
|
1719
|
-
// ~100ms the tuning targets.
|
|
1720
|
-
this.lossyDataStatCurrentBytes += bytes.byteLength;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
if (this.attemptingReconnect) {
|
|
1724
|
-
return;
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
dc.send(bytes);
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
this.updateAndEmitDCBufferStatus(kind);
|
|
1731
|
-
} catch (error: unknown) {
|
|
1732
|
-
// ensure same surface behaviour as before with missing data channel being silently ignored, just log an error message for clarity
|
|
1733
|
-
if (error instanceof TypeError) {
|
|
1734
|
-
this.log.error(error);
|
|
1735
|
-
} else {
|
|
1736
|
-
throw error;
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
private async resendReliableMessagesForResume(lastMessageSeq: number) {
|
|
1742
|
-
await this.ensurePublisherConnected(DataChannelKind.RELIABLE);
|
|
1743
|
-
const dc = this.dataChannelForKind(DataChannelKind.RELIABLE);
|
|
1744
|
-
if (dc) {
|
|
1745
|
-
this.reliableMessageBuffer.popToSequence(lastMessageSeq);
|
|
1746
|
-
// Hold the headroom lock across the whole replay. Releasing it between messages would let a
|
|
1747
|
-
// concurrent send — whose (newer) sequence was already assigned in sendDataPacket before it
|
|
1748
|
-
// queued on the lock — hit the wire mid-replay, and receivers would then discard the
|
|
1749
|
-
// remaining lower-sequence resent messages as duplicates.
|
|
1750
|
-
const unlock = await this.getBufferHeadroomLock(DataChannelKind.RELIABLE).lock();
|
|
1751
|
-
try {
|
|
1752
|
-
// Everything left after the ack cutoff must be re-handed to the current channel.
|
|
1753
|
-
this.reliableMessageBuffer.markAllUnsent();
|
|
1754
|
-
// Drain in passes, re-scanning the live buffer each time: a send that arrives (deferred,
|
|
1755
|
-
// sent:false) during our own awaits appends after this pass started, so we pick it up on
|
|
1756
|
-
// the next one. We mark each packet only once we've actually handed it to the channel — a
|
|
1757
|
-
// blanket "mark all sent" would flip such a late arrival to sent without transmitting it,
|
|
1758
|
-
// and a later alignBufferedAmount would then drop it for good. If the loop throws
|
|
1759
|
-
// mid-drain, unsent entries keep their flag and the next replay picks them up.
|
|
1760
|
-
for (
|
|
1761
|
-
let batch = this.reliableMessageBuffer.getUnsent();
|
|
1762
|
-
batch.length > 0;
|
|
1763
|
-
batch = this.reliableMessageBuffer.getUnsent()
|
|
1764
|
-
) {
|
|
1765
|
-
for (const item of batch) {
|
|
1766
|
-
// Respect flow control on resume too, so a large resend doesn't overflow the buffer.
|
|
1767
|
-
// We already hold the lock across the whole replay, so use the lock-free wait.
|
|
1768
|
-
await this.waitForBufferHeadroomWithoutLock(DataChannelKind.RELIABLE);
|
|
1769
|
-
dc.send(item.data);
|
|
1770
|
-
this.reliableMessageBuffer.markSent(item);
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
} finally {
|
|
1774
|
-
unlock();
|
|
1775
|
-
}
|
|
1776
|
-
}
|
|
1777
|
-
this.updateAndEmitDCBufferStatus(DataChannelKind.RELIABLE);
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
private updateAndEmitDCBufferStatus = (kind: DataChannelKind) => {
|
|
1481
|
+
// The full-buffer policy (drop for lossy, wait/replay for reliable) is the channel's own, as
|
|
1482
|
+
// is emitting the buffer-status change once the send settles.
|
|
1781
1483
|
if (kind === DataChannelKind.RELIABLE) {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
try {
|
|
1788
|
-
const status = this.isBelowLowWaterMark(kind);
|
|
1789
|
-
if (typeof status !== 'undefined' && status !== this.dcBufferStatus.get(kind)) {
|
|
1790
|
-
this.dcBufferStatus.set(kind, status);
|
|
1791
|
-
this.emit(EngineEvent.DCBufferStatusChanged, status, kind);
|
|
1792
|
-
}
|
|
1793
|
-
} catch (e) {
|
|
1794
|
-
this.log.warn('could not update buffer status', { error: e });
|
|
1795
|
-
}
|
|
1796
|
-
};
|
|
1797
|
-
|
|
1798
|
-
/**
|
|
1799
|
-
* Whether the send buffer has room to accept more data (the send gate). Senders proceed while
|
|
1800
|
-
* this is true and block once it goes false.
|
|
1801
|
-
*/
|
|
1802
|
-
private isBelowHighWaterMark = (kind: DataChannelKind): Throws<boolean, TypeError> => {
|
|
1803
|
-
const dc = this.dataChannelForKind(kind);
|
|
1804
|
-
if (!dc) {
|
|
1805
|
-
throw new TypeError(`Could not get data channel for kind ${kind}`);
|
|
1806
|
-
}
|
|
1807
|
-
// because RTCDatachannel has no high water mark built in we read the statically defined versions as constants
|
|
1808
|
-
const highMark =
|
|
1809
|
-
kind === DataChannelKind.RELIABLE
|
|
1810
|
-
? reliableDataChannelWaterMarkHigh
|
|
1811
|
-
: lossyDataChannelWaterMarkHigh;
|
|
1812
|
-
return dc.bufferedAmount <= highMark;
|
|
1813
|
-
};
|
|
1814
|
-
|
|
1815
|
-
/**
|
|
1816
|
-
* Whether the send buffer has drained to its low-water mark. Drives the public
|
|
1817
|
-
* {@link EngineEvent.DCBufferStatusChanged} event.
|
|
1818
|
-
*/
|
|
1819
|
-
private isBelowLowWaterMark = (kind: DataChannelKind): Throws<boolean, TypeError> => {
|
|
1820
|
-
const dc = this.dataChannelForKind(kind);
|
|
1821
|
-
if (!dc) {
|
|
1822
|
-
throw new TypeError(`Could not get data channel for kind ${kind}`);
|
|
1484
|
+
await this.reliableChannel.send(msg, packet.sequence);
|
|
1485
|
+
} else {
|
|
1486
|
+
await this.lossyChannel.send(msg);
|
|
1823
1487
|
}
|
|
1824
|
-
|
|
1825
|
-
return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
|
|
1826
|
-
};
|
|
1827
|
-
|
|
1828
|
-
/** Per-kind lock serializing senders that have to wait for the buffer to drain. */
|
|
1829
|
-
private waitForBufferHeadroomLocks = new Map<DataChannelKind, Mutex>();
|
|
1488
|
+
}
|
|
1830
1489
|
|
|
1831
1490
|
/**
|
|
1832
|
-
*
|
|
1833
|
-
*
|
|
1834
|
-
*
|
|
1835
|
-
*
|
|
1836
|
-
*
|
|
1491
|
+
* Sends pre-serialized bytes on the data-track channel. This is the one send path that doesn't
|
|
1492
|
+
* go through {@link sendDataPacket} — Room's `packetAvailable` handler calls it directly with
|
|
1493
|
+
* bytes the data-track pipeline already serialized.
|
|
1494
|
+
*
|
|
1495
|
+
* @internal
|
|
1837
1496
|
*/
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
this.waiterAbortControllers.set(kind, controller);
|
|
1845
|
-
}
|
|
1846
|
-
return controller.signal;
|
|
1497
|
+
async sendDataTrackFrame(bytes: NonSharedUint8Array) {
|
|
1498
|
+
// Make sure we do have a data connection: on lazily negotiated publisher connections this is
|
|
1499
|
+
// what kicks negotiation off, and it waits for the channel to open. Memoized, so the
|
|
1500
|
+
// steady-state cost is one await on an already-resolved promise.
|
|
1501
|
+
await this.ensurePublisherConnected(DataChannelKind.DATA_TRACK_LOSSY);
|
|
1502
|
+
await this.dataTrackChannel.send(bytes);
|
|
1847
1503
|
}
|
|
1848
1504
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
controller.abort(reason);
|
|
1853
|
-
}
|
|
1854
|
-
this.waiterAbortControllers.clear();
|
|
1505
|
+
private async resendReliableMessagesForResume(lastMessageSeq: number) {
|
|
1506
|
+
await this.ensurePublisherConnected(DataChannelKind.RELIABLE);
|
|
1507
|
+
await this.reliableChannel.replay(lastMessageSeq);
|
|
1855
1508
|
}
|
|
1856
1509
|
|
|
1857
|
-
/**
|
|
1858
|
-
private
|
|
1859
|
-
|
|
1860
|
-
if (!lock) {
|
|
1861
|
-
lock = new Mutex();
|
|
1862
|
-
this.waitForBufferHeadroomLocks.set(kind, lock);
|
|
1863
|
-
}
|
|
1864
|
-
return lock;
|
|
1510
|
+
/** The flow-control gate for `kind` — see {@link FlowControlledDataChannel}. */
|
|
1511
|
+
private flowControlFor(kind: DataChannelKind): FlowControlledDataChannel {
|
|
1512
|
+
return this.dataChannels.channelFor(kind);
|
|
1865
1513
|
}
|
|
1866
1514
|
|
|
1867
1515
|
/**
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1870
|
-
* resume replay) acquire it via {@link getBufferHeadroomLock} and call
|
|
1871
|
-
* {@link waitForBufferHeadroomWithoutLock} per message instead.
|
|
1516
|
+
* Resolves once the caller may send on the `kind` channel — see
|
|
1517
|
+
* {@link FlowControlledDataChannel.waitForHeadroomWithLock}.
|
|
1872
1518
|
*/
|
|
1873
|
-
async
|
|
1874
|
-
|
|
1875
|
-
try {
|
|
1876
|
-
await this.waitForBufferHeadroomWithoutLock(kind);
|
|
1877
|
-
} finally {
|
|
1878
|
-
unlock();
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
/**
|
|
1883
|
-
* Waits until the send buffer for `kind` is at or below the high-water mark (draining, if
|
|
1884
|
-
* needed, via the `bufferedamountlow` event). Does no locking of its own — the caller must
|
|
1885
|
-
* already hold the kind's headroom lock (via {@link getBufferHeadroomLock}). The resume replay
|
|
1886
|
-
* holds the lock across its whole batch and calls this per message so no other sender can
|
|
1887
|
-
* interleave; the single-send path goes through {@link waitForBufferHeadroomWithLock}.
|
|
1888
|
-
*
|
|
1889
|
-
* Serializing through the per-kind lock means that, once the buffer drains, queued callers
|
|
1890
|
-
* refill it one at a time (up to the high-water mark) rather than all at once and overflowing
|
|
1891
|
-
* the SCTP send buffer (see livekit/client-sdk-js#1995).
|
|
1892
|
-
*/
|
|
1893
|
-
private async waitForBufferHeadroomWithoutLock(
|
|
1894
|
-
kind: DataChannelKind,
|
|
1895
|
-
): Promise<Throws<void, UnexpectedConnectionState | TypeError>> {
|
|
1896
|
-
if (this.isClosed) {
|
|
1897
|
-
throw new UnexpectedConnectionState('engine closed');
|
|
1898
|
-
}
|
|
1899
|
-
if (this.isBelowHighWaterMark(kind)) {
|
|
1900
|
-
return;
|
|
1901
|
-
}
|
|
1902
|
-
const dc = this.dataChannelForKind(kind);
|
|
1903
|
-
if (!dc) {
|
|
1904
|
-
throw new UnexpectedConnectionState(`DataChannel not found, kind: ${kind}`);
|
|
1905
|
-
}
|
|
1906
|
-
const abortSignal = this.waiterAbortSignal(kind);
|
|
1907
|
-
await new TypedPromise<void, UnexpectedConnectionState>((resolve, reject) => {
|
|
1908
|
-
const onBufferedAmountLow = () => {
|
|
1909
|
-
cleanup();
|
|
1910
|
-
resolve();
|
|
1911
|
-
};
|
|
1912
|
-
const onDCClose = () => {
|
|
1913
|
-
cleanup();
|
|
1914
|
-
reject(
|
|
1915
|
-
new UnexpectedConnectionState(`DataChannel ${kind} closed while draining the buffer`),
|
|
1916
|
-
);
|
|
1917
|
-
};
|
|
1918
|
-
const onAbort = () => {
|
|
1919
|
-
cleanup();
|
|
1920
|
-
reject(
|
|
1921
|
-
new UnexpectedConnectionState(
|
|
1922
|
-
`DataChannel ${kind} was replaced or torn down while waiting for headroom`,
|
|
1923
|
-
),
|
|
1924
|
-
);
|
|
1925
|
-
};
|
|
1926
|
-
const cleanup = () => {
|
|
1927
|
-
dc.removeEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
1928
|
-
dc.removeEventListener('close', onDCClose);
|
|
1929
|
-
abortSignal.removeEventListener('abort', onAbort);
|
|
1930
|
-
};
|
|
1931
|
-
if (abortSignal.aborted) {
|
|
1932
|
-
onAbort();
|
|
1933
|
-
return;
|
|
1934
|
-
}
|
|
1935
|
-
dc.addEventListener('bufferedamountlow', onBufferedAmountLow);
|
|
1936
|
-
// Proxy along any error caused by the data channel closing while we wait.
|
|
1937
|
-
dc.addEventListener('close', onDCClose);
|
|
1938
|
-
// The channel object we're parked on can be abandoned without ever firing another event
|
|
1939
|
-
// (e.g. createDataChannels replacing it); the abort is our way out.
|
|
1940
|
-
abortSignal.addEventListener('abort', onAbort);
|
|
1941
|
-
});
|
|
1519
|
+
async waitForBufferHeadroom(kind: DataChannelKind) {
|
|
1520
|
+
return this.flowControlFor(kind).waitForHeadroomWithLock();
|
|
1942
1521
|
}
|
|
1943
1522
|
|
|
1944
1523
|
/**
|
|
@@ -2040,9 +1619,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
2040
1619
|
// don't negotiate without any transceivers or data channel, it will generate sdp without ice frag then negotiate failed
|
|
2041
1620
|
if (
|
|
2042
1621
|
this.pcManager.publisher.getTransceivers().length == 0 &&
|
|
2043
|
-
!this.
|
|
2044
|
-
!this.reliableDC &&
|
|
2045
|
-
!this.dataTrackDC
|
|
1622
|
+
!this.dataChannels.hasPublisherChannels
|
|
2046
1623
|
) {
|
|
2047
1624
|
this.createDataChannels();
|
|
2048
1625
|
}
|
|
@@ -2091,26 +1668,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
|
|
|
2091
1668
|
}
|
|
2092
1669
|
|
|
2093
1670
|
dataChannelForKind(kind: DataChannelKind, sub?: boolean): RTCDataChannel | undefined {
|
|
2094
|
-
|
|
2095
|
-
case DataChannelKind.RELIABLE:
|
|
2096
|
-
if (!sub) {
|
|
2097
|
-
return this.reliableDC;
|
|
2098
|
-
} else {
|
|
2099
|
-
return this.reliableDCSub;
|
|
2100
|
-
}
|
|
2101
|
-
case DataChannelKind.LOSSY:
|
|
2102
|
-
if (!sub) {
|
|
2103
|
-
return this.lossyDC;
|
|
2104
|
-
} else {
|
|
2105
|
-
return this.lossyDCSub;
|
|
2106
|
-
}
|
|
2107
|
-
case DataChannelKind.DATA_TRACK_LOSSY:
|
|
2108
|
-
if (!sub) {
|
|
2109
|
-
return this.dataTrackDC;
|
|
2110
|
-
} else {
|
|
2111
|
-
return this.dataTrackDCSub;
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
1671
|
+
return this.dataChannels.getHandle(kind, sub);
|
|
2114
1672
|
}
|
|
2115
1673
|
|
|
2116
1674
|
/** @internal */
|