livekit-client 2.18.9 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) 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 +5609 -644
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +3553 -2813
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.pt.worker.js +2 -0
  8. package/dist/livekit-client.pt.worker.js.map +1 -0
  9. package/dist/livekit-client.pt.worker.mjs +5834 -0
  10. package/dist/livekit-client.pt.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 +2 -1
  14. package/dist/src/api/SignalClient.d.ts.map +1 -1
  15. package/dist/src/e2ee/E2eeManager.d.ts +8 -7
  16. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  17. package/dist/src/e2ee/types.d.ts +35 -8
  18. package/dist/src/e2ee/types.d.ts.map +1 -1
  19. package/dist/src/e2ee/utils.d.ts +5 -5
  20. package/dist/src/e2ee/utils.d.ts.map +1 -1
  21. package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
  22. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  23. package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
  24. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  25. package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
  26. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
  27. package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
  28. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  29. package/dist/src/index.d.ts +4 -1
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/options.d.ts +7 -0
  32. package/dist/src/options.d.ts.map +1 -1
  33. package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
  34. package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
  35. package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
  36. package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
  37. package/dist/src/packetTrailer/types.d.ts +57 -0
  38. package/dist/src/packetTrailer/types.d.ts.map +1 -0
  39. package/dist/src/packetTrailer/utils.d.ts +9 -0
  40. package/dist/src/packetTrailer/utils.d.ts.map +1 -0
  41. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  42. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
  43. package/dist/src/room/RTCEngine.d.ts +2 -4
  44. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  45. package/dist/src/room/Room.d.ts +7 -3
  46. package/dist/src/room/Room.d.ts.map +1 -1
  47. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
  48. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  49. package/dist/src/room/data-track/depacketizer.d.ts +12 -4
  50. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  51. package/dist/src/room/data-track/frame.d.ts +3 -3
  52. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  53. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  54. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  55. package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
  56. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  57. package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
  58. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  60. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  61. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  62. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
  64. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  65. package/dist/src/room/data-track/types.d.ts +7 -0
  66. package/dist/src/room/data-track/types.d.ts.map +1 -1
  67. package/dist/src/room/events.d.ts +2 -2
  68. package/dist/src/room/participant/LocalParticipant.d.ts +8 -14
  69. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  70. package/dist/src/room/participant/Participant.d.ts +1 -1
  71. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  72. package/dist/src/room/participant/RemoteParticipant.d.ts +5 -1
  73. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  74. package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
  75. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
  76. package/dist/src/room/rpc/client/events.d.ts +8 -0
  77. package/dist/src/room/rpc/client/events.d.ts.map +1 -0
  78. package/dist/src/room/rpc/index.d.ts +6 -0
  79. package/dist/src/room/rpc/index.d.ts.map +1 -0
  80. package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
  81. package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
  82. package/dist/src/room/rpc/server/events.d.ts +8 -0
  83. package/dist/src/room/rpc/server/events.d.ts.map +1 -0
  84. package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  85. package/dist/src/room/rpc/utils.d.ts.map +1 -0
  86. package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
  87. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
  88. package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
  89. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  90. package/dist/src/room/track/Track.d.ts +1 -1
  91. package/dist/src/room/track/Track.d.ts.map +1 -1
  92. package/dist/src/room/track/create.d.ts.map +1 -1
  93. package/dist/src/room/track/options.d.ts +10 -0
  94. package/dist/src/room/track/options.d.ts.map +1 -1
  95. package/dist/src/room/track/utils.d.ts.map +1 -1
  96. package/dist/src/room/utils.d.ts +4 -3
  97. package/dist/src/room/utils.d.ts.map +1 -1
  98. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  99. package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
  100. package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
  101. package/dist/src/version.d.ts +9 -1
  102. package/dist/src/version.d.ts.map +1 -1
  103. package/dist/ts4.2/api/SignalClient.d.ts +2 -1
  104. package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
  105. package/dist/ts4.2/e2ee/types.d.ts +35 -8
  106. package/dist/ts4.2/e2ee/utils.d.ts +5 -5
  107. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
  108. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
  109. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
  110. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
  111. package/dist/ts4.2/index.d.ts +5 -1
  112. package/dist/ts4.2/options.d.ts +7 -0
  113. package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
  114. package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
  115. package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
  116. package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
  117. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  118. package/dist/ts4.2/room/RTCEngine.d.ts +2 -4
  119. package/dist/ts4.2/room/Room.d.ts +7 -3
  120. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
  121. package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
  122. package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
  123. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  124. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
  125. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
  126. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  127. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
  128. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
  129. package/dist/ts4.2/room/data-track/types.d.ts +7 -0
  130. package/dist/ts4.2/room/events.d.ts +2 -2
  131. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -14
  132. package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
  133. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
  134. package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
  135. package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
  136. package/dist/ts4.2/room/rpc/index.d.ts +7 -0
  137. package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
  138. package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
  139. package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  140. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
  141. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
  142. package/dist/ts4.2/room/track/Track.d.ts +1 -1
  143. package/dist/ts4.2/room/track/options.d.ts +10 -0
  144. package/dist/ts4.2/room/utils.d.ts +4 -3
  145. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
  146. package/dist/ts4.2/version.d.ts +9 -1
  147. package/package.json +24 -16
  148. package/src/api/SignalClient.test.ts +102 -10
  149. package/src/api/SignalClient.ts +4 -2
  150. package/src/api/WebSocketStream.test.ts +0 -1
  151. package/src/e2ee/E2eeManager.ts +82 -30
  152. package/src/e2ee/types.ts +37 -8
  153. package/src/e2ee/utils.ts +7 -6
  154. package/src/e2ee/worker/DataCryptor.ts +6 -6
  155. package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
  156. package/src/e2ee/worker/FrameCryptor.ts +94 -14
  157. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
  158. package/src/e2ee/worker/e2ee.worker.ts +13 -5
  159. package/src/e2ee/worker/naluUtils.ts +4 -4
  160. package/src/e2ee/worker/sifPayload.ts +10 -8
  161. package/src/index.ts +7 -0
  162. package/src/options.ts +8 -0
  163. package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
  164. package/src/packetTrailer/PacketTrailerManager.ts +250 -0
  165. package/src/packetTrailer/packetTrailer.test.ts +174 -0
  166. package/src/packetTrailer/packetTrailer.ts +276 -0
  167. package/src/packetTrailer/types.ts +75 -0
  168. package/src/packetTrailer/utils.test.ts +105 -0
  169. package/src/packetTrailer/utils.ts +50 -0
  170. package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
  171. package/src/packetTrailer/worker/tsconfig.json +14 -0
  172. package/src/room/BackOffStrategy.test.ts +1 -1
  173. package/src/room/RTCEngine.test.ts +219 -0
  174. package/src/room/RTCEngine.ts +86 -46
  175. package/src/room/Room.test.ts +62 -1
  176. package/src/room/Room.ts +111 -86
  177. package/src/room/data-track/RemoteDataTrack.ts +8 -1
  178. package/src/room/data-track/depacketizer.test.ts +433 -1
  179. package/src/room/data-track/depacketizer.ts +79 -61
  180. package/src/room/data-track/frame.ts +2 -2
  181. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
  182. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
  183. package/src/room/data-track/incoming/pipeline.ts +13 -2
  184. package/src/room/data-track/outgoing/types.ts +3 -2
  185. package/src/room/data-track/packet/extensions.ts +2 -2
  186. package/src/room/data-track/packet/index.ts +6 -6
  187. package/src/room/data-track/packet/serializable.ts +1 -1
  188. package/src/room/data-track/types.ts +8 -0
  189. package/src/room/events.ts +2 -2
  190. package/src/room/participant/LocalParticipant.test.ts +81 -0
  191. package/src/room/participant/LocalParticipant.ts +64 -187
  192. package/src/room/participant/Participant.ts +1 -1
  193. package/src/room/participant/RemoteParticipant.ts +9 -0
  194. package/src/room/participant/publishUtils.ts +1 -1
  195. package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
  196. package/src/room/rpc/client/RpcClientManager.ts +269 -0
  197. package/src/room/rpc/client/events.ts +9 -0
  198. package/src/room/rpc/index.ts +14 -0
  199. package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
  200. package/src/room/rpc/server/RpcServerManager.ts +293 -0
  201. package/src/room/rpc/server/events.ts +9 -0
  202. package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
  203. package/src/room/track/PacketTrailerExtractor.ts +43 -0
  204. package/src/room/track/RemoteVideoTrack.ts +23 -2
  205. package/src/room/track/Track.ts +1 -1
  206. package/src/room/track/create.ts +0 -4
  207. package/src/room/track/options.ts +11 -0
  208. package/src/room/track/record.ts +1 -1
  209. package/src/room/track/utils.ts +4 -1
  210. package/src/room/utils.test.ts +14 -1
  211. package/src/room/utils.ts +19 -4
  212. package/src/test/MockMediaStreamTrack.ts +0 -1
  213. package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
  214. package/src/utils/dataPacketBuffer.ts +1 -1
  215. package/src/version.ts +11 -1
  216. package/dist/src/room/rpc.d.ts.map +0 -1
  217. package/src/room/rpc.test.ts +0 -301
@@ -148,7 +148,7 @@ export type TrackEventCallbacks = {
148
148
  timestamp: number;
149
149
  rtpTimestamp: number;
150
150
  }) => void;
151
- preConnectBufferFlushed: (buffer: Uint8Array[]) => void;
151
+ preConnectBufferFlushed: (buffer: NonSharedUint8Array[]) => void;
152
152
  cpuConstrained: () => void;
153
153
  };
154
154
  export {};
@@ -1,3 +1,4 @@
1
+ import type { PacketTrailerPublishOptions } from '../../packetTrailer/types';
1
2
  import type { Track } from './Track';
2
3
  import type { AudioProcessorOptions, TrackProcessor, VideoProcessorOptions } from './processor/types';
3
4
  export interface TrackPublishDefaults {
@@ -111,6 +112,15 @@ export interface TrackPublishDefaults {
111
112
  * Defaults to false.
112
113
  */
113
114
  preConnectBuffer?: boolean;
115
+ /**
116
+ * Packet trailer metadata to append to published video frames.
117
+ *
118
+ * Requires either room-level packet trailer worker configuration or E2EE,
119
+ * because encoded frame transforms are used to write the trailer.
120
+ *
121
+ * @experimental
122
+ */
123
+ packetTrailer?: PacketTrailerPublishOptions;
114
124
  }
115
125
  /**
116
126
  * Options when publishing tracks
@@ -1,4 +1,4 @@
1
- import { ChatMessage as ChatMessageModel, ClientInfo, DisconnectReason, Transcription as TranscriptionModel } from '@livekit/protocol';
1
+ import { ChatMessage as ChatMessageModel, ClientInfo, ClientInfo_Capability, DisconnectReason, Transcription as TranscriptionModel } from '@livekit/protocol';
2
2
  import type { Throws } from '@livekit/throws-transformer/throws';
3
3
  import TypedPromise from '../utils/TypedPromise';
4
4
  import type { BrowserDetails } from '../utils/browserParser';
@@ -40,6 +40,7 @@ export declare function supportsAudioOutputSelection(): boolean;
40
40
  export declare function isBrowserSupported(): boolean;
41
41
  export declare function isFireFox(): boolean;
42
42
  export declare function isChromiumBased(): boolean;
43
+ export declare function isScriptTransformSupportedForWorker(): boolean;
43
44
  export declare function isSafari(): boolean;
44
45
  export declare function isSafariBased(): boolean;
45
46
  export declare function isSafari17Based(): boolean;
@@ -67,7 +68,7 @@ export interface ObservableMediaElement extends HTMLMediaElement {
67
68
  handleResize: (entry: ResizeObserverEntry) => void;
68
69
  handleVisibilityChanged: (entry: IntersectionObserverEntry) => void;
69
70
  }
70
- export declare function getClientInfo(): ClientInfo;
71
+ export declare function getClientInfo(capabilities?: ClientInfo_Capability[]): ClientInfo;
71
72
  export declare function getEmptyVideoStreamTrack(): MediaStreamTrack;
72
73
  export declare function createDummyVideoStreamTrack(width?: number, height?: number, enabled?: boolean, paintContent?: boolean): MediaStreamTrack;
73
74
  export declare function getEmptyAudioStreamTrack(): MediaStreamTrack;
@@ -143,7 +144,7 @@ export declare function isLocalPub(pub: TrackPublication | undefined): pub is Lo
143
144
  export declare function isRemoteVideoTrack(track: Track | undefined): track is RemoteVideoTrack;
144
145
  export declare function isLocalParticipant(p: Participant): p is LocalParticipant;
145
146
  export declare function isRemoteParticipant(p: Participant): p is RemoteParticipant;
146
- export declare function splitUtf8(s: string, n: number): Uint8Array[];
147
+ export declare function splitUtf8(s: string, n: number): NonSharedUint8Array[];
147
148
  export declare function extractMaxAgeFromRequestHeaders(headers: Headers): number | undefined;
148
149
  export declare function isCompressionStreamSupported(): boolean;
149
150
  //# sourceMappingURL=utils.d.ts.map
@@ -1,5 +1,5 @@
1
1
  export interface DataPacketItem {
2
- data: Uint8Array;
2
+ data: NonSharedUint8Array;
3
3
  sequence: number;
4
4
  }
5
5
  export declare class DataPacketBuffer {
@@ -1,3 +1,11 @@
1
1
  export declare const version: string;
2
- export declare const protocolVersion = 16;
2
+ export declare const protocolVersion = 17;
3
+ /** Initial client protocol. */
4
+ export declare const CLIENT_PROTOCOL_DEFAULT = 0;
5
+ /** Replaces RPC v1 protocol with a v2 data streams based one to support unlimited request /
6
+ * response payload length. */
7
+ export declare const CLIENT_PROTOCOL_DATA_STREAM_RPC = 1;
8
+ /** The client protocol version indicates what level of support that the client has for
9
+ * client <-> client api interactions. */
10
+ export declare const clientProtocol = 1;
3
11
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livekit-client",
3
- "version": "2.18.9",
3
+ "version": "2.19.0",
4
4
  "description": "JavaScript/TypeScript client SDK for LiveKit",
5
5
  "main": "./dist/livekit-client.umd.js",
6
6
  "unpkg": "./dist/livekit-client.umd.js",
@@ -15,6 +15,11 @@
15
15
  "types": "./dist/src/e2ee/worker/e2ee.worker.d.ts",
16
16
  "import": "./dist/livekit-client.e2ee.worker.mjs",
17
17
  "require": "./dist/livekit-client.e2ee.worker.js"
18
+ },
19
+ "./packet-trailer-worker": {
20
+ "types": "./dist/src/packetTrailer/worker/packetTrailer.worker.d.ts",
21
+ "import": "./dist/livekit-client.pt.worker.mjs",
22
+ "require": "./dist/livekit-client.pt.worker.js"
18
23
  }
19
24
  },
20
25
  "files": [
@@ -29,6 +34,9 @@
29
34
  ],
30
35
  "./dist/src/e2ee/worker/e2ee.worker.d.ts": [
31
36
  "./dist/ts4.2/e2ee/worker/e2ee.worker.d.ts"
37
+ ],
38
+ "./dist/src/packetTrailer/worker/packetTrailer.worker.d.ts": [
39
+ "./dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts"
32
40
  ]
33
41
  }
34
42
  },
@@ -37,7 +45,7 @@
37
45
  "license": "Apache-2.0",
38
46
  "dependencies": {
39
47
  "@livekit/mutex": "1.1.1",
40
- "@livekit/protocol": "1.45.3",
48
+ "@livekit/protocol": "1.45.8",
41
49
  "events": "^3.3.0",
42
50
  "jose": "^6.1.0",
43
51
  "loglevel": "^1.9.2",
@@ -51,28 +59,28 @@
51
59
  },
52
60
  "devDependencies": {
53
61
  "@babel/core": "7.29.0",
54
- "@babel/preset-env": "7.29.0",
62
+ "@babel/preset-env": "7.29.2",
55
63
  "@bufbuild/protoc-gen-es": "^1.10.0",
56
- "@changesets/cli": "2.29.8",
57
- "@eslint/js": "9.39.2",
64
+ "@changesets/cli": "2.31.0",
65
+ "@eslint/js": "10.0.1",
58
66
  "@livekit/changesets-changelog-github": "^0.0.4",
59
67
  "@livekit/throws-transformer": "^0.1.3",
60
68
  "@rollup/plugin-babel": "7.0.0",
61
- "@rollup/plugin-commonjs": "28.0.9",
69
+ "@rollup/plugin-commonjs": "29.0.2",
62
70
  "@rollup/plugin-json": "6.1.0",
63
71
  "@rollup/plugin-node-resolve": "16.0.3",
64
72
  "@rollup/plugin-terser": "^0.4.4",
65
73
  "@size-limit/file": "^11.2.0",
66
74
  "@size-limit/webpack": "^11.2.0",
67
- "@stylistic/eslint-plugin": "^3.1.0",
75
+ "@stylistic/eslint-plugin": "^5.10.0",
68
76
  "@trivago/prettier-plugin-sort-imports": "^5.0.0",
69
77
  "@types/events": "^3.0.3",
70
78
  "@types/sdp-transform": "2.15.0",
71
79
  "@types/ua-parser-js": "0.7.39",
72
- "@typescript-eslint/eslint-plugin": "7.18.0",
73
- "@typescript-eslint/parser": "7.18.0",
80
+ "@typescript-eslint/eslint-plugin": "8.59.2",
81
+ "@typescript-eslint/parser": "8.59.2",
74
82
  "downlevel-dts": "^0.11.0",
75
- "eslint": "9.39.2",
83
+ "eslint": "10.2.1",
76
84
  "eslint-config-airbnb-extended": "^2.3.2",
77
85
  "eslint-config-prettier": "10.1.8",
78
86
  "eslint-plugin-compat": "^6.0.2",
@@ -83,20 +91,20 @@
83
91
  "happy-dom": "^20.0.0",
84
92
  "jsdom": "^26.1.0",
85
93
  "prettier": "^3.4.2",
86
- "rollup": "4.59.0",
94
+ "rollup": "4.60.2",
87
95
  "rollup-plugin-delete": "^2.1.0",
88
- "rollup-plugin-typescript2": "0.36.0",
96
+ "rollup-plugin-typescript2": "0.37.0",
89
97
  "size-limit": "^11.2.0",
90
98
  "tsx": "^4.21.0",
91
- "typedoc": "0.28.16",
99
+ "typedoc": "0.28.19",
92
100
  "typedoc-plugin-no-inherit": "1.6.1",
93
- "typescript": "5.8.3",
101
+ "typescript": "5.9.3",
94
102
  "typescript-eslint": "^8.47.0",
95
- "vite": "7.3.1",
103
+ "vite": "7.3.2",
96
104
  "vitest": "^3.0.0"
97
105
  },
98
106
  "scripts": {
99
- "build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && pnpm downlevel-dts",
107
+ "build": "rollup --config --bundleConfigAsCjs && rollup --config rollup.config.worker.js --bundleConfigAsCjs && rollup --config rollup.config.pt-worker.js --bundleConfigAsCjs && pnpm downlevel-dts",
100
108
  "build:clean": "rm -rf ./dist && pnpm build",
101
109
  "build:watch": "rollup --watch --config --bundleConfigAsCjs",
102
110
  "build:worker:watch": "rollup --watch --config rollup.config.worker.js --bundleConfigAsCjs",
@@ -1,10 +1,14 @@
1
1
  import {
2
+ ClientInfo_Capability,
2
3
  DisconnectReason,
4
+ JoinRequest,
3
5
  JoinResponse,
4
6
  LeaveRequest,
5
7
  ReconnectResponse,
6
8
  SignalRequest,
7
9
  SignalResponse,
10
+ WrappedJoinRequest,
11
+ WrappedJoinRequest_Compression,
8
12
  } from '@livekit/protocol';
9
13
  import { beforeEach, describe, expect, it, vi } from 'vitest';
10
14
  import { ConnectionError, ConnectionErrorReason } from '../room/errors';
@@ -60,6 +64,7 @@ interface MockWebSocketStreamOptions {
60
64
  connection?: WebSocketConnection;
61
65
  opened?: Promise<WebSocketConnection>;
62
66
  closed?: Promise<WebSocketCloseInfo>;
67
+ onUrl?: (url: string) => void;
63
68
  readyState?: number;
64
69
  }
65
70
 
@@ -69,18 +74,19 @@ function mockWebSocketStream(options: MockWebSocketStreamOptions = {}) {
69
74
  opened = connection ? Promise.resolve(connection) : new Promise(() => {}),
70
75
  closed = new Promise(() => {}),
71
76
  readyState = 1,
77
+ onUrl,
72
78
  } = options;
73
79
 
74
- return vi.mocked(WebSocketStream).mockImplementationOnce(
75
- () =>
76
- ({
77
- url: 'wss://test.livekit.io',
78
- opened,
79
- closed,
80
- close: vi.fn(),
81
- readyState,
82
- }) as any,
83
- );
80
+ return vi.mocked(WebSocketStream).mockImplementationOnce((url) => {
81
+ onUrl?.(url);
82
+ return {
83
+ url: 'wss://test.livekit.io',
84
+ opened,
85
+ closed,
86
+ close: vi.fn(),
87
+ readyState,
88
+ } as any;
89
+ });
84
90
  }
85
91
 
86
92
  describe('SignalClient.connect', () => {
@@ -99,6 +105,47 @@ describe('SignalClient.connect', () => {
99
105
  signalClient = new SignalClient(false);
100
106
  });
101
107
 
108
+ async function decodeJoinRequestFromUrl(url: string): Promise<JoinRequest> {
109
+ const joinRequestParam = new URL(url).searchParams.get('join_request');
110
+ expect(joinRequestParam).toBeTruthy();
111
+
112
+ const paddedBase64Url = joinRequestParam!.padEnd(
113
+ joinRequestParam!.length + ((4 - (joinRequestParam!.length % 4)) % 4),
114
+ '=',
115
+ );
116
+ const binaryString = atob(paddedBase64Url.replace(/-/g, '+').replace(/_/g, '/'));
117
+ const wrappedBytes = new Uint8Array(binaryString.length);
118
+ for (let i = 0; i < binaryString.length; i += 1) {
119
+ wrappedBytes[i] = binaryString.charCodeAt(i);
120
+ }
121
+
122
+ const wrappedJoinRequest = WrappedJoinRequest.fromBinary(wrappedBytes);
123
+ if (wrappedJoinRequest.compression === WrappedJoinRequest_Compression.NONE) {
124
+ return JoinRequest.fromBinary(wrappedJoinRequest.joinRequest);
125
+ }
126
+
127
+ const stream = new DecompressionStream('gzip');
128
+ const writer = stream.writable.getWriter();
129
+ writer.write(wrappedJoinRequest.joinRequest);
130
+ writer.close();
131
+
132
+ const chunks: Uint8Array[] = [];
133
+ const reader = stream.readable.getReader();
134
+ while (true) {
135
+ const { done, value } = await reader.read();
136
+ if (done) break;
137
+ chunks.push(value);
138
+ }
139
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
140
+ const bytes = new Uint8Array(totalLength);
141
+ let offset = 0;
142
+ for (const chunk of chunks) {
143
+ bytes.set(chunk, offset);
144
+ offset += chunk.length;
145
+ }
146
+ return JoinRequest.fromBinary(bytes);
147
+ }
148
+
102
149
  describe('Happy Path - Initial Join', () => {
103
150
  it('should successfully connect and receive join response', async () => {
104
151
  const joinResponse = createJoinResponse();
@@ -113,6 +160,51 @@ describe('SignalClient.connect', () => {
113
160
  expect(result).toEqual(joinResponse);
114
161
  expect(signalClient.currentState).toBe(SignalConnectionState.CONNECTED);
115
162
  });
163
+
164
+ it('does not advertise packet trailer capability by default', async () => {
165
+ const joinResponse = createJoinResponse();
166
+ const signalResponse = createSignalResponse('join', joinResponse);
167
+ const mockReadable = createMockReadableStream([signalResponse]);
168
+ const mockConnection = createMockConnection(mockReadable);
169
+ let capturedUrl = '';
170
+
171
+ mockWebSocketStream({
172
+ connection: mockConnection,
173
+ onUrl: (url) => {
174
+ capturedUrl = url;
175
+ },
176
+ });
177
+
178
+ await signalClient.join('wss://test.livekit.io', 'test-token', defaultOptions);
179
+
180
+ const joinRequest = await decodeJoinRequestFromUrl(capturedUrl);
181
+ expect(joinRequest.clientInfo?.capabilities).toEqual([]);
182
+ });
183
+
184
+ it('advertises packet trailer capability when provided', async () => {
185
+ const joinResponse = createJoinResponse();
186
+ const signalResponse = createSignalResponse('join', joinResponse);
187
+ const mockReadable = createMockReadableStream([signalResponse]);
188
+ const mockConnection = createMockConnection(mockReadable);
189
+ let capturedUrl = '';
190
+
191
+ mockWebSocketStream({
192
+ connection: mockConnection,
193
+ onUrl: (url) => {
194
+ capturedUrl = url;
195
+ },
196
+ });
197
+
198
+ await signalClient.join('wss://test.livekit.io', 'test-token', {
199
+ ...defaultOptions,
200
+ clientInfoCapabilities: [ClientInfo_Capability.CAP_PACKET_TRAILER],
201
+ });
202
+
203
+ const joinRequest = await decodeJoinRequestFromUrl(capturedUrl);
204
+ expect(joinRequest.clientInfo?.capabilities).toEqual([
205
+ ClientInfo_Capability.CAP_PACKET_TRAILER,
206
+ ]);
207
+ });
116
208
  });
117
209
 
118
210
  describe('Happy Path - Reconnect', () => {
@@ -3,6 +3,7 @@ import {
3
3
  AddTrackRequest,
4
4
  AudioTrackFeature,
5
5
  ClientInfo,
6
+ ClientInfo_Capability,
6
7
  ConnectionQualityUpdate,
7
8
  ConnectionSettings,
8
9
  DataTrackSubscriberHandles,
@@ -83,6 +84,7 @@ interface ConnectOpts extends SignalOptions {
83
84
  export interface SignalOptions {
84
85
  autoSubscribe: boolean;
85
86
  adaptiveStream?: boolean;
87
+ clientInfoCapabilities?: ClientInfo_Capability[];
86
88
  maxRetries: number;
87
89
  e2eeEnabled: boolean;
88
90
  websocketTimeout: number;
@@ -320,7 +322,7 @@ export class SignalClient {
320
322
  this.connectOptions = opts;
321
323
  this.useV0SignalPath = useV0Path;
322
324
 
323
- const clientInfo = getClientInfo();
325
+ const clientInfo = getClientInfo(opts.clientInfoCapabilities);
324
326
  const params = useV0Path
325
327
  ? createConnectionParams(token, clientInfo, opts)
326
328
  : await createJoinRequestConnectionParams(token, clientInfo, opts, publisherOffer);
@@ -771,7 +773,7 @@ export class SignalClient {
771
773
  if (this.useJSON) {
772
774
  await this.streamWriter.write(req.toJsonString());
773
775
  } else {
774
- await this.streamWriter.write(req.toBinary());
776
+ await this.streamWriter.write((req.toBinary() as NonSharedUint8Array).buffer);
775
777
  }
776
778
  } catch (e) {
777
779
  this.log.error('error sending signal message', { error: e });
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
2
  import { WebSocketStream } from './WebSocketStream';
4
3
 
@@ -2,16 +2,24 @@ import { Encryption_Type, TrackInfo } from '@livekit/protocol';
2
2
  import { EventEmitter } from 'events';
3
3
  import type TypedEventEmitter from 'typed-emitter';
4
4
  import log, { LogLevel, workerLogger } from '../logger';
5
+ import { hasPacketTrailerPublishOptions } from '../packetTrailer/utils';
5
6
  import type RTCEngine from '../room/RTCEngine';
6
7
  import type Room from '../room/Room';
7
8
  import { ConnectionState } from '../room/Room';
8
9
  import { DeviceUnsupportedError } from '../room/errors';
9
10
  import { EngineEvent, ParticipantEvent, RoomEvent } from '../room/events';
10
11
  import type RemoteTrack from '../room/track/RemoteTrack';
12
+ import RemoteVideoTrack from '../room/track/RemoteVideoTrack';
11
13
  import type { Track } from '../room/track/Track';
12
- import type { VideoCodec } from '../room/track/options';
14
+ import type { TrackPublishOptions, VideoCodec } from '../room/track/options';
13
15
  import { mimeTypeToVideoCodecString } from '../room/track/utils';
14
- import { Future, isChromiumBased, isLocalTrack, isSafariBased, isVideoTrack } from '../room/utils';
16
+ import {
17
+ Future,
18
+ isLocalTrack,
19
+ isSafariBased,
20
+ isScriptTransformSupportedForWorker,
21
+ isVideoTrack,
22
+ } from '../room/utils';
15
23
  import type { BaseKeyProvider } from './KeyProvider';
16
24
  import { E2EE_FLAG } from './constants';
17
25
  import { type E2EEManagerCallbacks, EncryptionEvent, KeyProviderEvent } from './events';
@@ -34,7 +42,7 @@ import type {
34
42
  SifTrailerMessage,
35
43
  UpdateCodecMessage,
36
44
  } from './types';
37
- import { isE2EESupported, isScriptTransformSupported } from './utils';
45
+ import { isE2EESupported } from './utils';
38
46
 
39
47
  export interface BaseE2EEManager {
40
48
  setup(room: Room): void;
@@ -42,11 +50,11 @@ export interface BaseE2EEManager {
42
50
  isEnabled: boolean;
43
51
  isDataChannelEncryptionEnabled: boolean;
44
52
  setParticipantCryptorEnabled(enabled: boolean, participantIdentity: string): void;
45
- setSifTrailer(trailer: Uint8Array): void;
46
- encryptData(data: Uint8Array): Promise<EncryptDataResponseMessage['data']>;
53
+ setSifTrailer(trailer: NonSharedUint8Array): void;
54
+ encryptData(data: NonSharedUint8Array): Promise<EncryptDataResponseMessage['data']>;
47
55
  handleEncryptedData(
48
- payload: Uint8Array,
49
- iv: Uint8Array,
56
+ payload: NonSharedUint8Array,
57
+ iv: NonSharedUint8Array,
50
58
  participantIdentity: string,
51
59
  keyIndex: number,
52
60
  ): Promise<DecryptDataResponseMessage['data']>;
@@ -133,7 +141,7 @@ export class E2EEManager
133
141
  /**
134
142
  * @internal
135
143
  */
136
- setSifTrailer(trailer: Uint8Array) {
144
+ setSifTrailer(trailer: NonSharedUint8Array) {
137
145
  if (!trailer || trailer.length === 0) {
138
146
  log.warn("ignoring server sent trailer as it's empty");
139
147
  } else {
@@ -221,6 +229,9 @@ export class E2EEManager
221
229
  encryptFuture.resolve(data as EncryptDataResponseMessage['data']);
222
230
  }
223
231
  break;
232
+ case 'packetTrailerMetadata':
233
+ this.handlePacketTrailerMetadata(data.trackId, data.rtpTimestamp, data.ssrc, data.metadata);
234
+ break;
224
235
  default:
225
236
  break;
226
237
  }
@@ -231,6 +242,33 @@ export class E2EEManager
231
242
  this.emit(EncryptionEvent.EncryptionError, ev.error, undefined);
232
243
  };
233
244
 
245
+ private handlePacketTrailerMetadata(
246
+ trackId: string,
247
+ rtpTimestamp: number,
248
+ ssrc: number,
249
+ metadata: { userTimestamp: bigint; frameId: number },
250
+ ) {
251
+ if (!this.room) {
252
+ return;
253
+ }
254
+ for (const participant of [
255
+ this.room.localParticipant,
256
+ ...this.room.remoteParticipants.values(),
257
+ ]) {
258
+ for (const pub of participant.trackPublications.values()) {
259
+ if (
260
+ pub.track &&
261
+ pub.track.mediaStreamID === trackId &&
262
+ pub.track instanceof RemoteVideoTrack &&
263
+ pub.track.packetTrailerExtractor
264
+ ) {
265
+ pub.track.packetTrailerExtractor.storeMetadata(rtpTimestamp, ssrc, metadata);
266
+ return;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
234
272
  public setupEngine(engine: RTCEngine) {
235
273
  engine.on(EngineEvent.RTPVideoMapUpdate, (rtpMap) => {
236
274
  this.postRTPMap(rtpMap);
@@ -299,6 +337,7 @@ export class E2EEManager
299
337
  trackId: publication.track!.mediaStreamID,
300
338
  codec: mimeTypeToVideoCodecString(publication.trackInfo!.codecs[0].mimeType),
301
339
  participantIdentity: this.room!.localParticipant.identity,
340
+ hasPacketTrailer: false,
302
341
  },
303
342
  };
304
343
 
@@ -314,7 +353,7 @@ export class E2EEManager
314
353
  );
315
354
  }
316
355
 
317
- async encryptData(data: Uint8Array): Promise<EncryptDataResponseMessage['data']> {
356
+ async encryptData(data: NonSharedUint8Array): Promise<EncryptDataResponseMessage['data']> {
318
357
  if (!this.worker) {
319
358
  throw Error('could not encrypt data, worker is missing');
320
359
  }
@@ -337,8 +376,8 @@ export class E2EEManager
337
376
  }
338
377
 
339
378
  handleEncryptedData(
340
- payload: Uint8Array,
341
- iv: Uint8Array,
379
+ payload: NonSharedUint8Array,
380
+ iv: NonSharedUint8Array,
342
381
  participantIdentity: string,
343
382
  keyIndex: number,
344
383
  ) {
@@ -428,7 +467,7 @@ export class E2EEManager
428
467
  this.worker.postMessage(msg);
429
468
  }
430
469
 
431
- private postSifTrailer(trailer: Uint8Array) {
470
+ private postSifTrailer(trailer: NonSharedUint8Array) {
432
471
  if (!this.worker) {
433
472
  throw Error('could not post SIF trailer, worker is missing');
434
473
  }
@@ -448,11 +487,16 @@ export class E2EEManager
448
487
  if (!trackInfo?.mimeType || trackInfo.mimeType === '') {
449
488
  throw new TypeError('MimeType missing from trackInfo, cannot set up E2EE cryptor');
450
489
  }
490
+ const hasPacketTrailer =
491
+ track.kind === 'video' &&
492
+ !!trackInfo.packetTrailerFeatures &&
493
+ trackInfo.packetTrailerFeatures.length > 0;
451
494
  this.handleReceiver(
452
495
  track.receiver,
453
496
  track.mediaStreamID,
454
497
  remoteId,
455
498
  track.kind === 'video' ? mimeTypeToVideoCodecString(trackInfo.mimeType) : undefined,
499
+ hasPacketTrailer,
456
500
  );
457
501
  }
458
502
 
@@ -461,7 +505,12 @@ export class E2EEManager
461
505
  if (!sender) log.warn('early return because sender is not ready');
462
506
  return;
463
507
  }
464
- this.handleSender(sender, track.mediaStreamID, undefined);
508
+ this.handleSender(
509
+ sender,
510
+ track.mediaStreamID,
511
+ undefined,
512
+ isVideoTrack(track) ? track.publishOptions?.packetTrailer : undefined,
513
+ );
465
514
  }
466
515
 
467
516
  /**
@@ -473,35 +522,33 @@ export class E2EEManager
473
522
  receiver: RTCRtpReceiver,
474
523
  trackId: string,
475
524
  participantIdentity: string,
476
- codec?: VideoCodec,
525
+ codec: VideoCodec | undefined,
526
+ hasPacketTrailer: boolean,
477
527
  ) {
478
528
  if (!this.worker) {
479
529
  return;
480
530
  }
481
531
 
482
- if (
483
- isScriptTransformSupported() &&
484
- // Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
485
- // Disabling it for Chrome based browsers until the API has stabilized
486
- !isChromiumBased()
487
- ) {
532
+ if (isScriptTransformSupportedForWorker()) {
488
533
  const options: ScriptTransformOptions = {
489
534
  kind: 'decode',
490
535
  participantIdentity,
491
536
  trackId,
492
537
  codec,
538
+ hasPacketTrailer,
493
539
  };
494
540
  // @ts-ignore
495
541
  receiver.transform = new RTCRtpScriptTransform(this.worker, options);
496
542
  } else {
497
543
  if (E2EE_FLAG in receiver && codec) {
498
- // only update codec
544
+ // update track-specific state when the transceiver is reused
499
545
  const msg: UpdateCodecMessage = {
500
546
  kind: 'updateCodec',
501
547
  data: {
502
548
  trackId,
503
549
  codec,
504
- participantIdentity: participantIdentity,
550
+ participantIdentity,
551
+ hasPacketTrailer,
505
552
  },
506
553
  };
507
554
  this.worker.postMessage(msg);
@@ -532,6 +579,7 @@ export class E2EEManager
532
579
  codec,
533
580
  participantIdentity: participantIdentity,
534
581
  isReuse: E2EE_FLAG in receiver,
582
+ hasPacketTrailer,
535
583
  },
536
584
  };
537
585
  this.worker.postMessage(msg, [readable, writable]);
@@ -546,7 +594,12 @@ export class E2EEManager
546
594
  * a frame encoder.
547
595
  *
548
596
  */
549
- private handleSender(sender: RTCRtpSender, trackId: string, codec?: VideoCodec) {
597
+ private handleSender(
598
+ sender: RTCRtpSender,
599
+ trackId: string,
600
+ codec?: VideoCodec,
601
+ packetTrailer?: TrackPublishOptions['packetTrailer'],
602
+ ) {
550
603
  if (E2EE_FLAG in sender || !this.worker) {
551
604
  return;
552
605
  }
@@ -555,18 +608,15 @@ export class E2EEManager
555
608
  throw TypeError('local identity needs to be known in order to set up encrypted sender');
556
609
  }
557
610
 
558
- if (
559
- isScriptTransformSupported() &&
560
- // Chrome occasionally throws an `InvalidState` error when using script transforms directly after introducing this API in 141.
561
- // Disabling it for Chrome based browsers until the API has stabilized
562
- !isChromiumBased()
563
- ) {
611
+ if (isScriptTransformSupportedForWorker()) {
564
612
  log.info('initialize script transform');
565
- const options = {
613
+ const options: ScriptTransformOptions = {
566
614
  kind: 'encode',
567
615
  participantIdentity: this.room.localParticipant.identity,
568
616
  trackId,
569
617
  codec,
618
+ hasPacketTrailer: hasPacketTrailerPublishOptions(packetTrailer),
619
+ packetTrailer,
570
620
  };
571
621
  // @ts-ignore
572
622
  sender.transform = new RTCRtpScriptTransform(this.worker, options);
@@ -583,6 +633,8 @@ export class E2EEManager
583
633
  trackId,
584
634
  participantIdentity: this.room.localParticipant.identity,
585
635
  isReuse: false,
636
+ hasPacketTrailer: hasPacketTrailerPublishOptions(packetTrailer),
637
+ packetTrailer,
586
638
  },
587
639
  };
588
640
  this.worker.postMessage(msg, [senderStreams.readable, senderStreams.writable]);