livekit-client 2.17.3 → 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 (183) 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 +7823 -5772
  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 +27 -9
  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 +48 -0
  30. package/dist/src/room/data-track/LocalDataTrack.d.ts.map +1 -0
  31. package/dist/src/room/data-track/RemoteDataTrack.d.ts +46 -0
  32. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -0
  33. package/dist/src/room/data-track/depacketizer.d.ts +6 -6
  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/handle.d.ts +2 -2
  38. package/dist/src/room/data-track/handle.d.ts.map +1 -1
  39. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +104 -0
  40. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -0
  41. package/dist/src/room/data-track/incoming/errors.d.ts +24 -0
  42. package/dist/src/room/data-track/incoming/errors.d.ts.map +1 -0
  43. package/dist/src/room/data-track/incoming/pipeline.d.ts +38 -0
  44. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -0
  45. package/dist/src/room/data-track/incoming/types.d.ts +20 -0
  46. package/dist/src/room/data-track/incoming/types.d.ts.map +1 -0
  47. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +63 -28
  48. package/dist/src/room/data-track/outgoing/OutgoingDataTrackManager.d.ts.map +1 -1
  49. package/dist/src/room/data-track/outgoing/errors.d.ts +20 -10
  50. package/dist/src/room/data-track/outgoing/errors.d.ts.map +1 -1
  51. package/dist/src/room/data-track/outgoing/pipeline.d.ts +9 -8
  52. package/dist/src/room/data-track/outgoing/pipeline.d.ts.map +1 -1
  53. package/dist/src/room/data-track/outgoing/types.d.ts +16 -7
  54. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  55. package/dist/src/room/data-track/packet/errors.d.ts +2 -4
  56. package/dist/src/room/data-track/packet/errors.d.ts.map +1 -1
  57. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  58. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  60. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  61. package/dist/src/room/data-track/packet/serializable.d.ts +4 -4
  62. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packetizer.d.ts +6 -6
  64. package/dist/src/room/data-track/packetizer.d.ts.map +1 -1
  65. package/dist/src/room/data-track/track-interfaces.d.ts +23 -0
  66. package/dist/src/room/data-track/track-interfaces.d.ts.map +1 -0
  67. package/dist/src/room/data-track/types.d.ts +15 -0
  68. package/dist/src/room/data-track/types.d.ts.map +1 -0
  69. package/dist/src/room/events.d.ts +24 -3
  70. package/dist/src/room/events.d.ts.map +1 -1
  71. package/dist/src/room/participant/LocalParticipant.d.ts +11 -1
  72. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  73. package/dist/src/room/participant/RemoteParticipant.d.ts +14 -1
  74. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  75. package/dist/src/room/token-source/TokenSource.d.ts +1 -1
  76. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  77. package/dist/src/room/token-source/types.d.ts +1 -0
  78. package/dist/src/room/token-source/types.d.ts.map +1 -1
  79. package/dist/src/room/utils.d.ts +2 -1
  80. package/dist/src/room/utils.d.ts.map +1 -1
  81. package/dist/src/utils/abort-signal-polyfill.d.ts +13 -0
  82. package/dist/src/utils/abort-signal-polyfill.d.ts.map +1 -0
  83. package/dist/src/utils/deferrable-map.d.ts +32 -0
  84. package/dist/src/utils/deferrable-map.d.ts.map +1 -0
  85. package/dist/src/utils/subscribeToEvents.d.ts +3 -0
  86. package/dist/src/utils/subscribeToEvents.d.ts.map +1 -1
  87. package/dist/ts4.2/api/SignalClient.d.ts +12 -4
  88. package/dist/ts4.2/e2ee/types.d.ts +6 -0
  89. package/dist/ts4.2/e2ee/utils.d.ts +2 -1
  90. package/dist/ts4.2/index.d.ts +5 -4
  91. package/dist/ts4.2/room/PCTransport.d.ts +5 -0
  92. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -1
  93. package/dist/ts4.2/room/RTCEngine.d.ts +27 -9
  94. package/dist/ts4.2/room/Room.d.ts +13 -3
  95. package/dist/ts4.2/room/data-track/LocalDataTrack.d.ts +48 -0
  96. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +46 -0
  97. package/dist/ts4.2/room/data-track/depacketizer.d.ts +6 -6
  98. package/dist/ts4.2/room/data-track/frame.d.ts +14 -0
  99. package/dist/ts4.2/room/data-track/handle.d.ts +2 -2
  100. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +110 -0
  101. package/dist/ts4.2/room/data-track/incoming/errors.d.ts +24 -0
  102. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +38 -0
  103. package/dist/ts4.2/room/data-track/incoming/types.d.ts +20 -0
  104. package/dist/ts4.2/room/data-track/outgoing/OutgoingDataTrackManager.d.ts +63 -29
  105. package/dist/ts4.2/room/data-track/outgoing/errors.d.ts +20 -10
  106. package/dist/ts4.2/room/data-track/outgoing/pipeline.d.ts +9 -8
  107. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +16 -7
  108. package/dist/ts4.2/room/data-track/packet/errors.d.ts +2 -4
  109. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  110. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -6
  111. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +4 -4
  112. package/dist/ts4.2/room/data-track/packetizer.d.ts +6 -6
  113. package/dist/ts4.2/room/data-track/track-interfaces.d.ts +23 -0
  114. package/dist/ts4.2/room/data-track/types.d.ts +15 -0
  115. package/dist/ts4.2/room/events.d.ts +24 -3
  116. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +11 -1
  117. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +14 -1
  118. package/dist/ts4.2/room/token-source/TokenSource.d.ts +1 -1
  119. package/dist/ts4.2/room/token-source/types.d.ts +1 -0
  120. package/dist/ts4.2/room/utils.d.ts +2 -1
  121. package/dist/ts4.2/utils/abort-signal-polyfill.d.ts +13 -0
  122. package/dist/ts4.2/utils/deferrable-map.d.ts +32 -0
  123. package/dist/ts4.2/utils/subscribeToEvents.d.ts +3 -0
  124. package/package.json +4 -2
  125. package/src/api/SignalClient.test.ts +9 -4
  126. package/src/api/SignalClient.ts +116 -9
  127. package/src/e2ee/constants.ts +1 -0
  128. package/src/e2ee/types.ts +6 -0
  129. package/src/e2ee/utils.ts +4 -3
  130. package/src/e2ee/worker/DataCryptor.ts +1 -4
  131. package/src/e2ee/worker/FrameCryptor.ts +1 -4
  132. package/src/e2ee/worker/ParticipantKeyHandler.ts +1 -1
  133. package/src/index.ts +6 -4
  134. package/src/room/PCTransport.ts +41 -1
  135. package/src/room/PCTransportManager.ts +1 -1
  136. package/src/room/RTCEngine.ts +274 -112
  137. package/src/room/Room.ts +152 -15
  138. package/src/room/data-stream/outgoing/OutgoingDataStreamManager.ts +9 -9
  139. package/src/room/data-track/LocalDataTrack.ts +126 -0
  140. package/src/room/data-track/RemoteDataTrack.ts +80 -0
  141. package/src/room/data-track/depacketizer.ts +23 -26
  142. package/src/room/data-track/frame.ts +28 -2
  143. package/src/room/data-track/handle.ts +2 -8
  144. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +555 -0
  145. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +589 -0
  146. package/src/room/data-track/incoming/errors.ts +57 -0
  147. package/src/room/data-track/incoming/pipeline.ts +121 -0
  148. package/src/room/data-track/incoming/types.ts +22 -0
  149. package/src/room/data-track/outgoing/OutgoingDataTrackManager.test.ts +240 -27
  150. package/src/room/data-track/outgoing/OutgoingDataTrackManager.ts +165 -84
  151. package/src/room/data-track/outgoing/errors.ts +40 -11
  152. package/src/room/data-track/outgoing/pipeline.ts +25 -23
  153. package/src/room/data-track/outgoing/types.ts +14 -6
  154. package/src/room/data-track/packet/errors.ts +2 -14
  155. package/src/room/data-track/packet/extensions.ts +21 -26
  156. package/src/room/data-track/packet/index.test.ts +22 -33
  157. package/src/room/data-track/packet/index.ts +4 -6
  158. package/src/room/data-track/packet/serializable.ts +4 -4
  159. package/src/room/data-track/packetizer.test.ts +2 -2
  160. package/src/room/data-track/packetizer.ts +7 -10
  161. package/src/room/data-track/track-interfaces.ts +53 -0
  162. package/src/room/data-track/types.ts +31 -0
  163. package/src/room/events.ts +26 -1
  164. package/src/room/participant/LocalParticipant.ts +57 -6
  165. package/src/room/participant/RemoteParticipant.ts +26 -1
  166. package/src/room/token-source/TokenSource.ts +8 -2
  167. package/src/room/token-source/types.ts +4 -0
  168. package/src/room/utils.ts +5 -1
  169. package/src/utils/abort-signal-polyfill.ts +63 -0
  170. package/src/utils/deferrable-map.ts +109 -0
  171. package/src/utils/subscribeToEvents.ts +18 -1
  172. package/dist/src/room/data-track/e2ee.d.ts +0 -12
  173. package/dist/src/room/data-track/e2ee.d.ts.map +0 -1
  174. package/dist/src/room/data-track/track.d.ts +0 -30
  175. package/dist/src/room/data-track/track.d.ts.map +0 -1
  176. package/dist/src/utils/throws.d.ts +0 -36
  177. package/dist/src/utils/throws.d.ts.map +0 -1
  178. package/dist/ts4.2/room/data-track/e2ee.d.ts +0 -12
  179. package/dist/ts4.2/room/data-track/track.d.ts +0 -30
  180. package/dist/ts4.2/utils/throws.d.ts +0 -39
  181. package/src/room/data-track/e2ee.ts +0 -14
  182. package/src/room/data-track/track.ts +0 -50
  183. package/src/utils/throws.ts +0 -42
package/src/room/Room.ts CHANGED
@@ -47,7 +47,7 @@ import TypedPromise from '../utils/TypedPromise';
47
47
  import { getBrowser } from '../utils/browserParser';
48
48
  import { BackOffStrategy } from './BackOffStrategy';
49
49
  import DeviceManager from './DeviceManager';
50
- import RTCEngine from './RTCEngine';
50
+ import RTCEngine, { DataChannelKind } from './RTCEngine';
51
51
  import { RegionUrlProvider } from './RegionUrlProvider';
52
52
  import IncomingDataStreamManager from './data-stream/incoming/IncomingDataStreamManager';
53
53
  import {
@@ -55,6 +55,11 @@ import {
55
55
  type TextStreamHandler,
56
56
  } from './data-stream/incoming/StreamReader';
57
57
  import OutgoingDataStreamManager from './data-stream/outgoing/OutgoingDataStreamManager';
58
+ import type LocalDataTrack from './data-track/LocalDataTrack';
59
+ import type RemoteDataTrack from './data-track/RemoteDataTrack';
60
+ import IncomingDataTrackManager from './data-track/incoming/IncomingDataTrackManager';
61
+ import OutgoingDataTrackManager from './data-track/outgoing/OutgoingDataTrackManager';
62
+ import { DataTrackInfo, type DataTrackSid } from './data-track/types';
58
63
  import {
59
64
  audioDefaults,
60
65
  publishDefaults,
@@ -70,7 +75,7 @@ import {
70
75
  } from './errors';
71
76
  import { EngineEvent, ParticipantEvent, RoomEvent, TrackEvent } from './events';
72
77
  import LocalParticipant from './participant/LocalParticipant';
73
- import type Participant from './participant/Participant';
78
+ import Participant from './participant/Participant';
74
79
  import { type ConnectionQuality, ParticipantKind } from './participant/Participant';
75
80
  import RemoteParticipant from './participant/RemoteParticipant';
76
81
  import { MAX_PAYLOAD_BYTES, RpcError, type RpcInvocationData, byteLength } from './rpc';
@@ -205,6 +210,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
205
210
 
206
211
  private outgoingDataStreamManager: OutgoingDataStreamManager;
207
212
 
213
+ private incomingDataTrackManager: IncomingDataTrackManager;
214
+
215
+ private outgoingDataTrackManager: OutgoingDataTrackManager;
216
+
208
217
  private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
209
218
 
210
219
  get hasE2EESetup(): boolean {
@@ -243,6 +252,46 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
243
252
  this.incomingDataStreamManager = new IncomingDataStreamManager();
244
253
  this.outgoingDataStreamManager = new OutgoingDataStreamManager(this.engine, this.log);
245
254
 
255
+ this.incomingDataTrackManager = new IncomingDataTrackManager({ e2eeManager: this.e2eeManager });
256
+ this.incomingDataTrackManager
257
+ .on('sfuUpdateSubscription', (event) => {
258
+ this.engine.client.sendUpdateDataSubscription(event.sid, event.subscribe);
259
+ })
260
+ .on('trackPublished', (event) => {
261
+ if (event.track.publisherIdentity === this.localParticipant.identity) {
262
+ // Only advertize tracks from other participants
263
+ return;
264
+ }
265
+ this.emit(RoomEvent.DataTrackPublished, event.track);
266
+ this.remoteParticipants.get(event.track.publisherIdentity)?.addRemoteDataTrack(event.track);
267
+ })
268
+ .on('trackUnpublished', (event) => {
269
+ if (event.publisherIdentity === this.localParticipant.identity) {
270
+ // Only advertize tracks from other participants
271
+ return;
272
+ }
273
+ this.emit(RoomEvent.DataTrackUnpublished, event.sid);
274
+ this.remoteParticipants.get(event.publisherIdentity)?.removeRemoteDataTrack(event.sid);
275
+ });
276
+
277
+ this.outgoingDataTrackManager = new OutgoingDataTrackManager({ e2eeManager: this.e2eeManager });
278
+ this.outgoingDataTrackManager
279
+ .on('sfuPublishRequest', (event) => {
280
+ this.engine.client.sendPublishDataTrackRequest(event.handle, event.name, event.usesE2ee);
281
+ })
282
+ .on('sfuUnpublishRequest', (event) => {
283
+ this.engine.client.sendUnPublishDataTrackRequest(event.handle);
284
+ })
285
+ .on('trackPublished', (event) => {
286
+ this.emit(RoomEvent.LocalDataTrackPublished, event.track);
287
+ })
288
+ .on('trackUnpublished', (event) => {
289
+ this.emit(RoomEvent.LocalDataTrackUnpublished, event.sid);
290
+ })
291
+ .on('packetAvailable', ({ bytes }) => {
292
+ this.engine.sendLossyBytes(bytes, DataChannelKind.DATA_TRACK_LOSSY, 'wait');
293
+ });
294
+
246
295
  this.disconnectLock = new Mutex();
247
296
 
248
297
  this.localParticipant = new LocalParticipant(
@@ -252,6 +301,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
252
301
  this.options,
253
302
  this.rpcHandlers,
254
303
  this.outgoingDataStreamManager,
304
+ this.outgoingDataTrackManager,
255
305
  );
256
306
 
257
307
  if (this.options.e2ee || this.options.encryption) {
@@ -259,6 +309,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
259
309
  }
260
310
 
261
311
  this.engine.e2eeManager = this.e2eeManager;
312
+ this.incomingDataTrackManager.updateE2eeManager(this.e2eeManager ?? null);
313
+ this.outgoingDataTrackManager.updateE2eeManager(this.e2eeManager ?? null);
262
314
 
263
315
  if (this.options.videoCaptureDefaults.deviceId) {
264
316
  this.localParticipant.activeDeviceMap.set(
@@ -406,7 +458,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
406
458
  room: this.name,
407
459
  roomID: this.roomInfo?.sid,
408
460
  participant: this.localParticipant.identity,
409
- pID: this.localParticipant.sid,
461
+ participantID: this.localParticipant.sid,
410
462
  };
411
463
  }
412
464
 
@@ -464,7 +516,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
464
516
  }
465
517
 
466
518
  private maybeCreateEngine() {
467
- if (this.engine && !this.engine.isClosed) {
519
+ if (this.engine && (this.engine.isNewlyCreated || !this.engine.isClosed)) {
468
520
  return;
469
521
  }
470
522
 
@@ -515,6 +567,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
515
567
  }
516
568
  })
517
569
  .on(EngineEvent.Restarting, this.handleRestarting)
570
+ .on(EngineEvent.Restarted, this.handleRestarted)
518
571
  .on(EngineEvent.SignalRestarted, this.handleSignalRestarted)
519
572
  .on(EngineEvent.Offline, () => {
520
573
  if (this.setAndEmitConnectionState(ConnectionState.Reconnecting)) {
@@ -560,6 +613,66 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
560
613
  } else {
561
614
  this.handleParticipantUpdates(roomMoved.otherParticipants);
562
615
  }
616
+ })
617
+ .on(EngineEvent.PublishDataTrackResponse, (event) => {
618
+ if (!event.info) {
619
+ this.log.warn(
620
+ `received PublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
621
+ this.logContext,
622
+ );
623
+ return;
624
+ }
625
+
626
+ this.outgoingDataTrackManager.receivedSfuPublishResponse(event.info.pubHandle, {
627
+ type: 'ok',
628
+ data: {
629
+ sid: event.info.sid,
630
+ pubHandle: event.info.pubHandle,
631
+ name: event.info.name,
632
+ usesE2ee: event.info.encryption !== Encryption_Type.NONE,
633
+ },
634
+ });
635
+ })
636
+ .on(EngineEvent.UnPublishDataTrackResponse, (event) => {
637
+ if (!event.info) {
638
+ this.log.warn(
639
+ `received UnPublishDataTrackResponse, but event.info was ${event.info}, so skipping.`,
640
+ this.logContext,
641
+ );
642
+ return;
643
+ }
644
+
645
+ this.outgoingDataTrackManager.receivedSfuUnpublishResponse(event.info.pubHandle);
646
+ })
647
+ .on(EngineEvent.DataTrackSubscriberHandles, (event) => {
648
+ const handleToSidMapping = new Map(
649
+ Object.entries(event.subHandles).map(([key, value]) => {
650
+ return [parseInt(key, 10), value.trackSid];
651
+ }),
652
+ );
653
+
654
+ this.incomingDataTrackManager.receivedSfuSubscriberHandles(handleToSidMapping);
655
+ })
656
+ .on(EngineEvent.DataTrackPacketReceived, (packetBytes) => {
657
+ try {
658
+ this.incomingDataTrackManager.packetReceived(packetBytes);
659
+ } catch (err) {
660
+ // NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't
661
+ // propagate upwards into the public interface.
662
+ throw err;
663
+ }
664
+ })
665
+ .on(EngineEvent.Joined, (joinResponse) => {
666
+ // Ingest data track publication updates into data tracks infrastructure
667
+ const mapped = new Map(
668
+ joinResponse.otherParticipants.map((participant) => {
669
+ return [
670
+ participant.identity,
671
+ participant.dataTracks.map((info) => DataTrackInfo.from(info)),
672
+ ];
673
+ }),
674
+ );
675
+ this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
563
676
  });
564
677
 
565
678
  if (this.localParticipant) {
@@ -1456,10 +1569,13 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1456
1569
  ) as RemoteParticipant | undefined;
1457
1570
 
1458
1571
  if (!participant) {
1459
- this.log.error(
1460
- `Tried to add a track for a participant, that's not present. Sid: ${participantSid}`,
1461
- this.logContext,
1462
- );
1572
+ // server could require extra media sections to accelerate subscription.
1573
+ if (participantSid.startsWith('PA')) {
1574
+ this.log.error(
1575
+ `Tried to add a track for a participant, that's not present. Sid: ${participantSid}`,
1576
+ this.logContext,
1577
+ );
1578
+ }
1463
1579
  return;
1464
1580
  }
1465
1581
 
@@ -1481,7 +1597,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1481
1597
  if (!trackId.startsWith('TR')) {
1482
1598
  this.log.warn(
1483
1599
  `Tried to add a track whose 'sid' could not be determined for a participant, that's not present. Sid: ${participantSid}, streamId: ${streamId}, trackId: ${trackId}`,
1484
- { ...this.logContext, rpID: participantSid, streamId, trackId },
1600
+ { ...this.logContext, remoteParticipantID: participantSid, streamId, trackId },
1485
1601
  );
1486
1602
  }
1487
1603
 
@@ -1527,6 +1643,11 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1527
1643
  }
1528
1644
  };
1529
1645
 
1646
+ private handleRestarted = () => {
1647
+ this.outgoingDataTrackManager.sfuWillRepublishTracks();
1648
+ this.incomingDataTrackManager.resendSubscriptionUpdates();
1649
+ };
1650
+
1530
1651
  private handleSignalRestarted = async (joinResponse: JoinResponse) => {
1531
1652
  this.log.debug(`signal reconnected to server, region ${joinResponse.serverRegion}`, {
1532
1653
  ...this.logContext,
@@ -1640,10 +1761,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1640
1761
 
1641
1762
  private handleParticipantUpdates = (participantInfos: ParticipantInfo[]) => {
1642
1763
  // handle changes to participant state, and send events
1643
- participantInfos.forEach((info) => {
1764
+ for (const info of participantInfos) {
1644
1765
  if (info.identity === this.localParticipant.identity) {
1645
1766
  this.localParticipant.updateInfo(info);
1646
- return;
1767
+ continue;
1647
1768
  }
1648
1769
 
1649
1770
  // LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
@@ -1661,7 +1782,17 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1661
1782
  // create participant if doesn't exist
1662
1783
  remoteParticipant = this.getOrCreateParticipant(info.identity, info);
1663
1784
  }
1664
- });
1785
+ }
1786
+
1787
+ // Ingest data track publication updates into data tracks infrastructure
1788
+ const mapped = new Map(
1789
+ participantInfos
1790
+ .filter((p) => p.identity !== this.localParticipant.identity)
1791
+ .map((info) => {
1792
+ return [info.identity, info.dataTracks.map((dataTrack) => DataTrackInfo.from(dataTrack))];
1793
+ }),
1794
+ );
1795
+ this.incomingDataTrackManager.receiveSfuPublicationUpdates(mapped);
1665
1796
  };
1666
1797
 
1667
1798
  private handleParticipantDisconnected(identity: string, participant?: RemoteParticipant) {
@@ -1672,6 +1803,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
1672
1803
  }
1673
1804
 
1674
1805
  this.incomingDataStreamManager.validateParticipantHasNoActiveDataStreams(identity);
1806
+ this.incomingDataTrackManager.handleRemoteParticipantDisconnected(identity);
1675
1807
 
1676
1808
  participant.trackPublications.forEach((publication) => {
1677
1809
  participant.unpublishTrack(publication.trackSid, true);
@@ -2192,7 +2324,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2192
2324
  track.on(TrackEvent.VideoPlaybackFailed, this.handleVideoPlaybackFailed);
2193
2325
  track.on(TrackEvent.VideoPlaybackStarted, this.handleVideoPlaybackStarted);
2194
2326
  }
2195
- this.emit(RoomEvent.TrackSubscribed, track, publication, participant);
2327
+ this.emitWhenConnected(RoomEvent.TrackSubscribed, track, publication, participant);
2196
2328
  },
2197
2329
  )
2198
2330
  .on(ParticipantEvent.TrackUnpublished, (publication: RemoteTrackPublication) => {
@@ -2270,7 +2402,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
2270
2402
  return acc;
2271
2403
  }, [] as RemoteTrackPublication[]);
2272
2404
  const localTracks = this.localParticipant.getTrackPublications() as LocalTrackPublication[]; // FIXME would be nice to have this return LocalTrackPublications directly instead of the type cast
2273
- this.engine.sendSyncState(remoteTracks, localTracks);
2405
+ const localDataTrackInfos = this.outgoingDataTrackManager.queryPublished();
2406
+ this.engine.sendSyncState(remoteTracks, localTracks, localDataTrackInfos);
2274
2407
  }
2275
2408
 
2276
2409
  /**
@@ -2732,10 +2865,14 @@ export type RoomEventCallbacks = {
2732
2865
  recordingStatusChanged: (recording: boolean) => void;
2733
2866
  participantEncryptionStatusChanged: (encrypted: boolean, participant?: Participant) => void;
2734
2867
  encryptionError: (error: Error, participant?: Participant) => void;
2735
- dcBufferStatusChanged: (isLow: boolean, kind: DataPacket_Kind) => void;
2868
+ dcBufferStatusChanged: (isLow: boolean, kind: DataChannelKind) => void;
2736
2869
  activeDeviceChanged: (kind: MediaDeviceKind, deviceId: string) => void;
2737
2870
  chatMessage: (message: ChatMessage, participant?: RemoteParticipant | LocalParticipant) => void;
2738
2871
  localTrackSubscribed: (publication: LocalTrackPublication, participant: LocalParticipant) => void;
2739
2872
  metricsReceived: (metrics: MetricsBatch, participant?: Participant) => void;
2740
2873
  participantActive: (participant: Participant) => void;
2874
+ dataTrackPublished: (track: RemoteDataTrack) => void;
2875
+ dataTrackUnpublished: (sid: DataTrackSid) => void;
2876
+ localDataTrackPublished: (track: LocalDataTrack) => void;
2877
+ localDataTrackUnpublished: (sid: DataTrackSid) => void;
2741
2878
  };
@@ -1,7 +1,6 @@
1
1
  import { Mutex } from '@livekit/mutex';
2
2
  import {
3
3
  DataPacket,
4
- DataPacket_Kind,
5
4
  DataStream_ByteHeader,
6
5
  DataStream_Chunk,
7
6
  DataStream_Header,
@@ -12,6 +11,7 @@ import {
12
11
  } from '@livekit/protocol';
13
12
  import { type StructuredLogger } from '../../../logger';
14
13
  import type RTCEngine from '../../RTCEngine';
14
+ import { DataChannelKind } from '../../RTCEngine';
15
15
  import { EngineEvent } from '../../events';
16
16
  import type {
17
17
  ByteStreamInfo,
@@ -114,7 +114,7 @@ export default class OutgoingDataStreamManager {
114
114
  mimeType: info.mimeType,
115
115
  topic: info.topic,
116
116
  timestamp: numberToBigInt(info.timestamp),
117
- totalLength: numberToBigInt(options?.totalSize),
117
+ totalLength: numberToBigInt(info.size),
118
118
  attributes: info.attributes,
119
119
  contentHeader: {
120
120
  case: 'textHeader',
@@ -137,7 +137,7 @@ export default class OutgoingDataStreamManager {
137
137
  value: header,
138
138
  },
139
139
  });
140
- await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
140
+ await this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
141
141
 
142
142
  let chunkId = 0;
143
143
  const engine = this.engine;
@@ -158,7 +158,7 @@ export default class OutgoingDataStreamManager {
158
158
  value: chunk,
159
159
  },
160
160
  });
161
- await engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
161
+ await engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE);
162
162
 
163
163
  chunkId += 1;
164
164
  }
@@ -174,7 +174,7 @@ export default class OutgoingDataStreamManager {
174
174
  value: trailer,
175
175
  },
176
176
  });
177
- await engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
177
+ await engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
178
178
  },
179
179
  abort(err) {
180
180
  console.log('Sink error:', err);
@@ -240,7 +240,7 @@ export default class OutgoingDataStreamManager {
240
240
  };
241
241
 
242
242
  const header = new DataStream_Header({
243
- totalLength: numberToBigInt(info.size ?? 0),
243
+ totalLength: numberToBigInt(info.size),
244
244
  mimeType: info.mimeType,
245
245
  streamId,
246
246
  topic: info.topic,
@@ -262,7 +262,7 @@ export default class OutgoingDataStreamManager {
262
262
  },
263
263
  });
264
264
 
265
- await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
265
+ await this.engine.sendDataPacket(packet, DataChannelKind.RELIABLE);
266
266
 
267
267
  let chunkId = 0;
268
268
  const writeMutex = new Mutex();
@@ -288,7 +288,7 @@ export default class OutgoingDataStreamManager {
288
288
  }),
289
289
  },
290
290
  });
291
- await engine.sendDataPacket(chunkPacket, DataPacket_Kind.RELIABLE);
291
+ await engine.sendDataPacket(chunkPacket, DataChannelKind.RELIABLE);
292
292
  chunkId += 1;
293
293
  byteOffset += subChunk.byteLength;
294
294
  }
@@ -307,7 +307,7 @@ export default class OutgoingDataStreamManager {
307
307
  value: trailer,
308
308
  },
309
309
  });
310
- await engine.sendDataPacket(trailerPacket, DataPacket_Kind.RELIABLE);
310
+ await engine.sendDataPacket(trailerPacket, DataChannelKind.RELIABLE);
311
311
  },
312
312
  abort(err) {
313
313
  logLocal.error('Sink error:', err);
@@ -0,0 +1,126 @@
1
+ import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
2
+ import { type DataTrackFrame, DataTrackFrameInternal } from './frame';
3
+ import type { DataTrackHandle } from './handle';
4
+ import type OutgoingDataTrackManager from './outgoing/OutgoingDataTrackManager';
5
+ import { DataTrackPushFrameError } from './outgoing/errors';
6
+ import type { DataTrackOptions } from './outgoing/types';
7
+ import {
8
+ DataTrackSymbol,
9
+ type IDataTrack,
10
+ type ILocalTrack,
11
+ TrackSymbol,
12
+ } from './track-interfaces';
13
+ import type { DataTrackInfo } from './types';
14
+
15
+ export default class LocalDataTrack implements ILocalTrack, IDataTrack {
16
+ readonly trackSymbol = TrackSymbol;
17
+
18
+ readonly isLocal = true;
19
+
20
+ readonly typeSymbol = DataTrackSymbol;
21
+
22
+ protected options: DataTrackOptions;
23
+
24
+ /** Represents the currently active {@link DataTrackHandle} for the publication. */
25
+ protected handle: DataTrackHandle | null = null;
26
+
27
+ protected manager: OutgoingDataTrackManager;
28
+
29
+ protected log: StructuredLogger = log;
30
+
31
+ /** @internal */
32
+ constructor(options: DataTrackOptions, manager: OutgoingDataTrackManager) {
33
+ this.options = options;
34
+ this.manager = manager;
35
+
36
+ this.log = getLogger(LoggerNames.DataTracks);
37
+ }
38
+
39
+ /** @internal */
40
+ static withExplicitHandle(
41
+ options: DataTrackOptions,
42
+ manager: OutgoingDataTrackManager,
43
+ handle: DataTrackHandle,
44
+ ) {
45
+ const track = new LocalDataTrack(options, manager);
46
+ track.handle = handle;
47
+ return track;
48
+ }
49
+
50
+ /** Metrics about the data track publication. */
51
+ get info() {
52
+ const descriptor = this.descriptor;
53
+ if (descriptor?.type === 'active') {
54
+ return descriptor.info;
55
+ } else {
56
+ return undefined;
57
+ }
58
+ }
59
+
60
+ /** The raw descriptor from the manager containing the internal state for this local track. */
61
+ protected get descriptor() {
62
+ return this.handle ? this.manager.getDescriptor(this.handle) : null;
63
+ }
64
+
65
+ /**
66
+ * Publish the track to the SFU. This must be done before calling {@link tryPush} for the first time.
67
+ * @internal
68
+ * */
69
+ async publish(signal?: AbortSignal) {
70
+ try {
71
+ this.handle = await this.manager.publishRequest(this.options, signal);
72
+ } catch (err) {
73
+ // NOTE: Rethrow errors to break Throws<...> type boundary
74
+ throw err;
75
+ }
76
+ }
77
+
78
+ isPublished(): this is { info: DataTrackInfo } {
79
+ // NOTE: a track which is internally in the "resubscribing" state is still considered
80
+ // published from the public API perspective.
81
+ return this.descriptor?.type === 'active' && this.descriptor.publishState !== 'unpublished';
82
+ }
83
+
84
+ /** Try pushing a frame to subscribers of the track.
85
+ *
86
+ * Pushing a frame can fail for several reasons:
87
+ *
88
+ * - The track has been unpublished by the local participant or SFU
89
+ * - The room is no longer connected
90
+ */
91
+ tryPush(frame: DataTrackFrame) {
92
+ if (!this.handle) {
93
+ throw DataTrackPushFrameError.trackUnpublished();
94
+ }
95
+
96
+ const internalFrame = DataTrackFrameInternal.from(frame);
97
+
98
+ try {
99
+ return this.manager.tryProcessAndSend(this.handle, internalFrame);
100
+ } catch (err) {
101
+ // NOTE: wrapping in the bare try/catch like this means that the Throws<...> type doesn't
102
+ // propagate upwards into the public interface.
103
+ throw err;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Unpublish the track from the SFU. Once this is called, any further calls to {@link tryPush}
109
+ * will fail.
110
+ * */
111
+ async unpublish() {
112
+ if (!this.handle) {
113
+ log.warn(
114
+ `Data track "${this.options.name}" is not published, so unpublishing has no effect.`,
115
+ );
116
+ return;
117
+ }
118
+
119
+ try {
120
+ await this.manager.unpublishRequest(this.handle);
121
+ } catch (err) {
122
+ // NOTE: Rethrow errors to break Throws<...> type boundary
123
+ throw err;
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,80 @@
1
+ import type Participant from '../participant/Participant';
2
+ import { type DataTrackFrame } from './frame';
3
+ import type IncomingDataTrackManager from './incoming/IncomingDataTrackManager';
4
+ import {
5
+ DataTrackSymbol,
6
+ type IDataTrack,
7
+ type IRemoteTrack,
8
+ TrackSymbol,
9
+ } from './track-interfaces';
10
+ import { type DataTrackInfo } from './types';
11
+
12
+ type RemoteDataTrackOptions = {
13
+ publisherIdentity: Participant['identity'];
14
+ };
15
+
16
+ export type DataTrackSubscribeOptions = {
17
+ signal?: AbortSignal;
18
+
19
+ /** The number of {@link DataTrackFrame}s to hold in the ReadableStream before disgarding extra
20
+ * frames. Defaults to 16, but this may not be good enough for especially high frequency data. */
21
+ bufferSize?: number;
22
+ };
23
+
24
+ export default class RemoteDataTrack implements IRemoteTrack, IDataTrack {
25
+ readonly trackSymbol = TrackSymbol;
26
+
27
+ readonly isLocal = false;
28
+
29
+ readonly typeSymbol = DataTrackSymbol;
30
+
31
+ info: DataTrackInfo;
32
+
33
+ publisherIdentity: Participant['identity'];
34
+
35
+ protected manager: IncomingDataTrackManager;
36
+
37
+ /** @internal */
38
+ constructor(
39
+ info: DataTrackInfo,
40
+ manager: IncomingDataTrackManager,
41
+ options: RemoteDataTrackOptions,
42
+ ) {
43
+ this.info = info;
44
+ this.manager = manager;
45
+ this.publisherIdentity = options.publisherIdentity;
46
+ }
47
+
48
+ /** Subscribes to the data track to receive frames.
49
+ *
50
+ * # Returns
51
+ *
52
+ * A stream that yields {@link DataTrackFrame}s as they arrive.
53
+ *
54
+ * # Multiple Subscriptions
55
+ *
56
+ * An application may call `subscribe` more than once to process frames in
57
+ * multiple places. For example, one async task might plot values on a graph
58
+ * while another writes them to a file.
59
+ *
60
+ * Internally, only the first call to `subscribe` communicates with the SFU and
61
+ * allocates the resources required to receive frames. Additional subscriptions
62
+ * reuse the same underlying pipeline and do not trigger additional signaling.
63
+ *
64
+ * Note that newly created subscriptions only receive frames published after
65
+ * the initial subscription is established.
66
+ */
67
+ subscribe(options?: DataTrackSubscribeOptions): ReadableStream<DataTrackFrame> {
68
+ try {
69
+ const [stream] = this.manager.openSubscriptionStream(
70
+ this.info.sid,
71
+ options?.signal,
72
+ options?.bufferSize,
73
+ );
74
+ return stream;
75
+ } catch (err) {
76
+ // NOTE: Rethrow errors to break Throws<...> type boundary
77
+ throw err;
78
+ }
79
+ }
80
+ }