@livekit/rtc-node 0.13.24 → 0.13.26

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 (283) hide show
  1. package/dist/audio_filter.cjs +2 -2
  2. package/dist/audio_filter.cjs.map +1 -1
  3. package/dist/audio_filter.js +1 -1
  4. package/dist/audio_filter.js.map +1 -1
  5. package/dist/audio_frame.cjs +9 -5
  6. package/dist/audio_frame.cjs.map +1 -1
  7. package/dist/audio_frame.d.cts +2 -7
  8. package/dist/audio_frame.d.ts +2 -7
  9. package/dist/audio_frame.d.ts.map +1 -1
  10. package/dist/audio_frame.js +8 -4
  11. package/dist/audio_frame.js.map +1 -1
  12. package/dist/audio_mixer.cjs +10 -4
  13. package/dist/audio_mixer.cjs.map +1 -1
  14. package/dist/audio_mixer.d.cts +4 -7
  15. package/dist/audio_mixer.d.ts +4 -7
  16. package/dist/audio_mixer.d.ts.map +1 -1
  17. package/dist/audio_mixer.js +10 -4
  18. package/dist/audio_mixer.js.map +1 -1
  19. package/dist/audio_resampler.cjs +20 -12
  20. package/dist/audio_resampler.cjs.map +1 -1
  21. package/dist/audio_resampler.d.cts +6 -6
  22. package/dist/audio_resampler.d.ts +6 -6
  23. package/dist/audio_resampler.d.ts.map +1 -1
  24. package/dist/audio_resampler.js +12 -4
  25. package/dist/audio_resampler.js.map +1 -1
  26. package/dist/audio_source.cjs +11 -7
  27. package/dist/audio_source.cjs.map +1 -1
  28. package/dist/audio_source.d.cts +1 -7
  29. package/dist/audio_source.d.ts +1 -7
  30. package/dist/audio_source.d.ts.map +1 -1
  31. package/dist/audio_source.js +8 -3
  32. package/dist/audio_source.js.map +1 -1
  33. package/dist/audio_stream.cjs +16 -6
  34. package/dist/audio_stream.cjs.map +1 -1
  35. package/dist/audio_stream.d.cts +1 -14
  36. package/dist/audio_stream.d.ts +1 -14
  37. package/dist/audio_stream.d.ts.map +1 -1
  38. package/dist/audio_stream.js +15 -5
  39. package/dist/audio_stream.js.map +1 -1
  40. package/dist/data_streams/index.d.cts +2 -10
  41. package/dist/data_streams/index.d.ts +2 -10
  42. package/dist/data_streams/stream_reader.cjs +8 -0
  43. package/dist/data_streams/stream_reader.cjs.map +1 -1
  44. package/dist/data_streams/stream_reader.d.cts +2 -10
  45. package/dist/data_streams/stream_reader.d.ts +2 -10
  46. package/dist/data_streams/stream_reader.d.ts.map +1 -1
  47. package/dist/data_streams/stream_reader.js +8 -0
  48. package/dist/data_streams/stream_reader.js.map +1 -1
  49. package/dist/data_streams/stream_writer.d.cts +2 -10
  50. package/dist/data_streams/stream_writer.d.ts +2 -10
  51. package/dist/data_streams/types.cjs.map +1 -1
  52. package/dist/data_streams/types.d.cts +2 -10
  53. package/dist/data_streams/types.d.ts +2 -10
  54. package/dist/data_streams/types.d.ts.map +1 -1
  55. package/dist/e2ee.cjs +21 -21
  56. package/dist/e2ee.cjs.map +1 -1
  57. package/dist/e2ee.d.cts +1 -2
  58. package/dist/e2ee.d.ts +1 -2
  59. package/dist/e2ee.d.ts.map +1 -1
  60. package/dist/e2ee.js +2 -2
  61. package/dist/e2ee.js.map +1 -1
  62. package/dist/ffi_client.cjs +30 -15
  63. package/dist/ffi_client.cjs.map +1 -1
  64. package/dist/ffi_client.d.cts +6 -17
  65. package/dist/ffi_client.d.ts +6 -17
  66. package/dist/ffi_client.d.ts.map +1 -1
  67. package/dist/ffi_client.js +24 -6
  68. package/dist/ffi_client.js.map +1 -1
  69. package/dist/frame_processor.cjs +13 -2
  70. package/dist/frame_processor.cjs.map +1 -1
  71. package/dist/frame_processor.d.cts +5 -8
  72. package/dist/frame_processor.d.ts +5 -8
  73. package/dist/frame_processor.d.ts.map +1 -1
  74. package/dist/frame_processor.js +10 -1
  75. package/dist/frame_processor.js.map +1 -1
  76. package/dist/index.cjs +25 -23
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.d.cts +3 -16
  79. package/dist/index.d.ts +3 -16
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +9 -7
  82. package/dist/index.js.map +1 -1
  83. package/dist/participant.cjs +146 -101
  84. package/dist/participant.cjs.map +1 -1
  85. package/dist/participant.d.cts +4 -16
  86. package/dist/participant.d.ts +4 -16
  87. package/dist/participant.d.ts.map +1 -1
  88. package/dist/participant.js +120 -75
  89. package/dist/participant.js.map +1 -1
  90. package/dist/room.cjs +116 -58
  91. package/dist/room.cjs.map +1 -1
  92. package/dist/room.d.cts +6 -15
  93. package/dist/room.d.ts +6 -15
  94. package/dist/room.d.ts.map +1 -1
  95. package/dist/room.js +113 -55
  96. package/dist/room.js.map +1 -1
  97. package/dist/rpc.cjs.map +1 -1
  98. package/dist/rpc.d.cts +1 -2
  99. package/dist/rpc.d.ts +1 -2
  100. package/dist/rpc.d.ts.map +1 -1
  101. package/dist/rpc.js.map +1 -1
  102. package/dist/{stream_reader-Ch2zvjU3.d.cts → stream_reader-CuG4b8Y-.d.cts} +1 -1
  103. package/dist/{stream_reader-DRyR29vo.d.ts → stream_reader-CuG4b8Y-.d.ts} +1 -1
  104. package/dist/track.cjs +3 -3
  105. package/dist/track.cjs.map +1 -1
  106. package/dist/track.d.cts +1 -14
  107. package/dist/track.d.ts +1 -14
  108. package/dist/track.d.ts.map +1 -1
  109. package/dist/track.js +1 -1
  110. package/dist/track.js.map +1 -1
  111. package/dist/track_publication.cjs +4 -4
  112. package/dist/track_publication.cjs.map +1 -1
  113. package/dist/track_publication.d.cts +1 -14
  114. package/dist/track_publication.d.ts +1 -14
  115. package/dist/track_publication.d.ts.map +1 -1
  116. package/dist/track_publication.js +2 -2
  117. package/dist/track_publication.js.map +1 -1
  118. package/dist/version.cjs +1 -1
  119. package/dist/version.cjs.map +1 -1
  120. package/dist/version.d.cts +1 -1
  121. package/dist/version.d.ts +1 -1
  122. package/dist/version.js +1 -1
  123. package/dist/version.js.map +1 -1
  124. package/dist/video_frame.cjs +42 -42
  125. package/dist/video_frame.cjs.map +1 -1
  126. package/dist/video_frame.d.cts +1 -6
  127. package/dist/video_frame.d.ts +1 -6
  128. package/dist/video_frame.d.ts.map +1 -1
  129. package/dist/video_frame.js +2 -2
  130. package/dist/video_frame.js.map +1 -1
  131. package/dist/video_source.cjs +5 -5
  132. package/dist/video_source.cjs.map +1 -1
  133. package/dist/video_source.d.cts +1 -14
  134. package/dist/video_source.d.ts +1 -14
  135. package/dist/video_source.d.ts.map +1 -1
  136. package/dist/video_source.js +2 -2
  137. package/dist/video_source.js.map +1 -1
  138. package/dist/video_stream.cjs +12 -4
  139. package/dist/video_stream.cjs.map +1 -1
  140. package/dist/video_stream.d.cts +1 -14
  141. package/dist/video_stream.d.ts +1 -14
  142. package/dist/video_stream.d.ts.map +1 -1
  143. package/dist/video_stream.js +10 -2
  144. package/dist/video_stream.js.map +1 -1
  145. package/package.json +7 -37
  146. package/src/audio_filter.ts +2 -2
  147. package/src/audio_frame.ts +17 -5
  148. package/src/audio_mixer.test.ts +102 -0
  149. package/src/audio_mixer.ts +10 -4
  150. package/src/audio_resampler.ts +14 -4
  151. package/src/audio_source.ts +13 -5
  152. package/src/audio_stream.ts +21 -6
  153. package/src/data_streams/stream_reader.ts +19 -1
  154. package/src/data_streams/types.ts +1 -1
  155. package/src/e2ee.ts +3 -3
  156. package/src/ffi_client.ts +33 -8
  157. package/src/frame_processor.ts +21 -0
  158. package/src/index.ts +6 -5
  159. package/src/participant.ts +117 -66
  160. package/src/room.ts +161 -69
  161. package/src/rpc.ts +1 -1
  162. package/src/tests/e2e.test.ts +179 -8
  163. package/src/track.ts +4 -4
  164. package/src/track_publication.ts +6 -6
  165. package/src/version.ts +1 -1
  166. package/src/video_frame.ts +3 -3
  167. package/src/video_source.ts +3 -3
  168. package/src/video_stream.ts +15 -3
  169. package/Cargo.toml +0 -25
  170. package/build.rs +0 -9
  171. package/dist/napi/native.cjs +0 -301
  172. package/dist/napi/native.d.cjs +0 -17
  173. package/dist/napi/native.d.cjs.map +0 -1
  174. package/dist/napi/native.d.d.cts +0 -21
  175. package/dist/napi/native.d.d.ts +0 -21
  176. package/dist/napi/native.d.js +0 -1
  177. package/dist/napi/native.d.js.map +0 -1
  178. package/dist/napi/native.d.ts +0 -19
  179. package/dist/napi/native.js +0 -15
  180. package/dist/native.cjs +0 -301
  181. package/dist/native.d.ts +0 -19
  182. package/dist/native.js +0 -15
  183. package/dist/proto/audio_frame_pb.cjs +0 -1357
  184. package/dist/proto/audio_frame_pb.cjs.map +0 -1
  185. package/dist/proto/audio_frame_pb.d.cts +0 -1183
  186. package/dist/proto/audio_frame_pb.d.ts +0 -1183
  187. package/dist/proto/audio_frame_pb.d.ts.map +0 -1
  188. package/dist/proto/audio_frame_pb.js +0 -1283
  189. package/dist/proto/audio_frame_pb.js.map +0 -1
  190. package/dist/proto/data_stream_pb.cjs +0 -1638
  191. package/dist/proto/data_stream_pb.cjs.map +0 -1
  192. package/dist/proto/data_stream_pb.d.cts +0 -1449
  193. package/dist/proto/data_stream_pb.d.ts +0 -1449
  194. package/dist/proto/data_stream_pb.d.ts.map +0 -1
  195. package/dist/proto/data_stream_pb.js +0 -1559
  196. package/dist/proto/data_stream_pb.js.map +0 -1
  197. package/dist/proto/e2ee_pb.cjs +0 -746
  198. package/dist/proto/e2ee_pb.cjs.map +0 -1
  199. package/dist/proto/e2ee_pb.d.cts +0 -653
  200. package/dist/proto/e2ee_pb.d.ts +0 -653
  201. package/dist/proto/e2ee_pb.d.ts.map +0 -1
  202. package/dist/proto/e2ee_pb.js +0 -696
  203. package/dist/proto/e2ee_pb.js.map +0 -1
  204. package/dist/proto/ffi_pb.cjs +0 -477
  205. package/dist/proto/ffi_pb.cjs.map +0 -1
  206. package/dist/proto/ffi_pb.d.cts +0 -1321
  207. package/dist/proto/ffi_pb.d.ts +0 -1321
  208. package/dist/proto/ffi_pb.d.ts.map +0 -1
  209. package/dist/proto/ffi_pb.js +0 -444
  210. package/dist/proto/ffi_pb.js.map +0 -1
  211. package/dist/proto/handle_pb.cjs +0 -53
  212. package/dist/proto/handle_pb.cjs.map +0 -1
  213. package/dist/proto/handle_pb.d.cts +0 -31
  214. package/dist/proto/handle_pb.d.ts +0 -31
  215. package/dist/proto/handle_pb.d.ts.map +0 -1
  216. package/dist/proto/handle_pb.js +0 -29
  217. package/dist/proto/handle_pb.js.map +0 -1
  218. package/dist/proto/participant_pb.cjs +0 -173
  219. package/dist/proto/participant_pb.cjs.map +0 -1
  220. package/dist/proto/participant_pb.d.cts +0 -218
  221. package/dist/proto/participant_pb.d.ts +0 -218
  222. package/dist/proto/participant_pb.d.ts.map +0 -1
  223. package/dist/proto/participant_pb.js +0 -145
  224. package/dist/proto/participant_pb.js.map +0 -1
  225. package/dist/proto/room_pb.cjs +0 -3374
  226. package/dist/proto/room_pb.cjs.map +0 -1
  227. package/dist/proto/room_pb.d.cts +0 -3028
  228. package/dist/proto/room_pb.d.ts +0 -3028
  229. package/dist/proto/room_pb.d.ts.map +0 -1
  230. package/dist/proto/room_pb.js +0 -3232
  231. package/dist/proto/room_pb.js.map +0 -1
  232. package/dist/proto/rpc_pb.cjs +0 -328
  233. package/dist/proto/rpc_pb.cjs.map +0 -1
  234. package/dist/proto/rpc_pb.d.cts +0 -267
  235. package/dist/proto/rpc_pb.d.ts +0 -267
  236. package/dist/proto/rpc_pb.d.ts.map +0 -1
  237. package/dist/proto/rpc_pb.js +0 -294
  238. package/dist/proto/rpc_pb.js.map +0 -1
  239. package/dist/proto/stats_pb.cjs +0 -1370
  240. package/dist/proto/stats_pb.cjs.map +0 -1
  241. package/dist/proto/stats_pb.d.cts +0 -1764
  242. package/dist/proto/stats_pb.d.ts +0 -1764
  243. package/dist/proto/stats_pb.d.ts.map +0 -1
  244. package/dist/proto/stats_pb.js +0 -1300
  245. package/dist/proto/stats_pb.js.map +0 -1
  246. package/dist/proto/track_pb.cjs +0 -630
  247. package/dist/proto/track_pb.cjs.map +0 -1
  248. package/dist/proto/track_pb.d.cts +0 -542
  249. package/dist/proto/track_pb.d.ts +0 -542
  250. package/dist/proto/track_pb.d.ts.map +0 -1
  251. package/dist/proto/track_pb.js +0 -584
  252. package/dist/proto/track_pb.js.map +0 -1
  253. package/dist/proto/track_publication_pb.cjs +0 -194
  254. package/dist/proto/track_publication_pb.cjs.map +0 -1
  255. package/dist/proto/track_publication_pb.d.cts +0 -135
  256. package/dist/proto/track_publication_pb.d.ts +0 -135
  257. package/dist/proto/track_publication_pb.d.ts.map +0 -1
  258. package/dist/proto/track_publication_pb.js +0 -164
  259. package/dist/proto/track_publication_pb.js.map +0 -1
  260. package/dist/proto/video_frame_pb.cjs +0 -723
  261. package/dist/proto/video_frame_pb.cjs.map +0 -1
  262. package/dist/proto/video_frame_pb.d.cts +0 -667
  263. package/dist/proto/video_frame_pb.d.ts +0 -667
  264. package/dist/proto/video_frame_pb.d.ts.map +0 -1
  265. package/dist/proto/video_frame_pb.js +0 -673
  266. package/dist/proto/video_frame_pb.js.map +0 -1
  267. package/src/lib.rs +0 -9
  268. package/src/napi/native.cjs +0 -301
  269. package/src/napi/native.d.ts +0 -19
  270. package/src/napi/native.js +0 -15
  271. package/src/nodejs.rs +0 -134
  272. package/src/proto/audio_frame_pb.ts +0 -2270
  273. package/src/proto/data_stream_pb.ts +0 -2674
  274. package/src/proto/e2ee_pb.ts +0 -1231
  275. package/src/proto/ffi_pb.ts +0 -1700
  276. package/src/proto/handle_pb.ts +0 -69
  277. package/src/proto/participant_pb.ts +0 -351
  278. package/src/proto/room_pb.ts +0 -5661
  279. package/src/proto/rpc_pb.ts +0 -539
  280. package/src/proto/stats_pb.ts +0 -3050
  281. package/src/proto/track_pb.ts +0 -1040
  282. package/src/proto/track_publication_pb.ts +0 -284
  283. package/src/proto/video_frame_pb.ts +0 -1236
@@ -239,7 +239,7 @@ export class AudioMixer {
239
239
 
240
240
  for (const result of results) {
241
241
  if (result.status !== 'fulfilled') {
242
- log.warn('AudioMixer: Stream contribution failed:', result.reason);
242
+ log.warn({ reason: result.reason }, 'AudioMixer: Stream contribution failed');
243
243
  continue;
244
244
  }
245
245
 
@@ -311,7 +311,7 @@ export class AudioMixer {
311
311
  // Accumulate data until we have at least chunkSize samples
312
312
  while (buf.length < this.chunkSize * this.numChannels && !exhausted && !this.closed) {
313
313
  try {
314
- const result = await Promise.race([iterator.next(), this.timeout(this.streamTimeoutMs)]);
314
+ const result = await this.timeoutRace(iterator.next(), this.streamTimeoutMs);
315
315
 
316
316
  if (result === 'timeout') {
317
317
  console.warn(`AudioMixer: stream timeout after ${this.streamTimeoutMs}ms`);
@@ -412,7 +412,13 @@ export class AudioMixer {
412
412
  return new Promise((resolve) => setTimeout(resolve, ms));
413
413
  }
414
414
 
415
- private timeout(ms: number): Promise<'timeout'> {
416
- return new Promise((resolve) => setTimeout(() => resolve('timeout'), ms));
415
+ /** Race a promise against a timeout. The losing setTimeout is automatically
416
+ * cleared via `.finally()` so callers don't need to manage cleanup. */
417
+ private timeoutRace<T>(promise: Promise<T>, ms: number): Promise<T | 'timeout'> {
418
+ let timer: ReturnType<typeof setTimeout>;
419
+ const timeoutPromise = new Promise<'timeout'>((resolve) => {
420
+ timer = setTimeout(() => resolve('timeout'), ms);
421
+ });
422
+ return Promise.race([promise.finally(() => clearTimeout(timer)), timeoutPromise]);
417
423
  }
418
424
  }
@@ -1,20 +1,20 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { AudioFrame } from './audio_frame.js';
5
- import { FfiClient, FfiHandle } from './ffi_client.js';
6
4
  import type {
7
5
  FlushSoxResamplerResponse,
8
6
  NewSoxResamplerResponse,
9
7
  PushSoxResamplerResponse,
10
- } from './proto/audio_frame_pb.js';
8
+ } from '@livekit/rtc-ffi-bindings';
11
9
  import {
12
10
  FlushSoxResamplerRequest,
13
11
  NewSoxResamplerRequest,
14
12
  PushSoxResamplerRequest,
15
13
  SoxQualityRecipe,
16
14
  SoxResamplerDataType,
17
- } from './proto/audio_frame_pb.js';
15
+ } from '@livekit/rtc-ffi-bindings';
16
+ import { AudioFrame } from './audio_frame.js';
17
+ import { FfiClient, FfiHandle } from './ffi_client.js';
18
18
 
19
19
  /**
20
20
  * Resampler quality. Higher quality settings result in better audio quality but require more
@@ -97,6 +97,14 @@ export class AudioResampler {
97
97
  return this.#channels;
98
98
  }
99
99
 
100
+ /**
101
+ * Releases the underlying native resampler handle. Must be called when
102
+ * the resampler is no longer needed to avoid leaking the FD.
103
+ */
104
+ close() {
105
+ this.#ffiHandle.dispose();
106
+ }
107
+
100
108
  /**
101
109
  * Push audio data into the resampler and retrieve any available resampled data.
102
110
  *
@@ -131,12 +139,14 @@ export class AudioResampler {
131
139
  }
132
140
 
133
141
  const outputData = FfiClient.instance.copyBuffer(res.outputPtr, res.size!);
142
+
134
143
  return [
135
144
  new AudioFrame(
136
145
  new Int16Array(outputData.buffer),
137
146
  this.#outputRate,
138
147
  this.#channels,
139
148
  Math.trunc(outputData.length / this.#channels / 2),
149
+ data.userdata,
140
150
  ),
141
151
  ];
142
152
  }
@@ -1,22 +1,22 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import type { AudioFrame } from './audio_frame.js';
5
- import { FfiClient } from './ffi_client.js';
6
- import { FfiHandle } from './napi/native.js';
7
4
  import type {
8
5
  AudioSourceInfo,
9
6
  CaptureAudioFrameCallback,
10
7
  CaptureAudioFrameResponse,
11
8
  ClearAudioBufferResponse,
12
9
  NewAudioSourceResponse,
13
- } from './proto/audio_frame_pb.js';
10
+ } from '@livekit/rtc-ffi-bindings';
14
11
  import {
15
12
  AudioSourceType,
16
13
  CaptureAudioFrameRequest,
17
14
  ClearAudioBufferRequest,
15
+ FfiHandle,
18
16
  NewAudioSourceRequest,
19
- } from './proto/audio_frame_pb.js';
17
+ } from '@livekit/rtc-ffi-bindings';
18
+ import type { AudioFrame } from './audio_frame.js';
19
+ import { FfiClient } from './ffi_client.js';
20
20
 
21
21
  export class AudioSource {
22
22
  /** @internal */
@@ -145,6 +145,14 @@ export class AudioSource {
145
145
  }
146
146
 
147
147
  async close() {
148
+ // Clear any pending playout timeout so its callback doesn't fire after
149
+ // the handle is disposed, which would reference freed native state.
150
+ if (this.timeout) {
151
+ clearTimeout(this.timeout);
152
+ this.timeout = undefined;
153
+ }
154
+ // Resolve any pending waitForPlayout() promise so callers don't hang.
155
+ this.release();
148
156
  this.ffiHandle.dispose();
149
157
  this.closed = true;
150
158
  }
@@ -1,14 +1,14 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import type { NewAudioStreamResponse } from '@livekit/rtc-ffi-bindings';
5
+ import { AudioStreamType, NewAudioStreamRequest } from '@livekit/rtc-ffi-bindings';
4
6
  import type { UnderlyingSource } from 'node:stream/web';
5
7
  import { AudioFrame } from './audio_frame.js';
6
8
  import type { FfiEvent } from './ffi_client.js';
7
9
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
8
- import { FrameProcessor } from './frame_processor.js';
10
+ import { type FrameProcessor, isFrameProcessor } from './frame_processor.js';
9
11
  import { log } from './log.js';
10
- import type { NewAudioStreamResponse } from './proto/audio_frame_pb.js';
11
- import { AudioStreamType, NewAudioStreamRequest } from './proto/audio_frame_pb.js';
12
12
  import type { Track } from './track.js';
13
13
 
14
14
  export interface AudioStreamOptions {
@@ -27,6 +27,7 @@ export interface NoiseCancellationOptions {
27
27
  class AudioStreamSource implements UnderlyingSource<AudioFrame> {
28
28
  private controller?: ReadableStreamDefaultController<AudioFrame>;
29
29
  private ffiHandle: FfiHandle;
30
+ private disposed = false;
30
31
  private sampleRate: number;
31
32
  private numChannels: number;
32
33
  private legacyNcOptions?: NoiseCancellationOptions;
@@ -41,7 +42,7 @@ class AudioStreamSource implements UnderlyingSource<AudioFrame> {
41
42
  if (sampleRateOrOptions !== undefined && typeof sampleRateOrOptions !== 'number') {
42
43
  this.sampleRate = sampleRateOrOptions.sampleRate ?? 48000;
43
44
  this.numChannels = sampleRateOrOptions.numChannels ?? 1;
44
- if (sampleRateOrOptions.noiseCancellation instanceof FrameProcessor) {
45
+ if (isFrameProcessor(sampleRateOrOptions.noiseCancellation)) {
45
46
  this.frameProcessor = sampleRateOrOptions.noiseCancellation;
46
47
  } else {
47
48
  this.legacyNcOptions = sampleRateOrOptions.noiseCancellation;
@@ -106,7 +107,15 @@ class AudioStreamSource implements UnderlyingSource<AudioFrame> {
106
107
  case 'eos':
107
108
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
108
109
  this.controller.close();
109
- this.frameProcessor?.close();
110
+ // Dispose the native handle so the FD is released on stream end,
111
+ // not just when cancel() is called explicitly by the consumer.
112
+ // Guard against double-dispose if cancel() is called after EOS
113
+ // while buffered frames are still in the ReadableStream queue.
114
+ if (!this.disposed) {
115
+ this.disposed = true;
116
+ this.ffiHandle.dispose();
117
+ this.frameProcessor?.close();
118
+ }
110
119
  break;
111
120
  }
112
121
  };
@@ -117,7 +126,13 @@ class AudioStreamSource implements UnderlyingSource<AudioFrame> {
117
126
 
118
127
  cancel() {
119
128
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
120
- this.ffiHandle.dispose();
129
+ if (!this.disposed) {
130
+ this.disposed = true;
131
+ this.ffiHandle.dispose();
132
+ // Also close the frame processor on cancel for symmetry with the EOS path,
133
+ // so resources are released regardless of how the stream ends.
134
+ this.frameProcessor?.close();
135
+ }
121
136
  }
122
137
  }
123
138
 
@@ -1,8 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import type { DataStream_Chunk } from '@livekit/rtc-ffi-bindings';
4
5
  import { log } from '../log.js';
5
- import type { DataStream_Chunk } from '../proto/room_pb.js';
6
6
  import { bigIntToNumber } from '../utils.js';
7
7
  import type { BaseStreamInfo, ByteStreamInfo, TextStreamInfo } from './types.js';
8
8
 
@@ -53,12 +53,18 @@ export class ByteStreamReader extends BaseStreamReader<ByteStreamInfo> {
53
53
  try {
54
54
  const { done, value } = await reader.read();
55
55
  if (done) {
56
+ // Release the lock when the stream is exhausted so the
57
+ // underlying ReadableStream can be garbage-collected.
58
+ reader.releaseLock();
56
59
  return { done: true, value: undefined as unknown };
57
60
  } else {
58
61
  this.handleChunkReceived(value);
59
62
  return { done: false, value: value.content! };
60
63
  }
61
64
  } catch (error: unknown) {
65
+ // Release the lock on error so it doesn't stay held when the
66
+ // consumer never calls return() (e.g. breaking out of for-await).
67
+ reader.releaseLock();
62
68
  log.error('error processing stream update: %s', error);
63
69
  return { done: true, value: undefined as unknown };
64
70
  }
@@ -121,12 +127,18 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
121
127
  [Symbol.asyncIterator]() {
122
128
  const reader = this.reader.getReader();
123
129
  const decoder = new TextDecoder();
130
+ const receivedChunks = this.receivedChunks;
124
131
 
125
132
  return {
126
133
  next: async (): Promise<IteratorResult<string>> => {
127
134
  try {
128
135
  const { done, value } = await reader.read();
129
136
  if (done) {
137
+ // Release the lock when the stream is exhausted so the
138
+ // underlying ReadableStream can be garbage-collected.
139
+ reader.releaseLock();
140
+ // Clear received chunks so the buffered data can be GC'd.
141
+ receivedChunks.clear();
130
142
  return { done: true, value: undefined };
131
143
  } else {
132
144
  this.handleChunkReceived(value);
@@ -136,6 +148,10 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
136
148
  };
137
149
  }
138
150
  } catch (error: unknown) {
151
+ // Release the lock on error so it doesn't stay held when the
152
+ // consumer never calls return() (e.g. breaking out of for-await).
153
+ reader.releaseLock();
154
+ receivedChunks.clear();
139
155
  log.error('error processing stream update: %s', error);
140
156
  return { done: true, value: undefined };
141
157
  }
@@ -143,6 +159,8 @@ export class TextStreamReader extends BaseStreamReader<TextStreamInfo> {
143
159
 
144
160
  return(): IteratorResult<string> {
145
161
  reader.releaseLock();
162
+ // Clear received chunks so the buffered data can be GC'd.
163
+ receivedChunks.clear();
146
164
  return { done: true, value: undefined };
147
165
  },
148
166
  };
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import type { DataStream_Chunk, DataStream_Header } from '../proto/room_pb.js';
4
+ import type { DataStream_Chunk, DataStream_Header } from '@livekit/rtc-ffi-bindings';
5
5
  import type { ByteStreamReader, TextStreamReader } from './stream_reader.js';
6
6
 
7
7
  export interface StreamController<T extends DataStream_Chunk> {
package/src/e2ee.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { FfiClient } from './ffi_client.js';
5
4
  import type {
6
5
  E2eeManagerGetFrameCryptorsResponse,
7
6
  E2eeResponse,
@@ -9,7 +8,7 @@ import type {
9
8
  GetSharedKeyResponse,
10
9
  RatchetKeyResponse,
11
10
  RatchetSharedKeyResponse,
12
- } from './proto/e2ee_pb.js';
11
+ } from '@livekit/rtc-ffi-bindings';
13
12
  import {
14
13
  E2eeManagerSetEnabledRequest,
15
14
  E2eeRequest,
@@ -22,7 +21,8 @@ import {
22
21
  RatchetSharedKeyRequest,
23
22
  SetKeyRequest,
24
23
  SetSharedKeyRequest,
25
- } from './proto/e2ee_pb.js';
24
+ } from '@livekit/rtc-ffi-bindings';
25
+ import { FfiClient } from './ffi_client.js';
26
26
 
27
27
  const DEFAULT_RATCHET_SALT = new TextEncoder().encode('LKFrameEncryptionKey');
28
28
  const DEFAULT_RATCHET_WINDOW_SIZE = 16;
package/src/ffi_client.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import type { PartialMessage } from '@bufbuild/protobuf';
5
- import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
6
- import EventEmitter from 'events';
7
4
  import {
5
+ FfiEvent,
8
6
  FfiHandle,
7
+ FfiRequest,
8
+ FfiResponse,
9
+ type PartialMessage,
9
10
  livekitCopyBuffer,
10
11
  livekitDispose,
11
12
  livekitFfiRequest,
12
13
  livekitInitialize,
13
14
  livekitRetrievePtr,
14
- } from './napi/native.js';
15
- import { FfiEvent, FfiRequest, FfiResponse } from './proto/ffi_pb.js';
15
+ } from '@livekit/rtc-ffi-bindings';
16
+ import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
17
+ import EventEmitter from 'events';
16
18
  import { SDK_VERSION } from './version.js';
17
19
 
18
20
  export { FfiHandle, type FfiEvent, type FfiResponse, FfiRequest, livekitDispose as dispose };
@@ -68,14 +70,37 @@ export class FfiClient extends (EventEmitter as new () => TypedEmitter<FfiClient
68
70
  return livekitRetrievePtr(data);
69
71
  }
70
72
 
71
- async waitFor<T>(predicate: (ev: FfiEvent) => boolean): Promise<T> {
72
- return new Promise<T>((resolve) => {
73
+ async waitFor<T>(
74
+ predicate: (ev: FfiEvent) => boolean,
75
+ options?: { signal?: AbortSignal },
76
+ ): Promise<T> {
77
+ return new Promise<T>((resolve, reject) => {
73
78
  const listener = (ev: FfiEvent) => {
74
79
  if (predicate(ev)) {
75
- this.off(FfiClientEvent.FfiEvent, listener);
80
+ cleanup();
76
81
  resolve(ev.message.value as T);
77
82
  }
78
83
  };
84
+
85
+ const cleanup = () => {
86
+ this.off(FfiClientEvent.FfiEvent, listener);
87
+ options?.signal?.removeEventListener('abort', onAbort);
88
+ };
89
+
90
+ // If an AbortSignal is provided, remove the listener when the signal
91
+ // fires so that pending waitFor() calls don't leak listeners after
92
+ // the room disconnects or the operation is cancelled.
93
+ const onAbort = () => {
94
+ cleanup();
95
+ reject(options?.signal?.reason ?? new Error('waitFor aborted'));
96
+ };
97
+
98
+ if (options?.signal?.aborted) {
99
+ reject(options.signal.reason ?? new Error('waitFor aborted'));
100
+ return;
101
+ }
102
+
103
+ options?.signal?.addEventListener('abort', onAbort);
79
104
  this.on(FfiClientEvent.FfiEvent, listener);
80
105
  });
81
106
  }
@@ -15,7 +15,28 @@ export type FrameProcessorCredentials = {
15
15
  url: string;
16
16
  };
17
17
 
18
+ export const FrameProcessorSymbol = Symbol.for('lk.frame-processor');
19
+
20
+ export function isFrameProcessor<
21
+ T extends FrameProcessor<AudioFrame> | FrameProcessor<VideoFrame> | unknown,
22
+ >(
23
+ maybeProcessor: unknown,
24
+ ): maybeProcessor is T extends FrameProcessor<AudioFrame>
25
+ ? FrameProcessor<AudioFrame>
26
+ : T extends FrameProcessor<VideoFrame>
27
+ ? FrameProcessor<VideoFrame>
28
+ : FrameProcessor<AudioFrame> | FrameProcessor<VideoFrame> {
29
+ return (
30
+ maybeProcessor !== null &&
31
+ typeof maybeProcessor === 'object' &&
32
+ 'symbol' in maybeProcessor &&
33
+ maybeProcessor.symbol === FrameProcessorSymbol
34
+ );
35
+ }
36
+
18
37
  export abstract class FrameProcessor<Frame extends VideoFrame | AudioFrame> {
38
+ readonly symbol = FrameProcessorSymbol;
39
+
19
40
  abstract isEnabled(): boolean;
20
41
  abstract setEnabled(enabled: boolean): void;
21
42
 
package/src/index.ts CHANGED
@@ -14,8 +14,8 @@ export { E2EEManager, FrameCryptor, KeyProvider } from './e2ee.js';
14
14
  export type { E2EEOptions, KeyProviderOptions } from './e2ee.js';
15
15
  export { dispose } from './ffi_client.js';
16
16
  export { LocalParticipant, Participant, RemoteParticipant } from './participant.js';
17
- export { EncryptionState, EncryptionType } from './proto/e2ee_pb.js';
18
- export { DisconnectReason, ParticipantKind } from './proto/participant_pb.js';
17
+ export { EncryptionState, EncryptionType } from '@livekit/rtc-ffi-bindings';
18
+ export { DisconnectReason, ParticipantKind } from '@livekit/rtc-ffi-bindings';
19
19
  export {
20
20
  ConnectionQuality,
21
21
  ConnectionState,
@@ -24,9 +24,9 @@ export {
24
24
  IceServer,
25
25
  IceTransportType,
26
26
  TrackPublishOptions,
27
- } from './proto/room_pb.js';
28
- export { StreamState, TrackKind, TrackSource } from './proto/track_pb.js';
29
- export { VideoBufferType, VideoCodec, VideoRotation } from './proto/video_frame_pb.js';
27
+ } from '@livekit/rtc-ffi-bindings';
28
+ export { StreamState, TrackKind, TrackSource } from '@livekit/rtc-ffi-bindings';
29
+ export { VideoBufferType, VideoCodec, VideoRotation } from '@livekit/rtc-ffi-bindings';
30
30
  export { ConnectError, Room, RoomEvent, type RoomOptions, type RtcConfiguration } from './room.js';
31
31
  export { RpcError, type PerformRpcParams, type RpcInvocationData } from './rpc.js';
32
32
  export {
@@ -54,4 +54,5 @@ export {
54
54
  FrameProcessor,
55
55
  type FrameProcessorStreamInfo,
56
56
  type FrameProcessorCredentials,
57
+ isFrameProcessor,
57
58
  } from './frame_processor.js';