@livekit/rtc-node 0.8.0 → 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 (57) 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 +1 -3
  32. package/dist/room.js.map +1 -1
  33. package/dist/track_publication.d.ts +5 -0
  34. package/dist/track_publication.d.ts.map +1 -1
  35. package/dist/track_publication.js +14 -0
  36. package/dist/track_publication.js.map +1 -1
  37. package/dist/utils.d.ts +7 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +41 -0
  40. package/dist/utils.js.map +1 -0
  41. package/dist/video_stream.d.ts +10 -10
  42. package/dist/video_stream.d.ts.map +1 -1
  43. package/dist/video_stream.js +35 -11
  44. package/dist/video_stream.js.map +1 -1
  45. package/package.json +7 -7
  46. package/src/audio_source.ts +69 -2
  47. package/src/audio_stream.ts +36 -17
  48. package/src/ffi_client.ts +1 -1
  49. package/src/index.ts +3 -2
  50. package/src/napi/native.d.ts +5 -8
  51. package/src/participant.ts +5 -1
  52. package/src/proto/audio_frame_pb.ts +71 -3
  53. package/src/proto/ffi_pb.ts +31 -17
  54. package/src/room.ts +2 -1
  55. package/src/track_publication.ts +18 -0
  56. package/src/utils.ts +36 -0
  57. package/src/video_stream.ts +37 -13
@@ -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
  *
@@ -22,7 +22,7 @@ import { Message, proto3, protoInt64 } from "@bufbuild/protobuf";
22
22
  import { ConnectCallback, ConnectRequest, ConnectResponse, DisconnectCallback, DisconnectRequest, DisconnectResponse, GetSessionStatsCallback, GetSessionStatsRequest, GetSessionStatsResponse, PublishDataCallback, PublishDataRequest, PublishDataResponse, PublishSipDtmfCallback, PublishSipDtmfRequest, PublishSipDtmfResponse, PublishTrackCallback, PublishTrackRequest, PublishTrackResponse, PublishTranscriptionCallback, PublishTranscriptionRequest, PublishTranscriptionResponse, RoomEvent, SetLocalAttributesCallback, SetLocalAttributesRequest, SetLocalAttributesResponse, SetLocalMetadataCallback, SetLocalMetadataRequest, SetLocalMetadataResponse, SetLocalNameCallback, SetLocalNameRequest, SetLocalNameResponse, SetSubscribedRequest, SetSubscribedResponse, UnpublishTrackCallback, UnpublishTrackRequest, UnpublishTrackResponse } from "./room_pb.js";
23
23
  import { CreateAudioTrackRequest, CreateAudioTrackResponse, CreateVideoTrackRequest, CreateVideoTrackResponse, EnableRemoteTrackRequest, EnableRemoteTrackResponse, GetStatsCallback, GetStatsRequest, GetStatsResponse, LocalTrackMuteRequest, LocalTrackMuteResponse, TrackEvent } from "./track_pb.js";
24
24
  import { CaptureVideoFrameRequest, CaptureVideoFrameResponse, NewVideoSourceRequest, NewVideoSourceResponse, NewVideoStreamRequest, NewVideoStreamResponse, VideoConvertRequest, VideoConvertResponse, VideoStreamEvent, VideoStreamFromParticipantRequest, VideoStreamFromParticipantResponse } from "./video_frame_pb.js";
25
- import { AudioStreamEvent, AudioStreamFromParticipantRequest, AudioStreamFromParticipantResponse, CaptureAudioFrameCallback, CaptureAudioFrameRequest, CaptureAudioFrameResponse, NewAudioResamplerRequest, NewAudioResamplerResponse, NewAudioSourceRequest, NewAudioSourceResponse, NewAudioStreamRequest, NewAudioStreamResponse, RemixAndResampleRequest, RemixAndResampleResponse } from "./audio_frame_pb.js";
25
+ import { AudioStreamEvent, AudioStreamFromParticipantRequest, AudioStreamFromParticipantResponse, CaptureAudioFrameCallback, CaptureAudioFrameRequest, CaptureAudioFrameResponse, ClearAudioBufferRequest, ClearAudioBufferResponse, NewAudioResamplerRequest, NewAudioResamplerResponse, NewAudioSourceRequest, NewAudioSourceResponse, NewAudioStreamRequest, NewAudioStreamResponse, RemixAndResampleRequest, RemixAndResampleResponse } from "./audio_frame_pb.js";
26
26
  import { E2eeRequest, E2eeResponse } from "./e2ee_pb.js";
27
27
 
28
28
  /**
@@ -239,25 +239,31 @@ export class FfiRequest extends Message<FfiRequest> {
239
239
  case: "captureAudioFrame";
240
240
  } | {
241
241
  /**
242
- * @generated from field: livekit.proto.NewAudioResamplerRequest new_audio_resampler = 28;
242
+ * @generated from field: livekit.proto.ClearAudioBufferRequest clear_audio_buffer = 28;
243
+ */
244
+ value: ClearAudioBufferRequest;
245
+ case: "clearAudioBuffer";
246
+ } | {
247
+ /**
248
+ * @generated from field: livekit.proto.NewAudioResamplerRequest new_audio_resampler = 29;
243
249
  */
244
250
  value: NewAudioResamplerRequest;
245
251
  case: "newAudioResampler";
246
252
  } | {
247
253
  /**
248
- * @generated from field: livekit.proto.RemixAndResampleRequest remix_and_resample = 29;
254
+ * @generated from field: livekit.proto.RemixAndResampleRequest remix_and_resample = 30;
249
255
  */
250
256
  value: RemixAndResampleRequest;
251
257
  case: "remixAndResample";
252
258
  } | {
253
259
  /**
254
- * @generated from field: livekit.proto.E2eeRequest e2ee = 30;
260
+ * @generated from field: livekit.proto.E2eeRequest e2ee = 31;
255
261
  */
256
262
  value: E2eeRequest;
257
263
  case: "e2ee";
258
264
  } | {
259
265
  /**
260
- * @generated from field: livekit.proto.AudioStreamFromParticipantRequest audio_stream_from_participant = 31;
266
+ * @generated from field: livekit.proto.AudioStreamFromParticipantRequest audio_stream_from_participant = 32;
261
267
  */
262
268
  value: AudioStreamFromParticipantRequest;
263
269
  case: "audioStreamFromParticipant";
@@ -297,10 +303,11 @@ export class FfiRequest extends Message<FfiRequest> {
297
303
  { no: 25, name: "new_audio_stream", kind: "message", T: NewAudioStreamRequest, oneof: "message" },
298
304
  { no: 26, name: "new_audio_source", kind: "message", T: NewAudioSourceRequest, oneof: "message" },
299
305
  { no: 27, name: "capture_audio_frame", kind: "message", T: CaptureAudioFrameRequest, oneof: "message" },
300
- { no: 28, name: "new_audio_resampler", kind: "message", T: NewAudioResamplerRequest, oneof: "message" },
301
- { no: 29, name: "remix_and_resample", kind: "message", T: RemixAndResampleRequest, oneof: "message" },
302
- { no: 30, name: "e2ee", kind: "message", T: E2eeRequest, oneof: "message" },
303
- { no: 31, name: "audio_stream_from_participant", kind: "message", T: AudioStreamFromParticipantRequest, oneof: "message" },
306
+ { no: 28, name: "clear_audio_buffer", kind: "message", T: ClearAudioBufferRequest, oneof: "message" },
307
+ { no: 29, name: "new_audio_resampler", kind: "message", T: NewAudioResamplerRequest, oneof: "message" },
308
+ { no: 30, name: "remix_and_resample", kind: "message", T: RemixAndResampleRequest, oneof: "message" },
309
+ { no: 31, name: "e2ee", kind: "message", T: E2eeRequest, oneof: "message" },
310
+ { no: 32, name: "audio_stream_from_participant", kind: "message", T: AudioStreamFromParticipantRequest, oneof: "message" },
304
311
  ]);
305
312
 
306
313
  static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): FfiRequest {
@@ -495,25 +502,31 @@ export class FfiResponse extends Message<FfiResponse> {
495
502
  case: "captureAudioFrame";
496
503
  } | {
497
504
  /**
498
- * @generated from field: livekit.proto.NewAudioResamplerResponse new_audio_resampler = 28;
505
+ * @generated from field: livekit.proto.ClearAudioBufferResponse clear_audio_buffer = 28;
506
+ */
507
+ value: ClearAudioBufferResponse;
508
+ case: "clearAudioBuffer";
509
+ } | {
510
+ /**
511
+ * @generated from field: livekit.proto.NewAudioResamplerResponse new_audio_resampler = 29;
499
512
  */
500
513
  value: NewAudioResamplerResponse;
501
514
  case: "newAudioResampler";
502
515
  } | {
503
516
  /**
504
- * @generated from field: livekit.proto.RemixAndResampleResponse remix_and_resample = 29;
517
+ * @generated from field: livekit.proto.RemixAndResampleResponse remix_and_resample = 30;
505
518
  */
506
519
  value: RemixAndResampleResponse;
507
520
  case: "remixAndResample";
508
521
  } | {
509
522
  /**
510
- * @generated from field: livekit.proto.AudioStreamFromParticipantResponse audio_stream_from_participant = 30;
523
+ * @generated from field: livekit.proto.AudioStreamFromParticipantResponse audio_stream_from_participant = 31;
511
524
  */
512
525
  value: AudioStreamFromParticipantResponse;
513
526
  case: "audioStreamFromParticipant";
514
527
  } | {
515
528
  /**
516
- * @generated from field: livekit.proto.E2eeResponse e2ee = 31;
529
+ * @generated from field: livekit.proto.E2eeResponse e2ee = 32;
517
530
  */
518
531
  value: E2eeResponse;
519
532
  case: "e2ee";
@@ -553,10 +566,11 @@ export class FfiResponse extends Message<FfiResponse> {
553
566
  { no: 25, name: "new_audio_stream", kind: "message", T: NewAudioStreamResponse, oneof: "message" },
554
567
  { no: 26, name: "new_audio_source", kind: "message", T: NewAudioSourceResponse, oneof: "message" },
555
568
  { no: 27, name: "capture_audio_frame", kind: "message", T: CaptureAudioFrameResponse, oneof: "message" },
556
- { no: 28, name: "new_audio_resampler", kind: "message", T: NewAudioResamplerResponse, oneof: "message" },
557
- { no: 29, name: "remix_and_resample", kind: "message", T: RemixAndResampleResponse, oneof: "message" },
558
- { no: 30, name: "audio_stream_from_participant", kind: "message", T: AudioStreamFromParticipantResponse, oneof: "message" },
559
- { no: 31, name: "e2ee", kind: "message", T: E2eeResponse, oneof: "message" },
569
+ { no: 28, name: "clear_audio_buffer", kind: "message", T: ClearAudioBufferResponse, oneof: "message" },
570
+ { no: 29, name: "new_audio_resampler", kind: "message", T: NewAudioResamplerResponse, oneof: "message" },
571
+ { no: 30, name: "remix_and_resample", kind: "message", T: RemixAndResampleResponse, oneof: "message" },
572
+ { no: 31, name: "audio_stream_from_participant", kind: "message", T: AudioStreamFromParticipantResponse, oneof: "message" },
573
+ { no: 32, name: "e2ee", kind: "message", T: E2eeResponse, oneof: "message" },
560
574
  ]);
561
575
 
562
576
  static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): FfiResponse {
package/src/room.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
+ import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
4
5
  import EventEmitter from 'events';
5
- import type TypedEmitter from 'typed-emitter';
6
6
  import type { E2EEOptions } from './e2ee.js';
7
7
  import { E2EEManager } from './e2ee.js';
8
8
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
@@ -188,6 +188,7 @@ export class Room extends (EventEmitter as new () => TypedEmitter<RoomCallbacks>
188
188
  this.emit(RoomEvent.LocalTrackUnpublished, publication, this.localParticipant);
189
189
  } else if (ev.case == 'localTrackSubscribed') {
190
190
  const publication = this.localParticipant.trackPublications.get(ev.value.trackSid);
191
+ publication.resolveFirstSubscription();
191
192
  this.emit(RoomEvent.LocalTrackSubscribed, publication.track);
192
193
  } else if (ev.case == 'trackPublished') {
193
194
  const participant = this.remoteParticipants.get(ev.value.participantIdentity);
@@ -69,8 +69,26 @@ export abstract class TrackPublication {
69
69
  }
70
70
 
71
71
  export class LocalTrackPublication extends TrackPublication {
72
+ private firstSubscription: Promise<void>;
73
+ private firstSubscriptionResolver: (() => void) | null = null;
74
+
72
75
  constructor(ownedInfo: OwnedTrackPublication) {
73
76
  super(ownedInfo);
77
+ this.firstSubscription = new Promise<void>((resolve) => {
78
+ this.firstSubscriptionResolver = resolve;
79
+ });
80
+ }
81
+
82
+ async waitForSubscription(): Promise<void> {
83
+ await this.firstSubscription;
84
+ }
85
+
86
+ /** @internal */
87
+ resolveFirstSubscription(): void {
88
+ if (this.firstSubscriptionResolver) {
89
+ this.firstSubscriptionResolver();
90
+ this.firstSubscriptionResolver = null;
91
+ }
74
92
  }
75
93
  }
76
94
 
package/src/utils.ts ADDED
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ export class Mutex {
5
+ #locking: Promise<void>;
6
+ #locks: number;
7
+ #limit: number;
8
+
9
+ constructor(limit = 1) {
10
+ this.#locking = Promise.resolve();
11
+ this.#locks = 0;
12
+ this.#limit = limit;
13
+ }
14
+
15
+ isLocked(): boolean {
16
+ return this.#locks >= this.#limit;
17
+ }
18
+
19
+ async lock(): Promise<() => void> {
20
+ this.#locks += 1;
21
+
22
+ let unlockNext: () => void;
23
+
24
+ const willLock = new Promise<void>(
25
+ (resolve) =>
26
+ (unlockNext = () => {
27
+ this.#locks -= 1;
28
+ resolve();
29
+ }),
30
+ );
31
+
32
+ const willUnlock = this.#locking.then(() => unlockNext);
33
+ this.#locking = this.#locking.then(() => willLock);
34
+ return willUnlock;
35
+ }
36
+ }
@@ -1,8 +1,6 @@
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 type { FfiEvent } from './ffi_client.js';
7
5
  import { FfiClient, FfiClientEvent, FfiHandle } from './ffi_client.js';
8
6
  import type {
@@ -12,6 +10,7 @@ import type {
12
10
  } from './proto/video_frame_pb.js';
13
11
  import { NewVideoStreamRequest, VideoStreamType } from './proto/video_frame_pb.js';
14
12
  import type { Track } from './track.js';
13
+ import { Mutex } from './utils.js';
15
14
  import { VideoFrame } from './video_frame.js';
16
15
 
17
16
  export type VideoFrameEvent = {
@@ -20,24 +19,21 @@ export type VideoFrameEvent = {
20
19
  rotation: VideoRotation;
21
20
  };
22
21
 
23
- export type VideoStreamCallbacks = {
24
- frameReceived: (evt: VideoFrameEvent) => void;
25
- };
26
-
27
- export enum VideoStreamEvent {
28
- FrameReceived = 'frameReceived',
29
- }
30
-
31
- export class VideoStream extends (EventEmitter as new () => TypedEmitter<VideoStreamCallbacks>) {
22
+ export class VideoStream implements AsyncIterableIterator<VideoFrameEvent> {
32
23
  /** @internal */
33
24
  info: VideoStreamInfo;
34
25
  /** @internal */
35
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();
36
33
 
37
34
  track: Track;
38
35
 
39
36
  constructor(track: Track) {
40
- super();
41
37
  this.track = track;
42
38
 
43
39
  const req = new NewVideoStreamRequest({
@@ -72,7 +68,12 @@ export class VideoStream extends (EventEmitter as new () => TypedEmitter<VideoSt
72
68
  const rotation = streamEvent.value.rotation;
73
69
  const timestampUs = streamEvent.value.timestampUs;
74
70
  const frame = VideoFrame.fromOwnedInfo(streamEvent.value.buffer);
75
- this.emit(VideoStreamEvent.FrameReceived, { frame, timestampUs, rotation });
71
+ const value = { rotation, timestampUs, frame };
72
+ if (this.queueResolve) {
73
+ this.queueResolve({ done: false, value });
74
+ } else {
75
+ this.eventQueue.push(value);
76
+ }
76
77
  break;
77
78
  case 'eos':
78
79
  FfiClient.instance.off(FfiClientEvent.FfiEvent, this.onEvent);
@@ -80,7 +81,30 @@ export class VideoStream extends (EventEmitter as new () => TypedEmitter<VideoSt
80
81
  }
81
82
  };
82
83
 
84
+ async next(): Promise<IteratorResult<VideoFrameEvent>> {
85
+ const unlock = await this.mutex.lock();
86
+ if (this.eventQueue.length > 0) {
87
+ unlock();
88
+ const value = this.eventQueue.shift();
89
+ if (value) {
90
+ return { done: false, value };
91
+ } else {
92
+ return { done: true, value: undefined };
93
+ }
94
+ }
95
+ const promise = new Promise<IteratorResult<VideoFrameEvent>>(
96
+ (resolve) => (this.queueResolve = resolve),
97
+ );
98
+ unlock();
99
+ return promise;
100
+ }
101
+
83
102
  close() {
103
+ this.eventQueue.push(null);
84
104
  this.ffiHandle.dispose();
85
105
  }
106
+
107
+ [Symbol.asyncIterator](): VideoStream {
108
+ return this;
109
+ }
86
110
  }