@livekit/rtc-node 0.8.1 → 0.9.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 (52) hide show
  1. package/dist/audio_source.d.ts +16 -1
  2. package/dist/audio_source.d.ts.map +1 -1
  3. package/dist/audio_source.js +49 -3
  4. package/dist/audio_source.js.map +1 -1
  5. package/dist/audio_stream.d.ts +10 -13
  6. package/dist/audio_stream.d.ts.map +1 -1
  7. package/dist/audio_stream.js +34 -8
  8. package/dist/audio_stream.js.map +1 -1
  9. package/dist/ffi_client.d.ts +1 -1
  10. package/dist/ffi_client.d.ts.map +1 -1
  11. package/dist/ffi_client.js.map +1 -1
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/napi/native.d.ts +5 -8
  17. package/dist/participant.d.ts +2 -1
  18. package/dist/participant.d.ts.map +1 -1
  19. package/dist/participant.js +3 -0
  20. package/dist/participant.js.map +1 -1
  21. package/dist/proto/audio_frame_pb.d.ts +32 -2
  22. package/dist/proto/audio_frame_pb.d.ts.map +1 -1
  23. package/dist/proto/audio_frame_pb.js +59 -1
  24. package/dist/proto/audio_frame_pb.js.map +1 -1
  25. package/dist/proto/ffi_pb.d.ts +21 -9
  26. package/dist/proto/ffi_pb.d.ts.map +1 -1
  27. package/dist/proto/ffi_pb.js +11 -9
  28. package/dist/proto/ffi_pb.js.map +1 -1
  29. package/dist/room.d.ts +1 -1
  30. package/dist/room.d.ts.map +1 -1
  31. package/dist/room.js +0 -3
  32. package/dist/room.js.map +1 -1
  33. package/dist/utils.d.ts +7 -0
  34. package/dist/utils.d.ts.map +1 -0
  35. package/dist/utils.js +41 -0
  36. package/dist/utils.js.map +1 -0
  37. package/dist/video_stream.d.ts +10 -10
  38. package/dist/video_stream.d.ts.map +1 -1
  39. package/dist/video_stream.js +35 -11
  40. package/dist/video_stream.js.map +1 -1
  41. package/package.json +7 -7
  42. package/src/audio_source.ts +69 -2
  43. package/src/audio_stream.ts +36 -17
  44. package/src/ffi_client.ts +1 -1
  45. package/src/index.ts +3 -2
  46. package/src/napi/native.d.ts +5 -8
  47. package/src/participant.ts +5 -1
  48. package/src/proto/audio_frame_pb.ts +71 -3
  49. package/src/proto/ffi_pb.ts +31 -17
  50. package/src/room.ts +1 -1
  51. package/src/utils.ts +36 -0
  52. package/src/video_stream.ts +37 -13
@@ -0,0 +1,7 @@
1
+ export declare class Mutex {
2
+ #private;
3
+ constructor(limit?: number);
4
+ isLocked(): boolean;
5
+ lock(): Promise<() => void>;
6
+ }
7
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,qBAAa,KAAK;;gBAKJ,KAAK,SAAI;IAMrB,QAAQ,IAAI,OAAO;IAIb,IAAI,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;CAiBlC"}
package/dist/utils.js ADDED
@@ -0,0 +1,41 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Mutex_locking, _Mutex_locks, _Mutex_limit;
13
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
14
+ //
15
+ // SPDX-License-Identifier: Apache-2.0
16
+ export class Mutex {
17
+ constructor(limit = 1) {
18
+ _Mutex_locking.set(this, void 0);
19
+ _Mutex_locks.set(this, void 0);
20
+ _Mutex_limit.set(this, void 0);
21
+ __classPrivateFieldSet(this, _Mutex_locking, Promise.resolve(), "f");
22
+ __classPrivateFieldSet(this, _Mutex_locks, 0, "f");
23
+ __classPrivateFieldSet(this, _Mutex_limit, limit, "f");
24
+ }
25
+ isLocked() {
26
+ return __classPrivateFieldGet(this, _Mutex_locks, "f") >= __classPrivateFieldGet(this, _Mutex_limit, "f");
27
+ }
28
+ async lock() {
29
+ __classPrivateFieldSet(this, _Mutex_locks, __classPrivateFieldGet(this, _Mutex_locks, "f") + 1, "f");
30
+ let unlockNext;
31
+ const willLock = new Promise((resolve) => (unlockNext = () => {
32
+ __classPrivateFieldSet(this, _Mutex_locks, __classPrivateFieldGet(this, _Mutex_locks, "f") - 1, "f");
33
+ resolve();
34
+ }));
35
+ const willUnlock = __classPrivateFieldGet(this, _Mutex_locking, "f").then(() => unlockNext);
36
+ __classPrivateFieldSet(this, _Mutex_locking, __classPrivateFieldGet(this, _Mutex_locking, "f").then(() => willLock), "f");
37
+ return willUnlock;
38
+ }
39
+ }
40
+ _Mutex_locking = new WeakMap(), _Mutex_locks = new WeakMap(), _Mutex_limit = new WeakMap();
41
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,6CAA6C;AAC7C,EAAE;AACF,sCAAsC;AACtC,MAAM,OAAO,KAAK;IAKhB,YAAY,KAAK,GAAG,CAAC;QAJrB,iCAAwB;QACxB,+BAAe;QACf,+BAAe;QAGb,uBAAA,IAAI,kBAAY,OAAO,CAAC,OAAO,EAAE,MAAA,CAAC;QAClC,uBAAA,IAAI,gBAAU,CAAC,MAAA,CAAC;QAChB,uBAAA,IAAI,gBAAU,KAAK,MAAA,CAAC;IACtB,CAAC;IAED,QAAQ;QACN,OAAO,uBAAA,IAAI,oBAAO,IAAI,uBAAA,IAAI,oBAAO,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,6FAAe,CAAC,MAAA,CAAC;QAEjB,IAAI,UAAsB,CAAC;QAE3B,MAAM,QAAQ,GAAG,IAAI,OAAO,CAC1B,CAAC,OAAO,EAAE,EAAE,CACV,CAAC,UAAU,GAAG,GAAG,EAAE;YACjB,6FAAe,CAAC,MAAA,CAAC;YACjB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CACL,CAAC;QAEF,MAAM,UAAU,GAAG,uBAAA,IAAI,sBAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;QACxD,uBAAA,IAAI,kBAAY,uBAAA,IAAI,sBAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAA,CAAC;QACnD,OAAO,UAAU,CAAC;IACpB,CAAC;CACF"}
@@ -1,29 +1,29 @@
1
- import type TypedEmitter from 'typed-emitter';
2
1
  import { FfiHandle } from './ffi_client.js';
3
2
  import type { VideoRotation, VideoStreamInfo } from './proto/video_frame_pb.js';
4
3
  import type { Track } from './track.js';
4
+ import { Mutex } from './utils.js';
5
5
  import { VideoFrame } from './video_frame.js';
6
6
  export type VideoFrameEvent = {
7
7
  frame: VideoFrame;
8
8
  timestampUs: bigint;
9
9
  rotation: VideoRotation;
10
10
  };
11
- export type VideoStreamCallbacks = {
12
- frameReceived: (evt: VideoFrameEvent) => void;
13
- };
14
- export declare enum VideoStreamEvent {
15
- FrameReceived = "frameReceived"
16
- }
17
- declare const VideoStream_base: new () => TypedEmitter<VideoStreamCallbacks>;
18
- export declare class VideoStream extends VideoStream_base {
11
+ export declare class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
19
12
  /** @internal */
20
13
  info: VideoStreamInfo;
21
14
  /** @internal */
22
15
  ffiHandle: FfiHandle;
16
+ /** @internal */
17
+ eventQueue: (VideoFrameEvent | null)[];
18
+ /** @internal */
19
+ queueResolve: ((value: IteratorResult<VideoFrameEvent>) => void) | null;
20
+ /** @internal */
21
+ mutex: Mutex;
23
22
  track: Track;
24
23
  constructor(track: Track);
25
24
  private onEvent;
25
+ next(): Promise<IteratorResult<VideoFrameEvent>>;
26
26
  close(): void;
27
+ [Symbol.asyncIterator](): VideoStream;
27
28
  }
28
- export {};
29
29
  //# sourceMappingURL=video_stream.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"video_stream.d.ts","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,YAAY,MAAM,eAAe,CAAC;AAE9C,OAAO,EAA6B,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAEV,aAAa,EACb,eAAe,EAChB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,oBAAY,gBAAgB;IAC1B,aAAa,kBAAkB;CAChC;0CAE2D,YAAY,CAAC,oBAAoB,CAAC;AAA9F,qBAAa,WAAY,SAAQ,gBAA8D;IAC7F,gBAAgB;IAChB,IAAI,EAAE,eAAe,CAAC;IACtB,gBAAgB;IAChB,SAAS,EAAE,SAAS,CAAC;IAErB,KAAK,EAAE,KAAK,CAAC;gBAED,KAAK,EAAE,KAAK;IAsBxB,OAAO,CAAC,OAAO,CAoBb;IAEF,KAAK;CAGN"}
1
+ {"version":3,"file":"video_stream.d.ts","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAIA,OAAO,EAA6B,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,KAAK,EAEV,aAAa,EACb,eAAe,EAChB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,qBAAa,WAAY,YAAW,qBAAqB,CAAC,eAAe,CAAC;IACxE,gBAAgB;IAChB,IAAI,EAAE,eAAe,CAAC;IACtB,gBAAgB;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,gBAAgB;IAChB,UAAU,EAAE,CAAC,eAAe,GAAG,IAAI,CAAC,EAAE,CAAM;IAC5C,gBAAgB;IAChB,YAAY,EAAE,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC/E,gBAAgB;IAChB,KAAK,QAAe;IAEpB,KAAK,EAAE,KAAK,CAAC;gBAED,KAAK,EAAE,KAAK;IAqBxB,OAAO,CAAC,OAAO,CAyBb;IAEI,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IAkBtD,KAAK;IAKL,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW;CAGtC"}
@@ -1,17 +1,15 @@
1
- // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
- //
3
- // SPDX-License-Identifier: Apache-2.0
4
- import EventEmitter from 'events';
5
1
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
6
2
  import { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';
3
+ import { Mutex } from './utils.js';
7
4
  import { VideoFrame } from './video_frame.js';
8
- export var VideoStreamEvent;
9
- (function (VideoStreamEvent) {
10
- VideoStreamEvent["FrameReceived"] = "frameReceived";
11
- })(VideoStreamEvent || (VideoStreamEvent = {}));
12
- export class VideoStream extends EventEmitter {
5
+ export class VideoStream {
13
6
  constructor(track) {
14
- super();
7
+ /** @internal */
8
+ this.eventQueue = [];
9
+ /** @internal */
10
+ this.queueResolve = null;
11
+ /** @internal */
12
+ this.mutex = new Mutex();
15
13
  this.onEvent = (ev) => {
16
14
  if (ev.message.case != 'videoStreamEvent' ||
17
15
  ev.message.value.streamHandle != this.ffiHandle.handle) {
@@ -23,7 +21,13 @@ export class VideoStream extends EventEmitter {
23
21
  const rotation = streamEvent.value.rotation;
24
22
  const timestampUs = streamEvent.value.timestampUs;
25
23
  const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer);
26
- this.emit(VideoStreamEvent.FrameReceived, { frame, timestampUs, rotation });
24
+ const value = { rotation, timestampUs, frame };
25
+ if (this.queueResolve) {
26
+ this.queueResolve({ done: false, value });
27
+ }
28
+ else {
29
+ this.eventQueue.push(value);
30
+ }
27
31
  break;
28
32
  case 'eos':
29
33
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
@@ -45,8 +49,28 @@ export class VideoStream extends EventEmitter {
45
49
  this.ffiHandle = new FfiHandle(res.stream.handle.id);
46
50
  FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);
47
51
  }
52
+ async next() {
53
+ const unlock = await this.mutex.lock();
54
+ if (this.eventQueue.length > 0) {
55
+ unlock();
56
+ const value = this.eventQueue.shift();
57
+ if (value) {
58
+ return { done: false, value };
59
+ }
60
+ else {
61
+ return { done: true, value: undefined };
62
+ }
63
+ }
64
+ const promise = new Promise((resolve) => (this.queueResolve = resolve));
65
+ unlock();
66
+ return promise;
67
+ }
48
68
  close() {
69
+ this.eventQueue.push(null);
49
70
  this.ffiHandle.dispose();
50
71
  }
72
+ [Symbol.asyncIterator]() {
73
+ return this;
74
+ }
51
75
  }
52
76
  //# sourceMappingURL=video_stream.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"video_stream.js","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,sCAAsC;AACtC,OAAO,YAAY,MAAM,QAAQ,CAAC;AAGlC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMvE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAY9C,MAAM,CAAN,IAAY,gBAEX;AAFD,WAAY,gBAAgB;IAC1B,mDAA+B,CAAA;AACjC,CAAC,EAFW,gBAAgB,KAAhB,gBAAgB,QAE3B;AAED,MAAM,OAAO,WAAY,SAAS,YAA6D;IAQ7F,YAAY,KAAY;QACtB,KAAK,EAAE,CAAC;QAqBF,YAAO,GAAG,CAAC,EAAY,EAAE,EAAE;YACjC,IACE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,kBAAkB;gBACrC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EACtD,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;YAC7C,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAC;gBACzB,KAAK,eAAe;oBAClB,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;oBAC5C,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;oBAClD,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACjE,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;oBAC5E,MAAM;gBACR,KAAK,KAAK;oBACR,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC9D,MAAM;YACV,CAAC;QACH,CAAC,CAAC;QAxCA,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,MAAM,GAAG,GAAG,IAAI,qBAAqB,CAAC;YACpC,IAAI,EAAE,eAAe,CAAC,mBAAmB;YACzC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;SACrC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAyB;YAC7D,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB;gBACtB,KAAK,EAAE,GAAG;aACX;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAErD,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/D,CAAC;IAwBD,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF"}
1
+ {"version":3,"file":"video_stream.js","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMvE,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEnF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQ9C,MAAM,OAAO,WAAW;IActB,YAAY,KAAY;QATxB,gBAAgB;QAChB,eAAU,GAA+B,EAAE,CAAC;QAC5C,gBAAgB;QAChB,iBAAY,GAA8D,IAAI,CAAC;QAC/E,gBAAgB;QAChB,UAAK,GAAG,IAAI,KAAK,EAAE,CAAC;QAyBZ,YAAO,GAAG,CAAC,EAAY,EAAE,EAAE;YACjC,IACE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,kBAAkB;gBACrC,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EACtD,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;YAC7C,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAC;gBACzB,KAAK,eAAe;oBAClB,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;oBAC5C,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;oBAClD,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACjE,MAAM,KAAK,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;oBAC/C,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;wBACtB,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;oBAC5C,CAAC;yBAAM,CAAC;wBACN,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC9B,CAAC;oBACD,MAAM;gBACR,KAAK,KAAK;oBACR,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC9D,MAAM;YACV,CAAC;QACH,CAAC,CAAC;QA7CA,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,MAAM,GAAG,GAAG,IAAI,qBAAqB,CAAC;YACpC,IAAI,EAAE,eAAe,CAAC,mBAAmB;YACzC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;SACrC,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAyB;YAC7D,OAAO,EAAE;gBACP,IAAI,EAAE,gBAAgB;gBACtB,KAAK,EAAE,GAAG;aACX;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5B,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAErD,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/D,CAAC;IA6BD,KAAK,CAAC,IAAI;QACR,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,EAAE,CAAC;YACT,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,OAAO,CACzB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,CAC3C,CAAC;QACF,MAAM,EAAE,CAAC;QACT,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,CAAC,MAAM,CAAC,aAAa,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "LiveKit RTC Node",
4
4
  "license": "Apache-2.0",
5
5
  "author": "LiveKit",
6
- "version": "0.8.1",
6
+ "version": "0.9.0",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
9
9
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^1.4.2",
37
- "typed-emitter": "^2.1.0"
37
+ "@livekit/typed-emitter": "^3.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@napi-rs/cli": "^2.18.0",
@@ -43,11 +43,11 @@
43
43
  "prettier": "^3.0.3"
44
44
  },
45
45
  "optionalDependencies": {
46
- "@livekit/rtc-node-darwin-arm64": "0.8.1",
47
- "@livekit/rtc-node-darwin-x64": "0.8.1",
48
- "@livekit/rtc-node-linux-arm64-gnu": "0.8.1",
49
- "@livekit/rtc-node-linux-x64-gnu": "0.8.1",
50
- "@livekit/rtc-node-win32-x64-msvc": "0.8.1"
46
+ "@livekit/rtc-node-darwin-arm64": "0.9.0",
47
+ "@livekit/rtc-node-darwin-x64": "0.9.0",
48
+ "@livekit/rtc-node-linux-arm64-gnu": "0.9.0",
49
+ "@livekit/rtc-node-linux-x64-gnu": "0.9.0",
50
+ "@livekit/rtc-node-win32-x64-msvc": "0.9.0"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">= 18"
@@ -8,11 +8,13 @@ import type {
8
8
  AudioSourceInfo,
9
9
  CaptureAudioFrameCallback,
10
10
  CaptureAudioFrameResponse,
11
+ ClearAudioBufferResponse,
11
12
  NewAudioSourceResponse,
12
13
  } from './proto/audio_frame_pb.js';
13
14
  import {
14
15
  AudioSourceType,
15
16
  CaptureAudioFrameRequest,
17
+ ClearAudioBufferRequest,
16
18
  NewAudioSourceRequest,
17
19
  } from './proto/audio_frame_pb.js';
18
20
 
@@ -21,19 +23,33 @@ export class AudioSource {
21
23
  info: AudioSourceInfo;
22
24
  /** @internal */
23
25
  ffiHandle: FfiHandle;
26
+ /** @internal */
27
+ lastCapture: number;
28
+ /** @internal */
29
+ currentQueueSize: number;
30
+ /** @internal */
31
+ release = () => {};
32
+ promise = this.newPromise();
33
+ /** @internal */
34
+ timeout?: ReturnType<typeof setTimeout> = undefined;
24
35
 
25
36
  sampleRate: number;
26
37
  numChannels: number;
38
+ queueSize: number;
27
39
 
28
- constructor(sampleRate: number, numChannels: number, enableQueue?: boolean) {
40
+ constructor(sampleRate: number, numChannels: number, queueSize = 1000) {
29
41
  this.sampleRate = sampleRate;
30
42
  this.numChannels = numChannels;
43
+ this.queueSize = queueSize;
44
+
45
+ this.lastCapture = 0;
46
+ this.currentQueueSize = 0;
31
47
 
32
48
  const req = new NewAudioSourceRequest({
33
49
  type: AudioSourceType.AUDIO_SOURCE_NATIVE,
34
50
  sampleRate: sampleRate,
35
51
  numChannels: numChannels,
36
- enableQueue: enableQueue,
52
+ queueSizeMs: queueSize,
37
53
  });
38
54
 
39
55
  const res = FfiClient.instance.request<NewAudioSourceResponse>({
@@ -47,7 +63,58 @@ export class AudioSource {
47
63
  this.ffiHandle = new FfiHandle(res.source.handle.id);
48
64
  }
49
65
 
66
+ get queuedDuration(): number {
67
+ return Math.max(
68
+ this.currentQueueSize - Number(process.hrtime.bigint() / 1000000n) + this.lastCapture,
69
+ 0,
70
+ );
71
+ }
72
+
73
+ clearQueue() {
74
+ const req = new ClearAudioBufferRequest({
75
+ sourceHandle: this.ffiHandle.handle,
76
+ });
77
+
78
+ FfiClient.instance.request<ClearAudioBufferResponse>({
79
+ message: {
80
+ case: 'clearAudioBuffer',
81
+ value: req,
82
+ },
83
+ });
84
+
85
+ this.release();
86
+ }
87
+
88
+ /** @internal */
89
+ async newPromise() {
90
+ return new Promise<void>((resolve) => {
91
+ this.release = resolve;
92
+ });
93
+ }
94
+
95
+ async waitForPlayout() {
96
+ return this.promise.then(() => {
97
+ this.lastCapture = 0;
98
+ this.currentQueueSize = 0;
99
+ this.promise = this.newPromise();
100
+ });
101
+ }
102
+
50
103
  async captureFrame(frame: AudioFrame) {
104
+ const now = Number(process.hrtime.bigint() / 1000000n);
105
+ const elapsed = this.lastCapture === 0 ? 0 : now - this.lastCapture;
106
+ this.currentQueueSize += (frame.samplesPerChannel / frame.sampleRate - elapsed) * 1000;
107
+
108
+ this.lastCapture = now;
109
+
110
+ if (this.timeout) {
111
+ clearTimeout(this.timeout);
112
+ }
113
+
114
+ // remove 50ms to account for processing time
115
+ // (e.g. using wait_for_playout for very small chunks)
116
+ this.timeout = setTimeout(this.release, this.currentQueueSize - 50);
117
+
51
118
  const req = new CaptureAudioFrameRequest({
52
119
  sourceHandle: this.ffiHandle.handle,
53
120
  buffer: frame.protoInfo(),
@@ -1,39 +1,31 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import EventEmitter from 'events';
5
- import type TypedEmitter from 'typed-emitter';
6
4
  import { AudioFrame } from './audio_frame.js';
7
5
  import type { FfiEvent } from './ffi_client.js';
8
6
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
9
7
  import type { AudioStreamInfo, NewAudioStreamResponse } from './proto/audio_frame_pb.js';
10
8
  import { AudioStreamType, NewAudioStreamRequest } from './proto/audio_frame_pb.js';
11
9
  import type { Track } from './track.js';
10
+ import { Mutex } from './utils.js';
12
11
 
13
- export type AudioFrameEvent = {
14
- frame: AudioFrame;
15
- };
16
-
17
- export type AudioStreamCallbacks = {
18
- frameReceived: (frame: AudioFrameEvent) => void;
19
- };
20
-
21
- export enum AudioStreamEvent {
22
- FrameReceived = 'frameReceived',
23
- }
24
-
25
- export class AudioStream extends (EventEmitter as new () => TypedEmitter<AudioStreamCallbacks>) {
12
+ export class AudioStream implements AsyncIterableIterator<AudioFrame> {
26
13
  /** @internal */
27
14
  info: AudioStreamInfo;
28
15
  /** @internal */
29
16
  ffiHandle: FfiHandle;
17
+ /** @internal */
18
+ eventQueue: (AudioFrame | null)[] = [];
19
+ /** @internal */
20
+ queueResolve: ((value: IteratorResult<AudioFrame>) => void) | null = null;
21
+ /** @internal */
22
+ mutex = new Mutex();
30
23
 
31
24
  track: Track;
32
25
  sampleRate: number;
33
26
  numChannels: number;
34
27
 
35
28
  constructor(track: Track, sampleRate: number = 48000, numChannels: number = 1) {
36
- super();
37
29
  this.track = track;
38
30
  this.sampleRate = sampleRate;
39
31
  this.numChannels = numChannels;
@@ -70,7 +62,11 @@ export class AudioStream extends (EventEmitter as new () => TypedEmitter<AudioSt
70
62
  switch (streamEvent.case) {
71
63
  case 'frameReceived':
72
64
  const frame = AudioFrame.fromOwnedInfo(streamEvent.value.frame);
73
- this.emit(AudioStreamEvent.FrameReceived, { frame });
65
+ if (this.queueResolve) {
66
+ this.queueResolve({ done: false, value: frame });
67
+ } else {
68
+ this.eventQueue.push(frame);
69
+ }
74
70
  break;
75
71
  case 'eos':
76
72
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
@@ -78,7 +74,30 @@ export class AudioStream extends (EventEmitter as new () => TypedEmitter<AudioSt
78
74
  }
79
75
  };
80
76
 
77
+ async next(): Promise<IteratorResult<AudioFrame>> {
78
+ const unlock = await this.mutex.lock();
79
+ if (this.eventQueue.length > 0) {
80
+ unlock();
81
+ const value = this.eventQueue.shift();
82
+ if (value) {
83
+ return { done: false, value };
84
+ } else {
85
+ return { done: true, value: undefined };
86
+ }
87
+ }
88
+ const promise = new Promise<IteratorResult<AudioFrame>>(
89
+ (resolve) => (this.queueResolve = resolve),
90
+ );
91
+ unlock();
92
+ return promise;
93
+ }
94
+
81
95
  close() {
96
+ this.eventQueue.push(null);
82
97
  this.ffiHandle.dispose();
83
98
  }
99
+
100
+ [Symbol.asyncIterator](): AudioStream {
101
+ return this;
102
+ }
84
103
  }
package/src/ffi_client.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import type { PartialMessage } from '@bufbuild/protobuf';
5
+ import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
5
6
  import EventEmitter from 'events';
6
- import type TypedEmitter from 'typed-emitter';
7
7
  import {
8
8
  FfiHandle,
9
9
  livekitCopyBuffer,
package/src/index.ts CHANGED
@@ -17,8 +17,8 @@ export {
17
17
  } from './track.js';
18
18
  export { VideoFrame } from './video_frame.js';
19
19
  export { AudioFrame } from './audio_frame.js';
20
- export { AudioStream, AudioStreamEvent, AudioFrameEvent } from './audio_stream.js';
21
- export { VideoStream, VideoStreamEvent, VideoFrameEvent } from './video_stream.js';
20
+ export { AudioStream } from './audio_stream.js';
21
+ export { VideoStream, VideoFrameEvent } from './video_stream.js';
22
22
  export { AudioSource } from './audio_source.js';
23
23
  export { VideoSource } from './video_source.js';
24
24
  export {
@@ -40,4 +40,5 @@ export {
40
40
  export { EncryptionType, EncryptionState } from './proto/e2ee_pb.js';
41
41
  export { StreamState, TrackKind, TrackSource } from './proto/track_pb.js';
42
42
  export { VideoBufferType, VideoRotation } from './proto/video_frame_pb.js';
43
+ export { ParticipantKind } from './proto/participant_pb.js';
43
44
  export { dispose } from './ffi_client.js';
@@ -3,14 +3,11 @@
3
3
 
4
4
  /* auto-generated by NAPI-RS */
5
5
 
6
- export declare function livekitInitialize(
7
- callback: (data: Uint8Array) => void,
8
- captureLogs: boolean,
9
- ): void;
10
- export declare function livekitFfiRequest(data: Uint8Array): Uint8Array;
11
- export declare function livekitRetrievePtr(handle: Uint8Array): bigint;
12
- export declare function livekitCopyBuffer(ptr: bigint, len: number): Uint8Array;
13
- export declare function livekitDispose(): Promise<void>;
6
+ export function livekitInitialize(callback: (data: Uint8Array) => void, captureLogs: boolean): void;
7
+ export function livekitFfiRequest(data: Uint8Array): Uint8Array;
8
+ export function livekitRetrievePtr(handle: Uint8Array): bigint;
9
+ export function livekitCopyBuffer(ptr: bigint, len: number): Uint8Array;
10
+ export function livekitDispose(): Promise<void>;
14
11
  export declare class FfiHandle {
15
12
  constructor(handle: bigint);
16
13
  dispose(): void;
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { FfiClient, FfiHandle } from './ffi_client.js';
5
- import type { OwnedParticipant, ParticipantInfo } from './proto/participant_pb.js';
5
+ import type { OwnedParticipant, ParticipantInfo, ParticipantKind } from './proto/participant_pb.js';
6
6
  import type {
7
7
  PublishDataCallback,
8
8
  PublishDataResponse,
@@ -71,6 +71,10 @@ export abstract class Participant {
71
71
  get attributes(): Record<string, string> {
72
72
  return this.info.attributes;
73
73
  }
74
+
75
+ get kind(): ParticipantKind {
76
+ return this.info.kind;
77
+ }
74
78
  }
75
79
 
76
80
  export type DataPublishOptions = {
@@ -276,9 +276,9 @@ export class NewAudioSourceRequest extends Message<NewAudioSourceRequest> {
276
276
  numChannels = 0;
277
277
 
278
278
  /**
279
- * @generated from field: optional bool enable_queue = 5;
279
+ * @generated from field: uint32 queue_size_ms = 5;
280
280
  */
281
- enableQueue?: boolean;
281
+ queueSizeMs = 0;
282
282
 
283
283
  constructor(data?: PartialMessage<NewAudioSourceRequest>) {
284
284
  super();
@@ -292,7 +292,7 @@ export class NewAudioSourceRequest extends Message<NewAudioSourceRequest> {
292
292
  { no: 2, name: "options", kind: "message", T: AudioSourceOptions, opt: true },
293
293
  { no: 3, name: "sample_rate", kind: "scalar", T: 13 /* ScalarType.UINT32 */ },
294
294
  { no: 4, name: "num_channels", kind: "scalar", T: 13 /* ScalarType.UINT32 */ },
295
- { no: 5, name: "enable_queue", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true },
295
+ { no: 5, name: "queue_size_ms", kind: "scalar", T: 13 /* ScalarType.UINT32 */ },
296
296
  ]);
297
297
 
298
298
  static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): NewAudioSourceRequest {
@@ -475,6 +475,74 @@ export class CaptureAudioFrameCallback extends Message<CaptureAudioFrameCallback
475
475
  }
476
476
  }
477
477
 
478
+ /**
479
+ * @generated from message livekit.proto.ClearAudioBufferRequest
480
+ */
481
+ export class ClearAudioBufferRequest extends Message<ClearAudioBufferRequest> {
482
+ /**
483
+ * @generated from field: uint64 source_handle = 1;
484
+ */
485
+ sourceHandle = protoInt64.zero;
486
+
487
+ constructor(data?: PartialMessage<ClearAudioBufferRequest>) {
488
+ super();
489
+ proto3.util.initPartial(data, this);
490
+ }
491
+
492
+ static readonly runtime: typeof proto3 = proto3;
493
+ static readonly typeName = "livekit.proto.ClearAudioBufferRequest";
494
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
495
+ { no: 1, name: "source_handle", kind: "scalar", T: 4 /* ScalarType.UINT64 */ },
496
+ ]);
497
+
498
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ClearAudioBufferRequest {
499
+ return new ClearAudioBufferRequest().fromBinary(bytes, options);
500
+ }
501
+
502
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ClearAudioBufferRequest {
503
+ return new ClearAudioBufferRequest().fromJson(jsonValue, options);
504
+ }
505
+
506
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ClearAudioBufferRequest {
507
+ return new ClearAudioBufferRequest().fromJsonString(jsonString, options);
508
+ }
509
+
510
+ static equals(a: ClearAudioBufferRequest | PlainMessage<ClearAudioBufferRequest> | undefined, b: ClearAudioBufferRequest | PlainMessage<ClearAudioBufferRequest> | undefined): boolean {
511
+ return proto3.util.equals(ClearAudioBufferRequest, a, b);
512
+ }
513
+ }
514
+
515
+ /**
516
+ * @generated from message livekit.proto.ClearAudioBufferResponse
517
+ */
518
+ export class ClearAudioBufferResponse extends Message<ClearAudioBufferResponse> {
519
+ constructor(data?: PartialMessage<ClearAudioBufferResponse>) {
520
+ super();
521
+ proto3.util.initPartial(data, this);
522
+ }
523
+
524
+ static readonly runtime: typeof proto3 = proto3;
525
+ static readonly typeName = "livekit.proto.ClearAudioBufferResponse";
526
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
527
+ ]);
528
+
529
+ static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): ClearAudioBufferResponse {
530
+ return new ClearAudioBufferResponse().fromBinary(bytes, options);
531
+ }
532
+
533
+ static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): ClearAudioBufferResponse {
534
+ return new ClearAudioBufferResponse().fromJson(jsonValue, options);
535
+ }
536
+
537
+ static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): ClearAudioBufferResponse {
538
+ return new ClearAudioBufferResponse().fromJsonString(jsonString, options);
539
+ }
540
+
541
+ static equals(a: ClearAudioBufferResponse | PlainMessage<ClearAudioBufferResponse> | undefined, b: ClearAudioBufferResponse | PlainMessage<ClearAudioBufferResponse> | undefined): boolean {
542
+ return proto3.util.equals(ClearAudioBufferResponse, a, b);
543
+ }
544
+ }
545
+
478
546
  /**
479
547
  * Create a new AudioResampler
480
548
  *