livekit-client 2.19.2 → 2.20.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 (185) 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 +31 -27
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +334 -126
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.fm.worker.js +2 -0
  8. package/dist/{livekit-client.pt.worker.js.map → livekit-client.fm.worker.js.map} +1 -1
  9. package/dist/{livekit-client.pt.worker.mjs → livekit-client.fm.worker.mjs} +12 -4
  10. package/dist/livekit-client.fm.worker.mjs.map +1 -0
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/api/SignalClient.d.ts.map +1 -1
  14. package/dist/src/e2ee/E2eeManager.d.ts +2 -1
  15. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  16. package/dist/src/e2ee/types.d.ts +6 -5
  17. package/dist/src/e2ee/types.d.ts.map +1 -1
  18. package/dist/src/e2ee/utils.d.ts +1 -0
  19. package/dist/src/e2ee/utils.d.ts.map +1 -1
  20. package/dist/src/e2ee/worker/DataCryptor.d.ts +1 -0
  21. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  22. package/dist/src/e2ee/worker/FrameCryptor.d.ts +9 -8
  23. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  24. package/dist/src/e2ee/worker/naluUtils.d.ts +1 -0
  25. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
  26. package/dist/src/e2ee/worker/sifPayload.d.ts +1 -0
  27. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  28. package/dist/src/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
  29. package/dist/src/{packetTrailer/PacketTrailerManager.d.ts.map → frameMetadata/FrameMetadataManager.d.ts.map} +1 -1
  30. package/dist/src/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +8 -7
  31. package/dist/src/frameMetadata/frameMetadata.d.ts.map +1 -0
  32. package/dist/src/{packetTrailer → frameMetadata}/types.d.ts +11 -6
  33. package/dist/src/frameMetadata/types.d.ts.map +1 -0
  34. package/dist/src/frameMetadata/utils.d.ts +9 -0
  35. package/dist/src/{packetTrailer → frameMetadata}/utils.d.ts.map +1 -1
  36. package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
  37. package/dist/src/frameMetadata/worker/frameMetadata.worker.d.ts.map +1 -0
  38. package/dist/src/index.d.ts +10 -2
  39. package/dist/src/index.d.ts.map +1 -1
  40. package/dist/src/options.d.ts +8 -4
  41. package/dist/src/options.d.ts.map +1 -1
  42. package/dist/src/room/PCTransport.d.ts +41 -1
  43. package/dist/src/room/PCTransport.d.ts.map +1 -1
  44. package/dist/src/room/PCTransportManager.d.ts +1 -0
  45. package/dist/src/room/PCTransportManager.d.ts.map +1 -1
  46. package/dist/src/room/RTCEngine.d.ts +4 -1
  47. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  48. package/dist/src/room/Room.d.ts +3 -2
  49. package/dist/src/room/Room.d.ts.map +1 -1
  50. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  51. package/dist/src/room/data-track/frame.d.ts +1 -0
  52. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  53. package/dist/src/room/data-track/outgoing/types.d.ts +1 -0
  54. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  55. package/dist/src/room/data-track/packet/extensions.d.ts +1 -0
  56. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  57. package/dist/src/room/data-track/packet/index.d.ts +1 -0
  58. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/serializable.d.ts +1 -0
  60. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  61. package/dist/src/room/participant/LocalParticipant.d.ts +3 -2
  62. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  63. package/dist/src/room/participant/Participant.d.ts +1 -0
  64. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  65. package/dist/src/room/participant/publishUtils.d.ts.map +1 -1
  66. package/dist/src/room/token-source/TokenSource.d.ts.map +1 -1
  67. package/dist/src/room/token-source/types.d.ts +1 -0
  68. package/dist/src/room/token-source/types.d.ts.map +1 -1
  69. package/dist/src/room/token-source/utils.d.ts.map +1 -1
  70. package/dist/src/room/track/FrameMetadataExtractor.d.ts +19 -0
  71. package/dist/src/room/track/FrameMetadataExtractor.d.ts.map +1 -0
  72. package/dist/src/room/track/RemoteVideoTrack.d.ts +7 -7
  73. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  74. package/dist/src/room/track/Track.d.ts +1 -0
  75. package/dist/src/room/track/Track.d.ts.map +1 -1
  76. package/dist/src/room/track/options.d.ts +17 -5
  77. package/dist/src/room/track/options.d.ts.map +1 -1
  78. package/dist/src/room/track/record.d.ts.map +1 -1
  79. package/dist/src/room/utils.d.ts +1 -0
  80. package/dist/src/room/utils.d.ts.map +1 -1
  81. package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
  82. package/dist/src/type-polyfills/non-shared-typed-arrays.d.ts.map +1 -0
  83. package/dist/src/utils/dataPacketBuffer.d.ts +1 -0
  84. package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
  85. package/dist/ts4.2/e2ee/E2eeManager.d.ts +2 -1
  86. package/dist/ts4.2/e2ee/types.d.ts +6 -5
  87. package/dist/ts4.2/e2ee/utils.d.ts +1 -0
  88. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +1 -0
  89. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +9 -8
  90. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -0
  91. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +1 -0
  92. package/dist/ts4.2/{packetTrailer/PacketTrailerManager.d.ts → frameMetadata/FrameMetadataManager.d.ts} +8 -4
  93. package/dist/ts4.2/{packetTrailer/packetTrailer.d.ts → frameMetadata/frameMetadata.d.ts} +8 -7
  94. package/dist/ts4.2/{packetTrailer → frameMetadata}/types.d.ts +11 -6
  95. package/dist/ts4.2/frameMetadata/utils.d.ts +9 -0
  96. package/dist/ts4.2/frameMetadata/worker/frameMetadata.worker.d.ts +2 -0
  97. package/dist/ts4.2/index.d.ts +9 -3
  98. package/dist/ts4.2/options.d.ts +8 -4
  99. package/dist/ts4.2/room/PCTransport.d.ts +41 -1
  100. package/dist/ts4.2/room/PCTransportManager.d.ts +1 -0
  101. package/dist/ts4.2/room/RTCEngine.d.ts +4 -1
  102. package/dist/ts4.2/room/Room.d.ts +3 -2
  103. package/dist/ts4.2/room/data-track/frame.d.ts +1 -0
  104. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +1 -0
  105. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +1 -0
  106. package/dist/ts4.2/room/data-track/packet/index.d.ts +1 -0
  107. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -0
  108. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +3 -2
  109. package/dist/ts4.2/room/participant/Participant.d.ts +1 -0
  110. package/dist/ts4.2/room/token-source/types.d.ts +1 -0
  111. package/dist/ts4.2/room/track/FrameMetadataExtractor.d.ts +19 -0
  112. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +7 -7
  113. package/dist/ts4.2/room/track/Track.d.ts +1 -0
  114. package/dist/ts4.2/room/track/options.d.ts +17 -5
  115. package/dist/ts4.2/room/utils.d.ts +1 -0
  116. package/dist/ts4.2/type-polyfills/non-shared-typed-arrays.d.ts +2 -0
  117. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -0
  118. package/package.json +24 -19
  119. package/src/api/SignalClient.test.ts +4 -4
  120. package/src/api/SignalClient.ts +8 -3
  121. package/src/api/WebSocketStream.test.ts +7 -7
  122. package/src/e2ee/E2eeManager.ts +16 -12
  123. package/src/e2ee/types.ts +6 -5
  124. package/src/e2ee/utils.ts +1 -0
  125. package/src/e2ee/worker/DataCryptor.ts +1 -0
  126. package/src/e2ee/worker/FrameCryptor.test.ts +6 -5
  127. package/src/e2ee/worker/FrameCryptor.ts +28 -23
  128. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +1 -0
  129. package/src/e2ee/worker/e2ee.worker.ts +6 -5
  130. package/src/e2ee/worker/naluUtils.ts +1 -0
  131. package/src/e2ee/worker/sifPayload.ts +1 -0
  132. package/src/e2ee/worker/tsconfig.json +4 -1
  133. package/src/{packetTrailer/PacketTrailerManager.test.ts → frameMetadata/FrameMetadataManager.test.ts} +10 -8
  134. package/src/{packetTrailer/PacketTrailerManager.ts → frameMetadata/FrameMetadataManager.ts} +21 -15
  135. package/src/{packetTrailer/packetTrailer.test.ts → frameMetadata/frameMetadata.test.ts} +84 -1
  136. package/src/{packetTrailer/packetTrailer.ts → frameMetadata/frameMetadata.ts} +17 -9
  137. package/src/{packetTrailer → frameMetadata}/types.ts +13 -6
  138. package/src/{packetTrailer → frameMetadata}/utils.test.ts +19 -19
  139. package/src/{packetTrailer → frameMetadata}/utils.ts +11 -11
  140. package/src/{packetTrailer/worker/packetTrailer.worker.ts → frameMetadata/worker/frameMetadata.worker.ts} +5 -5
  141. package/src/{packetTrailer → frameMetadata}/worker/tsconfig.json +4 -1
  142. package/src/index.ts +13 -2
  143. package/src/options.ts +9 -4
  144. package/src/room/PCTransport.test.ts +150 -0
  145. package/src/room/PCTransport.ts +117 -0
  146. package/src/room/PCTransportManager.ts +4 -0
  147. package/src/room/RTCEngine.test.ts +143 -9
  148. package/src/room/RTCEngine.ts +112 -31
  149. package/src/room/Room.ts +11 -10
  150. package/src/room/data-track/depacketizer.ts +1 -0
  151. package/src/room/data-track/frame.ts +1 -0
  152. package/src/room/data-track/outgoing/types.ts +1 -0
  153. package/src/room/data-track/packet/extensions.ts +1 -0
  154. package/src/room/data-track/packet/index.ts +1 -0
  155. package/src/room/data-track/packet/serializable.ts +1 -0
  156. package/src/room/participant/LocalParticipant.test.ts +19 -19
  157. package/src/room/participant/LocalParticipant.ts +22 -16
  158. package/src/room/participant/Participant.ts +1 -0
  159. package/src/room/participant/publishUtils.test.ts +6 -0
  160. package/src/room/participant/publishUtils.ts +4 -1
  161. package/src/room/rpc/client/RpcClientManager.ts +1 -1
  162. package/src/room/rpc/server/RpcServerManager.ts +1 -1
  163. package/src/room/token-source/TokenSource.ts +9 -23
  164. package/src/room/token-source/types.ts +1 -0
  165. package/src/room/token-source/utils.ts +1 -0
  166. package/src/room/track/{PacketTrailerExtractor.ts → FrameMetadataExtractor.ts} +8 -8
  167. package/src/room/track/RemoteVideoTrack.ts +7 -11
  168. package/src/room/track/Track.ts +1 -0
  169. package/src/room/track/options.ts +19 -5
  170. package/src/room/track/record.ts +1 -0
  171. package/src/room/utils.ts +1 -0
  172. package/src/type-polyfills/{non-shared-typed-arrays.d.ts → non-shared-typed-arrays.ts} +6 -1
  173. package/src/utils/dataPacketBuffer.ts +2 -0
  174. package/dist/livekit-client.pt.worker.js +0 -2
  175. package/dist/livekit-client.pt.worker.mjs.map +0 -1
  176. package/dist/src/packetTrailer/packetTrailer.d.ts.map +0 -1
  177. package/dist/src/packetTrailer/types.d.ts.map +0 -1
  178. package/dist/src/packetTrailer/utils.d.ts +0 -9
  179. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
  180. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +0 -1
  181. package/dist/src/room/track/PacketTrailerExtractor.d.ts +0 -19
  182. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +0 -1
  183. package/dist/ts4.2/packetTrailer/utils.d.ts +0 -9
  184. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +0 -2
  185. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +0 -19
@@ -54,13 +54,14 @@ import {
54
54
  } from '../api/SignalClient';
55
55
  import type { BaseE2EEManager } from '../e2ee/E2eeManager';
56
56
  import { asEncryptablePacket, isInsertableStreamSupported } from '../e2ee/utils';
57
+ import {
58
+ hasFrameMetadataPublishOptions,
59
+ isFrameMetadataSupported,
60
+ shouldUseFrameMetadataScriptTransform,
61
+ } from '../frameMetadata/utils';
57
62
  import log, { LoggerNames, getLogger } from '../logger';
58
63
  import type { InternalRoomOptions } from '../options';
59
- import {
60
- hasPacketTrailerPublishOptions,
61
- isPacketTrailerSupported,
62
- shouldUsePacketTrailerScriptTransform,
63
- } from '../packetTrailer/utils';
64
+ import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
64
65
  import TypedPromise from '../utils/TypedPromise';
65
66
  import { DataPacketBuffer } from '../utils/dataPacketBuffer';
66
67
  import { TTLMap } from '../utils/ttlmap';
@@ -73,6 +74,7 @@ import {
73
74
  ConnectionError,
74
75
  ConnectionErrorReason,
75
76
  NegotiationError,
77
+ PublishDataError,
76
78
  SignalReconnectError,
77
79
  TrackInvalidError,
78
80
  UnexpectedConnectionState,
@@ -91,6 +93,7 @@ import type { LoggerOptions } from './types';
91
93
  import {
92
94
  Future,
93
95
  isPublisherOfferWithJoinSupported,
96
+ isReactNative,
94
97
  isVideoCodec,
95
98
  isVideoTrack,
96
99
  isWeb,
@@ -108,6 +111,7 @@ const leaveReconnect = 'leave-reconnect';
108
111
  const reliabeReceiveStateTTL = 30_000;
109
112
  const lossyDataChannelBufferThresholdMin = 8 * 1024;
110
113
  const lossyDataChannelBufferThresholdMax = 256 * 1024;
114
+
111
115
  const initialMediaSectionsAudio = 3;
112
116
  const initialMediaSectionsVideo = 3;
113
117
 
@@ -125,6 +129,11 @@ export enum DataChannelKind {
125
129
  DATA_TRACK_LOSSY = 2,
126
130
  }
127
131
 
132
+ // Default data-channel max message size (bytes), used when the remote SDP
133
+ // answer does not advertise an `a=max-message-size` attribute (RFC 8841).
134
+ // `0` means "no limit".
135
+ const DEFAULT_MAX_MESSAGE_SIZE = 64_000;
136
+
128
137
  /** @internal */
129
138
  export default class RTCEngine extends (EventEmitter as new () => TypedEventEmitter<EngineEventCallbacks>) {
130
139
  client: SignalClient;
@@ -449,18 +458,25 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
449
458
  }
450
459
 
451
460
  async cleanupPeerConnections() {
452
- await this.pcManager?.close();
453
- this.pcManager = undefined;
454
-
455
461
  const dcCleanup = (dc: RTCDataChannel | undefined) => {
456
- if (!dc) return;
457
- dc.close();
462
+ if (!dc) {
463
+ return;
464
+ }
465
+
466
+ // Detach the data channel handlers before closing anything. Closing a peer connection tears
467
+ // down the SCTP transport, which can dispatch `error`/`close` events on the still-open data
468
+ // channels; if our handlers are still attached at that point, handleDataError logs a spurious
469
+ // "Unknown DataChannel error" during an otherwise graceful disconnect. Removing the handlers
470
+ // before dc.close()/pcManager.close() makes this deterministic regardless of how/when the
471
+ // browser dispatches those teardown events. See livekit/client-sdk-js#1953.
458
472
  dc.onbufferedamountlow = null;
459
473
  dc.onclose = null;
460
474
  dc.onclosing = null;
461
475
  dc.onerror = null;
462
476
  dc.onmessage = null;
463
477
  dc.onopen = null;
478
+
479
+ dc.close();
464
480
  };
465
481
  dcCleanup(this.lossyDC);
466
482
  dcCleanup(this.lossyDCSub);
@@ -469,6 +485,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
469
485
  dcCleanup(this.dataTrackDC);
470
486
  dcCleanup(this.dataTrackDCSub);
471
487
 
488
+ await this.pcManager?.close();
489
+ this.pcManager = undefined;
490
+
472
491
  this.lossyDC = undefined;
473
492
  this.lossyDCSub = undefined;
474
493
  this.reliableDC = undefined;
@@ -788,7 +807,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
788
807
  // used; RTCRtpScriptTransform does not need the PeerConnection flag.
789
808
  const needsInsertableStreams =
790
809
  this.signalOpts?.e2eeEnabled ||
791
- (this.options.packetTrailer?.worker && !shouldUsePacketTrailerScriptTransform());
810
+ (this.frameMetadataWorker && !shouldUseFrameMetadataScriptTransform());
792
811
 
793
812
  if (needsInsertableStreams && isInsertableStreamSupported()) {
794
813
  this.log.debug('E2EE - setting up transports with insertable streams');
@@ -840,7 +859,15 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
840
859
  */
841
860
  private applyInitialPublisherLayout() {
842
861
  this.createDataChannels();
843
- this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
862
+ /**
863
+ * Native libwebrtc does not support pre-populating the media sections,
864
+ * so we skip it for React Native.
865
+ *
866
+ * Related: https://github.com/livekit/rust-sdks/pull/1151
867
+ */
868
+ if (!isReactNative()) {
869
+ this.addMediaSections(initialMediaSectionsAudio, initialMediaSectionsVideo);
870
+ }
844
871
  }
845
872
 
846
873
  private addMediaSections(numAudios: number, numVideos: number) {
@@ -862,14 +889,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
862
889
  if (this.lossyDC) {
863
890
  this.lossyDC.onmessage = null;
864
891
  this.lossyDC.onerror = null;
892
+ this.lossyDC.onclose = null;
865
893
  }
866
894
  if (this.reliableDC) {
867
895
  this.reliableDC.onmessage = null;
868
896
  this.reliableDC.onerror = null;
897
+ this.reliableDC.onclose = null;
869
898
  }
870
899
  if (this.dataTrackDC) {
871
900
  this.dataTrackDC.onmessage = null;
872
901
  this.dataTrackDC.onerror = null;
902
+ this.dataTrackDC.onclose = null;
873
903
  }
874
904
 
875
905
  // create data channels
@@ -895,6 +925,11 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
895
925
  this.reliableDC.onerror = this.handleDataError;
896
926
  this.dataTrackDC.onerror = this.handleDataError;
897
927
 
928
+ // detect unexpected publisher data channel closes
929
+ this.lossyDC.onclose = this.handleDataChannelClose(DataChannelKind.LOSSY);
930
+ this.reliableDC.onclose = this.handleDataChannelClose(DataChannelKind.RELIABLE);
931
+ this.dataTrackDC.onclose = this.handleDataChannelClose(DataChannelKind.DATA_TRACK_LOSSY);
932
+
898
933
  // set up dc buffer threshold, set to 64kB (otherwise 0 by default)
899
934
  this.lossyDC.bufferedAmountLowThreshold = 65535;
900
935
  this.reliableDC.bufferedAmountLowThreshold = 65535;
@@ -1023,17 +1058,41 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1023
1058
  };
1024
1059
 
1025
1060
  private handleDataError = (event: Event) => {
1061
+ // Errors fired while we're tearing the connection down (e.g. the SCTP transport aborting as
1062
+ // the peer connection closes) carry no actionable information — the channel is going away
1063
+ // regardless. Suppress them so a graceful disconnect doesn't surface spurious errors.
1064
+ // See livekit/client-sdk-js#1953.
1065
+ if (this._isClosed) {
1066
+ return;
1067
+ }
1068
+
1026
1069
  const channel = event.currentTarget as RTCDataChannel;
1027
1070
  const channelKind = channel.maxRetransmits === 0 ? 'lossy' : 'reliable';
1028
1071
 
1029
- if (event instanceof ErrorEvent && event.error) {
1030
- const { error } = event.error;
1031
- this.log.error(`DataChannel error on ${channelKind}: ${event.message}`, { error });
1072
+ if (typeof RTCErrorEvent !== 'undefined' && event instanceof RTCErrorEvent && event.error) {
1073
+ const { error } = event;
1074
+ this.log.error(`DataChannel error on ${channelKind}: ${error.message}`, {
1075
+ error,
1076
+ errorDetail: error.errorDetail,
1077
+ sctpCauseCode: error.sctpCauseCode,
1078
+ });
1032
1079
  } else {
1033
1080
  this.log.error(`Unknown DataChannel error on ${channelKind}`, { event });
1034
1081
  }
1035
1082
  };
1036
1083
 
1084
+ private handleDataChannelClose = (kind: DataChannelKind) => () => {
1085
+ // A publisher DC closing while the session is up and the publisher PC is still
1086
+ // connected is the signature of an oversized message having aborted the channel
1087
+ // (see livekit/rust-sdks#1137). Surface it; do not attempt renegotiation.
1088
+ if (!this._isClosed && this.pcManager?.publisher.getConnectionState() === 'connected') {
1089
+ this.log.error(
1090
+ `publisher data channel '${DataChannelKind[kind]}' closed unexpectedly`,
1091
+ this.logContext,
1092
+ );
1093
+ }
1094
+ };
1095
+
1037
1096
  private handleBufferedAmountLow = (channelKind: DataChannelKind) => {
1038
1097
  this.updateAndEmitDCBufferStatus(channelKind);
1039
1098
  };
@@ -1052,7 +1111,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1052
1111
  } else {
1053
1112
  throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
1054
1113
  }
1055
- this.setupPacketTrailerSender(sender, opts);
1114
+ this.setupFrameMetadataSender(sender, opts);
1056
1115
  return sender;
1057
1116
  }
1058
1117
 
@@ -1072,50 +1131,55 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1072
1131
  throw new UnexpectedConnectionState('Cannot stream on this device');
1073
1132
  }
1074
1133
  if (sender) {
1075
- this.setupPacketTrailerSender(sender, opts);
1134
+ this.setupFrameMetadataSender(sender, opts);
1076
1135
  }
1077
1136
  return sender;
1078
1137
  }
1079
1138
 
1080
- private setupPacketTrailerSender(sender: RTCRtpSender, opts: TrackPublishOptions = {}) {
1081
- if (!this.options.packetTrailer?.worker || this.signalOpts?.e2eeEnabled) {
1139
+ private get frameMetadataWorker(): Worker | undefined {
1140
+ return (this.options.frameMetadata ?? this.options.packetTrailer)?.worker;
1141
+ }
1142
+
1143
+ private setupFrameMetadataSender(sender: RTCRtpSender, opts: TrackPublishOptions = {}) {
1144
+ const worker = this.frameMetadataWorker;
1145
+ if (!worker || this.signalOpts?.e2eeEnabled) {
1082
1146
  return;
1083
1147
  }
1084
1148
 
1085
- const packetTrailer = opts.packetTrailer;
1086
- const hasPacketTrailer = hasPacketTrailerPublishOptions(packetTrailer);
1149
+ const frameMetadata = opts.frameMetadata ?? opts.packetTrailer;
1150
+ const hasMetadata = hasFrameMetadataPublishOptions(frameMetadata);
1087
1151
 
1088
- if (shouldUsePacketTrailerScriptTransform()) {
1089
- if (hasPacketTrailer) {
1152
+ if (shouldUseFrameMetadataScriptTransform()) {
1153
+ if (hasMetadata) {
1090
1154
  // @ts-ignore
1091
- sender.transform = new RTCRtpScriptTransform(this.options.packetTrailer.worker, {
1155
+ sender.transform = new RTCRtpScriptTransform(worker, {
1092
1156
  kind: 'encode',
1093
- packetTrailer,
1157
+ packetTrailer: frameMetadata,
1094
1158
  });
1095
1159
  }
1096
1160
  return;
1097
1161
  }
1098
1162
 
1099
1163
  if (
1100
- !isPacketTrailerSupported(this.options.packetTrailer) ||
1164
+ !isFrameMetadataSupported(this.options.frameMetadata ?? this.options.packetTrailer) ||
1101
1165
  !('createEncodedStreams' in sender)
1102
1166
  ) {
1103
- if (hasPacketTrailer) {
1104
- this.log.warn('packet trailer transform not supported; skipping write', this.logContext);
1167
+ if (hasMetadata) {
1168
+ this.log.warn('frame metadata transform not supported; skipping write', this.logContext);
1105
1169
  }
1106
1170
  return;
1107
1171
  }
1108
1172
 
1109
1173
  // @ts-ignore
1110
1174
  const { readable, writable } = sender.createEncodedStreams();
1111
- if (hasPacketTrailer) {
1112
- this.options.packetTrailer.worker.postMessage(
1175
+ if (hasMetadata) {
1176
+ worker.postMessage(
1113
1177
  {
1114
1178
  kind: 'encode',
1115
1179
  data: {
1116
1180
  readableStream: readable,
1117
1181
  writableStream: writable,
1118
- packetTrailer,
1182
+ packetTrailer: frameMetadata,
1119
1183
  },
1120
1184
  },
1121
1185
  [readable, writable],
@@ -1535,6 +1599,23 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1535
1599
 
1536
1600
  const msg = packet.toBinary() as Uint8Array<ArrayBuffer>;
1537
1601
 
1602
+ // Clamp to the SDK default - libwebrtc advertises larger (~256 KiB)
1603
+ // than LiveKit/pion can deliver end-to-end (~64 KiB), so we trust
1604
+ // the answer up untilthe built in ceiling.
1605
+ const maxPublisherMessageSizeBytes = Math.min(
1606
+ this.pcManager?.getMaxPublisherMessageSize() ?? DEFAULT_MAX_MESSAGE_SIZE,
1607
+ DEFAULT_MAX_MESSAGE_SIZE,
1608
+ );
1609
+ if (
1610
+ typeof maxPublisherMessageSizeBytes !== 'undefined' &&
1611
+ maxPublisherMessageSizeBytes !== 0 /* 0 means "no limit" */ &&
1612
+ msg.byteLength > maxPublisherMessageSizeBytes
1613
+ ) {
1614
+ throw new PublishDataError(
1615
+ `cannot publish data packet larger than ${maxPublisherMessageSizeBytes} bytes (got ${msg.byteLength})`,
1616
+ );
1617
+ }
1618
+
1538
1619
  switch (kind) {
1539
1620
  case DataChannelKind.LOSSY:
1540
1621
  case DataChannelKind.DATA_TRACK_LOSSY:
package/src/room/Room.ts CHANGED
@@ -37,6 +37,8 @@ import type TypedEmitter from 'typed-emitter';
37
37
  import { ensureTrailingSlash } from '../api/utils';
38
38
  import { EncryptionEvent } from '../e2ee';
39
39
  import { type BaseE2EEManager, E2EEManager } from '../e2ee/E2eeManager';
40
+ import { FrameMetadataManager } from '../frameMetadata/FrameMetadataManager';
41
+ import { isFrameMetadataSupported } from '../frameMetadata/utils';
40
42
  import log, { LoggerNames, getLogger } from '../logger';
41
43
  import type {
42
44
  InternalRoomConnectOptions,
@@ -44,8 +46,7 @@ import type {
44
46
  RoomConnectOptions,
45
47
  RoomOptions,
46
48
  } from '../options';
47
- import { PacketTrailerManager } from '../packetTrailer/PacketTrailerManager';
48
- import { isPacketTrailerSupported } from '../packetTrailer/utils';
49
+ import type { NonSharedUint8Array } from '../type-polyfills/non-shared-typed-arrays';
49
50
  import TypedPromise from '../utils/TypedPromise';
50
51
  import { getBrowser } from '../utils/browserParser';
51
52
  import { CLIENT_PROTOCOL_DEFAULT } from '../version';
@@ -198,7 +199,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
198
199
 
199
200
  private e2eeManager: BaseE2EEManager | undefined;
200
201
 
201
- private packetTrailerManager: PacketTrailerManager | undefined;
202
+ private frameMetadataManager: FrameMetadataManager | undefined;
202
203
 
203
204
  private e2eeStateMutex: Mutex = new Mutex();
204
205
 
@@ -343,7 +344,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
343
344
  this.rpcServerManager,
344
345
  );
345
346
 
346
- this.setupPacketTrailer();
347
+ this.setupFrameMetadata();
347
348
 
348
349
  if (this.options.e2ee || this.options.encryption) {
349
350
  this.setupE2EE();
@@ -514,11 +515,10 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
514
515
  }
515
516
  }
516
517
 
517
- private setupPacketTrailer() {
518
- // The manager is always created so tracks that advertise packet trailer
519
- // features can be wired up when the app passes a packet trailer worker.
520
- this.packetTrailerManager = new PacketTrailerManager(this.options.packetTrailer);
521
- this.packetTrailerManager.setup(this);
518
+ private setupFrameMetadata() {
519
+ const opts = this.options.frameMetadata ?? this.options.packetTrailer;
520
+ this.frameMetadataManager = new FrameMetadataManager(opts);
521
+ this.frameMetadataManager.setup(this);
522
522
  }
523
523
 
524
524
  private get logContext() {
@@ -972,7 +972,8 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
972
972
  adaptiveStream:
973
973
  typeof roomOptions.adaptiveStream === 'object' ? true : roomOptions.adaptiveStream,
974
974
  clientInfoCapabilities:
975
- isPacketTrailerSupported(roomOptions.packetTrailer) || !!this.e2eeManager
975
+ isFrameMetadataSupported(roomOptions.frameMetadata ?? roomOptions.packetTrailer) ||
976
+ !!this.e2eeManager
976
977
  ? [ClientInfo_Capability.CAP_PACKET_TRAILER]
977
978
  : undefined,
978
979
  maxRetries: connectOptions.maxRetries,
@@ -1,5 +1,6 @@
1
1
  import { type Throws } from '@livekit/throws-transformer/throws';
2
2
  import { LoggerNames, getLogger } from '../../logger';
3
+ import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
3
4
  import { LivekitReasonedError } from '../errors';
4
5
  import { type DataTrackFrameInternal } from './frame';
5
6
  import { DataTrackPacket, FrameMarker } from './packet';
@@ -1,3 +1,4 @@
1
+ import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
1
2
  import { DataTrackExtensions, DataTrackUserTimestampExtension } from './packet/extensions';
2
3
 
3
4
  /** A pair of payload bytes and packet extensions which can be fed into a {@link DataTrackPacketizer}. */
@@ -1,3 +1,4 @@
1
+ import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
1
2
  import type LocalDataTrack from '../LocalDataTrack';
2
3
  import { type DataTrackHandle } from '../handle';
3
4
  import { type DataTrackInfo, type DataTrackSid } from '../types';
@@ -1,4 +1,5 @@
1
1
  import { type Throws } from '@livekit/throws-transformer/throws';
2
+ import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
2
3
  import { coerceToDataView } from '../utils';
3
4
  import { EXT_TAG_PADDING, U8_LENGTH_BYTES, U64_LENGTH_BYTES } from './constants';
4
5
  import { DataTrackDeserializeError, DataTrackDeserializeErrorReason } from './errors';
@@ -1,4 +1,5 @@
1
1
  import { type Throws } from '@livekit/throws-transformer/throws';
2
+ import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
2
3
  import { DataTrackHandle, DataTrackHandleError, DataTrackHandleErrorReason } from '../handle';
3
4
  import {
4
5
  DataTrackTimestamp,
@@ -1,4 +1,5 @@
1
1
  import { type Throws } from '@livekit/throws-transformer/throws';
2
+ import type { NonSharedUint8Array } from '../../../type-polyfills/non-shared-typed-arrays';
2
3
  import { DataTrackSerializeError } from './errors';
3
4
 
4
5
  /** An abstract class implementing common behavior related to data track binary serialization. */
@@ -5,18 +5,18 @@ import { Track } from '../track/Track';
5
5
  import type { TrackPublishOptions } from '../track/options';
6
6
  import LocalParticipant from './LocalParticipant';
7
7
 
8
- type PacketTrailerTestParticipant = {
9
- canPublishPacketTrailer: () => boolean;
8
+ type FrameMetadataTestParticipant = {
9
+ canPublishFrameMetadata: () => boolean;
10
10
  log: { warn: ReturnType<typeof vi.fn> };
11
- normalizeRequestedPacketTrailerOptions: (
11
+ normalizeRequestedFrameMetadataOptions: (
12
12
  track: LocalTrack,
13
13
  opts: TrackPublishOptions,
14
14
  ) => PacketTrailerFeature[];
15
15
  };
16
16
 
17
- function makeParticipant(canPublishPacketTrailer: boolean) {
18
- const participant = Object.create(LocalParticipant.prototype) as PacketTrailerTestParticipant;
19
- participant.canPublishPacketTrailer = () => canPublishPacketTrailer;
17
+ function makeParticipant(canPublishFrameMetadata: boolean) {
18
+ const participant = Object.create(LocalParticipant.prototype) as FrameMetadataTestParticipant;
19
+ participant.canPublishFrameMetadata = () => canPublishFrameMetadata;
20
20
  participant.log = { warn: vi.fn() };
21
21
  return participant;
22
22
  }
@@ -35,12 +35,12 @@ function makeTrack(kind: Track.Kind) {
35
35
  } as unknown as LocalTrack;
36
36
  }
37
37
 
38
- describe('LocalParticipant packet trailer publish options', () => {
39
- it('normalizes requested video packet trailer options to advertised features', () => {
38
+ describe('LocalParticipant frame metadata publish options', () => {
39
+ it('normalizes requested video frame metadata options to advertised features', () => {
40
40
  const participant = makeParticipant(true);
41
- const opts: TrackPublishOptions = { packetTrailer: { timestamp: true, frameId: true } };
41
+ const opts: TrackPublishOptions = { frameMetadata: { timestamp: true, frameId: true } };
42
42
 
43
- const features = participant.normalizeRequestedPacketTrailerOptions(
43
+ const features = participant.normalizeRequestedFrameMetadataOptions(
44
44
  makeTrack(Track.Kind.Video),
45
45
  opts,
46
46
  );
@@ -49,33 +49,33 @@ describe('LocalParticipant packet trailer publish options', () => {
49
49
  PacketTrailerFeature.PTF_USER_TIMESTAMP,
50
50
  PacketTrailerFeature.PTF_FRAME_ID,
51
51
  ]);
52
- expect(opts.packetTrailer).toEqual({ timestamp: true, frameId: true });
52
+ expect(opts.frameMetadata).toEqual({ timestamp: true, frameId: true });
53
53
  });
54
54
 
55
- it('clears packet trailer options for non-video tracks', () => {
55
+ it('clears frame metadata options for non-video tracks', () => {
56
56
  const participant = makeParticipant(true);
57
- const opts: TrackPublishOptions = { packetTrailer: { timestamp: true } };
57
+ const opts: TrackPublishOptions = { frameMetadata: { timestamp: true } };
58
58
 
59
- const features = participant.normalizeRequestedPacketTrailerOptions(
59
+ const features = participant.normalizeRequestedFrameMetadataOptions(
60
60
  makeTrack(Track.Kind.Audio),
61
61
  opts,
62
62
  );
63
63
 
64
64
  expect(features).toEqual([]);
65
- expect(opts.packetTrailer).toBeUndefined();
65
+ expect(opts.frameMetadata).toBeUndefined();
66
66
  });
67
67
 
68
- it('clears packet trailer options when publishing packet trailers is unsupported', () => {
68
+ it('clears frame metadata options when publishing frame metadata is unsupported', () => {
69
69
  const participant = makeParticipant(false);
70
- const opts: TrackPublishOptions = { packetTrailer: { frameId: true } };
70
+ const opts: TrackPublishOptions = { frameMetadata: { frameId: true } };
71
71
 
72
- const features = participant.normalizeRequestedPacketTrailerOptions(
72
+ const features = participant.normalizeRequestedFrameMetadataOptions(
73
73
  makeTrack(Track.Kind.Video),
74
74
  opts,
75
75
  );
76
76
 
77
77
  expect(features).toEqual([]);
78
- expect(opts.packetTrailer).toBeUndefined();
78
+ expect(opts.frameMetadata).toBeUndefined();
79
79
  expect(participant.log.warn).toHaveBeenCalledOnce();
80
80
  });
81
81
  });
@@ -22,13 +22,14 @@ import {
22
22
  protoInt64,
23
23
  } from '@livekit/protocol';
24
24
  import { SignalConnectionState } from '../../api/SignalClient';
25
- import type { InternalRoomOptions } from '../../options';
26
25
  import {
27
- getPacketTrailerFeatures,
28
- getPacketTrailerPublishOptions,
29
- hasPacketTrailerPublishOptions,
30
- isPacketTrailerSupported,
31
- } from '../../packetTrailer/utils';
26
+ getFrameMetadataFeatures,
27
+ getFrameMetadataPublishOptions,
28
+ hasFrameMetadataPublishOptions,
29
+ isFrameMetadataSupported,
30
+ } from '../../frameMetadata/utils';
31
+ import type { InternalRoomOptions } from '../../options';
32
+ import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
32
33
  import TypedPromise from '../../utils/TypedPromise';
33
34
  import { PCTransportState } from '../PCTransportManager';
34
35
  import type RTCEngine from '../RTCEngine';
@@ -1085,7 +1086,7 @@ export default class LocalParticipant extends Participant {
1085
1086
  audioFeatures.push(AudioTrackFeature.TF_PRECONNECT_BUFFER);
1086
1087
  }
1087
1088
  const packetTrailerFeatures: PacketTrailerFeature[] =
1088
- this.normalizeRequestedPacketTrailerOptions(track, opts);
1089
+ this.normalizeRequestedFrameMetadataOptions(track, opts);
1089
1090
 
1090
1091
  // create track publication from track
1091
1092
  const req = new AddTrackRequest({
@@ -1416,31 +1417,36 @@ export default class LocalParticipant extends Participant {
1416
1417
  return publication;
1417
1418
  }
1418
1419
 
1419
- private canPublishPacketTrailer() {
1420
+ private canPublishFrameMetadata() {
1420
1421
  return !!(
1421
1422
  this.roomOptions.e2ee ||
1422
1423
  this.roomOptions.encryption ||
1423
- isPacketTrailerSupported(this.roomOptions.packetTrailer)
1424
+ isFrameMetadataSupported(this.roomOptions.frameMetadata ?? this.roomOptions.packetTrailer)
1424
1425
  );
1425
1426
  }
1426
1427
 
1427
- private normalizeRequestedPacketTrailerOptions(track: LocalTrack, opts: TrackPublishOptions) {
1428
- if (track.kind !== Track.Kind.Video || !hasPacketTrailerPublishOptions(opts.packetTrailer)) {
1428
+ private normalizeRequestedFrameMetadataOptions(track: LocalTrack, opts: TrackPublishOptions) {
1429
+ const fmOpts = opts.frameMetadata ?? opts.packetTrailer;
1430
+ if (track.kind !== Track.Kind.Video || !hasFrameMetadataPublishOptions(fmOpts)) {
1431
+ opts.frameMetadata = undefined;
1429
1432
  opts.packetTrailer = undefined;
1430
1433
  return [];
1431
1434
  }
1432
1435
 
1433
- if (!this.canPublishPacketTrailer()) {
1434
- this.log.warn('packet trailer transform not supported; not advertising features', {
1436
+ if (!this.canPublishFrameMetadata()) {
1437
+ this.log.warn('frame metadata transform not supported; not advertising features', {
1435
1438
  ...this.logContext,
1436
1439
  ...getLogContextFromTrack(track),
1437
1440
  });
1441
+ opts.frameMetadata = undefined;
1438
1442
  opts.packetTrailer = undefined;
1439
1443
  return [];
1440
1444
  }
1441
1445
 
1442
- const features = getPacketTrailerFeatures(opts.packetTrailer);
1443
- opts.packetTrailer = getPacketTrailerPublishOptions(features);
1446
+ const features = getFrameMetadataFeatures(fmOpts);
1447
+ const normalized = getFrameMetadataPublishOptions(features);
1448
+ opts.frameMetadata = normalized;
1449
+ opts.packetTrailer = normalized;
1444
1450
  return features;
1445
1451
  }
1446
1452
 
@@ -1496,7 +1502,7 @@ export default class LocalParticipant extends Participant {
1496
1502
  if (!simulcastTrack) {
1497
1503
  return;
1498
1504
  }
1499
- const packetTrailerFeatures = this.normalizeRequestedPacketTrailerOptions(track, opts);
1505
+ const packetTrailerFeatures = this.normalizeRequestedFrameMetadataOptions(track, opts);
1500
1506
 
1501
1507
  const req = new AddTrackRequest({
1502
1508
  cid: simulcastTrack.mediaStreamTrack.id,
@@ -12,6 +12,7 @@ import {
12
12
  import { EventEmitter } from 'events';
13
13
  import type TypedEmitter from 'typed-emitter';
14
14
  import log, { LoggerNames, type StructuredLogger, getLogger } from '../../logger';
15
+ import type { NonSharedUint8Array } from '../../type-polyfills/non-shared-typed-arrays';
15
16
  import { ParticipantEvent, TrackEvent } from '../events';
16
17
  import type LocalTrackPublication from '../track/LocalTrackPublication';
17
18
  import type LocalVideoTrack from '../track/LocalVideoTrack';
@@ -174,6 +174,12 @@ describe('customSimulcastLayers', () => {
174
174
  expect(sortedPresets[1].encoding.maxFramerate).toBe(15);
175
175
  expect(sortedPresets[2].encoding.maxFramerate).toBe(20);
176
176
  });
177
+ it('does not mutate the passed-in array', () => {
178
+ const presets = [VideoPresets.h1440, VideoPresets.h360, VideoPresets.h1080, VideoPresets.h90];
179
+ const original = [...presets];
180
+ sortPresets(presets);
181
+ expect(presets).toEqual(original);
182
+ });
177
183
  });
178
184
 
179
185
  describe('screenShareSimulcastDefaults', () => {
@@ -419,7 +419,10 @@ function encodingsFromPresets(
419
419
  /** @internal */
420
420
  export function sortPresets(presets: Array<VideoPreset> | undefined) {
421
421
  if (!presets) return;
422
- return presets.sort((a, b) => {
422
+ // Sort a copy so we don't mutate the caller's array in place. Mutating the
423
+ // passed-in simulcast layers can cause consumers that compare options by value
424
+ // (e.g. components-react's useLiveKitRoom) to detect a spurious change.
425
+ return presets.slice().sort((a, b) => {
423
426
  const { encoding: aEnc } = a;
424
427
  const { encoding: bEnc } = b;
425
428
 
@@ -1,5 +1,5 @@
1
1
  import { DataPacket, DataPacket_Kind, RpcRequest } from '@livekit/protocol';
2
- import EventEmitter from 'events';
2
+ import { EventEmitter } from 'events';
3
3
  import type TypedEmitter from 'typed-emitter';
4
4
  import { type StructuredLogger } from '../../../logger';
5
5
  import { CLIENT_PROTOCOL_DATA_STREAM_RPC } from '../../../version';
@@ -1,5 +1,5 @@
1
1
  import { DataPacket, DataPacket_Kind, RpcAck, RpcRequest, RpcResponse } from '@livekit/protocol';
2
- import EventEmitter from 'events';
2
+ import { EventEmitter } from 'events';
3
3
  import type TypedEmitter from 'typed-emitter';
4
4
  import { type StructuredLogger } from '../../../logger';
5
5
  import { CLIENT_PROTOCOL_DATA_STREAM_RPC } from '../../../version';