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
@@ -1,8 +1,8 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import type { TrackInfo } from '@livekit/protocol';
3
- import { PacketTrailerManager } from './PacketTrailerManager';
3
+ import { FrameMetadataManager } from './FrameMetadataManager';
4
4
 
5
- describe('PacketTrailerManager', () => {
5
+ describe('FrameMetadataManager', () => {
6
6
  const originalRTCRtpSender = window.RTCRtpSender;
7
7
  const originalUserAgent = navigator.userAgent;
8
8
  const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
@@ -63,7 +63,7 @@ describe('PacketTrailerManager', () => {
63
63
  });
64
64
  }
65
65
 
66
- function setupWorkerReceiver(manager: PacketTrailerManager, receiver: RTCRtpReceiver) {
66
+ function setupWorkerReceiver(manager: FrameMetadataManager, receiver: RTCRtpReceiver) {
67
67
  (
68
68
  manager as unknown as {
69
69
  setupWorkerReceiver: (receiver: RTCRtpReceiver, newTrackId: string) => void;
@@ -72,7 +72,7 @@ describe('PacketTrailerManager', () => {
72
72
  }
73
73
 
74
74
  function setupReceiver(
75
- manager: PacketTrailerManager,
75
+ manager: FrameMetadataManager,
76
76
  receiver: RTCRtpReceiver,
77
77
  trackId: string,
78
78
  trackInfo?: TrackInfo,
@@ -103,11 +103,13 @@ describe('PacketTrailerManager', () => {
103
103
  it('uses RTCRtpScriptTransform for packet trailer extraction when supported', () => {
104
104
  useSafariUserAgent();
105
105
  const transform = {};
106
- const RTCRtpScriptTransform = vi.fn(() => transform);
106
+ const RTCRtpScriptTransform = vi.fn(function () {
107
+ return transform;
108
+ });
107
109
  setScriptTransform(RTCRtpScriptTransform);
108
110
 
109
111
  const worker = {} as Worker;
110
- const manager = new PacketTrailerManager({ worker });
112
+ const manager = new FrameMetadataManager({ worker });
111
113
  const receiver = {
112
114
  createEncodedStreams: vi.fn(),
113
115
  } as unknown as RTCRtpReceiver;
@@ -129,7 +131,7 @@ describe('PacketTrailerManager', () => {
129
131
  stubInsertableStreamsSupport();
130
132
 
131
133
  const worker = { postMessage: vi.fn() } as unknown as Worker;
132
- const manager = new PacketTrailerManager({ worker });
134
+ const manager = new FrameMetadataManager({ worker });
133
135
  const { receiver, readable, writable, createEncodedStreams } = makeReceiver();
134
136
 
135
137
  setupReceiver(manager, receiver, 'track-without-trailer');
@@ -153,7 +155,7 @@ describe('PacketTrailerManager', () => {
153
155
  stubInsertableStreamsSupport();
154
156
 
155
157
  const worker = { postMessage: vi.fn() } as unknown as Worker;
156
- const manager = new PacketTrailerManager({ worker });
158
+ const manager = new FrameMetadataManager({ worker });
157
159
  const { receiver } = makeReceiver();
158
160
  const trackInfo = { packetTrailerFeatures: [1] } as unknown as TrackInfo;
159
161
 
@@ -2,13 +2,13 @@ import type { TrackInfo } from '@livekit/protocol';
2
2
  import log from '../logger';
3
3
  import type Room from '../room/Room';
4
4
  import { RoomEvent } from '../room/events';
5
- import { PacketTrailerExtractor } from '../room/track/PacketTrailerExtractor';
5
+ import { FrameMetadataExtractor } from '../room/track/FrameMetadataExtractor';
6
6
  import type RemoteTrack from '../room/track/RemoteTrack';
7
7
  import RemoteVideoTrack from '../room/track/RemoteVideoTrack';
8
8
  import type { PTDecodeMessage, PTUpdateTrackIdMessage, PTWorkerMessage } from './types';
9
- import { isPacketTrailerSupported, shouldUsePacketTrailerScriptTransform } from './utils';
9
+ import { isFrameMetadataSupported, shouldUseFrameMetadataScriptTransform } from './utils';
10
10
 
11
- export interface PacketTrailerOptions {
11
+ export interface FrameMetadataOptions {
12
12
  /**
13
13
  * Dedicated worker for extracting packet trailers off the main thread.
14
14
  *
@@ -34,12 +34,12 @@ export interface PacketTrailerOptions {
34
34
  *
35
35
  * @experimental
36
36
  */
37
- export class PacketTrailerManager {
37
+ export class FrameMetadataManager {
38
38
  private worker?: Worker;
39
39
 
40
40
  private room?: Room;
41
41
 
42
- private extractors = new Map<string, PacketTrailerExtractor>();
42
+ private extractors = new Map<string, FrameMetadataExtractor>();
43
43
 
44
44
  /**
45
45
  * Tracks the trackId associated with each receiver that has had its
@@ -49,7 +49,7 @@ export class PacketTrailerManager {
49
49
  */
50
50
  private workerPipelines = new Map<RTCRtpReceiver, string>();
51
51
 
52
- constructor(options?: PacketTrailerOptions) {
52
+ constructor(options?: FrameMetadataOptions) {
53
53
  this.worker = options?.worker;
54
54
  }
55
55
 
@@ -100,18 +100,18 @@ export class PacketTrailerManager {
100
100
  }
101
101
 
102
102
  if (
103
- !isPacketTrailerSupported(this.worker ? { worker: this.worker } : undefined) &&
103
+ !isFrameMetadataSupported(this.worker ? { worker: this.worker } : undefined) &&
104
104
  !this.room?.hasE2EESetup
105
105
  ) {
106
- log.warn('packet trailer transform not supported; skipping extraction');
106
+ log.warn('frame metadata transform not supported; skipping extraction');
107
107
  return;
108
108
  }
109
109
 
110
- const extractor = new PacketTrailerExtractor();
110
+ const extractor = new FrameMetadataExtractor();
111
111
  const trackId = track.mediaStreamID;
112
112
 
113
113
  this.extractors.set(trackId, extractor);
114
- track.packetTrailerExtractor = extractor;
114
+ track.frameMetadataExtractor = extractor;
115
115
 
116
116
  if (this.room?.hasE2EESetup) {
117
117
  // E2EE worker strips the trailer and injects metadata directly into
@@ -123,7 +123,7 @@ export class PacketTrailerManager {
123
123
  }
124
124
 
125
125
  private setupPassthroughReceiver(receiver: RTCRtpReceiver, trackId: string) {
126
- if (shouldUsePacketTrailerScriptTransform()) {
126
+ if (shouldUseFrameMetadataScriptTransform()) {
127
127
  if ('transform' in receiver) {
128
128
  // @ts-ignore
129
129
  receiver.transform = null;
@@ -133,7 +133,7 @@ export class PacketTrailerManager {
133
133
 
134
134
  if (
135
135
  this.worker &&
136
- isPacketTrailerSupported({ worker: this.worker }) &&
136
+ isFrameMetadataSupported({ worker: this.worker }) &&
137
137
  !this.workerPipelines.has(receiver)
138
138
  ) {
139
139
  this.setupWorkerReceiver(receiver, trackId, false);
@@ -155,7 +155,7 @@ export class PacketTrailerManager {
155
155
  return;
156
156
  }
157
157
 
158
- if (shouldUsePacketTrailerScriptTransform()) {
158
+ if (shouldUseFrameMetadataScriptTransform()) {
159
159
  // @ts-ignore
160
160
  receiver.transform = new RTCRtpScriptTransform(worker, {
161
161
  kind: 'decode',
@@ -215,7 +215,7 @@ export class PacketTrailerManager {
215
215
  }
216
216
 
217
217
  if (track instanceof RemoteVideoTrack) {
218
- track.packetTrailerExtractor = undefined;
218
+ track.frameMetadataExtractor = undefined;
219
219
  }
220
220
 
221
221
  // The receiver pipeline is intentionally left running. If the receiver is
@@ -245,6 +245,12 @@ export class PacketTrailerManager {
245
245
  };
246
246
 
247
247
  private onWorkerError = (ev: ErrorEvent) => {
248
- log.error('packet trailer worker encountered an error:', { error: ev.error });
248
+ log.error('frame metadata worker encountered an error:', { error: ev.error });
249
249
  };
250
250
  }
251
+
252
+ /** @deprecated Use {@link FrameMetadataManager} instead. */
253
+ export const PacketTrailerManager = FrameMetadataManager;
254
+
255
+ /** @deprecated Use {@link FrameMetadataOptions} instead. */
256
+ export type PacketTrailerOptions = FrameMetadataOptions;
@@ -1,10 +1,56 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import {
3
+ PACKET_TRAILER_ENVELOPE_SIZE,
4
+ PACKET_TRAILER_FRAME_ID_TAG,
5
+ PACKET_TRAILER_MAGIC,
6
+ PACKET_TRAILER_TIMESTAMP_TAG,
7
+ PACKET_TRAILER_USER_DATA_TAG,
3
8
  appendPacketTrailer,
4
9
  appendPacketTrailerToEncodedFrame,
5
10
  extractPacketTrailer,
6
11
  processPacketTrailer,
7
- } from './packetTrailer';
12
+ } from './frameMetadata';
13
+
14
+ /**
15
+ * Builds a packet trailer (XORed TLVs + envelope) for the given fields. The
16
+ * SDK only writes timestamp/frameId, so this mirrors the native sender's TLV
17
+ * encoding to exercise user_data extraction.
18
+ */
19
+ function buildTrailer(
20
+ payload: Uint8Array,
21
+ fields: { userTimestamp?: bigint; frameId?: number; userData?: Uint8Array },
22
+ ): Uint8Array {
23
+ const tlvs: number[] = [];
24
+
25
+ if (fields.userTimestamp !== undefined) {
26
+ tlvs.push(PACKET_TRAILER_TIMESTAMP_TAG ^ 0xff, 8 ^ 0xff);
27
+ for (let i = 7; i >= 0; i -= 1) {
28
+ tlvs.push(Number((fields.userTimestamp >> BigInt(i * 8)) & BigInt(0xff)) ^ 0xff);
29
+ }
30
+ }
31
+
32
+ if (fields.frameId !== undefined) {
33
+ tlvs.push(PACKET_TRAILER_FRAME_ID_TAG ^ 0xff, 4 ^ 0xff);
34
+ for (let i = 3; i >= 0; i -= 1) {
35
+ tlvs.push(((fields.frameId >> (i * 8)) & 0xff) ^ 0xff);
36
+ }
37
+ }
38
+
39
+ if (fields.userData !== undefined) {
40
+ tlvs.push(PACKET_TRAILER_USER_DATA_TAG ^ 0xff, fields.userData.length ^ 0xff);
41
+ for (const byte of fields.userData) {
42
+ tlvs.push(byte ^ 0xff);
43
+ }
44
+ }
45
+
46
+ const trailerLength = tlvs.length + PACKET_TRAILER_ENVELOPE_SIZE;
47
+ const result = new Uint8Array(payload.length + trailerLength);
48
+ result.set(payload, 0);
49
+ result.set(tlvs, payload.length);
50
+ result[payload.length + tlvs.length] = trailerLength ^ 0xff;
51
+ result.set(PACKET_TRAILER_MAGIC, payload.length + tlvs.length + 1);
52
+ return result;
53
+ }
8
54
 
9
55
  describe('packetTrailer', () => {
10
56
  afterEach(() => {
@@ -47,6 +93,43 @@ describe('packetTrailer', () => {
47
93
  });
48
94
  });
49
95
 
96
+ it('extracts user_data from a trailer carrying only user_data', () => {
97
+ const payload = Uint8Array.from([1, 2, 3, 4]);
98
+ const userData = Uint8Array.from([0x00, 0x01, 0xfe, 0xff, 0x42]);
99
+ const trailer = buildTrailer(payload, { userData });
100
+ const extracted = extractPacketTrailer(trailer);
101
+
102
+ expect(Array.from(extracted.data)).toEqual(Array.from(payload));
103
+ expect(extracted.metadata?.userTimestamp).toBe(0n);
104
+ expect(extracted.metadata?.frameId).toBe(0);
105
+ expect(extracted.metadata?.userData).toBeInstanceOf(Uint8Array);
106
+ expect(Array.from(extracted.metadata!.userData!)).toEqual(Array.from(userData));
107
+ });
108
+
109
+ it('extracts user_data alongside timestamp and frameId', () => {
110
+ const payload = Uint8Array.from([9, 8, 7, 6, 5]);
111
+ const userData = Uint8Array.from([10, 20, 30, 40]);
112
+ const trailer = buildTrailer(payload, {
113
+ userTimestamp: 1_744_249_600_123_456n,
114
+ frameId: 42,
115
+ userData,
116
+ });
117
+ const extracted = extractPacketTrailer(trailer);
118
+
119
+ expect(Array.from(extracted.data)).toEqual(Array.from(payload));
120
+ expect(extracted.metadata?.userTimestamp).toBe(1_744_249_600_123_456n);
121
+ expect(extracted.metadata?.frameId).toBe(42);
122
+ expect(Array.from(extracted.metadata!.userData!)).toEqual(Array.from(userData));
123
+ });
124
+
125
+ it('leaves userData undefined when no user_data tag is present', () => {
126
+ const payload = Uint8Array.from([1, 2, 3, 4]);
127
+ const trailer = appendPacketTrailer(payload, 1_744_249_600_123_456n, 42);
128
+ const extracted = extractPacketTrailer(trailer);
129
+
130
+ expect(extracted.metadata?.userData).toBeUndefined();
131
+ });
132
+
50
133
  it('returns data unchanged when both timestamp and frameId are 0', () => {
51
134
  const payload = Uint8Array.from([1, 2, 3, 4]);
52
135
  const result = appendPacketTrailer(payload, 0n, 0);
@@ -1,5 +1,5 @@
1
- import type { PacketTrailerMetadata, PacketTrailerPublishOptions } from './types';
2
- import { hasPacketTrailerPublishOptions } from './utils';
1
+ import type { FrameMetadata, FrameMetadataPublishOptions } from './types';
2
+ import { hasFrameMetadataPublishOptions } from './utils';
3
3
 
4
4
  export const PACKET_TRAILER_MAGIC = Uint8Array.from([
5
5
  'L'.charCodeAt(0),
@@ -10,6 +10,7 @@ export const PACKET_TRAILER_MAGIC = Uint8Array.from([
10
10
 
11
11
  export const PACKET_TRAILER_TIMESTAMP_TAG = 0x01;
12
12
  export const PACKET_TRAILER_FRAME_ID_TAG = 0x02;
13
+ export const PACKET_TRAILER_USER_DATA_TAG = 0x03;
13
14
  export const PACKET_TRAILER_ENVELOPE_SIZE = 5;
14
15
 
15
16
  const TIMESTAMP_TLV_SIZE = 10;
@@ -17,7 +18,7 @@ const FRAME_ID_TLV_SIZE = 6;
17
18
 
18
19
  export interface ExtractPacketTrailerResult {
19
20
  data: Uint8Array;
20
- metadata?: PacketTrailerMetadata;
21
+ metadata?: FrameMetadata;
21
22
  }
22
23
 
23
24
  export function appendPacketTrailer(
@@ -64,10 +65,10 @@ export function appendPacketTrailer(
64
65
 
65
66
  export function appendPacketTrailerToEncodedFrame(
66
67
  frame: RTCEncodedVideoFrame,
67
- options: PacketTrailerPublishOptions | undefined,
68
+ options: FrameMetadataPublishOptions | undefined,
68
69
  frameId: number,
69
70
  ): boolean {
70
- if (!hasPacketTrailerPublishOptions(options) || frame.data.byteLength === 0) {
71
+ if (!hasFrameMetadataPublishOptions(options) || frame.data.byteLength === 0) {
71
72
  return false;
72
73
  }
73
74
 
@@ -108,7 +109,7 @@ export function extractPacketTrailer(data: ArrayBuffer | Uint8Array): ExtractPac
108
109
  const strippedData = bytes.subarray(0, trailerStart);
109
110
  let offset = trailerStart;
110
111
  let foundAny = false;
111
- const metadata: PacketTrailerMetadata = {
112
+ const metadata: FrameMetadata = {
112
113
  userTimestamp: BigInt(0),
113
114
  frameId: 0,
114
115
  };
@@ -127,6 +128,13 @@ export function extractPacketTrailer(data: ArrayBuffer | Uint8Array): ExtractPac
127
128
  } else if (tag === PACKET_TRAILER_FRAME_ID_TAG && length === 4) {
128
129
  metadata.frameId = readUint32Xor(bytes, offset, length);
129
130
  foundAny = true;
131
+ } else if (tag === PACKET_TRAILER_USER_DATA_TAG) {
132
+ const userData = new Uint8Array(length);
133
+ for (let index = 0; index < length; index += 1) {
134
+ userData[index] = bytes[offset + index] ^ 0xff;
135
+ }
136
+ metadata.userData = userData;
137
+ foundAny = true;
130
138
  }
131
139
 
132
140
  offset += length;
@@ -223,16 +231,16 @@ export function getFrameSsrc(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame)
223
231
  return 0;
224
232
  }
225
233
 
226
- export interface PacketTrailerFramePayload {
234
+ export interface FrameMetadataPayload {
227
235
  trackId: string;
228
236
  rtpTimestamp: number;
229
237
  ssrc: number;
230
- metadata: PacketTrailerMetadata;
238
+ metadata: FrameMetadata;
231
239
  }
232
240
 
233
241
  export interface ProcessPacketTrailerResult {
234
242
  data?: ArrayBuffer;
235
- payload?: PacketTrailerFramePayload;
243
+ payload?: FrameMetadataPayload;
236
244
  }
237
245
 
238
246
  /**
@@ -1,15 +1,22 @@
1
- import type { PacketTrailerFramePayload } from './packetTrailer';
1
+ import type { FrameMetadataPayload } from './frameMetadata';
2
2
 
3
- export interface PacketTrailerMetadata {
3
+ export interface FrameMetadata {
4
4
  userTimestamp: bigint;
5
5
  frameId: number;
6
+ userData?: Uint8Array;
6
7
  }
7
8
 
8
- export interface PacketTrailerPublishOptions {
9
+ /** @deprecated Use {@link FrameMetadata} instead. */
10
+ export type PacketTrailerMetadata = FrameMetadata;
11
+
12
+ export interface FrameMetadataPublishOptions {
9
13
  timestamp?: boolean;
10
14
  frameId?: boolean;
11
15
  }
12
16
 
17
+ /** @deprecated Use {@link FrameMetadataPublishOptions} instead. */
18
+ export type PacketTrailerPublishOptions = FrameMetadataPublishOptions;
19
+
13
20
  export interface PTBaseMessage {
14
21
  kind: string;
15
22
  data?: unknown;
@@ -38,7 +45,7 @@ export interface PTEncodeMessage extends PTBaseMessage {
38
45
  data: {
39
46
  readableStream: ReadableStream;
40
47
  writableStream: WritableStream;
41
- packetTrailer?: PacketTrailerPublishOptions;
48
+ packetTrailer?: FrameMetadataPublishOptions;
42
49
  };
43
50
  }
44
51
 
@@ -49,12 +56,12 @@ export type PTScriptTransformOptions =
49
56
  }
50
57
  | {
51
58
  kind: 'encode';
52
- packetTrailer?: PacketTrailerPublishOptions;
59
+ packetTrailer?: FrameMetadataPublishOptions;
53
60
  };
54
61
 
55
62
  export interface PTMetadataMessage extends PTBaseMessage {
56
63
  kind: 'metadata';
57
- data: PacketTrailerFramePayload;
64
+ data: FrameMetadataPayload;
58
65
  }
59
66
 
60
67
  export interface PTUpdateTrackIdMessage extends PTBaseMessage {
@@ -1,12 +1,12 @@
1
1
  import { PacketTrailerFeature } from '@livekit/protocol';
2
2
  import { afterEach, describe, expect, it } from 'vitest';
3
3
  import {
4
- getPacketTrailerFeatures,
5
- getPacketTrailerPublishOptions,
6
- isPacketTrailerSupported,
4
+ getFrameMetadataFeatures,
5
+ getFrameMetadataPublishOptions,
6
+ isFrameMetadataSupported,
7
7
  } from './utils';
8
8
 
9
- describe('packet trailer support', () => {
9
+ describe('frame metadata support', () => {
10
10
  const originalRTCRtpSender = window.RTCRtpSender;
11
11
  const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
12
12
  .RTCRtpScriptTransform;
@@ -46,20 +46,20 @@ describe('packet trailer support', () => {
46
46
  });
47
47
  }
48
48
 
49
- it('supports packet trailers with RTCRtpScriptTransform on Safari', () => {
49
+ it('supports frame metadata with RTCRtpScriptTransform on Safari', () => {
50
50
  stubScriptTransformSupport(
51
51
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
52
52
  );
53
53
 
54
- expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(true);
54
+ expect(isFrameMetadataSupported({ worker: {} as Worker })).toBe(true);
55
55
  });
56
56
 
57
- it('supports packet trailers with RTCRtpScriptTransform on Firefox', () => {
57
+ it('supports frame metadata with RTCRtpScriptTransform on Firefox', () => {
58
58
  stubScriptTransformSupport(
59
59
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14.0; rv:144.0) Gecko/20100101 Firefox/144.0',
60
60
  );
61
61
 
62
- expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(true);
62
+ expect(isFrameMetadataSupported({ worker: {} as Worker })).toBe(true);
63
63
  });
64
64
 
65
65
  it('does not use RTCRtpScriptTransform support on Chromium-based browsers', () => {
@@ -67,39 +67,39 @@ describe('packet trailer support', () => {
67
67
  'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
68
68
  );
69
69
 
70
- expect(isPacketTrailerSupported({ worker: {} as Worker })).toBe(false);
70
+ expect(isFrameMetadataSupported({ worker: {} as Worker })).toBe(false);
71
71
  });
72
72
  });
73
73
 
74
- describe('packet trailer publish features', () => {
74
+ describe('frame metadata publish features', () => {
75
75
  it('maps publish options to protocol features', () => {
76
- expect(getPacketTrailerFeatures({ timestamp: true, frameId: true })).toEqual([
76
+ expect(getFrameMetadataFeatures({ timestamp: true, frameId: true })).toEqual([
77
77
  PacketTrailerFeature.PTF_USER_TIMESTAMP,
78
78
  PacketTrailerFeature.PTF_FRAME_ID,
79
79
  ]);
80
- expect(getPacketTrailerFeatures({ timestamp: true })).toEqual([
80
+ expect(getFrameMetadataFeatures({ timestamp: true })).toEqual([
81
81
  PacketTrailerFeature.PTF_USER_TIMESTAMP,
82
82
  ]);
83
- expect(getPacketTrailerFeatures({ frameId: true })).toEqual([
83
+ expect(getFrameMetadataFeatures({ frameId: true })).toEqual([
84
84
  PacketTrailerFeature.PTF_FRAME_ID,
85
85
  ]);
86
- expect(getPacketTrailerFeatures()).toEqual([]);
86
+ expect(getFrameMetadataFeatures()).toEqual([]);
87
87
  });
88
88
 
89
89
  it('maps protocol features to publish options', () => {
90
90
  expect(
91
- getPacketTrailerPublishOptions([
91
+ getFrameMetadataPublishOptions([
92
92
  PacketTrailerFeature.PTF_USER_TIMESTAMP,
93
93
  PacketTrailerFeature.PTF_FRAME_ID,
94
94
  ]),
95
95
  ).toEqual({ timestamp: true, frameId: true });
96
- expect(getPacketTrailerPublishOptions([PacketTrailerFeature.PTF_USER_TIMESTAMP])).toEqual({
96
+ expect(getFrameMetadataPublishOptions([PacketTrailerFeature.PTF_USER_TIMESTAMP])).toEqual({
97
97
  timestamp: true,
98
98
  });
99
- expect(getPacketTrailerPublishOptions([PacketTrailerFeature.PTF_FRAME_ID])).toEqual({
99
+ expect(getFrameMetadataPublishOptions([PacketTrailerFeature.PTF_FRAME_ID])).toEqual({
100
100
  frameId: true,
101
101
  });
102
- expect(getPacketTrailerPublishOptions()).toBeUndefined();
103
- expect(getPacketTrailerPublishOptions([])).toBeUndefined();
102
+ expect(getFrameMetadataPublishOptions()).toBeUndefined();
103
+ expect(getFrameMetadataPublishOptions([])).toBeUndefined();
104
104
  });
105
105
  });
@@ -1,25 +1,25 @@
1
1
  import { PacketTrailerFeature } from '@livekit/protocol';
2
2
  import { isInsertableStreamSupported } from '../e2ee/utils';
3
3
  import { isScriptTransformSupportedForWorker } from '../room/utils';
4
- import type { PacketTrailerOptions } from './PacketTrailerManager';
5
- import type { PacketTrailerPublishOptions } from './types';
4
+ import type { FrameMetadataOptions } from './FrameMetadataManager';
5
+ import type { FrameMetadataPublishOptions } from './types';
6
6
 
7
- export function shouldUsePacketTrailerScriptTransform() {
7
+ export function shouldUseFrameMetadataScriptTransform() {
8
8
  return isScriptTransformSupportedForWorker();
9
9
  }
10
10
 
11
- export function isPacketTrailerSupported(options?: PacketTrailerOptions) {
11
+ export function isFrameMetadataSupported(options?: FrameMetadataOptions) {
12
12
  return (
13
- !!options?.worker && (isInsertableStreamSupported() || shouldUsePacketTrailerScriptTransform())
13
+ !!options?.worker && (isInsertableStreamSupported() || shouldUseFrameMetadataScriptTransform())
14
14
  );
15
15
  }
16
16
 
17
- export function hasPacketTrailerPublishOptions(options?: PacketTrailerPublishOptions): boolean {
17
+ export function hasFrameMetadataPublishOptions(options?: FrameMetadataPublishOptions): boolean {
18
18
  return !!(options?.timestamp || options?.frameId);
19
19
  }
20
20
 
21
- export function getPacketTrailerFeatures(
22
- options?: PacketTrailerPublishOptions,
21
+ export function getFrameMetadataFeatures(
22
+ options?: FrameMetadataPublishOptions,
23
23
  ): PacketTrailerFeature[] {
24
24
  const features: PacketTrailerFeature[] = [];
25
25
  if (options?.timestamp) {
@@ -31,14 +31,14 @@ export function getPacketTrailerFeatures(
31
31
  return features;
32
32
  }
33
33
 
34
- export function getPacketTrailerPublishOptions(
34
+ export function getFrameMetadataPublishOptions(
35
35
  features?: PacketTrailerFeature[],
36
- ): PacketTrailerPublishOptions | undefined {
36
+ ): FrameMetadataPublishOptions | undefined {
37
37
  if (!features || features.length === 0) {
38
38
  return undefined;
39
39
  }
40
40
 
41
- const options: PacketTrailerPublishOptions = {};
41
+ const options: FrameMetadataPublishOptions = {};
42
42
  if (features.includes(PacketTrailerFeature.PTF_USER_TIMESTAMP)) {
43
43
  options.timestamp = true;
44
44
  }
@@ -1,11 +1,11 @@
1
- import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../packetTrailer';
1
+ import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../frameMetadata';
2
2
  import type {
3
+ FrameMetadataPublishOptions,
3
4
  PTMetadataMessage,
4
5
  PTScriptTransformOptions,
5
6
  PTWorkerMessage,
6
- PacketTrailerPublishOptions,
7
7
  } from '../types';
8
- import { hasPacketTrailerPublishOptions } from '../utils';
8
+ import { hasFrameMetadataPublishOptions } from '../utils';
9
9
 
10
10
  /**
11
11
  * Holds the trackId currently associated with a pipeline. A mutable
@@ -97,9 +97,9 @@ function setupDecodeTransform(
97
97
  function setupEncodeTransform(
98
98
  readable: ReadableStream,
99
99
  writable: WritableStream,
100
- packetTrailer?: PacketTrailerPublishOptions,
100
+ packetTrailer?: FrameMetadataPublishOptions,
101
101
  ) {
102
- if (!hasPacketTrailerPublishOptions(packetTrailer)) {
102
+ if (!hasFrameMetadataPublishOptions(packetTrailer)) {
103
103
  readable.pipeTo(writable).catch(() => {});
104
104
  return;
105
105
  }
@@ -8,7 +8,10 @@
8
8
  "ES2018.Promise",
9
9
  "WebWorker",
10
10
  "ES2021.WeakRef",
11
- "DOM.AsyncIterable"
11
+ "DOM.AsyncIterable",
12
+ "ES2020.BigInt",
13
+ "ES2018.AsyncIterable",
14
+ "ES2018.AsyncGenerator"
12
15
  ]
13
16
  }
14
17
  }
package/src/index.ts CHANGED
@@ -64,11 +64,22 @@ import {
64
64
  import { getBrowser } from './utils/browserParser';
65
65
 
66
66
  export { RpcError, type RpcInvocationData, type PerformRpcParams } from './room/rpc';
67
- export type { PacketTrailerMetadata, PacketTrailerPublishOptions } from './packetTrailer/types';
67
+ export type {
68
+ FrameMetadata,
69
+ FrameMetadataPublishOptions,
70
+ /** @deprecated Use {@link FrameMetadata} instead. */
71
+ PacketTrailerMetadata,
72
+ /** @deprecated Use {@link FrameMetadataPublishOptions} instead. */
73
+ PacketTrailerPublishOptions,
74
+ } from './frameMetadata/types';
68
75
  export {
76
+ FrameMetadataManager,
77
+ /** @deprecated Use {@link FrameMetadataManager} instead. */
69
78
  PacketTrailerManager,
79
+ type FrameMetadataOptions,
80
+ /** @deprecated Use {@link FrameMetadataOptions} instead. */
70
81
  type PacketTrailerOptions,
71
- } from './packetTrailer/PacketTrailerManager';
82
+ } from './frameMetadata/FrameMetadataManager';
72
83
 
73
84
  export * from './connectionHelper/ConnectionCheck';
74
85
  export * from './connectionHelper/checks/Checker';
package/src/options.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { E2EEOptions } from './e2ee/types';
2
- import type { PacketTrailerOptions } from './packetTrailer/PacketTrailerManager';
2
+ import type { FrameMetadataOptions } from './frameMetadata/FrameMetadataManager';
3
3
  import type { ReconnectPolicy } from './room/ReconnectPolicy';
4
4
  import type {
5
5
  AudioCaptureOptions,
@@ -103,10 +103,15 @@ export interface InternalRoomOptions {
103
103
 
104
104
  /**
105
105
  * @experimental
106
- * Options for enabling packet trailers on video tracks.
107
- * Packet trailers carry frame-level metadata such as user timestamps and frame IDs.
106
+ * Options for enabling frame metadata on video tracks.
107
+ * Frame metadata carries frame-level information such as user timestamps and frame IDs.
108
108
  */
109
- packetTrailer?: PacketTrailerOptions;
109
+ frameMetadata?: FrameMetadataOptions;
110
+
111
+ /**
112
+ * @deprecated Use {@link InternalRoomOptions.frameMetadata} instead.
113
+ */
114
+ packetTrailer?: FrameMetadataOptions;
110
115
 
111
116
  /**
112
117
  * will attempt to connect via single peer connection mode.