@livekit/rtc-node 0.13.10 → 0.13.12

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 (62) hide show
  1. package/dist/audio_filter.cjs +48 -0
  2. package/dist/audio_filter.cjs.map +1 -0
  3. package/dist/audio_filter.d.cts +5 -0
  4. package/dist/audio_filter.d.ts +5 -0
  5. package/dist/audio_filter.d.ts.map +1 -0
  6. package/dist/audio_filter.js +24 -0
  7. package/dist/audio_filter.js.map +1 -0
  8. package/dist/audio_source.cjs +5 -1
  9. package/dist/audio_source.cjs.map +1 -1
  10. package/dist/audio_source.d.ts.map +1 -1
  11. package/dist/audio_source.js +5 -1
  12. package/dist/audio_source.js.map +1 -1
  13. package/dist/audio_stream.cjs +30 -41
  14. package/dist/audio_stream.cjs.map +1 -1
  15. package/dist/audio_stream.d.cts +18 -23
  16. package/dist/audio_stream.d.ts +18 -23
  17. package/dist/audio_stream.d.ts.map +1 -1
  18. package/dist/audio_stream.js +30 -41
  19. package/dist/audio_stream.js.map +1 -1
  20. package/dist/index.cjs +3 -0
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/proto/audio_frame_pb.cjs +312 -2
  28. package/dist/proto/audio_frame_pb.cjs.map +1 -1
  29. package/dist/proto/audio_frame_pb.d.cts +278 -1
  30. package/dist/proto/audio_frame_pb.d.ts +278 -1
  31. package/dist/proto/audio_frame_pb.d.ts.map +1 -1
  32. package/dist/proto/audio_frame_pb.js +301 -2
  33. package/dist/proto/audio_frame_pb.js.map +1 -1
  34. package/dist/proto/ffi_pb.cjs +12 -2
  35. package/dist/proto/ffi_pb.cjs.map +1 -1
  36. package/dist/proto/ffi_pb.d.cts +65 -1
  37. package/dist/proto/ffi_pb.d.ts +65 -1
  38. package/dist/proto/ffi_pb.d.ts.map +1 -1
  39. package/dist/proto/ffi_pb.js +13 -3
  40. package/dist/proto/ffi_pb.js.map +1 -1
  41. package/dist/version.cjs +1 -1
  42. package/dist/version.cjs.map +1 -1
  43. package/dist/version.d.cts +1 -1
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/version.js.map +1 -1
  47. package/dist/video_stream.cjs +20 -48
  48. package/dist/video_stream.cjs.map +1 -1
  49. package/dist/video_stream.d.cts +4 -19
  50. package/dist/video_stream.d.ts +4 -19
  51. package/dist/video_stream.d.ts.map +1 -1
  52. package/dist/video_stream.js +20 -48
  53. package/dist/video_stream.js.map +1 -1
  54. package/package.json +6 -6
  55. package/src/audio_filter.ts +27 -0
  56. package/src/audio_source.ts +7 -3
  57. package/src/audio_stream.ts +61 -49
  58. package/src/index.ts +2 -0
  59. package/src/proto/audio_frame_pb.ts +533 -0
  60. package/src/proto/ffi_pb.ts +75 -1
  61. package/src/version.ts +1 -1
  62. package/src/video_stream.ts +24 -60
@@ -21,19 +21,16 @@ __export(video_stream_exports, {
21
21
  VideoStream: () => VideoStream
22
22
  });
23
23
  module.exports = __toCommonJS(video_stream_exports);
24
- var import_mutex = require("@livekit/mutex");
24
+ var import_web = require("node:stream/web");
25
25
  var import_ffi_client = require("./ffi_client.cjs");
26
26
  var import_video_frame_pb = require("./proto/video_frame_pb.cjs");
27
27
  var import_video_frame = require("./video_frame.cjs");
28
- class VideoStream {
28
+ class VideoStreamSource {
29
29
  constructor(track) {
30
- /** @internal */
31
- this.eventQueue = [];
32
- /** @internal */
33
- this.queueResolve = null;
34
- /** @internal */
35
- this.mutex = new import_mutex.Mutex();
36
30
  this.onEvent = (ev) => {
31
+ if (!this.controller) {
32
+ throw new Error("Stream controller not initialized");
33
+ }
37
34
  if (ev.message.case != "videoStreamEvent" || ev.message.value.streamHandle != this.ffiHandle.handle) {
38
35
  return;
39
36
  }
@@ -44,31 +41,19 @@ class VideoStream {
44
41
  const timestampUs = streamEvent.value.timestampUs;
45
42
  const frame = import_video_frame.VideoFrame.fromOwnedInfo(streamEvent.value.buffer);
46
43
  const value = { rotation, timestampUs, frame };
47
- if (this.queueResolve) {
48
- this.queueResolve({
49
- done: false,
50
- value: {
51
- frame: value.frame,
52
- timestampUs: value.timestampUs,
53
- rotation: value.rotation
54
- }
55
- });
56
- this.queueResolve = null;
57
- } else {
58
- this.eventQueue.push({
59
- frame: value.frame,
60
- timestampUs: value.timestampUs,
61
- rotation: value.rotation
62
- });
63
- }
44
+ const videoFrameEvent = {
45
+ frame: value.frame,
46
+ timestampUs: value.timestampUs,
47
+ rotation: value.rotation
48
+ };
49
+ this.controller.enqueue(videoFrameEvent);
64
50
  break;
65
51
  case "eos":
66
52
  import_ffi_client.FfiClient.instance.off(import_ffi_client.FfiClientEvent.FfiEvent, this.onEvent);
53
+ this.controller.close();
67
54
  break;
68
55
  }
69
56
  };
70
- var _a;
71
- this.track = track;
72
57
  const req = new import_video_frame_pb.NewVideoStreamRequest({
73
58
  type: import_video_frame_pb.VideoStreamType.VIDEO_STREAM_NATIVE,
74
59
  trackHandle: track.ffi_handle.handle
@@ -79,33 +64,20 @@ class VideoStream {
79
64
  value: req
80
65
  }
81
66
  });
82
- this.info = (_a = res.stream) == null ? void 0 : _a.info;
83
67
  this.ffiHandle = new import_ffi_client.FfiHandle(res.stream.handle.id);
84
68
  import_ffi_client.FfiClient.instance.on(import_ffi_client.FfiClientEvent.FfiEvent, this.onEvent);
85
69
  }
86
- async next() {
87
- const unlock = await this.mutex.lock();
88
- if (this.eventQueue.length > 0) {
89
- unlock();
90
- const value = this.eventQueue.shift();
91
- if (value) {
92
- return { done: false, value };
93
- } else {
94
- return { done: true, value: void 0 };
95
- }
96
- }
97
- const promise = new Promise(
98
- (resolve) => this.queueResolve = resolve
99
- );
100
- unlock();
101
- return promise;
70
+ start(controller) {
71
+ this.controller = controller;
102
72
  }
103
- close() {
104
- this.eventQueue.push(null);
73
+ cancel() {
74
+ import_ffi_client.FfiClient.instance.off(import_ffi_client.FfiClientEvent.FfiEvent, this.onEvent);
105
75
  this.ffiHandle.dispose();
106
76
  }
107
- [Symbol.asyncIterator]() {
108
- return this;
77
+ }
78
+ class VideoStream extends import_web.ReadableStream {
79
+ constructor(track) {
80
+ super(new VideoStreamSource(track));
109
81
  }
110
82
  }
111
83
  // Annotate the CommonJS export names for ESM import in node:
@@ -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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAAsB;AAEtB,wBAAqD;AAMrD,4BAAuD;AAEvD,yBAA2B;AAQpB,MAAM,YAA8D;AAAA,EAczE,YAAY,OAAc;AAR1B;AAAA,sBAAyC,CAAC;AAE1C;AAAA,wBAA0E;AAE1E;AAAA,iBAAQ,IAAI,mBAAM;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,8BAAW,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,sCAAU,SAAS,IAAI,iCAAe,UAAU,KAAK,OAAO;AAC5D;AAAA,MACJ;AAAA,IACF;AA7FF;AAoCI,SAAK,QAAQ;AAEb,UAAM,MAAM,IAAI,4CAAsB;AAAA,MACpC,MAAM,sCAAgB;AAAA,MACtB,aAAa,MAAM,WAAW;AAAA,IAChC,CAAC;AAED,UAAM,MAAM,4BAAU,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,4BAAU,IAAI,OAAQ,OAAQ,EAAG;AAEtD,gCAAU,SAAS,GAAG,iCAAe,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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,iBAAsD;AAEtD,wBAAqD;AAErD,4BAAuD;AAEvD,yBAA2B;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,8BAAW,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,sCAAU,SAAS,IAAI,iCAAe,UAAU,KAAK,OAAO;AAC5D,eAAK,WAAW,MAAM;AACtB;AAAA,MACJ;AAAA,IACF;AA/CE,UAAM,MAAM,IAAI,4CAAsB;AAAA,MACpC,MAAM,sCAAgB;AAAA,MACtB,aAAa,MAAM,WAAW;AAAA,IAChC,CAAC;AAED,UAAM,MAAM,4BAAU,SAAS,QAAgC;AAAA,MAC7D,SAAS;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,SAAK,YAAY,IAAI,4BAAU,IAAI,OAAQ,OAAQ,EAAG;AACtD,gCAAU,SAAS,GAAG,iCAAe,UAAU,KAAK,OAAO;AAAA,EAC7D;AAAA,EAmCA,MAAM,YAA8D;AAClE,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,SAAS;AACP,gCAAU,SAAS,IAAI,iCAAe,UAAU,KAAK,OAAO;AAC5D,SAAK,UAAU,QAAQ;AAAA,EACzB;AACF;AAEO,MAAM,oBAAoB,0BAAgC;AAAA,EAC/D,YAAY,OAAc;AACxB,UAAM,IAAI,kBAAkB,KAAK,CAAC;AAAA,EACpC;AACF;","names":[]}
@@ -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.10",
6
+ "version": "0.13.12",
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.10",
65
- "@livekit/rtc-node-darwin-x64": "0.13.10",
66
- "@livekit/rtc-node-linux-arm64-gnu": "0.13.10",
67
- "@livekit/rtc-node-linux-x64-gnu": "0.13.10",
68
- "@livekit/rtc-node-win32-x64-msvc": "0.13.10"
64
+ "@livekit/rtc-node-darwin-arm64": "0.13.12",
65
+ "@livekit/rtc-node-darwin-x64": "0.13.12",
66
+ "@livekit/rtc-node-linux-arm64-gnu": "0.13.12",
67
+ "@livekit/rtc-node-linux-x64-gnu": "0.13.12",
68
+ "@livekit/rtc-node-win32-x64-msvc": "0.13.12"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">= 18"
@@ -0,0 +1,27 @@
1
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ import { FfiClient } from './ffi_client.js';
5
+ import type { LoadAudioFilterPluginResponse } from './proto/audio_frame_pb.js';
6
+ import { LoadAudioFilterPluginRequest } from './proto/audio_frame_pb.js';
7
+
8
+ export class AudioFilter {
9
+ constructor(moduleId: string, path: string, dependencies: string[] = []) {
10
+ const req = new LoadAudioFilterPluginRequest({
11
+ moduleId,
12
+ pluginPath: path,
13
+ dependencies,
14
+ });
15
+
16
+ const res = FfiClient.instance.request<LoadAudioFilterPluginResponse>({
17
+ message: {
18
+ case: 'loadAudioFilterPlugin',
19
+ value: req,
20
+ },
21
+ });
22
+
23
+ if (res.error) {
24
+ throw new Error(`Failed to initialize audio filter: ${res.error}`);
25
+ }
26
+ }
27
+ }
@@ -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,40 +1,57 @@
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
 
12
- export class AudioStream implements AsyncIterableIterator<AudioFrame> {
13
- /** @internal */
14
- info: AudioStreamInfo;
15
- /** @internal */
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();
12
+ export interface AudioStreamOptions {
13
+ noiseCancellation?: NoiseCancellationOptions;
14
+ sampleRate?: number;
15
+ numChannels?: number;
16
+ }
17
+
18
+ export interface NoiseCancellationOptions {
19
+ moduleId: string;
20
+ options: Record<string, any>;
21
+ }
23
22
 
24
- track: Track;
25
- sampleRate: number;
26
- numChannels: number;
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;
27
29
 
28
- constructor(track: Track, sampleRate: number = 48000, numChannels: number = 1) {
29
- this.track = track;
30
- this.sampleRate = sampleRate;
31
- this.numChannels = numChannels;
30
+ constructor(
31
+ track: Track,
32
+ sampleRateOrOptions?: number | AudioStreamOptions,
33
+ numChannels?: number,
34
+ ) {
35
+ if (sampleRateOrOptions !== undefined && typeof sampleRateOrOptions !== 'number') {
36
+ this.sampleRate = sampleRateOrOptions.sampleRate ?? 48000;
37
+ this.numChannels = sampleRateOrOptions.numChannels ?? 1;
38
+ this.ncOptions = sampleRateOrOptions.noiseCancellation;
39
+ } else {
40
+ this.sampleRate = (sampleRateOrOptions as number) ?? 48000;
41
+ this.numChannels = numChannels ?? 1;
42
+ }
32
43
 
33
44
  const req = new NewAudioStreamRequest({
34
45
  type: AudioStreamType.AUDIO_STREAM_NATIVE,
35
46
  trackHandle: track.ffi_handle.handle,
36
- sampleRate: sampleRate,
37
- numChannels: numChannels,
47
+ sampleRate: this.sampleRate,
48
+ numChannels: this.numChannels,
49
+ ...(this.ncOptions
50
+ ? {
51
+ audioFilterModuleId: this.ncOptions.moduleId,
52
+ audioFilterOptions: JSON.stringify(this.ncOptions.options),
53
+ }
54
+ : {}),
38
55
  });
39
56
 
40
57
  const res = FfiClient.instance.request<NewAudioStreamResponse>({
@@ -44,13 +61,16 @@ export class AudioStream implements AsyncIterableIterator<AudioFrame> {
44
61
  },
45
62
  });
46
63
 
47
- this.info = res.stream!.info!;
48
64
  this.ffiHandle = new FfiHandle(res.stream!.handle!.id!);
49
65
 
50
66
  FfiClient.instance.on(FfiClientEvent.FfiEvent, this.onEvent);
51
67
  }
52
68
 
53
69
  private onEvent = (ev: FfiEvent) => {
70
+ if (!this.controller) {
71
+ throw new Error('Stream controller not initialized');
72
+ }
73
+
54
74
  if (
55
75
  ev.message.case != 'audioStreamEvent' ||
56
76
  ev.message.value.streamHandle != this.ffiHandle.handle
@@ -62,43 +82,35 @@ export class AudioStream implements AsyncIterableIterator<AudioFrame> {
62
82
  switch (streamEvent.case) {
63
83
  case 'frameReceived':
64
84
  const frame = AudioFrame.fromOwnedInfo(streamEvent.value.frame!);
65
- if (this.queueResolve) {
66
- this.queueResolve({ done: false, value: frame });
67
- this.queueResolve = null;
68
- } else {
69
- this.eventQueue.push(frame);
70
- }
85
+ this.controller.enqueue(frame);
71
86
  break;
72
87
  case 'eos':
73
88
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
89
+ this.controller.close();
74
90
  break;
75
91
  }
76
92
  };
77
93
 
78
- async next(): Promise<IteratorResult<AudioFrame>> {
79
- const unlock = await this.mutex.lock();
80
- if (this.eventQueue.length > 0) {
81
- unlock();
82
- const value = this.eventQueue.shift();
83
- if (value) {
84
- return { done: false, value };
85
- } else {
86
- return { done: true, value: undefined };
87
- }
88
- }
89
- const promise = new Promise<IteratorResult<AudioFrame>>(
90
- (resolve) => (this.queueResolve = resolve),
91
- );
92
- unlock();
93
- return promise;
94
+ start(controller: ReadableStreamDefaultController<AudioFrame>) {
95
+ this.controller = controller;
94
96
  }
95
97
 
96
- close() {
97
- this.eventQueue.push(null);
98
+ cancel() {
99
+ FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
98
100
  this.ffiHandle.dispose();
99
101
  }
102
+ }
100
103
 
101
- [Symbol.asyncIterator](): AudioStream {
102
- 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));
103
115
  }
104
116
  }
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export { AudioFrame, combineAudioFrames } from './audio_frame.js';
6
6
  export { AudioResampler, AudioResamplerQuality } from './audio_resampler.js';
7
7
  export { AudioSource } from './audio_source.js';
8
8
  export { AudioStream } from './audio_stream.js';
9
+ export type { NoiseCancellationOptions } from './audio_stream.js';
10
+ export { AudioFilter } from './audio_filter.js';
9
11
  export * from './data_streams/index.js';
10
12
  export { E2EEManager, FrameCryptor, KeyProvider } from './e2ee.js';
11
13
  export type { E2EEOptions, KeyProviderOptions } from './e2ee.js';