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
@@ -0,0 +1,155 @@
1
+ import { appendPacketTrailerToEncodedFrame, processPacketTrailer } from '../packetTrailer';
2
+ import type {
3
+ PTMetadataMessage,
4
+ PTScriptTransformOptions,
5
+ PTWorkerMessage,
6
+ PacketTrailerPublishOptions,
7
+ } from '../types';
8
+ import { hasPacketTrailerPublishOptions } from '../utils';
9
+
10
+ /**
11
+ * Holds the trackId currently associated with a pipeline. A mutable
12
+ * wrapper is used so the transform closure always reads the latest
13
+ * trackId after a receiver gets re-bound to a new track.
14
+ */
15
+ interface PipelineState {
16
+ trackId: string;
17
+ hasPacketTrailer: boolean;
18
+ }
19
+
20
+ const pipelines = new Map<string, PipelineState>();
21
+
22
+ onmessage = (ev: MessageEvent<PTWorkerMessage>) => {
23
+ const msg = ev.data;
24
+
25
+ switch (msg.kind) {
26
+ case 'init':
27
+ postMessage({ kind: 'initAck' });
28
+ break;
29
+
30
+ case 'decode':
31
+ setupDecodeTransform(
32
+ msg.data.readableStream,
33
+ msg.data.writableStream,
34
+ msg.data.trackId,
35
+ msg.data.hasPacketTrailer,
36
+ );
37
+ break;
38
+
39
+ case 'encode':
40
+ setupEncodeTransform(
41
+ msg.data.readableStream,
42
+ msg.data.writableStream,
43
+ msg.data.packetTrailer,
44
+ );
45
+ break;
46
+
47
+ case 'updateTrackId':
48
+ updateTrackId(msg.data.oldTrackId, msg.data.newTrackId, msg.data.hasPacketTrailer);
49
+ break;
50
+
51
+ default:
52
+ break;
53
+ }
54
+ };
55
+
56
+ function setupDecodeTransform(
57
+ readable: ReadableStream,
58
+ writable: WritableStream,
59
+ trackId: string,
60
+ hasPacketTrailer: boolean,
61
+ ) {
62
+ const state: PipelineState = { trackId, hasPacketTrailer };
63
+ pipelines.set(trackId, state);
64
+
65
+ const transform = new TransformStream({
66
+ transform(
67
+ frame: RTCEncodedVideoFrame,
68
+ controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
69
+ ) {
70
+ try {
71
+ if (state.hasPacketTrailer) {
72
+ const result = processPacketTrailer(frame, state.trackId);
73
+ if (result.data) {
74
+ frame.data = result.data;
75
+ }
76
+ if (result.payload) {
77
+ const msg: PTMetadataMessage = { kind: 'metadata', data: result.payload };
78
+ postMessage(msg);
79
+ }
80
+ }
81
+ } catch {
82
+ // Never drop frames on trailer-extraction failure — pass through so
83
+ // video keeps decoding even if metadata is lost for this frame.
84
+ }
85
+ controller.enqueue(frame);
86
+ },
87
+ });
88
+
89
+ readable
90
+ .pipeThrough(transform)
91
+ .pipeTo(writable)
92
+ .catch(() => {
93
+ pipelines.delete(state.trackId);
94
+ });
95
+ }
96
+
97
+ function setupEncodeTransform(
98
+ readable: ReadableStream,
99
+ writable: WritableStream,
100
+ packetTrailer?: PacketTrailerPublishOptions,
101
+ ) {
102
+ if (!hasPacketTrailerPublishOptions(packetTrailer)) {
103
+ readable.pipeTo(writable).catch(() => {});
104
+ return;
105
+ }
106
+
107
+ let frameId = 0;
108
+ const transform = new TransformStream({
109
+ transform(
110
+ frame: RTCEncodedVideoFrame,
111
+ controller: TransformStreamDefaultController<RTCEncodedVideoFrame>,
112
+ ) {
113
+ try {
114
+ if (packetTrailer?.frameId) {
115
+ frameId = frameId === 0xffffffff ? 1 : frameId + 1;
116
+ }
117
+ appendPacketTrailerToEncodedFrame(frame, packetTrailer, frameId);
118
+ } catch {
119
+ // Never drop frames on trailer-write failure.
120
+ }
121
+ controller.enqueue(frame);
122
+ },
123
+ });
124
+
125
+ readable
126
+ .pipeThrough(transform)
127
+ .pipeTo(writable)
128
+ .catch(() => {});
129
+ }
130
+
131
+ function updateTrackId(oldTrackId: string, newTrackId: string, hasPacketTrailer: boolean) {
132
+ const state = pipelines.get(oldTrackId);
133
+ if (state) {
134
+ state.trackId = newTrackId;
135
+ state.hasPacketTrailer = hasPacketTrailer;
136
+ pipelines.delete(oldTrackId);
137
+ pipelines.set(newTrackId, state);
138
+ }
139
+ }
140
+
141
+ // Operations using RTCRtpScriptTransform.
142
+ // @ts-ignore
143
+ if (self.RTCTransformEvent) {
144
+ // @ts-ignore
145
+ self.onrtctransform = (event: RTCTransformEvent) => {
146
+ // @ts-ignore
147
+ const transformer = event.transformer;
148
+ const options = transformer.options as PTScriptTransformOptions;
149
+ if (options.kind === 'encode') {
150
+ setupEncodeTransform(transformer.readable, transformer.writable, options.packetTrailer);
151
+ } else {
152
+ setupDecodeTransform(transformer.readable, transformer.writable, options.trackId, true);
153
+ }
154
+ };
155
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": [
5
+ "DOM",
6
+ "DOM.Iterable",
7
+ "ES2017",
8
+ "ES2018.Promise",
9
+ "WebWorker",
10
+ "ES2021.WeakRef",
11
+ "DOM.AsyncIterable"
12
+ ]
13
+ }
14
+ }
@@ -6,7 +6,7 @@ vi.mock('./utils', async () => {
6
6
  const actual = await vi.importActual('./utils');
7
7
  return {
8
8
  ...actual,
9
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
9
+
10
10
  sleep: vi.fn((ms: number) => Promise.resolve()),
11
11
  extractProjectFromUrl: vi.fn((url: URL) => {
12
12
  // @ts-ignore
@@ -0,0 +1,219 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import RTCEngine from './RTCEngine';
3
+ import { roomOptionDefaults } from './defaults';
4
+
5
+ describe('RTCEngine', () => {
6
+ const originalRTCRtpSender = window.RTCRtpSender;
7
+ const originalRTCRtpScriptTransform = (window as unknown as { RTCRtpScriptTransform?: unknown })
8
+ .RTCRtpScriptTransform;
9
+ const originalUserAgent = navigator.userAgent;
10
+
11
+ afterEach(() => {
12
+ Object.defineProperty(window, 'RTCRtpSender', {
13
+ configurable: true,
14
+ value: originalRTCRtpSender,
15
+ writable: true,
16
+ });
17
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
18
+ configurable: true,
19
+ value: originalRTCRtpScriptTransform,
20
+ writable: true,
21
+ });
22
+ Object.defineProperty(window.navigator, 'userAgent', {
23
+ configurable: true,
24
+ value: originalUserAgent,
25
+ });
26
+ });
27
+
28
+ function stubInsertableStreamsSupport() {
29
+ class MockRTCRtpSender {
30
+ createEncodedStreams() {}
31
+ }
32
+
33
+ Object.defineProperty(window, 'RTCRtpSender', {
34
+ configurable: true,
35
+ value: MockRTCRtpSender,
36
+ writable: true,
37
+ });
38
+ }
39
+
40
+ function stubScriptTransformSupport() {
41
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
42
+ configurable: true,
43
+ value: class MockRTCRtpScriptTransform {},
44
+ writable: true,
45
+ });
46
+ Object.defineProperty(window.navigator, 'userAgent', {
47
+ configurable: true,
48
+ value:
49
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
50
+ });
51
+ }
52
+
53
+ function makeRTCConfiguration(engine: RTCEngine) {
54
+ return (
55
+ engine as unknown as { makeRTCConfiguration: () => RTCConfiguration }
56
+ ).makeRTCConfiguration();
57
+ }
58
+
59
+ function setupPacketTrailerSender(engine: RTCEngine, sender: RTCRtpSender, opts = {}) {
60
+ (
61
+ engine as unknown as {
62
+ setupPacketTrailerSender: (sender: RTCRtpSender, opts?: unknown) => void;
63
+ }
64
+ ).setupPacketTrailerSender(sender, opts);
65
+ }
66
+
67
+ it('does not enable encoded insertable streams without E2EE or a packet trailer worker', () => {
68
+ stubInsertableStreamsSupport();
69
+
70
+ const engine = new RTCEngine(roomOptionDefaults);
71
+
72
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
73
+ });
74
+
75
+ it('enables encoded insertable streams when a packet trailer worker is configured', () => {
76
+ stubInsertableStreamsSupport();
77
+
78
+ const engine = new RTCEngine({
79
+ ...roomOptionDefaults,
80
+ packetTrailer: { worker: {} as Worker },
81
+ });
82
+
83
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
84
+ });
85
+
86
+ it('does not enable encoded insertable streams for packet trailers when script transforms are supported', () => {
87
+ stubInsertableStreamsSupport();
88
+ stubScriptTransformSupport();
89
+
90
+ const engine = new RTCEngine({
91
+ ...roomOptionDefaults,
92
+ packetTrailer: { worker: {} as Worker },
93
+ });
94
+
95
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBeUndefined();
96
+ });
97
+
98
+ it('enables encoded insertable streams for E2EE', () => {
99
+ stubInsertableStreamsSupport();
100
+
101
+ const engine = new RTCEngine(roomOptionDefaults);
102
+ (
103
+ engine as unknown as {
104
+ signalOpts: {
105
+ autoSubscribe: boolean;
106
+ maxRetries: number;
107
+ e2eeEnabled: boolean;
108
+ websocketTimeout: number;
109
+ };
110
+ }
111
+ ).signalOpts = {
112
+ autoSubscribe: true,
113
+ maxRetries: 1,
114
+ e2eeEnabled: true,
115
+ websocketTimeout: 15_000,
116
+ };
117
+
118
+ expect(makeRTCConfiguration(engine).encodedInsertableStreams).toBe(true);
119
+ });
120
+
121
+ it('does not create sender encoded streams when packetTrailer has no worker', () => {
122
+ const engine = new RTCEngine({
123
+ ...roomOptionDefaults,
124
+ packetTrailer: {} as never,
125
+ });
126
+ const createEncodedStreams = vi.fn();
127
+ const sender = {
128
+ createEncodedStreams,
129
+ } as unknown as RTCRtpSender;
130
+
131
+ setupPacketTrailerSender(engine, sender);
132
+
133
+ expect(createEncodedStreams).not.toHaveBeenCalled();
134
+ });
135
+
136
+ it('does not create sender passthrough streams for packet trailers when script transforms are supported', () => {
137
+ stubScriptTransformSupport();
138
+
139
+ const engine = new RTCEngine({
140
+ ...roomOptionDefaults,
141
+ packetTrailer: { worker: {} as Worker },
142
+ });
143
+ const createEncodedStreams = vi.fn();
144
+ const sender = {
145
+ createEncodedStreams,
146
+ } as unknown as RTCRtpSender;
147
+
148
+ setupPacketTrailerSender(engine, sender);
149
+
150
+ expect(createEncodedStreams).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it('posts sender encode streams to the packet trailer worker when write features are enabled', () => {
154
+ stubInsertableStreamsSupport();
155
+
156
+ const worker = { postMessage: vi.fn() } as unknown as Worker;
157
+ const engine = new RTCEngine({
158
+ ...roomOptionDefaults,
159
+ packetTrailer: { worker },
160
+ });
161
+ const readable = {} as ReadableStream;
162
+ const writable = {} as WritableStream;
163
+ const createEncodedStreams = vi.fn(() => ({ readable, writable }));
164
+ const sender = {
165
+ createEncodedStreams,
166
+ } as unknown as RTCRtpSender;
167
+
168
+ setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true, frameId: true } });
169
+
170
+ expect(createEncodedStreams).toHaveBeenCalledTimes(1);
171
+ expect(worker.postMessage).toHaveBeenCalledWith(
172
+ {
173
+ kind: 'encode',
174
+ data: {
175
+ readableStream: readable,
176
+ writableStream: writable,
177
+ packetTrailer: { timestamp: true, frameId: true },
178
+ },
179
+ },
180
+ [readable, writable],
181
+ );
182
+ });
183
+
184
+ it('uses RTCRtpScriptTransform for sender packet trailer writes when supported', () => {
185
+ stubScriptTransformSupport();
186
+
187
+ const transform = {};
188
+ const RTCRtpScriptTransform = vi.fn(() => transform);
189
+ Object.defineProperty(window, 'RTCRtpScriptTransform', {
190
+ configurable: true,
191
+ value: RTCRtpScriptTransform,
192
+ writable: true,
193
+ });
194
+ Object.defineProperty(globalThis, 'RTCRtpScriptTransform', {
195
+ configurable: true,
196
+ value: RTCRtpScriptTransform,
197
+ writable: true,
198
+ });
199
+
200
+ const worker = {} as Worker;
201
+ const engine = new RTCEngine({
202
+ ...roomOptionDefaults,
203
+ packetTrailer: { worker },
204
+ });
205
+ const createEncodedStreams = vi.fn();
206
+ const sender = {
207
+ createEncodedStreams,
208
+ } as unknown as RTCRtpSender;
209
+
210
+ setupPacketTrailerSender(engine, sender, { packetTrailer: { timestamp: true } });
211
+
212
+ expect(RTCRtpScriptTransform).toHaveBeenCalledWith(worker, {
213
+ kind: 'encode',
214
+ packetTrailer: { timestamp: true },
215
+ });
216
+ expect((sender as unknown as { transform: unknown }).transform).toBe(transform);
217
+ expect(createEncodedStreams).not.toHaveBeenCalled();
218
+ });
219
+ });
@@ -26,7 +26,6 @@ import {
26
26
  Room as RoomModel,
27
27
  RoomMovedResponse,
28
28
  RpcAck,
29
- RpcResponse,
30
29
  ServerInfo,
31
30
  SessionDescription,
32
31
  SignalTarget,
@@ -54,9 +53,14 @@ import {
54
53
  toProtoSessionDescription,
55
54
  } from '../api/SignalClient';
56
55
  import type { BaseE2EEManager } from '../e2ee/E2eeManager';
57
- import { asEncryptablePacket } from '../e2ee/utils';
56
+ import { asEncryptablePacket, isInsertableStreamSupported } from '../e2ee/utils';
58
57
  import log, { LoggerNames, getLogger } from '../logger';
59
58
  import type { InternalRoomOptions } from '../options';
59
+ import {
60
+ hasPacketTrailerPublishOptions,
61
+ isPacketTrailerSupported,
62
+ shouldUsePacketTrailerScriptTransform,
63
+ } from '../packetTrailer/utils';
60
64
  import TypedPromise from '../utils/TypedPromise';
61
65
  import { DataPacketBuffer } from '../utils/dataPacketBuffer';
62
66
  import { TTLMap } from '../utils/ttlmap';
@@ -74,7 +78,6 @@ import {
74
78
  UnexpectedConnectionState,
75
79
  } from './errors';
76
80
  import { EngineEvent } from './events';
77
- import { RpcError } from './rpc';
78
81
  import CriticalTimers from './timers';
79
82
  import type LocalTrack from './track/LocalTrack';
80
83
  import type LocalTrackPublication from './track/LocalTrackPublication';
@@ -762,7 +765,14 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
762
765
  ): RTCConfiguration {
763
766
  const rtcConfig = { ...this.rtcConfig };
764
767
 
765
- if (this.signalOpts?.e2eeEnabled) {
768
+ // E2EE and packet trailer extraction both rely on encoded frame transforms.
769
+ // Only opt into the createEncodedStreams flavor when that path will be
770
+ // used; RTCRtpScriptTransform does not need the PeerConnection flag.
771
+ const needsInsertableStreams =
772
+ this.signalOpts?.e2eeEnabled ||
773
+ (this.options.packetTrailer?.worker && !shouldUsePacketTrailerScriptTransform());
774
+
775
+ if (needsInsertableStreams && isInsertableStreamSupported()) {
766
776
  this.log.debug('E2EE - setting up transports with insertable streams');
767
777
  // this makes sure that no data is sent before the transforms are ready
768
778
  // @ts-ignore
@@ -940,8 +950,8 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
940
950
  return;
941
951
  }
942
952
  const decryptedData = await this.e2eeManager?.handleEncryptedData(
943
- dp.value.value.encryptedValue,
944
- dp.value.value.iv,
953
+ dp.value.value.encryptedValue as NonSharedUint8Array,
954
+ dp.value.value.iv as NonSharedUint8Array,
945
955
  dp.participantIdentity,
946
956
  dp.value.value.keyIndex,
947
957
  );
@@ -1004,16 +1014,17 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1004
1014
  opts: TrackPublishOptions,
1005
1015
  encodings?: RTCRtpEncodingParameters[],
1006
1016
  ) {
1017
+ let sender: RTCRtpSender;
1007
1018
  if (supportsTransceiver()) {
1008
- const sender = await this.createTransceiverRTCRtpSender(track, opts, encodings);
1009
- return sender;
1010
- }
1011
- if (supportsAddTrack()) {
1019
+ sender = await this.createTransceiverRTCRtpSender(track, opts, encodings);
1020
+ } else if (supportsAddTrack()) {
1012
1021
  this.log.warn('using add-track fallback');
1013
- const sender = await this.createRTCRtpSender(track.mediaStreamTrack);
1014
- return sender;
1022
+ sender = await this.createRTCRtpSender(track.mediaStreamTrack);
1023
+ } else {
1024
+ throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
1015
1025
  }
1016
- throw new UnexpectedConnectionState('Required webRTC APIs not supported on this device');
1026
+ this.setupPacketTrailerSender(sender, opts);
1027
+ return sender;
1017
1028
  }
1018
1029
 
1019
1030
  async createSimulcastSender(
@@ -1022,16 +1033,67 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1022
1033
  opts: TrackPublishOptions,
1023
1034
  encodings?: RTCRtpEncodingParameters[],
1024
1035
  ) {
1025
- // store RTCRtpSender
1036
+ let sender: RTCRtpSender | undefined;
1026
1037
  if (supportsTransceiver()) {
1027
- return this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
1028
- }
1029
- if (supportsAddTrack()) {
1038
+ sender = await this.createSimulcastTransceiverSender(track, simulcastTrack, opts, encodings);
1039
+ } else if (supportsAddTrack()) {
1030
1040
  this.log.debug('using add-track fallback');
1031
- return this.createRTCRtpSender(track.mediaStreamTrack);
1041
+ sender = await this.createRTCRtpSender(track.mediaStreamTrack);
1042
+ } else {
1043
+ throw new UnexpectedConnectionState('Cannot stream on this device');
1044
+ }
1045
+ if (sender) {
1046
+ this.setupPacketTrailerSender(sender, opts);
1047
+ }
1048
+ return sender;
1049
+ }
1050
+
1051
+ private setupPacketTrailerSender(sender: RTCRtpSender, opts: TrackPublishOptions = {}) {
1052
+ if (!this.options.packetTrailer?.worker || this.signalOpts?.e2eeEnabled) {
1053
+ return;
1032
1054
  }
1033
1055
 
1034
- throw new UnexpectedConnectionState('Cannot stream on this device');
1056
+ const packetTrailer = opts.packetTrailer;
1057
+ const hasPacketTrailer = hasPacketTrailerPublishOptions(packetTrailer);
1058
+
1059
+ if (shouldUsePacketTrailerScriptTransform()) {
1060
+ if (hasPacketTrailer) {
1061
+ // @ts-ignore
1062
+ sender.transform = new RTCRtpScriptTransform(this.options.packetTrailer.worker, {
1063
+ kind: 'encode',
1064
+ packetTrailer,
1065
+ });
1066
+ }
1067
+ return;
1068
+ }
1069
+
1070
+ if (
1071
+ !isPacketTrailerSupported(this.options.packetTrailer) ||
1072
+ !('createEncodedStreams' in sender)
1073
+ ) {
1074
+ if (hasPacketTrailer) {
1075
+ this.log.warn('packet trailer transform not supported; skipping write', this.logContext);
1076
+ }
1077
+ return;
1078
+ }
1079
+
1080
+ // @ts-ignore
1081
+ const { readable, writable } = sender.createEncodedStreams();
1082
+ if (hasPacketTrailer) {
1083
+ this.options.packetTrailer.worker.postMessage(
1084
+ {
1085
+ kind: 'encode',
1086
+ data: {
1087
+ readableStream: readable,
1088
+ writableStream: writable,
1089
+ packetTrailer,
1090
+ },
1091
+ },
1092
+ [readable, writable],
1093
+ );
1094
+ } else {
1095
+ readable.pipeTo(writable);
1096
+ }
1035
1097
  }
1036
1098
 
1037
1099
  private async createTransceiverRTCRtpSender(
@@ -1400,30 +1462,6 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1400
1462
  });
1401
1463
  };
1402
1464
 
1403
- /** @internal */
1404
- async publishRpcResponse(
1405
- destinationIdentity: string,
1406
- requestId: string,
1407
- payload: string | null,
1408
- error: RpcError | null,
1409
- ) {
1410
- const packet = new DataPacket({
1411
- destinationIdentities: [destinationIdentity],
1412
- kind: DataPacket_Kind.RELIABLE,
1413
- value: {
1414
- case: 'rpcResponse',
1415
- value: new RpcResponse({
1416
- requestId,
1417
- value: error
1418
- ? { case: 'error', value: error.toProto() }
1419
- : { case: 'payload', value: payload ?? '' },
1420
- }),
1421
- },
1422
- });
1423
-
1424
- await this.sendDataPacket(packet, DataChannelKind.RELIABLE);
1425
- }
1426
-
1427
1465
  /** @internal */
1428
1466
  async publishRpcAck(destinationIdentity: string, requestId: string) {
1429
1467
  const packet = new DataPacket({
@@ -1447,7 +1485,9 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1447
1485
  if (this.e2eeManager && this.e2eeManager.isDataChannelEncryptionEnabled) {
1448
1486
  const encryptablePacket = asEncryptablePacket(packet);
1449
1487
  if (encryptablePacket) {
1450
- const encryptedData = await this.e2eeManager.encryptData(encryptablePacket.toBinary());
1488
+ const encryptedData = await this.e2eeManager.encryptData(
1489
+ encryptablePacket.toBinary() as NonSharedUint8Array,
1490
+ );
1451
1491
  packet.value = {
1452
1492
  case: 'encryptedPacket',
1453
1493
  value: new EncryptedPacket({
@@ -1464,7 +1504,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1464
1504
  this.reliableDataSequence += 1;
1465
1505
  }
1466
1506
 
1467
- const msg = packet.toBinary();
1507
+ const msg = packet.toBinary() as Uint8Array<ArrayBuffer>;
1468
1508
 
1469
1509
  switch (kind) {
1470
1510
  case DataChannelKind.LOSSY:
@@ -1491,7 +1531,7 @@ export default class RTCEngine extends (EventEmitter as new () => TypedEventEmit
1491
1531
 
1492
1532
  /* @internal */
1493
1533
  async sendLossyBytes(
1494
- bytes: Uint8Array,
1534
+ bytes: NonSharedUint8Array,
1495
1535
  kind: Exclude<DataChannelKind, DataChannelKind.RELIABLE>,
1496
1536
  bufferStatusLowBehavior: 'drop' | 'wait' = 'drop',
1497
1537
  ) {
@@ -1,5 +1,7 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { ClientInfo_Capability, JoinResponse } from '@livekit/protocol';
2
+ import { describe, expect, it, vi } from 'vitest';
2
3
  import Room from './Room';
4
+ import { roomConnectOptionDefaults, roomOptionDefaults } from './defaults';
3
5
  import { RoomEvent } from './events';
4
6
 
5
7
  describe('Active device switch', () => {
@@ -28,3 +30,62 @@ describe('Active device switch', () => {
28
30
  expect(kind).toBe('audioinput');
29
31
  });
30
32
  });
33
+
34
+ describe('Room signaling options', () => {
35
+ it('advertises packet trailer capability when E2EE can handle trailers', async () => {
36
+ const room = new Room();
37
+ const join = vi.fn().mockResolvedValue({
38
+ joinResponse: new JoinResponse({
39
+ room: { name: 'test-room', sid: 'room-sid' },
40
+ participant: { sid: 'participant-sid', identity: 'test-user' },
41
+ }),
42
+ serverInfo: { version: '1.0.0' },
43
+ });
44
+ const engine = { join };
45
+
46
+ (
47
+ room as unknown as {
48
+ e2eeManager: unknown;
49
+ connectSignal: (
50
+ url: string,
51
+ token: string,
52
+ engine: unknown,
53
+ connectOptions: typeof roomConnectOptionDefaults,
54
+ roomOptions: typeof roomOptionDefaults,
55
+ abortController: AbortController,
56
+ ) => Promise<JoinResponse>;
57
+ }
58
+ ).e2eeManager = {};
59
+
60
+ await (
61
+ room as unknown as {
62
+ connectSignal: (
63
+ url: string,
64
+ token: string,
65
+ engine: unknown,
66
+ connectOptions: typeof roomConnectOptionDefaults,
67
+ roomOptions: typeof roomOptionDefaults,
68
+ abortController: AbortController,
69
+ ) => Promise<JoinResponse>;
70
+ }
71
+ ).connectSignal(
72
+ 'wss://test.livekit.io',
73
+ 'test-token',
74
+ engine,
75
+ roomConnectOptionDefaults,
76
+ roomOptionDefaults,
77
+ new AbortController(),
78
+ );
79
+
80
+ expect(join).toHaveBeenCalledWith(
81
+ 'wss://test.livekit.io',
82
+ 'test-token',
83
+ expect.objectContaining({
84
+ clientInfoCapabilities: [ClientInfo_Capability.CAP_PACKET_TRAILER],
85
+ e2eeEnabled: true,
86
+ }),
87
+ expect.any(AbortSignal),
88
+ false,
89
+ );
90
+ });
91
+ });