livekit-client 2.18.0 → 2.18.1

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.
Files changed (133) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +8 -7
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +7832 -5799
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.umd.js +1 -1
  8. package/dist/livekit-client.umd.js.map +1 -1
  9. package/dist/src/api/SignalClient.d.ts +12 -4
  10. package/dist/src/api/SignalClient.d.ts.map +1 -1
  11. package/dist/src/e2ee/constants.d.ts.map +1 -1
  12. package/dist/src/e2ee/types.d.ts +6 -0
  13. package/dist/src/e2ee/types.d.ts.map +1 -1
  14. package/dist/src/e2ee/utils.d.ts +2 -1
  15. package/dist/src/e2ee/utils.d.ts.map +1 -1
  16. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  17. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  18. package/dist/src/index.d.ts +5 -4
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/room/PCTransport.d.ts +5 -0
  21. package/dist/src/room/PCTransport.d.ts.map +1 -1
  22. package/dist/src/room/PCTransportManager.d.ts +1 -1
  23. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  24. package/dist/src/room/RTCEngine.d.ts +24 -8
  25. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  26. package/dist/src/room/Room.d.ts +13 -3
  27. package/dist/src/room/Room.d.ts.map +1 -1
  28. package/dist/src/room/data-stream/outgoing/OutgoingDataStreamManager.d.ts.map +1 -1
  29. package/dist/src/room/data-track/LocalDataTrack.d.ts +28 -5
  30. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -1
  31. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -5
  32. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  33. package/dist/src/room/data-track/depacketizer.d.ts +4 -4
  34. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  35. package/dist/src/room/data-track/frame.d.ts +14 -0
  36. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  37. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +19 -11
  38. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  39. package/dist/src/room/data-track/incoming/pipeline.d.ts +6 -5
  40. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  41. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
  42. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
  43. package/dist/src/room/data-track/outgoing/errors.d.ts +16 -6
  44. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
  45. package/dist/src/room/data-track/outgoing/pipeline.d.ts +7 -6
  46. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
  47. package/dist/src/room/data-track/outgoing/types.d.ts +14 -4
  48. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  49. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  50. package/dist/src/room/data-track/packetizer.d.ts +4 -4
  51. package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
  52. package/dist/src/room/data-track/track-interfaces.d.ts +1 -1
  53. package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -1
  54. package/dist/src/room/data-track/types.d.ts +6 -1
  55. package/dist/src/room/data-track/types.d.ts.map +1 -1
  56. package/dist/src/room/events.d.ts +24 -3
  57. package/dist/src/room/events.d.ts.map +1 -1
  58. package/dist/src/room/participant/LocalParticipant.d.ts +11 -1
  59. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  60. package/dist/src/room/participant/RemoteParticipant.d.ts +13 -0
  61. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  62. package/dist/src/room/utils.d.ts +1 -0
  63. package/dist/src/room/utils.d.ts.map +1 -1
  64. package/dist/src/utils/deferrable-map.d.ts +32 -0
  65. package/dist/src/utils/deferrable-map.d.ts.map +1 -0
  66. package/dist/ts4.2/api/SignalClient.d.ts +12 -4
  67. package/dist/ts4.2/e2ee/types.d.ts +6 -0
  68. package/dist/ts4.2/e2ee/utils.d.ts +2 -1
  69. package/dist/ts4.2/index.d.ts +5 -4
  70. package/dist/ts4.2/room/PCTransport.d.ts +5 -0
  71. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
  72. package/dist/ts4.2/room/RTCEngine.d.ts +24 -8
  73. package/dist/ts4.2/room/Room.d.ts +13 -3
  74. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +27 -4
  75. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +4 -4
  76. package/dist/ts4.2/room/data-track/depacketizer.d.ts +4 -4
  77. package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
  78. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +21 -10
  79. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +6 -5
  80. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +57 -23
  81. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +16 -6
  82. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +7 -6
  83. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +14 -4
  84. package/dist/ts4.2/room/data-track/packetizer.d.ts +4 -4
  85. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +1 -1
  86. package/dist/ts4.2/room/data-track/types.d.ts +6 -1
  87. package/dist/ts4.2/room/events.d.ts +24 -3
  88. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +11 -1
  89. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +13 -0
  90. package/dist/ts4.2/room/utils.d.ts +1 -0
  91. package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
  92. package/package.json +1 -1
  93. package/src/api/SignalClient.test.ts +9 -4
  94. package/src/api/SignalClient.ts +116 -9
  95. package/src/e2ee/constants.ts +1 -0
  96. package/src/e2ee/types.ts +6 -0
  97. package/src/e2ee/utils.ts +4 -3
  98. package/src/e2ee/worker/DataCryptor.ts +1 -4
  99. package/src/e2ee/worker/FrameCryptor.ts +1 -4
  100. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
  101. package/src/index.ts +6 -4
  102. package/src/room/PCTransport.ts +41 -1
  103. package/src/room/PCTransportManager.ts +1 -1
  104. package/src/room/RTCEngine.ts +266 -111
  105. package/src/room/Room.ts +149 -12
  106. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +7 -7
  107. package/src/room/data-track/LocalDataTrack.ts +83 -10
  108. package/src/room/data-track/RemoteDataTrack.ts +7 -9
  109. package/src/room/data-track/depacketizer.ts +21 -12
  110. package/src/room/data-track/frame.ts +28 -2
  111. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +58 -73
  112. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +132 -80
  113. package/src/room/data-track/incoming/pipeline.ts +29 -24
  114. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +225 -32
  115. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +150 -75
  116. package/src/room/data-track/outgoing/errors.ts +36 -7
  117. package/src/room/data-track/outgoing/pipeline.ts +23 -17
  118. package/src/room/data-track/outgoing/types.ts +12 -3
  119. package/src/room/data-track/packet/extensions.ts +17 -22
  120. package/src/room/data-track/packet/index.test.ts +22 -33
  121. package/src/room/data-track/packetizer.test.ts +2 -2
  122. package/src/room/data-track/packetizer.ts +4 -4
  123. package/src/room/data-track/track-interfaces.ts +1 -1
  124. package/src/room/data-track/types.ts +21 -1
  125. package/src/room/events.ts +26 -1
  126. package/src/room/participant/LocalParticipant.ts +57 -6
  127. package/src/room/participant/RemoteParticipant.ts +25 -0
  128. package/src/room/utils.ts +4 -0
  129. package/src/utils/deferrable-map.ts +109 -0
  130. package/dist/src/room/data-track/e2ee.d.ts +0 -12
  131. package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
  132. package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
  133. package/src/room/data-track/e2ee.ts +0 -15
@@ -8,6 +8,7 @@ import {
8
8
  DataChannelReceiveState,
9
9
  DataPacket,
10
10
  DataPacket_Kind,
11
+ DataTrackSubscriberHandles,
11
12
  DisconnectReason,
12
13
  EncryptedPacket,
13
14
  EncryptedPacketPayload,
@@ -17,6 +18,7 @@ import {
17
18
  LeaveRequest_Action,
18
19
  MediaSectionsRequirement,
19
20
  ParticipantInfo,
21
+ PublishDataTrackResponse,
20
22
  ReconnectReason,
21
23
  type ReconnectResponse,
22
24
  RequestResponse,
@@ -24,6 +26,7 @@ import {
24
26
  RoomMovedResponse,
25
27
  RpcAck,
26
28
  RpcResponse,
29
+ SessionDescription,
27
30
  SignalTarget,
28
31
  SpeakerInfo,
29
32
  type StreamStateUpdate,
@@ -35,6 +38,7 @@ import {
35
38
  type TrackPublishedResponse,
36
39
  TrackUnpublishedResponse,
37
40
  Transcription,
41
+ UnpublishDataTrackResponse,
38
42
  UpdateSubscription,
39
43
  type UserPacket,
40
44
  } from '@livekit/protocol';
@@ -58,6 +62,7 @@ import PCTransport, { PCEvents } from './PCTransport';
58
62
  import { PCTransportManager, PCTransportState } from './PCTransportManager';
59
63
  import type { ReconnectContext, ReconnectPolicy } from './ReconnectPolicy';
60
64
  import { DEFAULT_MAX_AGE_MS, type RegionUrlProvider } from './RegionUrlProvider';
65
+ import { DataTrackInfo } from './data-track/types';
61
66
  import { roomConnectOptionDefaults } from './defaults';
62
67
  import {
63
68
  ConnectionError,
@@ -80,6 +85,7 @@ import type { TrackPublishOptions, VideoCodec } from './track/options';
80
85
  import { getTrackPublicationInfo } from './track/utils';
81
86
  import type { LoggerOptions } from './types';
82
87
  import {
88
+ isCompressionStreamSupported,
83
89
  isVideoCodec,
84
90
  isVideoTrack,
85
91
  isWeb,
@@ -91,11 +97,14 @@ import {
91
97
 
92
98
  const lossyDataChannel = '_lossy';
93
99
  const reliableDataChannel = '_reliable';
100
+ const dataTrackDataChannel = '_data_track';
94
101
  const minReconnectWait = 2 * 1000;
95
102
  const leaveReconnect = 'leave-reconnect';
96
103
  const reliabeReceiveStateTTL = 30_000;
97
104
  const lossyDataChannelBufferThresholdMin = 8 * 1024;
98
105
  const lossyDataChannelBufferThresholdMax = 256 * 1024;
106
+ const initialMediaSectionsAudio = 3;
107
+ const initialMediaSectionsVideo = 3;
99
108
 
100
109
  enum PCState {
101
110
  New,
@@ -105,6 +114,12 @@ enum PCState {
105
114
  Closed,
106
115
  }
107
116
 
117
+ export enum DataChannelKind {
118
+ RELIABLE = DataPacket_Kind.RELIABLE,
119
+ LOSSY = DataPacket_Kind.LOSSY,
120
+ DATA_TRACK_LOSSY,
121
+ }
122
+
108
123
  /** @internal */
109
124
  export default class RTCEngine extends (EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>) {
110
125
  client: SignalClient;
@@ -149,11 +164,16 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
149
164
 
150
165
  private reliableDC?: RTCDataChannel;
151
166
 
152
- private dcBufferStatus: Map<DataPacket_Kind, boolean>;
153
-
154
167
  // @ts-ignore noUnusedLocals
155
168
  private reliableDCSub?: RTCDataChannel;
156
169
 
170
+ private dataTrackDC?: RTCDataChannel;
171
+
172
+ // @ts-ignore noUnusedLocals
173
+ private dataTrackDCSub?: RTCDataChannel;
174
+
175
+ private dcBufferStatus: Map<DataChannelKind, boolean>;
176
+
157
177
  private subscriberPrimary: boolean = false;
158
178
 
159
179
  private pcState: PCState = PCState.New;
@@ -239,8 +259,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
239
259
  this.closingLock = new Mutex();
240
260
  this.dataProcessLock = new Mutex();
241
261
  this.dcBufferStatus = new Map([
242
- [DataPacket_Kind.LOSSY, true],
243
- [DataPacket_Kind.RELIABLE, true],
262
+ [DataChannelKind.RELIABLE, true],
263
+ [DataChannelKind.LOSSY, true],
264
+ [DataChannelKind.DATA_TRACK_LOSSY, true],
244
265
  ]);
245
266
 
246
267
  this.client.onParticipantUpdate = (updates) =>
@@ -255,6 +276,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
255
276
  this.client.onStreamStateUpdate = (update) => this.emit(EngineEvent.StreamStateChanged, update);
256
277
  this.client.onRequestResponse = (response) =>
257
278
  this.emit(EngineEvent.SignalRequestResponse, response);
279
+ this.client.onParticipantUpdate = (updates) =>
280
+ this.emit(EngineEvent.ParticipantUpdate, updates);
281
+ this.client.onJoined = (joinResponse) => this.emit(EngineEvent.Joined, joinResponse);
258
282
  }
259
283
 
260
284
  /** @internal */
@@ -284,20 +308,46 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
284
308
  this.joinAttempts += 1;
285
309
 
286
310
  this.setupSignalClientCallbacks();
287
- const joinResponse = await this.client.join(url, token, opts, abortSignal, useV0Path);
311
+ let offerProto: SessionDescription | undefined;
312
+ if (!useV0Path && isCompressionStreamSupported()) {
313
+ if (!this.pcManager) {
314
+ await this.configure();
315
+ this.createDataChannels();
316
+ this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
317
+ }
318
+ const offer = await this.pcManager?.publisher.createInitialOffer();
319
+ if (offer) {
320
+ offerProto = toProtoSessionDescription(offer.offer, offer.offerId);
321
+ }
322
+ }
323
+ if (abortSignal?.aborted) {
324
+ throw ConnectionError.cancelled('Connection aborted');
325
+ }
326
+ const joinResponse = await this.client.join(
327
+ url,
328
+ token,
329
+ opts,
330
+ abortSignal,
331
+ useV0Path,
332
+ offerProto,
333
+ );
288
334
  this._isClosed = false;
289
335
  this.latestJoinResponse = joinResponse;
336
+ this.participantSid = joinResponse.participant?.sid;
290
337
 
291
338
  this.subscriberPrimary = joinResponse.subscriberPrimary;
292
- if (!this.pcManager) {
293
- await this.configure(joinResponse, !useV0Path);
294
- }
295
-
296
- // create offer
297
- if (!this.subscriberPrimary || joinResponse.fastPublish) {
298
- this.negotiate().catch((err) => {
299
- log.error(err, this.logContext);
300
- });
339
+ if (!useV0Path && isCompressionStreamSupported()) {
340
+ this.pcManager?.updateConfiguration(this.makeRTCConfiguration(joinResponse));
341
+ } else {
342
+ if (!this.pcManager) {
343
+ await this.configure(joinResponse, !useV0Path);
344
+ }
345
+ // create offer
346
+ if (!this.subscriberPrimary || joinResponse.fastPublish) {
347
+ this.negotiate().catch((err) => {
348
+ log.error(err, this.logContext);
349
+ });
350
+ }
301
351
  }
302
352
 
303
353
  this.registerOnLineListener();
@@ -362,11 +412,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
362
412
  dcCleanup(this.lossyDCSub);
363
413
  dcCleanup(this.reliableDC);
364
414
  dcCleanup(this.reliableDCSub);
415
+ dcCleanup(this.dataTrackDC);
416
+ dcCleanup(this.dataTrackDCSub);
365
417
 
366
418
  this.lossyDC = undefined;
367
419
  this.lossyDCSub = undefined;
368
420
  this.reliableDC = undefined;
369
421
  this.reliableDCSub = undefined;
422
+ this.dataTrackDC = undefined;
423
+ this.dataTrackDCSub = undefined;
370
424
  this.reliableMessageBuffer = new DataPacketBuffer();
371
425
  this.reliableDataSequence = 1;
372
426
  this.reliableReceivedState.clear();
@@ -452,25 +506,28 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
452
506
  this.regionUrlProvider = provider;
453
507
  }
454
508
 
455
- private async configure(joinResponse: JoinResponse, useSinglePeerConnection: boolean) {
509
+ private async configure(joinResponse?: JoinResponse, useSinglePeerConnection?: boolean) {
456
510
  // already configured
457
511
  if (this.pcManager && this.pcManager.currentState !== PCTransportState.NEW) {
458
512
  return;
459
513
  }
460
514
 
461
- this.participantSid = joinResponse.participant?.sid;
462
-
463
- const rtcConfig = this.makeRTCConfiguration(joinResponse);
464
-
465
- this.pcManager = new PCTransportManager(
466
- rtcConfig,
467
- useSinglePeerConnection
468
- ? 'publisher-only'
469
- : joinResponse.subscriberPrimary
470
- ? 'subscriber-primary'
471
- : 'publisher-primary',
472
- this.loggerOptions,
473
- );
515
+ if (!joinResponse) {
516
+ const rtcConfig = this.makeRTCConfiguration();
517
+ this.pcManager = new PCTransportManager('publisher-only', this.loggerOptions, rtcConfig);
518
+ } else {
519
+ this.participantSid = joinResponse.participant?.sid;
520
+ const rtcConfig = this.makeRTCConfiguration(joinResponse);
521
+ this.pcManager = new PCTransportManager(
522
+ useSinglePeerConnection
523
+ ? 'publisher-only'
524
+ : joinResponse.subscriberPrimary
525
+ ? 'subscriber-primary'
526
+ : 'publisher-primary',
527
+ this.loggerOptions,
528
+ rtcConfig,
529
+ );
530
+ }
474
531
 
475
532
  this.emit(EngineEvent.TransportsCreated, this.pcManager.publisher, this.pcManager.subscriber);
476
533
 
@@ -494,7 +551,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
494
551
  const shouldEmit = this.pcState === PCState.New;
495
552
  this.pcState = PCState.Connected;
496
553
  if (shouldEmit) {
497
- this.emit(EngineEvent.Connected, joinResponse);
554
+ this.emit(EngineEvent.Connected, this.latestJoinResponse!);
498
555
  }
499
556
  } else if (connectionState === PCTransportState.FAILED) {
500
557
  // on Safari, PeerConnection will switch to 'disconnected' during renegotiation
@@ -529,10 +586,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
529
586
  if (ev.streams.length === 0) return;
530
587
  this.emit(EngineEvent.MediaTrackAdded, ev.track, ev.streams[0], ev.receiver);
531
588
  };
532
-
533
- if (!supportOptionalDatachannel(joinResponse.serverInfo?.protocol)) {
534
- this.createDataChannels();
535
- }
536
589
  }
537
590
 
538
591
  private setupSignalClientCallbacks() {
@@ -621,17 +674,22 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
621
674
  };
622
675
 
623
676
  this.client.onMediaSectionsRequirement = (requirement: MediaSectionsRequirement) => {
624
- const transceiverInit: RTCRtpTransceiverInit = { direction: 'recvonly' };
625
- for (let i: number = 0; i < requirement.numAudios; i++) {
626
- this.pcManager?.addPublisherTransceiverOfKind('audio', transceiverInit);
627
- }
628
- for (let i: number = 0; i < requirement.numVideos; i++) {
629
- this.pcManager?.addPublisherTransceiverOfKind('video', transceiverInit);
630
- }
631
-
677
+ this.addMediaSections(requirement.numAudios, requirement.numVideos);
632
678
  this.negotiate();
633
679
  };
634
680
 
681
+ this.client.onPublishDataTrackResponse = (event: PublishDataTrackResponse) => {
682
+ this.emit(EngineEvent.PublishDataTrackResponse, event);
683
+ };
684
+
685
+ this.client.onUnPublishDataTrackResponse = (event: UnpublishDataTrackResponse) => {
686
+ this.emit(EngineEvent.UnPublishDataTrackResponse, event);
687
+ };
688
+
689
+ this.client.onDataTrackSubscriberHandles = (event: DataTrackSubscriberHandles) => {
690
+ this.emit(EngineEvent.DataTrackSubscriberHandles, event);
691
+ };
692
+
635
693
  this.client.onClose = () => {
636
694
  this.handleDisconnect('signal', ReconnectReason.RR_SIGNAL_DISCONNECTED);
637
695
  };
@@ -665,7 +723,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
665
723
  };
666
724
  }
667
725
 
668
- private makeRTCConfiguration(serverResponse: JoinResponse | ReconnectResponse): RTCConfiguration {
726
+ private makeRTCConfiguration(
727
+ serverResponse?: JoinResponse | ReconnectResponse,
728
+ ): RTCConfiguration {
669
729
  const rtcConfig = { ...this.rtcConfig };
670
730
 
671
731
  if (this.signalOpts?.e2eeEnabled) {
@@ -675,6 +735,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
675
735
  rtcConfig.encodedInsertableStreams = true;
676
736
  }
677
737
 
738
+ // @ts-ignore
739
+ rtcConfig.sdpSemantics = 'unified-plan';
740
+ // @ts-ignore
741
+ rtcConfig.continualGatheringPolicy = 'gather_continually';
742
+
743
+ if (!serverResponse) {
744
+ return rtcConfig;
745
+ }
746
+
678
747
  // update ICE servers before creating PeerConnection
679
748
  if (serverResponse.iceServers && !rtcConfig.iceServers) {
680
749
  const rtcIceServers: RTCIceServer[] = [];
@@ -698,14 +767,19 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
698
767
  rtcConfig.iceTransportPolicy = 'relay';
699
768
  }
700
769
 
701
- // @ts-ignore
702
- rtcConfig.sdpSemantics = 'unified-plan';
703
- // @ts-ignore
704
- rtcConfig.continualGatheringPolicy = 'gather_continually';
705
-
706
770
  return rtcConfig;
707
771
  }
708
772
 
773
+ private addMediaSections(numAudios: number, numVideos: number) {
774
+ const transceiverInit: RTCRtpTransceiverInit = { direction: 'recvonly' };
775
+ for (let i: number = 0; i < numAudios; i++) {
776
+ this.pcManager?.addPublisherTransceiverOfKind('audio', transceiverInit);
777
+ }
778
+ for (let i: number = 0; i < numVideos; i++) {
779
+ this.pcManager?.addPublisherTransceiverOfKind('video', transceiverInit);
780
+ }
781
+ }
782
+
709
783
  private createDataChannels() {
710
784
  if (!this.pcManager) {
711
785
  return;
@@ -720,6 +794,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
720
794
  this.reliableDC.onmessage = null;
721
795
  this.reliableDC.onerror = null;
722
796
  }
797
+ if (this.dataTrackDC) {
798
+ this.dataTrackDC.onmessage = null;
799
+ this.dataTrackDC.onerror = null;
800
+ }
723
801
 
724
802
  // create data channels
725
803
  this.lossyDC = this.pcManager.createPublisherDataChannel(lossyDataChannel, {
@@ -729,29 +807,39 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
729
807
  this.reliableDC = this.pcManager.createPublisherDataChannel(reliableDataChannel, {
730
808
  ordered: true,
731
809
  });
810
+ this.dataTrackDC = this.pcManager.createPublisherDataChannel(dataTrackDataChannel, {
811
+ ordered: false,
812
+ maxRetransmits: 0,
813
+ });
732
814
 
733
815
  // also handle messages over the pub channel, for backwards compatibility
734
816
  this.lossyDC.onmessage = this.handleDataMessage;
735
817
  this.reliableDC.onmessage = this.handleDataMessage;
818
+ this.dataTrackDC.onmessage = this.handleDataTrackMessage;
736
819
 
737
820
  // handle datachannel errors
738
821
  this.lossyDC.onerror = this.handleDataError;
739
822
  this.reliableDC.onerror = this.handleDataError;
823
+ this.dataTrackDC.onerror = this.handleDataError;
740
824
 
741
825
  // set up dc buffer threshold, set to 64kB (otherwise 0 by default)
742
826
  this.lossyDC.bufferedAmountLowThreshold = 65535;
743
827
  this.reliableDC.bufferedAmountLowThreshold = 65535;
828
+ this.dataTrackDC.bufferedAmountLowThreshold = 65535;
744
829
 
745
830
  // handle buffer amount low events
746
- this.lossyDC.onbufferedamountlow = this.handleBufferedAmountLow;
747
- this.reliableDC.onbufferedamountlow = this.handleBufferedAmountLow;
831
+ this.lossyDC.onbufferedamountlow = () => this.handleBufferedAmountLow(DataChannelKind.LOSSY);
832
+ this.reliableDC.onbufferedamountlow = () =>
833
+ this.handleBufferedAmountLow(DataChannelKind.RELIABLE);
834
+ this.dataTrackDC.onbufferedamountlow = () =>
835
+ this.handleBufferedAmountLow(DataChannelKind.DATA_TRACK_LOSSY);
748
836
 
749
837
  this.cleanupLossyDataStats();
750
838
  this.lossyDataStatInterval = setInterval(() => {
751
839
  this.lossyDataStatByterate = this.lossyDataStatCurrentBytes;
752
840
  this.lossyDataStatCurrentBytes = 0;
753
841
 
754
- const dc = this.dataChannelForKind(DataPacket_Kind.LOSSY);
842
+ const dc = this.dataChannelForKind(DataChannelKind.LOSSY);
755
843
  if (dc) {
756
844
  // control buffered latency to ~100ms
757
845
  const threshold = this.lossyDataStatByterate / 10;
@@ -767,15 +855,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
767
855
  if (!channel) {
768
856
  return;
769
857
  }
858
+ let handler;
770
859
  if (channel.label === reliableDataChannel) {
771
860
  this.reliableDCSub = channel;
861
+ handler = this.handleDataMessage;
772
862
  } else if (channel.label === lossyDataChannel) {
773
863
  this.lossyDCSub = channel;
864
+ handler = this.handleDataMessage;
865
+ } else if (channel.label === dataTrackDataChannel) {
866
+ this.dataTrackDCSub = channel;
867
+ handler = this.handleDataTrackMessage;
774
868
  } else {
775
869
  return;
776
870
  }
777
871
  this.log.debug(`on data channel ${channel.id}, ${channel.label}`, this.logContext);
778
- channel.onmessage = this.handleDataMessage;
872
+ channel.onmessage = handler;
779
873
  };
780
874
 
781
875
  private handleDataMessage = async (message: MessageEvent) => {
@@ -840,6 +934,21 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
840
934
  }
841
935
  };
842
936
 
937
+ private handleDataTrackMessage = async (message: MessageEvent) => {
938
+ // Decode / normalize into a common format
939
+ let buffer: ArrayBuffer | undefined;
940
+ if (message.data instanceof ArrayBuffer) {
941
+ buffer = message.data;
942
+ } else if (message.data instanceof Blob) {
943
+ buffer = await message.data.arrayBuffer();
944
+ } else {
945
+ this.log.error('unsupported data type', { ...this.logContext, data: message.data });
946
+ return;
947
+ }
948
+
949
+ this.emit('dataTrackPacketReceived', new Uint8Array(buffer));
950
+ };
951
+
843
952
  private handleDataError = (event: Event) => {
844
953
  const channel = event.currentTarget as RTCDataChannel;
845
954
  const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
@@ -855,11 +964,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
855
964
  }
856
965
  };
857
966
 
858
- private handleBufferedAmountLow = (event: Event) => {
859
- const channel = event.currentTarget as RTCDataChannel;
860
- const channelKind =
861
- channel.maxRetransmits === 0 ? DataPacket_Kind.LOSSY : DataPacket_Kind.RELIABLE;
862
-
967
+ private handleBufferedAmountLow = (channelKind: DataChannelKind) => {
863
968
  this.updateAndEmitDCBufferStatus(channelKind);
864
969
  };
865
970
 
@@ -1288,7 +1393,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1288
1393
  },
1289
1394
  });
1290
1395
 
1291
- await this.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1396
+ await this.sendDataPacket(packet, DataChannelKind.RELIABLE);
1292
1397
  }
1293
1398
 
1294
1399
  /** @internal */
@@ -1303,11 +1408,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1303
1408
  }),
1304
1409
  },
1305
1410
  });
1306
- await this.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
1411
+ await this.sendDataPacket(packet, DataChannelKind.RELIABLE);
1307
1412
  }
1308
1413
 
1309
1414
  /* @internal */
1310
- async sendDataPacket(packet: DataPacket, kind: DataPacket_Kind) {
1415
+ async sendDataPacket(packet: DataPacket, kind: DataChannelKind) {
1311
1416
  // make sure we do have a data connection
1312
1417
  await this.ensurePublisherConnected(kind);
1313
1418
 
@@ -1326,57 +1431,93 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1326
1431
  }
1327
1432
  }
1328
1433
 
1329
- if (kind === DataPacket_Kind.RELIABLE) {
1434
+ if (kind === DataChannelKind.RELIABLE) {
1330
1435
  packet.sequence = this.reliableDataSequence;
1331
1436
  this.reliableDataSequence += 1;
1332
1437
  }
1333
1438
 
1334
1439
  const msg = packet.toBinary();
1335
1440
 
1441
+ switch (kind) {
1442
+ case DataChannelKind.LOSSY:
1443
+ case DataChannelKind.DATA_TRACK_LOSSY:
1444
+ return this.sendLossyBytes(msg, kind);
1445
+
1446
+ case DataChannelKind.RELIABLE:
1447
+ const dc = this.dataChannelForKind(kind);
1448
+ if (dc) {
1449
+ await this.waitForBufferStatusLow(kind);
1450
+ this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence });
1451
+
1452
+ if (this.attemptingReconnect) {
1453
+ return;
1454
+ }
1455
+
1456
+ dc.send(msg);
1457
+ }
1458
+
1459
+ this.updateAndEmitDCBufferStatus(kind);
1460
+ break;
1461
+ }
1462
+ }
1463
+
1464
+ /* @internal */
1465
+ async sendLossyBytes(
1466
+ bytes: Uint8Array,
1467
+ kind: Exclude<DataChannelKind, DataChannelKind.RELIABLE>,
1468
+ bufferStatusLowBehavior: 'drop' | 'wait' = 'drop',
1469
+ ) {
1470
+ // make sure we do have a data connection
1471
+ await this.ensurePublisherConnected(kind);
1472
+
1336
1473
  const dc = this.dataChannelForKind(kind);
1337
1474
  if (dc) {
1338
- if (kind === DataPacket_Kind.RELIABLE) {
1339
- await this.waitForBufferStatusLow(kind);
1340
- this.reliableMessageBuffer.push({ data: msg, sequence: packet.sequence });
1341
- } else {
1342
- // lossy channel, drop messages to reduce latency
1343
- if (!this.isBufferStatusLow(kind)) {
1344
- this.lossyDataDropCount += 1;
1345
- if (this.lossyDataDropCount % 100 === 0) {
1346
- this.log.warn(
1347
- `dropping lossy data channel messages, total dropped: ${this.lossyDataDropCount}`,
1348
- this.logContext,
1349
- );
1350
- }
1351
- return;
1475
+ if (!this.isBufferStatusLow(kind)) {
1476
+ // Depending on the exact circumstance that data is being sent, either drop or wait for the
1477
+ // buffer status to not be low before continuing.
1478
+ switch (bufferStatusLowBehavior) {
1479
+ case 'wait':
1480
+ await this.waitForBufferStatusLow(kind);
1481
+ break;
1482
+ case 'drop':
1483
+ // this.log.warn(`dropping lossy data channel message`, this.logContext);
1484
+ // Drop messages to reduce latency
1485
+ this.lossyDataDropCount += 1;
1486
+ if (this.lossyDataDropCount % 100 === 0) {
1487
+ this.log.warn(
1488
+ `dropping lossy data channel messages, total dropped: ${this.lossyDataDropCount}`,
1489
+ this.logContext,
1490
+ );
1491
+ }
1492
+ return;
1352
1493
  }
1353
- this.lossyDataStatCurrentBytes += msg.byteLength;
1354
1494
  }
1495
+ this.lossyDataStatCurrentBytes += bytes.byteLength;
1355
1496
 
1356
1497
  if (this.attemptingReconnect) {
1357
1498
  return;
1358
1499
  }
1359
1500
 
1360
- dc.send(msg);
1501
+ dc.send(bytes);
1361
1502
  }
1362
1503
 
1363
1504
  this.updateAndEmitDCBufferStatus(kind);
1364
1505
  }
1365
1506
 
1366
1507
  private async resendReliableMessagesForResume(lastMessageSeq: number) {
1367
- await this.ensurePublisherConnected(DataPacket_Kind.RELIABLE);
1368
- const dc = this.dataChannelForKind(DataPacket_Kind.RELIABLE);
1508
+ await this.ensurePublisherConnected(DataChannelKind.RELIABLE);
1509
+ const dc = this.dataChannelForKind(DataChannelKind.RELIABLE);
1369
1510
  if (dc) {
1370
1511
  this.reliableMessageBuffer.popToSequence(lastMessageSeq);
1371
1512
  this.reliableMessageBuffer.getAll().forEach((msg) => {
1372
1513
  dc.send(msg.data);
1373
1514
  });
1374
1515
  }
1375
- this.updateAndEmitDCBufferStatus(DataPacket_Kind.RELIABLE);
1516
+ this.updateAndEmitDCBufferStatus(DataChannelKind.RELIABLE);
1376
1517
  }
1377
1518
 
1378
- private updateAndEmitDCBufferStatus = (kind: DataPacket_Kind) => {
1379
- if (kind === DataPacket_Kind.RELIABLE) {
1519
+ private updateAndEmitDCBufferStatus = (kind: DataChannelKind) => {
1520
+ if (kind === DataChannelKind.RELIABLE) {
1380
1521
  const dc = this.dataChannelForKind(kind);
1381
1522
  if (dc) {
1382
1523
  this.reliableMessageBuffer.alignBufferedAmount(dc.bufferedAmount);
@@ -1390,14 +1531,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1390
1531
  }
1391
1532
  };
1392
1533
 
1393
- private isBufferStatusLow = (kind: DataPacket_Kind): boolean | undefined => {
1534
+ private isBufferStatusLow = (kind: DataChannelKind): boolean | undefined => {
1394
1535
  const dc = this.dataChannelForKind(kind);
1395
1536
  if (dc) {
1396
1537
  return dc.bufferedAmount <= dc.bufferedAmountLowThreshold;
1397
1538
  }
1398
1539
  };
1399
1540
 
1400
- waitForBufferStatusLow(kind: DataPacket_Kind): TypedPromise<void, UnexpectedConnectionState> {
1541
+ waitForBufferStatusLow(kind: DataChannelKind): TypedPromise<void, UnexpectedConnectionState> {
1401
1542
  return new TypedPromise(async (resolve, reject) => {
1402
1543
  if (this.isBufferStatusLow(kind)) {
1403
1544
  resolve();
@@ -1417,7 +1558,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1417
1558
  * @internal
1418
1559
  */
1419
1560
  async ensureDataTransportConnected(
1420
- kind: DataPacket_Kind,
1561
+ kind: DataChannelKind,
1421
1562
  subscriber: boolean = this.subscriberPrimary,
1422
1563
  ) {
1423
1564
  if (!this.pcManager) {
@@ -1472,7 +1613,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1472
1613
  );
1473
1614
  }
1474
1615
 
1475
- private async ensurePublisherConnected(kind: DataPacket_Kind) {
1616
+ private async ensurePublisherConnected(kind: DataChannelKind) {
1476
1617
  if (!this.publisherConnectionPromise) {
1477
1618
  this.publisherConnectionPromise = this.ensureDataTransportConnected(kind, false);
1478
1619
  }
@@ -1513,7 +1654,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1513
1654
  if (
1514
1655
  this.pcManager.publisher.getTransceivers().length == 0 &&
1515
1656
  !this.lossyDC &&
1516
- !this.reliableDC
1657
+ !this.reliableDC &&
1658
+ !this.dataTrackDC
1517
1659
  ) {
1518
1660
  this.createDataChannels();
1519
1661
  }
@@ -1573,26 +1715,35 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1573
1715
  });
1574
1716
  }
1575
1717
 
1576
- dataChannelForKind(kind: DataPacket_Kind, sub?: boolean): RTCDataChannel | undefined {
1577
- if (!sub) {
1578
- if (kind === DataPacket_Kind.LOSSY) {
1579
- return this.lossyDC;
1580
- }
1581
- if (kind === DataPacket_Kind.RELIABLE) {
1582
- return this.reliableDC;
1583
- }
1584
- } else {
1585
- if (kind === DataPacket_Kind.LOSSY) {
1586
- return this.lossyDCSub;
1587
- }
1588
- if (kind === DataPacket_Kind.RELIABLE) {
1589
- return this.reliableDCSub;
1590
- }
1718
+ dataChannelForKind(kind: DataChannelKind, sub?: boolean): RTCDataChannel | undefined {
1719
+ switch (kind) {
1720
+ case DataChannelKind.RELIABLE:
1721
+ if (!sub) {
1722
+ return this.reliableDC;
1723
+ } else {
1724
+ return this.reliableDCSub;
1725
+ }
1726
+ case DataChannelKind.LOSSY:
1727
+ if (!sub) {
1728
+ return this.lossyDC;
1729
+ } else {
1730
+ return this.lossyDCSub;
1731
+ }
1732
+ case DataChannelKind.DATA_TRACK_LOSSY:
1733
+ if (!sub) {
1734
+ return this.dataTrackDC;
1735
+ } else {
1736
+ return this.dataTrackDCSub;
1737
+ }
1591
1738
  }
1592
1739
  }
1593
1740
 
1594
1741
  /** @internal */
1595
- sendSyncState(remoteTracks: RemoteTrackPublication[], localTracks: LocalTrackPublication[]) {
1742
+ sendSyncState(
1743
+ remoteTracks: RemoteTrackPublication[],
1744
+ localTracks: LocalTrackPublication[],
1745
+ localDataTrackInfos: Array<DataTrackInfo>,
1746
+ ) {
1596
1747
  if (!this.pcManager) {
1597
1748
  this.log.warn('sync state cannot be sent without peer connection setup', this.logContext);
1598
1749
  return;
@@ -1664,6 +1815,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1664
1815
  lastSeq: seq,
1665
1816
  });
1666
1817
  }),
1818
+ publishDataTracks: localDataTrackInfos.map((info) => {
1819
+ return new PublishDataTrackResponse({ info: DataTrackInfo.toProtobuf(info) });
1820
+ }),
1667
1821
  }),
1668
1822
  );
1669
1823
  }
@@ -1687,10 +1841,10 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1687
1841
  );
1688
1842
  }
1689
1843
  };
1690
- getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY), SignalTarget.PUBLISHER);
1691
- getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE), SignalTarget.PUBLISHER);
1692
- getInfo(this.dataChannelForKind(DataPacket_Kind.LOSSY, true), SignalTarget.SUBSCRIBER);
1693
- getInfo(this.dataChannelForKind(DataPacket_Kind.RELIABLE, true), SignalTarget.SUBSCRIBER);
1844
+ getInfo(this.dataChannelForKind(DataChannelKind.LOSSY), SignalTarget.PUBLISHER);
1845
+ getInfo(this.dataChannelForKind(DataChannelKind.RELIABLE), SignalTarget.PUBLISHER);
1846
+ getInfo(this.dataChannelForKind(DataChannelKind.LOSSY, true), SignalTarget.SUBSCRIBER);
1847
+ getInfo(this.dataChannelForKind(DataChannelKind.RELIABLE, true), SignalTarget.SUBSCRIBER);
1694
1848
  return infos;
1695
1849
  }
1696
1850
 
@@ -1797,7 +1951,7 @@ export type EngineEventCallbacks = {
1797
1951
  /** @internal */
1798
1952
  trackSenderAdded: (track: Track, sender: RTCRtpSender) => void;
1799
1953
  rtpVideoMapUpdate: (rtpMap: Map<number, VideoCodec>) => void;
1800
- dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
1954
+ dcBufferStatusChanged: (isLow: boolean, kind: DataChannelKind) => void;
1801
1955
  participantUpdate: (infos: ParticipantInfo[]) => void;
1802
1956
  roomUpdate: (room: RoomModel) => void;
1803
1957
  roomMoved: (room: RoomMovedResponse) => void;
@@ -1813,12 +1967,13 @@ export type EngineEventCallbacks = {
1813
1967
  offline: () => void;
1814
1968
  signalRequestResponse: (response: RequestResponse) => void;
1815
1969
  signalConnected: (joinResp: JoinResponse) => void;
1970
+ publishDataTrackResponse: (event: PublishDataTrackResponse) => void;
1971
+ unPublishDataTrackResponse: (event: UnpublishDataTrackResponse) => void;
1972
+ dataTrackSubscriberHandles: (event: DataTrackSubscriberHandles) => void;
1973
+ dataTrackPacketReceived: (packet: Uint8Array) => void;
1974
+ joined: (joinResponse: JoinResponse) => void;
1816
1975
  };
1817
1976
 
1818
- function supportOptionalDatachannel(protocol: number | undefined): boolean {
1819
- return protocol !== undefined && protocol > 13;
1820
- }
1821
-
1822
1977
  function applyUserDataCompat(newObj: DataPacket, oldObj: UserPacket) {
1823
1978
  const participantIdentity = newObj.participantIdentity
1824
1979
  ? newObj.participantIdentity