@whereby.com/assistant-sdk 0.0.0-canary-20251002120040 → 0.0.0-canary-20251007140529

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.
@@ -2,16 +2,12 @@ import { WherebyClient } from '@whereby.com/core';
2
2
  import wrtc from '@roamhq/wrtc';
3
3
  import EventEmitter$1, { EventEmitter } from 'events';
4
4
  import { spawn } from 'child_process';
5
- import { PassThrough } from 'stream';
6
5
  import express from 'express';
7
6
  import assert from 'assert';
8
7
  import bodyParser from 'body-parser';
9
- import { networkInterfaces } from 'os';
10
- import * as dotenv from 'dotenv';
11
8
 
12
9
  const TRIGGER_EVENT_SUCCESS = "trigger_event_success";
13
10
 
14
- const AUDIO_STREAM_READY = "AUDIO_STREAM_READY";
15
11
  const ASSISTANT_JOINED_ROOM = "ASSISTANT_JOINED_ROOM";
16
12
  const ASSISTANT_LEFT_ROOM = "ASSISTANT_LEFT_ROOM";
17
13
  const PARTICIPANT_VIDEO_TRACK_ADDED = "PARTICIPANT_VIDEO_TRACK_ADDED";
@@ -19,48 +15,10 @@ const PARTICIPANT_VIDEO_TRACK_REMOVED = "PARTICIPANT_VIDEO_TRACK_REMOVED";
19
15
  const PARTICIPANT_AUDIO_TRACK_ADDED = "PARTICIPANT_AUDIO_TRACK_ADDED";
20
16
  const PARTICIPANT_AUDIO_TRACK_REMOVED = "PARTICIPANT_AUDIO_TRACK_REMOVED";
21
17
 
22
- /******************************************************************************
23
- Copyright (c) Microsoft Corporation.
24
-
25
- Permission to use, copy, modify, and/or distribute this software for any
26
- purpose with or without fee is hereby granted.
27
-
28
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
29
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
30
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
31
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
32
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
33
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
34
- PERFORMANCE OF THIS SOFTWARE.
35
- ***************************************************************************** */
36
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
37
-
38
-
39
- function __awaiter(thisArg, _arguments, P, generator) {
40
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
41
- return new (P || (P = Promise))(function (resolve, reject) {
42
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
43
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
44
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
45
- step((generator = generator.apply(thisArg, _arguments || [])).next());
46
- });
47
- }
48
-
49
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
- var e = new Error(message);
51
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
- };
53
-
54
- const { nonstandard: { RTCAudioSink }, } = wrtc;
55
- class AudioSource extends PassThrough {
56
- constructor() {
57
- super({
58
- allowHalfOpen: true,
59
- highWaterMark: 1 * 1024,
60
- });
61
- }
18
+ const { nonstandard: { RTCAudioSink, RTCAudioSource }, } = wrtc;
19
+ class AudioSource extends RTCAudioSource {
62
20
  }
63
- class AudioSink extends wrtc.nonstandard.RTCAudioSink {
21
+ class AudioSink extends RTCAudioSink {
64
22
  constructor(track) {
65
23
  super(track);
66
24
  this._sink = new RTCAudioSink(track);
@@ -168,7 +126,7 @@ function createFfmpegMixer() {
168
126
  * @param ff Child process handle from spawn("ffmpeg", ...)
169
127
  * @param slotCount Number of participant input slots (0..N-1 → fd 3..3+N-1)
170
128
  */
171
- function startPacer(ff, slotCount, rtcAudioSource, onAudioStreamReady) {
129
+ function startPacer(ff, slotCount, rtcAudioSource) {
172
130
  if (stopPacerFn) {
173
131
  stopPacerFn();
174
132
  stopPacerFn = null;
@@ -186,8 +144,6 @@ function createFfmpegMixer() {
186
144
  frameQueue: [],
187
145
  nextDueMs: t0 + outputFrameMs,
188
146
  rtcAudioSource,
189
- onAudioStreamReady,
190
- didEmitReadyEvent: false,
191
147
  };
192
148
  const iv = setInterval(() => {
193
149
  const t = nowMs();
@@ -215,10 +171,6 @@ function createFfmpegMixer() {
215
171
  const state = outputPacerState;
216
172
  if (t >= state.nextDueMs) {
217
173
  const samples = state.frameQueue.length > 0 ? state.frameQueue.shift() : new Int16Array(FRAME_10MS_SAMPLES); // silence
218
- if (!state.didEmitReadyEvent) {
219
- state.onAudioStreamReady();
220
- state.didEmitReadyEvent = true;
221
- }
222
174
  state.rtcAudioSource.onData({
223
175
  samples: samples,
224
176
  sampleRate: STREAM_INPUT_SAMPLE_RATE_IN_HZ,
@@ -321,11 +273,11 @@ function createFfmpegMixer() {
321
273
  * The process will log its output to stderr.
322
274
  * @return The spawned FFmpeg process.
323
275
  */
324
- function spawnFFmpegProcessDebug(rtcAudioSource, onAudioStreamReady) {
276
+ function spawnFFmpegProcessDebug(rtcAudioSource) {
325
277
  const stdio = ["ignore", "ignore", "pipe", ...Array(PARTICIPANT_SLOTS).fill("pipe")];
326
278
  const args = getFFmpegArgumentsDebug();
327
279
  const ffmpegProcess = spawn("ffmpeg", args, { stdio });
328
- startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource, onAudioStreamReady);
280
+ startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource);
329
281
  ffmpegProcess.stderr.setEncoding("utf8");
330
282
  ffmpegProcess.stderr.on("data", (d) => console.error("[ffmpeg]", String(d).trim()));
331
283
  ffmpegProcess.on("error", () => console.error("FFmpeg process error (debug): is ffmpeg installed?"));
@@ -339,11 +291,11 @@ function createFfmpegMixer() {
339
291
  * @param rtcAudioSource The RTCAudioSource to which the mixed audio will be sent.
340
292
  * @return The spawned FFmpeg process.
341
293
  */
342
- function spawnFFmpegProcess(rtcAudioSource, onAudioStreamReady) {
294
+ function spawnFFmpegProcess(rtcAudioSource) {
343
295
  const stdio = ["pipe", "pipe", "pipe", ...Array(PARTICIPANT_SLOTS).fill("pipe")];
344
296
  const args = getFFmpegArguments();
345
297
  const ffmpegProcess = spawn("ffmpeg", args, { stdio });
346
- startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource, onAudioStreamReady);
298
+ startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource);
347
299
  ffmpegProcess.stderr.setEncoding("utf8");
348
300
  ffmpegProcess.stderr.on("data", (d) => console.error("[ffmpeg]", String(d).trim()));
349
301
  ffmpegProcess.on("error", () => console.error("FFmpeg process error: is ffmpeg installed?"));
@@ -441,7 +393,7 @@ function createFfmpegMixer() {
441
393
  }
442
394
 
443
395
  class AudioMixer extends EventEmitter {
444
- constructor(onStreamReady) {
396
+ constructor() {
445
397
  super();
446
398
  this.ffmpegProcess = null;
447
399
  this.combinedAudioStream = null;
@@ -451,7 +403,6 @@ class AudioMixer extends EventEmitter {
451
403
  this.mixer = createFfmpegMixer();
452
404
  this.setupMediaStream();
453
405
  this.participantSlots = new Map(Array.from({ length: PARTICIPANT_SLOTS }, (_, i) => [i, ""]));
454
- this.onStreamReady = onStreamReady;
455
406
  }
456
407
  setupMediaStream() {
457
408
  this.rtcAudioSource = new wrtc.nonstandard.RTCAudioSource();
@@ -467,7 +418,7 @@ class AudioMixer extends EventEmitter {
467
418
  return;
468
419
  }
469
420
  if (!this.ffmpegProcess && this.rtcAudioSource) {
470
- this.ffmpegProcess = this.mixer.spawnFFmpegProcess(this.rtcAudioSource, this.onStreamReady);
421
+ this.ffmpegProcess = this.mixer.spawnFFmpegProcess(this.rtcAudioSource);
471
422
  }
472
423
  for (const p of participants)
473
424
  this.attachParticipantIfNeeded(p);
@@ -557,12 +508,28 @@ class AudioMixer extends EventEmitter {
557
508
  }
558
509
  }
559
510
 
511
+ const { nonstandard: { RTCVideoSink, RTCVideoSource }, } = wrtc;
512
+ class VideoSource extends RTCVideoSource {
513
+ }
514
+ class VideoSink extends RTCVideoSink {
515
+ constructor(track) {
516
+ super(track);
517
+ this._sink = new RTCVideoSink(track);
518
+ }
519
+ subscribe(cb) {
520
+ this._sink.onframe = cb;
521
+ return () => {
522
+ this._sink.onframe = undefined;
523
+ };
524
+ }
525
+ }
526
+
560
527
  class Assistant extends EventEmitter$1 {
561
- constructor({ assistantKey, startCombinedAudioStream = false, startLocalMedia = false }) {
528
+ constructor({ assistantKey }) {
562
529
  super();
563
- this.mediaStream = null;
564
- this.audioSource = null;
565
- this.combinedStream = null;
530
+ this.localAudioSource = null;
531
+ this.localVideoSource = null;
532
+ this.combinedAudioSink = null;
566
533
  this.remoteMediaTracks = {};
567
534
  this.roomUrl = null;
568
535
  this.stateSubscriptions = [];
@@ -583,28 +550,34 @@ class Assistant extends EventEmitter$1 {
583
550
  const tracks = stream.getTracks();
584
551
  tracks.forEach((track) => {
585
552
  if (!this.remoteMediaTracks[track.id]) {
586
- const eventName = track.kind === "video" ? PARTICIPANT_VIDEO_TRACK_ADDED : PARTICIPANT_AUDIO_TRACK_ADDED;
587
- this.emit(eventName, {
588
- participantId,
589
- stream,
590
- track,
591
- });
553
+ if (track.kind === "video") {
554
+ this.emit(PARTICIPANT_VIDEO_TRACK_ADDED, {
555
+ participantId,
556
+ trackId: track.id,
557
+ data: new VideoSink(track),
558
+ });
559
+ }
560
+ else {
561
+ this.emit(PARTICIPANT_AUDIO_TRACK_ADDED, {
562
+ participantId,
563
+ trackId: track.id,
564
+ data: new AudioSink(track),
565
+ });
566
+ }
592
567
  this.remoteMediaTracks[track.id] = {
593
568
  participantId,
594
- stream,
595
569
  track,
596
570
  };
597
571
  }
598
572
  });
599
573
  return tracks;
600
574
  });
601
- Object.values(this.remoteMediaTracks).forEach(({ participantId, stream, track }) => {
575
+ Object.values(this.remoteMediaTracks).forEach(({ participantId, track }) => {
602
576
  if (!currentRemoteMediaTracks.includes(track)) {
603
577
  const eventName = track.kind === "video" ? PARTICIPANT_VIDEO_TRACK_REMOVED : PARTICIPANT_AUDIO_TRACK_REMOVED;
604
578
  this.emit(eventName, {
605
579
  participantId,
606
- stream,
607
- track,
580
+ trackId: track.id,
608
581
  });
609
582
  delete this.remoteMediaTracks[track.id];
610
583
  }
@@ -614,135 +587,102 @@ class Assistant extends EventEmitter$1 {
614
587
  this.client = new WherebyClient();
615
588
  this.roomConnection = this.client.getRoomConnection();
616
589
  this.localMedia = this.client.getLocalMedia();
617
- if (startLocalMedia) {
618
- const outputAudioSource = new wrtc.nonstandard.RTCAudioSource();
619
- const outputMediaStream = new wrtc.MediaStream([outputAudioSource.createTrack()]);
620
- this.mediaStream = outputMediaStream;
621
- this.audioSource = outputAudioSource;
622
- }
623
- if (startCombinedAudioStream) {
624
- const handleStreamReady = () => {
625
- if (!this.combinedStream) {
626
- console.warn("Combined stream is not available");
627
- return;
628
- }
629
- this.emit(AUDIO_STREAM_READY, {
630
- stream: this.combinedStream,
631
- track: this.combinedStream.getAudioTracks()[0],
632
- });
633
- };
634
- const audioMixer = new AudioMixer(handleStreamReady);
635
- this.combinedStream = audioMixer.getCombinedAudioStream();
636
- this.stateSubscriptions.push(this.roomConnection.subscribeToRemoteParticipants(audioMixer.handleRemoteParticipants.bind(audioMixer)));
637
- }
638
590
  this.stateSubscriptions.push(this.roomConnection.subscribeToConnectionStatus(this.handleConnectionStatusChange));
639
591
  this.stateSubscriptions.push(this.roomConnection.subscribeToRemoteParticipants(this.handleRemoteParticipantsTracksChange));
640
592
  }
641
593
  joinRoom(roomUrl) {
642
- return __awaiter(this, void 0, void 0, function* () {
643
- if (this.mediaStream) {
644
- yield this.localMedia.startMedia(this.mediaStream);
645
- }
646
- this.roomUrl = roomUrl;
647
- this.roomConnection.initialize({
648
- localMediaOptions: {
649
- audio: false,
650
- video: false,
651
- },
652
- roomUrl,
653
- isNodeSdk: true,
654
- assistantKey: this.assistantKey,
655
- });
656
- return this.roomConnection.joinRoom();
594
+ this.roomUrl = roomUrl;
595
+ this.roomConnection.initialize({
596
+ localMediaOptions: {
597
+ audio: false,
598
+ video: false,
599
+ },
600
+ roomUrl,
601
+ isNodeSdk: true,
602
+ assistantKey: this.assistantKey,
657
603
  });
658
- }
659
- startLocalMedia() {
660
- if (!this.mediaStream) {
661
- const outputAudioSource = new wrtc.nonstandard.RTCAudioSource();
662
- const outputMediaStream = new wrtc.MediaStream([outputAudioSource.createTrack()]);
663
- this.mediaStream = outputMediaStream;
664
- this.audioSource = outputAudioSource;
665
- }
666
- this.localMedia.startMedia(this.mediaStream);
667
- }
668
- getLocalMediaStream() {
669
- return this.mediaStream;
670
- }
671
- getLocalAudioSource() {
672
- return this.audioSource;
604
+ return this.roomConnection.joinRoom();
673
605
  }
674
606
  getRoomConnection() {
675
607
  return this.roomConnection;
676
608
  }
677
- getCombinedAudioStream() {
678
- return this.combinedStream;
679
- }
680
- getRemoteParticipants() {
681
- return this.roomConnection.getState().remoteParticipants;
682
- }
683
- startCloudRecording() {
684
- this.roomConnection.startCloudRecording();
685
- }
686
- stopCloudRecording() {
687
- this.roomConnection.stopCloudRecording();
609
+ startLocalMedia() {
610
+ if (Boolean(this.localAudioSource) || Boolean(this.localVideoSource)) {
611
+ return;
612
+ }
613
+ this.localAudioSource = new AudioSource();
614
+ this.localVideoSource = new VideoSource();
615
+ const outputMediaStream = new wrtc.MediaStream([
616
+ this.localAudioSource.createTrack(),
617
+ this.localVideoSource.createTrack(),
618
+ ]);
619
+ this.localMedia.startMedia(outputMediaStream);
620
+ this.localMedia.toggleMicrophone(true);
621
+ }
622
+ stopLocalMedia() {
623
+ this.localMedia.stopMedia();
624
+ this.localAudioSource = null;
625
+ this.localVideoSource = null;
688
626
  }
689
- sendChatMessage(message) {
690
- this.roomConnection.sendChatMessage(message);
627
+ getLocalAudioSource() {
628
+ return this.localAudioSource;
691
629
  }
692
- spotlightParticipant(participantId) {
693
- this.roomConnection.spotlightParticipant(participantId);
630
+ getLocalVideoSource() {
631
+ return this.localVideoSource;
694
632
  }
695
- removeSpotlight(participantId) {
696
- this.roomConnection.removeSpotlight(participantId);
633
+ getLocalMedia() {
634
+ return this.localMedia;
697
635
  }
698
- requestAudioEnable(participantId, enable) {
699
- if (enable) {
700
- this.roomConnection.askToSpeak(participantId);
701
- }
702
- else {
703
- this.roomConnection.muteParticipants([participantId]);
636
+ getCombinedAudioSink() {
637
+ if (this.combinedAudioSink) {
638
+ return this.combinedAudioSink;
704
639
  }
705
- }
706
- requestVideoEnable(participantId, enable) {
707
- if (enable) {
708
- this.roomConnection.askToTurnOnCamera(participantId);
709
- }
710
- else {
711
- this.roomConnection.turnOffParticipantCameras([participantId]);
640
+ const audioMixer = new AudioMixer();
641
+ const stream = audioMixer.getCombinedAudioStream();
642
+ const audioTracks = stream === null || stream === void 0 ? void 0 : stream.getAudioTracks();
643
+ if (audioTracks === null || audioTracks === void 0 ? void 0 : audioTracks.length) {
644
+ this.combinedAudioSink = new AudioSink(audioTracks[0]);
645
+ this.stateSubscriptions.push(this.roomConnection.subscribeToRemoteParticipants(audioMixer.handleRemoteParticipants.bind(audioMixer)));
646
+ return this.combinedAudioSink;
712
647
  }
713
- }
714
- acceptWaitingParticipant(participantId) {
715
- this.roomConnection.acceptWaitingParticipant(participantId);
716
- }
717
- rejectWaitingParticipant(participantId) {
718
- this.roomConnection.rejectWaitingParticipant(participantId);
719
- }
720
- subscribeToRemoteParticipants(callback) {
721
- return this.roomConnection.subscribeToRemoteParticipants(callback);
722
- }
723
- subscribeToChatMessages(callback) {
724
- return this.roomConnection.subscribeToChatMessages(callback);
648
+ return null;
725
649
  }
726
650
  }
727
651
 
728
- dotenv.config();
729
- const { IS_LOCAL = "false", BIND_INTERFACE = "en0" } = process.env;
652
+ /******************************************************************************
653
+ Copyright (c) Microsoft Corporation.
654
+
655
+ Permission to use, copy, modify, and/or distribute this software for any
656
+ purpose with or without fee is hereby granted.
657
+
658
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
659
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
660
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
661
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
662
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
663
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
664
+ PERFORMANCE OF THIS SOFTWARE.
665
+ ***************************************************************************** */
666
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
667
+
668
+
669
+ function __awaiter(thisArg, _arguments, P, generator) {
670
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
671
+ return new (P || (P = Promise))(function (resolve, reject) {
672
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
673
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
674
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
675
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
676
+ });
677
+ }
678
+
679
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
680
+ var e = new Error(message);
681
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
682
+ };
683
+
730
684
  function buildRoomUrl(roomPath, wherebySubdomain, baseDomain = "whereby.com") {
731
- let wherebyDomain;
732
- if (IS_LOCAL === "true") {
733
- const ifaceAddrs = networkInterfaces()[BIND_INTERFACE];
734
- if (!ifaceAddrs) {
735
- throw new Error(`Unknown interface ${BIND_INTERFACE}`);
736
- }
737
- const [bindAddr] = ifaceAddrs.filter((iface) => iface.family === "IPv4");
738
- if (!bindAddr) {
739
- throw new Error(`No IPv4 address found for interface ${BIND_INTERFACE}`);
740
- }
741
- wherebyDomain = `${wherebySubdomain}-ip-${bindAddr.address.replace(/[.]/g, "-")}.hereby.dev:4443`;
742
- }
743
- else {
744
- wherebyDomain = `${wherebySubdomain}.${baseDomain}`;
745
- }
685
+ const wherebyDomain = `${wherebySubdomain}.${baseDomain}`;
746
686
  return `https://${wherebyDomain}${roomPath}`;
747
687
  }
748
688
 
@@ -801,4 +741,4 @@ class Trigger extends EventEmitter {
801
741
  }
802
742
  }
803
743
 
804
- export { ASSISTANT_JOINED_ROOM, ASSISTANT_LEFT_ROOM, AUDIO_STREAM_READY, Assistant, AudioSink, AudioSource, PARTICIPANT_AUDIO_TRACK_ADDED, PARTICIPANT_AUDIO_TRACK_REMOVED, PARTICIPANT_VIDEO_TRACK_ADDED, PARTICIPANT_VIDEO_TRACK_REMOVED, TRIGGER_EVENT_SUCCESS, Trigger };
744
+ export { ASSISTANT_JOINED_ROOM, ASSISTANT_LEFT_ROOM, Assistant, AudioSink, AudioSource, PARTICIPANT_AUDIO_TRACK_ADDED, PARTICIPANT_AUDIO_TRACK_REMOVED, PARTICIPANT_VIDEO_TRACK_ADDED, PARTICIPANT_VIDEO_TRACK_REMOVED, TRIGGER_EVENT_SUCCESS, Trigger };
package/dist/tools.cjs CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  var events = require('events');
4
4
  var child_process = require('child_process');
5
- require('stream');
6
5
  var wrtc = require('@roamhq/wrtc');
7
6
 
8
- const { nonstandard: { RTCAudioSink }, } = wrtc;
9
- class AudioSink extends wrtc.nonstandard.RTCAudioSink {
7
+ const { nonstandard: { RTCAudioSink, RTCAudioSource }, } = wrtc;
8
+ class AudioSink extends RTCAudioSink {
10
9
  constructor(track) {
11
10
  super(track);
12
11
  this._sink = new RTCAudioSink(track);
@@ -114,7 +113,7 @@ function createFfmpegMixer() {
114
113
  * @param ff Child process handle from spawn("ffmpeg", ...)
115
114
  * @param slotCount Number of participant input slots (0..N-1 → fd 3..3+N-1)
116
115
  */
117
- function startPacer(ff, slotCount, rtcAudioSource, onAudioStreamReady) {
116
+ function startPacer(ff, slotCount, rtcAudioSource) {
118
117
  if (stopPacerFn) {
119
118
  stopPacerFn();
120
119
  stopPacerFn = null;
@@ -132,8 +131,6 @@ function createFfmpegMixer() {
132
131
  frameQueue: [],
133
132
  nextDueMs: t0 + outputFrameMs,
134
133
  rtcAudioSource,
135
- onAudioStreamReady,
136
- didEmitReadyEvent: false,
137
134
  };
138
135
  const iv = setInterval(() => {
139
136
  const t = nowMs();
@@ -161,10 +158,6 @@ function createFfmpegMixer() {
161
158
  const state = outputPacerState;
162
159
  if (t >= state.nextDueMs) {
163
160
  const samples = state.frameQueue.length > 0 ? state.frameQueue.shift() : new Int16Array(FRAME_10MS_SAMPLES); // silence
164
- if (!state.didEmitReadyEvent) {
165
- state.onAudioStreamReady();
166
- state.didEmitReadyEvent = true;
167
- }
168
161
  state.rtcAudioSource.onData({
169
162
  samples: samples,
170
163
  sampleRate: STREAM_INPUT_SAMPLE_RATE_IN_HZ,
@@ -267,11 +260,11 @@ function createFfmpegMixer() {
267
260
  * The process will log its output to stderr.
268
261
  * @return The spawned FFmpeg process.
269
262
  */
270
- function spawnFFmpegProcessDebug(rtcAudioSource, onAudioStreamReady) {
263
+ function spawnFFmpegProcessDebug(rtcAudioSource) {
271
264
  const stdio = ["ignore", "ignore", "pipe", ...Array(PARTICIPANT_SLOTS).fill("pipe")];
272
265
  const args = getFFmpegArgumentsDebug();
273
266
  const ffmpegProcess = child_process.spawn("ffmpeg", args, { stdio });
274
- startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource, onAudioStreamReady);
267
+ startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource);
275
268
  ffmpegProcess.stderr.setEncoding("utf8");
276
269
  ffmpegProcess.stderr.on("data", (d) => console.error("[ffmpeg]", String(d).trim()));
277
270
  ffmpegProcess.on("error", () => console.error("FFmpeg process error (debug): is ffmpeg installed?"));
@@ -285,11 +278,11 @@ function createFfmpegMixer() {
285
278
  * @param rtcAudioSource The RTCAudioSource to which the mixed audio will be sent.
286
279
  * @return The spawned FFmpeg process.
287
280
  */
288
- function spawnFFmpegProcess(rtcAudioSource, onAudioStreamReady) {
281
+ function spawnFFmpegProcess(rtcAudioSource) {
289
282
  const stdio = ["pipe", "pipe", "pipe", ...Array(PARTICIPANT_SLOTS).fill("pipe")];
290
283
  const args = getFFmpegArguments();
291
284
  const ffmpegProcess = child_process.spawn("ffmpeg", args, { stdio });
292
- startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource, onAudioStreamReady);
285
+ startPacer(ffmpegProcess, PARTICIPANT_SLOTS, rtcAudioSource);
293
286
  ffmpegProcess.stderr.setEncoding("utf8");
294
287
  ffmpegProcess.stderr.on("data", (d) => console.error("[ffmpeg]", String(d).trim()));
295
288
  ffmpegProcess.on("error", () => console.error("FFmpeg process error: is ffmpeg installed?"));
@@ -387,7 +380,7 @@ function createFfmpegMixer() {
387
380
  }
388
381
 
389
382
  class AudioMixer extends events.EventEmitter {
390
- constructor(onStreamReady) {
383
+ constructor() {
391
384
  super();
392
385
  this.ffmpegProcess = null;
393
386
  this.combinedAudioStream = null;
@@ -397,7 +390,6 @@ class AudioMixer extends events.EventEmitter {
397
390
  this.mixer = createFfmpegMixer();
398
391
  this.setupMediaStream();
399
392
  this.participantSlots = new Map(Array.from({ length: PARTICIPANT_SLOTS }, (_, i) => [i, ""]));
400
- this.onStreamReady = onStreamReady;
401
393
  }
402
394
  setupMediaStream() {
403
395
  this.rtcAudioSource = new wrtc.nonstandard.RTCAudioSource();
@@ -413,7 +405,7 @@ class AudioMixer extends events.EventEmitter {
413
405
  return;
414
406
  }
415
407
  if (!this.ffmpegProcess && this.rtcAudioSource) {
416
- this.ffmpegProcess = this.mixer.spawnFFmpegProcess(this.rtcAudioSource, this.onStreamReady);
408
+ this.ffmpegProcess = this.mixer.spawnFFmpegProcess(this.rtcAudioSource);
417
409
  }
418
410
  for (const p of participants)
419
411
  this.attachParticipantIfNeeded(p);
package/dist/tools.d.ts CHANGED
@@ -7,9 +7,8 @@ declare class AudioMixer extends EventEmitter {
7
7
  private rtcAudioSource;
8
8
  private participantSlots;
9
9
  private activeSlots;
10
- private onStreamReady;
11
10
  private mixer;
12
- constructor(onStreamReady: () => void);
11
+ constructor();
13
12
  private setupMediaStream;
14
13
  getCombinedAudioStream(): MediaStream | null;
15
14
  handleRemoteParticipants(participants: RemoteParticipantState[]): void;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@whereby.com/assistant-sdk",
3
3
  "description": "Assistant SDK for whereby.com",
4
4
  "author": "Whereby AS",
5
- "version": "0.0.0-canary-20251002120040",
5
+ "version": "0.0.0-canary-20251007140529",
6
6
  "license": "MIT",
7
7
  "files": [
8
8
  "dist",
@@ -64,7 +64,7 @@
64
64
  "express": "5.1.0",
65
65
  "uuid": "^11.0.3",
66
66
  "ws": "^8.18.0",
67
- "@whereby.com/core": "0.0.0-canary-20251002120040"
67
+ "@whereby.com/core": "0.0.0-canary-20251007140529"
68
68
  },
69
69
  "prettier": "@whereby.com/prettier-config",
70
70
  "scripts": {