@livekit/rtc-node 0.13.11 → 0.13.13

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 (43) hide show
  1. package/dist/audio_filter.cjs.map +1 -1
  2. package/dist/audio_filter.d.ts.map +1 -1
  3. package/dist/audio_filter.js +1 -4
  4. package/dist/audio_filter.js.map +1 -1
  5. package/dist/audio_source.cjs +5 -1
  6. package/dist/audio_source.cjs.map +1 -1
  7. package/dist/audio_source.d.ts.map +1 -1
  8. package/dist/audio_source.js +5 -1
  9. package/dist/audio_source.js.map +1 -1
  10. package/dist/audio_stream.cjs +15 -36
  11. package/dist/audio_stream.cjs.map +1 -1
  12. package/dist/audio_stream.d.cts +4 -22
  13. package/dist/audio_stream.d.ts +4 -22
  14. package/dist/audio_stream.d.ts.map +1 -1
  15. package/dist/audio_stream.js +15 -36
  16. package/dist/audio_stream.js.map +1 -1
  17. package/dist/index.d.cts +0 -1
  18. package/dist/index.d.ts +0 -1
  19. package/dist/room.cjs +1 -0
  20. package/dist/room.cjs.map +1 -1
  21. package/dist/room.d.ts.map +1 -1
  22. package/dist/room.js +1 -0
  23. package/dist/room.js.map +1 -1
  24. package/dist/version.cjs +1 -1
  25. package/dist/version.cjs.map +1 -1
  26. package/dist/version.d.cts +1 -1
  27. package/dist/version.d.ts +1 -1
  28. package/dist/version.js +1 -1
  29. package/dist/version.js.map +1 -1
  30. package/dist/video_stream.cjs +20 -48
  31. package/dist/video_stream.cjs.map +1 -1
  32. package/dist/video_stream.d.cts +4 -19
  33. package/dist/video_stream.d.ts +4 -19
  34. package/dist/video_stream.d.ts.map +1 -1
  35. package/dist/video_stream.js +20 -48
  36. package/dist/video_stream.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/audio_filter.ts +2 -4
  39. package/src/audio_source.ts +7 -3
  40. package/src/audio_stream.ts +30 -51
  41. package/src/room.ts +2 -0
  42. package/src/version.ts +1 -1
  43. package/src/video_stream.ts +24 -60
@@ -1,6 +1,5 @@
1
- import { Mutex } from '@livekit/mutex';
2
- import { FfiHandle } from './napi/native.d.cjs';
3
- import { VideoRotation, VideoStreamInfo } from './proto/video_frame_pb.cjs';
1
+ import { ReadableStream } from 'node:stream/web';
2
+ import { VideoRotation } from './proto/video_frame_pb.cjs';
4
3
  import { Track } from './track.cjs';
5
4
  import { VideoFrame } from './video_frame.cjs';
6
5
  import '@bufbuild/protobuf';
@@ -11,6 +10,7 @@ import './proto/handle_pb.cjs';
11
10
  import './audio_source.cjs';
12
11
  import './audio_frame.cjs';
13
12
  import './proto/audio_frame_pb.cjs';
13
+ import './napi/native.d.cjs';
14
14
  import './video_source.cjs';
15
15
 
16
16
  type VideoFrameEvent = {
@@ -18,23 +18,8 @@ type VideoFrameEvent = {
18
18
  timestampUs: bigint;
19
19
  rotation: VideoRotation;
20
20
  };
21
- declare class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
22
- /** @internal */
23
- info?: VideoStreamInfo;
24
- /** @internal */
25
- ffiHandle: FfiHandle;
26
- /** @internal */
27
- eventQueue: (VideoFrameEvent | null)[];
28
- /** @internal */
29
- queueResolve: ((value: IteratorResult<VideoFrameEvent>) => void) | null;
30
- /** @internal */
31
- mutex: Mutex;
32
- track: Track;
21
+ declare class VideoStream extends ReadableStream<VideoFrameEvent> {
33
22
  constructor(track: Track);
34
- private onEvent;
35
- next(): Promise<IteratorResult<VideoFrameEvent>>;
36
- close(): void;
37
- [Symbol.asyncIterator](): VideoStream;
38
23
  }
39
24
 
40
25
  export { type VideoFrameEvent, VideoStream };
@@ -1,6 +1,5 @@
1
- import { Mutex } from '@livekit/mutex';
2
- import { FfiHandle } from './napi/native.d.js';
3
- import { VideoRotation, VideoStreamInfo } from './proto/video_frame_pb.js';
1
+ import { ReadableStream } from 'node:stream/web';
2
+ import { VideoRotation } from './proto/video_frame_pb.js';
4
3
  import { Track } from './track.js';
5
4
  import { VideoFrame } from './video_frame.js';
6
5
  import '@bufbuild/protobuf';
@@ -11,6 +10,7 @@ import './proto/handle_pb.js';
11
10
  import './audio_source.js';
12
11
  import './audio_frame.js';
13
12
  import './proto/audio_frame_pb.js';
13
+ import './napi/native.d.js';
14
14
  import './video_source.js';
15
15
 
16
16
  type VideoFrameEvent = {
@@ -18,23 +18,8 @@ type VideoFrameEvent = {
18
18
  timestampUs: bigint;
19
19
  rotation: VideoRotation;
20
20
  };
21
- declare class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
22
- /** @internal */
23
- info?: VideoStreamInfo;
24
- /** @internal */
25
- ffiHandle: FfiHandle;
26
- /** @internal */
27
- eventQueue: (VideoFrameEvent | null)[];
28
- /** @internal */
29
- queueResolve: ((value: IteratorResult<VideoFrameEvent>) => void) | null;
30
- /** @internal */
31
- mutex: Mutex;
32
- track: Track;
21
+ declare class VideoStream extends ReadableStream<VideoFrameEvent> {
33
22
  constructor(track: Track);
34
- private onEvent;
35
- next(): Promise<IteratorResult<VideoFrameEvent>>;
36
- close(): void;
37
- [Symbol.asyncIterator](): VideoStream;
38
23
  }
39
24
 
40
25
  export { type VideoFrameEvent, VideoStream };
@@ -1 +1 @@
1
- {"version":3,"file":"video_stream.d.ts","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEvC,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,qBAAa,WAAY,YAAW,qBAAqB,CAAC,eAAe,CAAC;IACxE,gBAAgB;IAChB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,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,CAqCb;IAEI,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;IAkBtD,KAAK;IAKL,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,WAAW;CAGtC"}
1
+ {"version":3,"file":"video_stream.d.ts","sourceRoot":"","sources":["../src/video_stream.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAyB,MAAM,iBAAiB,CAAC;AAGxE,OAAO,KAAK,EAA0B,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEvF,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;AAkEF,qBAAa,WAAY,SAAQ,cAAc,CAAC,eAAe,CAAC;gBAClD,KAAK,EAAE,KAAK;CAGzB"}
@@ -1,16 +1,13 @@
1
- import { Mutex } from "@livekit/mutex";
1
+ import { ReadableStream } from "node:stream/web";
2
2
  import { FfiClient, FfiClientEvent, FfiHandle } from "./ffi_client.js";
3
3
  import { NewVideoStreamRequest, VideoStreamType } from "./proto/video_frame_pb.js";
4
4
  import { VideoFrame } from "./video_frame.js";
5
- class VideoStream {
5
+ class VideoStreamSource {
6
6
  constructor(track) {
7
- /** @internal */
8
- this.eventQueue = [];
9
- /** @internal */
10
- this.queueResolve = null;
11
- /** @internal */
12
- this.mutex = new Mutex();
13
7
  this.onEvent = (ev) => {
8
+ if (!this.controller) {
9
+ throw new Error("Stream controller not initialized");
10
+ }
14
11
  if (ev.message.case != "videoStreamEvent" || ev.message.value.streamHandle != this.ffiHandle.handle) {
15
12
  return;
16
13
  }
@@ -21,31 +18,19 @@ class VideoStream {
21
18
  const timestampUs = streamEvent.value.timestampUs;
22
19
  const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer);
23
20
  const value = { rotation, timestampUs, frame };
24
- if (this.queueResolve) {
25
- this.queueResolve({
26
- done: false,
27
- value: {
28
- frame: value.frame,
29
- timestampUs: value.timestampUs,
30
- rotation: value.rotation
31
- }
32
- });
33
- this.queueResolve = null;
34
- } else {
35
- this.eventQueue.push({
36
- frame: value.frame,
37
- timestampUs: value.timestampUs,
38
- rotation: value.rotation
39
- });
40
- }
21
+ const videoFrameEvent = {
22
+ frame: value.frame,
23
+ timestampUs: value.timestampUs,
24
+ rotation: value.rotation
25
+ };
26
+ this.controller.enqueue(videoFrameEvent);
41
27
  break;
42
28
  case "eos":
43
29
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
30
+ this.controller.close();
44
31
  break;
45
32
  }
46
33
  };
47
- var _a;
48
- this.track = track;
49
34
  const req = new NewVideoStreamRequest({
50
35
  type: VideoStreamType.VIDEO_STREAM_NATIVE,
51
36
  trackHandle: track.ffi_handle.handle
@@ -56,33 +41,20 @@ class VideoStream {
56
41
  value: req
57
42
  }
58
43
  });
59
- this.info = (_a = res.stream) == null ? void 0 : _a.info;
60
44
  this.ffiHandle = new FfiHandle(res.stream.handle.id);
61
45
  FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);
62
46
  }
63
- async next() {
64
- const unlock = await this.mutex.lock();
65
- if (this.eventQueue.length > 0) {
66
- unlock();
67
- const value = this.eventQueue.shift();
68
- if (value) {
69
- return { done: false, value };
70
- } else {
71
- return { done: true, value: void 0 };
72
- }
73
- }
74
- const promise = new Promise(
75
- (resolve) => this.queueResolve = resolve
76
- );
77
- unlock();
78
- return promise;
47
+ start(controller) {
48
+ this.controller = controller;
79
49
  }
80
- close() {
81
- this.eventQueue.push(null);
50
+ cancel() {
51
+ FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
82
52
  this.ffiHandle.dispose();
83
53
  }
84
- [Symbol.asyncIterator]() {
85
- return this;
54
+ }
55
+ class VideoStream extends ReadableStream {
56
+ constructor(track) {
57
+ super(new VideoStreamSource(track));
86
58
  }
87
59
  }
88
60
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/video_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Mutex } from '@livekit/mutex';\nimport type { FfiEvent } from './ffi_client.js';\nimport { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';\nimport type {\n NewVideoStreamResponse,\n VideoRotation,\n VideoStreamInfo,\n} from './proto/video_frame_pb.js';\nimport { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';\nimport type { Track } from './track.js';\nimport { VideoFrame } from './video_frame.js';\n\nexport type VideoFrameEvent = {\n frame: VideoFrame;\n timestampUs: bigint;\n rotation: VideoRotation;\n};\n\nexport class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {\n /** @internal */\n info?: VideoStreamInfo;\n /** @internal */\n ffiHandle: FfiHandle;\n /** @internal */\n eventQueue: (VideoFrameEvent | null)[] = [];\n /** @internal */\n queueResolve: ((value: IteratorResult<VideoFrameEvent>) => void) | null = null;\n /** @internal */\n mutex = new Mutex();\n\n track: Track;\n\n constructor(track: Track) {\n this.track = track;\n\n const req = new NewVideoStreamRequest({\n type: VideoStreamType.VIDEO_STREAM_NATIVE,\n trackHandle: track.ffi_handle.handle,\n });\n\n const res = FfiClient.instance.request<NewVideoStreamResponse>({\n message: {\n case: 'newVideoStream',\n value: req,\n },\n });\n\n this.info = res.stream?.info;\n this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);\n\n FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);\n }\n\n private onEvent = (ev: FfiEvent) => {\n if (\n ev.message.case != 'videoStreamEvent' ||\n ev.message.value.streamHandle != this.ffiHandle.handle\n ) {\n return;\n }\n\n const streamEvent = ev.message.value.message;\n switch (streamEvent.case) {\n case 'frameReceived':\n const rotation = streamEvent.value.rotation;\n const timestampUs = streamEvent.value.timestampUs;\n const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer!);\n const value = { rotation, timestampUs, frame };\n if (this.queueResolve) {\n this.queueResolve({\n done: false,\n value: {\n frame: value.frame,\n timestampUs: value.timestampUs!,\n rotation: value.rotation!,\n },\n });\n this.queueResolve = null;\n } else {\n this.eventQueue.push({\n frame: value.frame,\n timestampUs: value.timestampUs!,\n rotation: value.rotation!,\n });\n }\n break;\n case 'eos':\n FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);\n break;\n }\n };\n\n async next(): Promise<IteratorResult<VideoFrameEvent>> {\n const unlock = await this.mutex.lock();\n if (this.eventQueue.length > 0) {\n unlock();\n const value = this.eventQueue.shift();\n if (value) {\n return { done: false, value };\n } else {\n return { done: true, value: undefined };\n }\n }\n const promise = new Promise<IteratorResult<VideoFrameEvent>>(\n (resolve) => (this.queueResolve = resolve),\n );\n unlock();\n return promise;\n }\n\n close() {\n this.eventQueue.push(null);\n this.ffiHandle.dispose();\n }\n\n [Symbol.asyncIterator](): VideoStream {\n return this;\n }\n}\n"],"mappings":"AAGA,SAAS,aAAa;AAEtB,SAAS,WAAW,gBAAgB,iBAAiB;AAMrD,SAAS,uBAAuB,uBAAuB;AAEvD,SAAS,kBAAkB;AAQpB,MAAM,YAA8D;AAAA,EAczE,YAAY,OAAc;AAR1B;AAAA,sBAAyC,CAAC;AAE1C;AAAA,wBAA0E;AAE1E;AAAA,iBAAQ,IAAI,MAAM;AAyBlB,SAAQ,UAAU,CAAC,OAAiB;AAClC,UACE,GAAG,QAAQ,QAAQ,sBACnB,GAAG,QAAQ,MAAM,gBAAgB,KAAK,UAAU,QAChD;AACA;AAAA,MACF;AAEA,YAAM,cAAc,GAAG,QAAQ,MAAM;AACrC,cAAQ,YAAY,MAAM;AAAA,QACxB,KAAK;AACH,gBAAM,WAAW,YAAY,MAAM;AACnC,gBAAM,cAAc,YAAY,MAAM;AACtC,gBAAM,QAAQ,WAAW,cAAc,YAAY,MAAM,MAAO;AAChE,gBAAM,QAAQ,EAAE,UAAU,aAAa,MAAM;AAC7C,cAAI,KAAK,cAAc;AACrB,iBAAK,aAAa;AAAA,cAChB,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,OAAO,MAAM;AAAA,gBACb,aAAa,MAAM;AAAA,gBACnB,UAAU,MAAM;AAAA,cAClB;AAAA,YACF,CAAC;AACD,iBAAK,eAAe;AAAA,UACtB,OAAO;AACL,iBAAK,WAAW,KAAK;AAAA,cACnB,OAAO,MAAM;AAAA,cACb,aAAa,MAAM;AAAA,cACnB,UAAU,MAAM;AAAA,YAClB,CAAC;AAAA,UACH;AACA;AAAA,QACF,KAAK;AACH,oBAAU,SAAS,IAAI,eAAe,UAAU,KAAK,OAAO;AAC5D;AAAA,MACJ;AAAA,IACF;AA7FF;AAoCI,SAAK,QAAQ;AAEb,UAAM,MAAM,IAAI,sBAAsB;AAAA,MACpC,MAAM,gBAAgB;AAAA,MACtB,aAAa,MAAM,WAAW;AAAA,IAChC,CAAC;AAED,UAAM,MAAM,UAAU,SAAS,QAAgC;AAAA,MAC7D,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,QAAO,SAAI,WAAJ,mBAAY;AACxB,SAAK,YAAY,IAAI,UAAU,IAAI,OAAQ,OAAQ,EAAG;AAEtD,cAAU,SAAS,GAAG,eAAe,UAAU,KAAK,OAAO;AAAA,EAC7D;AAAA,EAyCA,MAAM,OAAiD;AACrD,UAAM,SAAS,MAAM,KAAK,MAAM,KAAK;AACrC,QAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,aAAO;AACP,YAAM,QAAQ,KAAK,WAAW,MAAM;AACpC,UAAI,OAAO;AACT,eAAO,EAAE,MAAM,OAAO,MAAM;AAAA,MAC9B,OAAO;AACL,eAAO,EAAE,MAAM,MAAM,OAAO,OAAU;AAAA,MACxC;AAAA,IACF;AACA,UAAM,UAAU,IAAI;AAAA,MAClB,CAAC,YAAa,KAAK,eAAe;AAAA,IACpC;AACA,WAAO;AACP,WAAO;AAAA,EACT;AAAA,EAEA,QAAQ;AACN,SAAK,WAAW,KAAK,IAAI;AACzB,SAAK,UAAU,QAAQ;AAAA,EACzB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAiB;AACpC,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/video_stream.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { ReadableStream, type UnderlyingSource } from 'node:stream/web';\nimport type { FfiEvent } from './ffi_client.js';\nimport { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';\nimport type { NewVideoStreamResponse, VideoRotation } from './proto/video_frame_pb.js';\nimport { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';\nimport type { Track } from './track.js';\nimport { VideoFrame } from './video_frame.js';\n\nexport type VideoFrameEvent = {\n frame: VideoFrame;\n timestampUs: bigint;\n rotation: VideoRotation;\n};\n\nclass VideoStreamSource implements UnderlyingSource<VideoFrameEvent> {\n private controller?: ReadableStreamDefaultController<VideoFrameEvent>;\n private ffiHandle: FfiHandle;\n\n constructor(track: Track) {\n const req = new NewVideoStreamRequest({\n type: VideoStreamType.VIDEO_STREAM_NATIVE,\n trackHandle: track.ffi_handle.handle,\n });\n\n const res = FfiClient.instance.request<NewVideoStreamResponse>({\n message: {\n case: 'newVideoStream',\n value: req,\n },\n });\n\n this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);\n FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);\n }\n\n private onEvent = (ev: FfiEvent) => {\n if (!this.controller) {\n throw new Error('Stream controller not initialized');\n }\n\n if (\n ev.message.case != 'videoStreamEvent' ||\n ev.message.value.streamHandle != this.ffiHandle.handle\n ) {\n return;\n }\n\n const streamEvent = ev.message.value.message;\n switch (streamEvent.case) {\n case 'frameReceived':\n const rotation = streamEvent.value.rotation;\n const timestampUs = streamEvent.value.timestampUs;\n const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer!);\n const value = { rotation, timestampUs, frame };\n const videoFrameEvent = {\n frame: value.frame,\n timestampUs: value.timestampUs!,\n rotation: value.rotation!,\n };\n this.controller.enqueue(videoFrameEvent);\n break;\n case 'eos':\n FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);\n this.controller.close();\n break;\n }\n };\n\n start(controller: ReadableStreamDefaultController<VideoFrameEvent>) {\n this.controller = controller;\n }\n\n cancel() {\n FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);\n this.ffiHandle.dispose();\n }\n}\n\nexport class VideoStream extends ReadableStream<VideoFrameEvent> {\n constructor(track: Track) {\n super(new VideoStreamSource(track));\n }\n}\n"],"mappings":"AAGA,SAAS,sBAA6C;AAEtD,SAAS,WAAW,gBAAgB,iBAAiB;AAErD,SAAS,uBAAuB,uBAAuB;AAEvD,SAAS,kBAAkB;AAQ3B,MAAM,kBAA+D;AAAA,EAInE,YAAY,OAAc;AAiB1B,SAAQ,UAAU,CAAC,OAAiB;AAClC,UAAI,CAAC,KAAK,YAAY;AACpB,cAAM,IAAI,MAAM,mCAAmC;AAAA,MACrD;AAEA,UACE,GAAG,QAAQ,QAAQ,sBACnB,GAAG,QAAQ,MAAM,gBAAgB,KAAK,UAAU,QAChD;AACA;AAAA,MACF;AAEA,YAAM,cAAc,GAAG,QAAQ,MAAM;AACrC,cAAQ,YAAY,MAAM;AAAA,QACxB,KAAK;AACH,gBAAM,WAAW,YAAY,MAAM;AACnC,gBAAM,cAAc,YAAY,MAAM;AACtC,gBAAM,QAAQ,WAAW,cAAc,YAAY,MAAM,MAAO;AAChE,gBAAM,QAAQ,EAAE,UAAU,aAAa,MAAM;AAC7C,gBAAM,kBAAkB;AAAA,YACtB,OAAO,MAAM;AAAA,YACb,aAAa,MAAM;AAAA,YACnB,UAAU,MAAM;AAAA,UAClB;AACA,eAAK,WAAW,QAAQ,eAAe;AACvC;AAAA,QACF,KAAK;AACH,oBAAU,SAAS,IAAI,eAAe,UAAU,KAAK,OAAO;AAC5D,eAAK,WAAW,MAAM;AACtB;AAAA,MACJ;AAAA,IACF;AA/CE,UAAM,MAAM,IAAI,sBAAsB;AAAA,MACpC,MAAM,gBAAgB;AAAA,MACtB,aAAa,MAAM,WAAW;AAAA,IAChC,CAAC;AAED,UAAM,MAAM,UAAU,SAAS,QAAgC;AAAA,MAC7D,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,YAAY,IAAI,UAAU,IAAI,OAAQ,OAAQ,EAAG;AACtD,cAAU,SAAS,GAAG,eAAe,UAAU,KAAK,OAAO;AAAA,EAC7D;AAAA,EAmCA,MAAM,YAA8D;AAClE,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,SAAS;AACP,cAAU,SAAS,IAAI,eAAe,UAAU,KAAK,OAAO;AAC5D,SAAK,UAAU,QAAQ;AAAA,EACzB;AACF;AAEO,MAAM,oBAAoB,eAAgC;AAAA,EAC/D,YAAY,OAAc;AACxB,UAAM,IAAI,kBAAkB,KAAK,CAAC;AAAA,EACpC;AACF;","names":[]}
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.13.11",
6
+ "version": "0.13.13",
7
7
  "main": "dist/index.js",
8
8
  "require": "dist/index.cjs",
9
9
  "types": "dist/index.d.ts",
@@ -61,11 +61,11 @@
61
61
  "@bufbuild/protoc-gen-es": "^1.10.0"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@livekit/rtc-node-darwin-arm64": "0.13.11",
65
- "@livekit/rtc-node-darwin-x64": "0.13.11",
66
- "@livekit/rtc-node-linux-arm64-gnu": "0.13.11",
67
- "@livekit/rtc-node-linux-x64-gnu": "0.13.11",
68
- "@livekit/rtc-node-win32-x64-msvc": "0.13.11"
64
+ "@livekit/rtc-node-darwin-arm64": "0.13.13",
65
+ "@livekit/rtc-node-darwin-x64": "0.13.13",
66
+ "@livekit/rtc-node-linux-arm64-gnu": "0.13.13",
67
+ "@livekit/rtc-node-linux-x64-gnu": "0.13.13",
68
+ "@livekit/rtc-node-win32-x64-msvc": "0.13.13"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">= 18"
@@ -2,10 +2,8 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { FfiClient } from './ffi_client.js';
5
- import {
6
- LoadAudioFilterPluginRequest,
7
- LoadAudioFilterPluginResponse,
8
- } from './proto/audio_frame_pb.js';
5
+ import type { LoadAudioFilterPluginResponse } from './proto/audio_frame_pb.js';
6
+ import { LoadAudioFilterPluginRequest } from './proto/audio_frame_pb.js';
9
7
 
10
8
  export class AudioFilter {
11
9
  constructor(moduleId: string, path: string, dependencies: string[] = []) {
@@ -99,6 +99,7 @@ export class AudioSource {
99
99
  this.lastCapture = 0;
100
100
  this.currentQueueSize = 0;
101
101
  this.promise = this.newPromise();
102
+ this.timeout = undefined;
102
103
  });
103
104
  }
104
105
 
@@ -106,6 +107,11 @@ export class AudioSource {
106
107
  if (this.closed) {
107
108
  throw new Error('AudioSource is closed');
108
109
  }
110
+
111
+ if (frame.samplesPerChannel === 0) {
112
+ return;
113
+ }
114
+
109
115
  const now = Number(process.hrtime.bigint() / BigInt(1000000));
110
116
  const elapsed = this.lastCapture === 0 ? 0 : now - this.lastCapture;
111
117
  const frameDurationMs = (frame.samplesPerChannel / frame.sampleRate) * 1000;
@@ -117,9 +123,7 @@ export class AudioSource {
117
123
  clearTimeout(this.timeout);
118
124
  }
119
125
 
120
- // remove 50ms to account for processing time
121
- // (e.g. using wait_for_playout for very small chunks)
122
- this.timeout = setTimeout(this.release, this.currentQueueSize - 50);
126
+ this.timeout = setTimeout(this.release, this.currentQueueSize);
123
127
 
124
128
  const req = new CaptureAudioFrameRequest({
125
129
  sourceHandle: this.ffiHandle.handle,
@@ -1,11 +1,11 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { Mutex } from '@livekit/mutex';
4
+ import { ReadableStream, type UnderlyingSource } from 'node:stream/web';
5
5
  import { AudioFrame } from './audio_frame.js';
6
6
  import type { FfiEvent } from './ffi_client.js';
7
7
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
8
- import type { AudioStreamInfo, NewAudioStreamResponse } from './proto/audio_frame_pb.js';
8
+ import type { NewAudioStreamResponse } from './proto/audio_frame_pb.js';
9
9
  import { AudioStreamType, NewAudioStreamRequest } from './proto/audio_frame_pb.js';
10
10
  import type { Track } from './track.js';
11
11
 
@@ -20,34 +20,18 @@ export interface NoiseCancellationOptions {
20
20
  options: Record<string, any>;
21
21
  }
22
22
 
23
- export class AudioStream implements AsyncIterableIterator<AudioFrame> {
24
- /** @internal */
25
- info: AudioStreamInfo;
26
- /** @internal */
27
- ffiHandle: FfiHandle;
28
- /** @internal */
29
- eventQueue: (AudioFrame | null)[] = [];
30
- /** @internal */
31
- queueResolve: ((value: IteratorResult<AudioFrame>) => void) | null = null;
32
- /** @internal */
33
- mutex = new Mutex();
34
-
35
- track: Track;
36
- sampleRate: number;
37
- numChannels: number;
38
- ncOptions?: NoiseCancellationOptions;
39
-
40
- constructor(track: Track);
41
- constructor(track: Track, sampleRate: number);
42
- constructor(track: Track, sampleRate: number, numChannels: number);
43
- constructor(track: Track, options: AudioStreamOptions);
23
+ class AudioStreamSource implements UnderlyingSource<AudioFrame> {
24
+ private controller?: ReadableStreamDefaultController<AudioFrame>;
25
+ private ffiHandle: FfiHandle;
26
+ private sampleRate: number;
27
+ private numChannels: number;
28
+ private ncOptions?: NoiseCancellationOptions;
44
29
 
45
30
  constructor(
46
31
  track: Track,
47
32
  sampleRateOrOptions?: number | AudioStreamOptions,
48
33
  numChannels?: number,
49
34
  ) {
50
- this.track = track;
51
35
  if (sampleRateOrOptions !== undefined && typeof sampleRateOrOptions !== 'number') {
52
36
  this.sampleRate = sampleRateOrOptions.sampleRate ?? 48000;
53
37
  this.numChannels = sampleRateOrOptions.numChannels ?? 1;
@@ -77,13 +61,16 @@ export class AudioStream implements AsyncIterableIterator<AudioFrame> {
77
61
  },
78
62
  });
79
63
 
80
- this.info = res.stream!.info!;
81
64
  this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);
82
65
 
83
66
  FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);
84
67
  }
85
68
 
86
69
  private onEvent = (ev: FfiEvent) => {
70
+ if (!this.controller) {
71
+ throw new Error('Stream controller not initialized');
72
+ }
73
+
87
74
  if (
88
75
  ev.message.case != 'audioStreamEvent' ||
89
76
  ev.message.value.streamHandle != this.ffiHandle.handle
@@ -95,43 +82,35 @@ export class AudioStream implements AsyncIterableIterator<AudioFrame> {
95
82
  switch (streamEvent.case) {
96
83
  case 'frameReceived':
97
84
  const frame = AudioFrame.fromOwnedInfo(streamEvent.value.frame!);
98
- if (this.queueResolve) {
99
- this.queueResolve({ done: false, value: frame });
100
- this.queueResolve = null;
101
- } else {
102
- this.eventQueue.push(frame);
103
- }
85
+ this.controller.enqueue(frame);
104
86
  break;
105
87
  case 'eos':
106
88
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
89
+ this.controller.close();
107
90
  break;
108
91
  }
109
92
  };
110
93
 
111
- async next(): Promise<IteratorResult<AudioFrame>> {
112
- const unlock = await this.mutex.lock();
113
- if (this.eventQueue.length > 0) {
114
- unlock();
115
- const value = this.eventQueue.shift();
116
- if (value) {
117
- return { done: false, value };
118
- } else {
119
- return { done: true, value: undefined };
120
- }
121
- }
122
- const promise = new Promise<IteratorResult<AudioFrame>>(
123
- (resolve) => (this.queueResolve = resolve),
124
- );
125
- unlock();
126
- return promise;
94
+ start(controller: ReadableStreamDefaultController<AudioFrame>) {
95
+ this.controller = controller;
127
96
  }
128
97
 
129
- close() {
130
- this.eventQueue.push(null);
98
+ cancel() {
99
+ FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
131
100
  this.ffiHandle.dispose();
132
101
  }
102
+ }
133
103
 
134
- [Symbol.asyncIterator](): AudioStream {
135
- return this;
104
+ export class AudioStream extends ReadableStream<AudioFrame> {
105
+ constructor(track: Track);
106
+ constructor(track: Track, sampleRate: number);
107
+ constructor(track: Track, sampleRate: number, numChannels: number);
108
+ constructor(track: Track, options: AudioStreamOptions);
109
+ constructor(
110
+ track: Track,
111
+ sampleRateOrOptions?: number | AudioStreamOptions,
112
+ numChannels?: number,
113
+ ) {
114
+ super(new AudioStreamSource(track, sampleRateOrOptions, numChannels));
136
115
  }
137
116
  }
package/src/room.ts CHANGED
@@ -148,6 +148,8 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
148
148
  return ev.message.case == 'connect' && ev.message.value.asyncId == res.asyncId;
149
149
  });
150
150
 
151
+ log.debug('Connect callback received');
152
+
151
153
  switch (cb.message.case) {
152
154
  case 'result':
153
155
  this.ffiHandle = new FfiHandle(cb.message.value.room!.handle!.id!);
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const SDK_VERSION = "0.13.11";
1
+ export const SDK_VERSION = "0.13.13";
@@ -1,14 +1,10 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
- import { Mutex } from '@livekit/mutex';
4
+ import { ReadableStream, type UnderlyingSource } from 'node:stream/web';
5
5
  import type { FfiEvent } from './ffi_client.js';
6
6
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
7
- import type {
8
- NewVideoStreamResponse,
9
- VideoRotation,
10
- VideoStreamInfo,
11
- } from './proto/video_frame_pb.js';
7
+ import type { NewVideoStreamResponse, VideoRotation } from './proto/video_frame_pb.js';
12
8
  import { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';
13
9
  import type { Track } from './track.js';
14
10
  import { VideoFrame } from './video_frame.js';
@@ -19,23 +15,11 @@ export type VideoFrameEvent = {
19
15
  rotation: VideoRotation;
20
16
  };
21
17
 
22
- export class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
23
- /** @internal */
24
- info?: VideoStreamInfo;
25
- /** @internal */
26
- ffiHandle: FfiHandle;
27
- /** @internal */
28
- eventQueue: (VideoFrameEvent | null)[] = [];
29
- /** @internal */
30
- queueResolve: ((value: IteratorResult<VideoFrameEvent>) => void) | null = null;
31
- /** @internal */
32
- mutex = new Mutex();
33
-
34
- track: Track;
18
+ class VideoStreamSource implements UnderlyingSource<VideoFrameEvent> {
19
+ private controller?: ReadableStreamDefaultController<VideoFrameEvent>;
20
+ private ffiHandle: FfiHandle;
35
21
 
36
22
  constructor(track: Track) {
37
- this.track = track;
38
-
39
23
  const req = new NewVideoStreamRequest({
40
24
  type: VideoStreamType.VIDEO_STREAM_NATIVE,
41
25
  trackHandle: track.ffi_handle.handle,
@@ -48,13 +32,15 @@ export class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
48
32
  },
49
33
  });
50
34
 
51
- this.info = res.stream?.info;
52
35
  this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);
53
-
54
36
  FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);
55
37
  }
56
38
 
57
39
  private onEvent = (ev: FfiEvent) => {
40
+ if (!this.controller) {
41
+ throw new Error('Stream controller not initialized');
42
+ }
43
+
58
44
  if (
59
45
  ev.message.case != 'videoStreamEvent' ||
60
46
  ev.message.value.streamHandle != this.ffiHandle.handle
@@ -69,54 +55,32 @@ export class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
69
55
  const timestampUs = streamEvent.value.timestampUs;
70
56
  const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer!);
71
57
  const value = { rotation, timestampUs, frame };
72
- if (this.queueResolve) {
73
- this.queueResolve({
74
- done: false,
75
- value: {
76
- frame: value.frame,
77
- timestampUs: value.timestampUs!,
78
- rotation: value.rotation!,
79
- },
80
- });
81
- this.queueResolve = null;
82
- } else {
83
- this.eventQueue.push({
84
- frame: value.frame,
85
- timestampUs: value.timestampUs!,
86
- rotation: value.rotation!,
87
- });
88
- }
58
+ const videoFrameEvent = {
59
+ frame: value.frame,
60
+ timestampUs: value.timestampUs!,
61
+ rotation: value.rotation!,
62
+ };
63
+ this.controller.enqueue(videoFrameEvent);
89
64
  break;
90
65
  case 'eos':
91
66
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
67
+ this.controller.close();
92
68
  break;
93
69
  }
94
70
  };
95
71
 
96
- async next(): Promise<IteratorResult<VideoFrameEvent>> {
97
- const unlock = await this.mutex.lock();
98
- if (this.eventQueue.length > 0) {
99
- unlock();
100
- const value = this.eventQueue.shift();
101
- if (value) {
102
- return { done: false, value };
103
- } else {
104
- return { done: true, value: undefined };
105
- }
106
- }
107
- const promise = new Promise<IteratorResult<VideoFrameEvent>>(
108
- (resolve) => (this.queueResolve = resolve),
109
- );
110
- unlock();
111
- return promise;
72
+ start(controller: ReadableStreamDefaultController<VideoFrameEvent>) {
73
+ this.controller = controller;
112
74
  }
113
75
 
114
- close() {
115
- this.eventQueue.push(null);
76
+ cancel() {
77
+ FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
116
78
  this.ffiHandle.dispose();
117
79
  }
80
+ }
118
81
 
119
- [Symbol.asyncIterator](): VideoStream {
120
- return this;
82
+ export class VideoStream extends ReadableStream<VideoFrameEvent> {
83
+ constructor(track: Track) {
84
+ super(new VideoStreamSource(track));
121
85
  }
122
86
  }