@vapi-ai/web 2.7.0 → 2.7.1

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.
package/dist/vapi.d.ts CHANGED
@@ -154,6 +154,25 @@ export default class Vapi extends VapiEventEmitter {
154
154
  * consumers register. Audio processing is non-critical, so the call continues.
155
155
  */
156
156
  private emitAudioProcessingError;
157
+ /**
158
+ * Starts Daily's local (microphone) audio level observer.
159
+ *
160
+ * Only runs when something is listening for 'local-volume-level'. The observer
161
+ * costs an AudioContext plus an AudioWorklet for the lifetime of the call and
162
+ * nothing else in the SDK reads the level, so starting it unconditionally
163
+ * charges every consumer for a feature almost none of them use. Listeners
164
+ * attached after the call is under way should call the public
165
+ * `startLocalAudioLevelObserver()`.
166
+ *
167
+ * Must run after the noise-cancellation processor has settled. Krisp replaces
168
+ * the microphone track, Daily reacts to any local track change by closing the
169
+ * AudioContext its in-flight `audioWorklet.addModule()` is still loading into,
170
+ * and Chrome and Firefox reject that load with "AbortError: Unable to load a
171
+ * worklet's module". Daily answers by stopping the observer for the rest of
172
+ * the call, so losing this race costs the feature, not just console noise.
173
+ * Tracked upstream at https://github.com/daily-co/daily-js/issues/317.
174
+ */
175
+ private maybeStartLocalAudioLevelObserver;
157
176
  start(assistant?: CreateAssistantDTO | string, assistantOverrides?: AssistantOverrides, squad?: CreateSquadDTO | string, workflow?: CreateWorkflowDTO | string, workflowOverrides?: WorkflowOverrides, options?: StartCallOptions): Promise<Call | null>;
158
177
  private onAppMessage;
159
178
  private handleRemoteParticipantsAudioLevel;
package/dist/vapi.js CHANGED
@@ -181,6 +181,35 @@ class Vapi extends VapiEventEmitter {
181
181
  timestamp: new Date().toISOString(),
182
182
  });
183
183
  }
184
+ /**
185
+ * Starts Daily's local (microphone) audio level observer.
186
+ *
187
+ * Only runs when something is listening for 'local-volume-level'. The observer
188
+ * costs an AudioContext plus an AudioWorklet for the lifetime of the call and
189
+ * nothing else in the SDK reads the level, so starting it unconditionally
190
+ * charges every consumer for a feature almost none of them use. Listeners
191
+ * attached after the call is under way should call the public
192
+ * `startLocalAudioLevelObserver()`.
193
+ *
194
+ * Must run after the noise-cancellation processor has settled. Krisp replaces
195
+ * the microphone track, Daily reacts to any local track change by closing the
196
+ * AudioContext its in-flight `audioWorklet.addModule()` is still loading into,
197
+ * and Chrome and Firefox reject that load with "AbortError: Unable to load a
198
+ * worklet's module". Daily answers by stopping the observer for the rest of
199
+ * the call, so losing this race costs the feature, not just console noise.
200
+ * Tracked upstream at https://github.com/daily-co/daily-js/issues/317.
201
+ */
202
+ async maybeStartLocalAudioLevelObserver() {
203
+ if (!this.call || this.listenerCount('local-volume-level') === 0) {
204
+ return;
205
+ }
206
+ try {
207
+ await this.call.startLocalAudioLevelObserver(100);
208
+ }
209
+ catch (error) {
210
+ this.emit('local-audio-level-observer-error', serializeError(error));
211
+ }
212
+ }
184
213
  async start(assistant, assistantOverrides, squad, workflow, workflowOverrides, options) {
185
214
  const startTime = Date.now();
186
215
  // Input validation with detailed error messages
@@ -542,7 +571,6 @@ class Vapi extends VapiEventEmitter {
542
571
  const audioObserverStartTime = Date.now();
543
572
  try {
544
573
  this.call.startRemoteParticipantsAudioLevelObserver(100);
545
- this.call.startLocalAudioLevelObserver(100);
546
574
  const audioObserverDuration = Date.now() - audioObserverStartTime;
547
575
  this.emit('call-start-progress', {
548
576
  stage: 'audio-observer-setup',
@@ -608,17 +636,26 @@ class Vapi extends VapiEventEmitter {
608
636
  });
609
637
  const audioProcessingStartTime = Date.now();
610
638
  try {
611
- this.call
612
- .updateInputSettings({
639
+ const audioProcessingUpdate = this.call.updateInputSettings({
613
640
  audio: {
614
641
  processor: {
615
642
  type: 'noise-cancellation',
616
643
  },
617
644
  },
618
- })
619
- .catch((error) => {
645
+ });
646
+ audioProcessingUpdate.catch((error) => {
620
647
  this.emitAudioProcessingError('audio-processing-setup', error);
621
648
  });
649
+ // The observer waits for this update rather than starting alongside the
650
+ // remote one above, so that Krisp has already swapped the microphone
651
+ // track. See maybeStartLocalAudioLevelObserver().
652
+ //
653
+ // A separate chain, not another link on the one above: EventEmitter
654
+ // rethrows out of emit('error') when a consumer registered no 'error'
655
+ // listener, and whether the observer starts must not hinge on that.
656
+ audioProcessingUpdate
657
+ .catch(() => { })
658
+ .then(() => this.maybeStartLocalAudioLevelObserver());
622
659
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
623
660
  this.emit('call-start-progress', {
624
661
  stage: 'audio-processing-setup',
@@ -1361,7 +1398,6 @@ class Vapi extends VapiEventEmitter {
1361
1398
  const audioObserverStartTime = Date.now();
1362
1399
  try {
1363
1400
  this.call.startRemoteParticipantsAudioLevelObserver(100);
1364
- this.call.startLocalAudioLevelObserver(100);
1365
1401
  const audioObserverDuration = Date.now() - audioObserverStartTime;
1366
1402
  this.emit('call-start-progress', {
1367
1403
  stage: 'audio-observer-setup',
@@ -1390,17 +1426,26 @@ class Vapi extends VapiEventEmitter {
1390
1426
  });
1391
1427
  const audioProcessingStartTime = Date.now();
1392
1428
  try {
1393
- this.call
1394
- .updateInputSettings({
1429
+ const audioProcessingUpdate = this.call.updateInputSettings({
1395
1430
  audio: {
1396
1431
  processor: {
1397
1432
  type: 'noise-cancellation',
1398
1433
  },
1399
1434
  },
1400
- })
1401
- .catch((error) => {
1435
+ });
1436
+ audioProcessingUpdate.catch((error) => {
1402
1437
  this.emitAudioProcessingError('audio-processing-setup', error);
1403
1438
  });
1439
+ // The observer waits for this update rather than starting alongside the
1440
+ // remote one above, so that Krisp has already swapped the microphone
1441
+ // track. See maybeStartLocalAudioLevelObserver().
1442
+ //
1443
+ // A separate chain, not another link on the one above: EventEmitter
1444
+ // rethrows out of emit('error') when a consumer registered no 'error'
1445
+ // listener, and whether the observer starts must not hinge on that.
1446
+ audioProcessingUpdate
1447
+ .catch(() => { })
1448
+ .then(() => this.maybeStartLocalAudioLevelObserver());
1404
1449
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
1405
1450
  this.emit('call-start-progress', {
1406
1451
  stage: 'audio-processing-setup',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vapi-ai/web",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "",
5
5
  "main": "dist/vapi.js",
6
6
  "types": "dist/vapi.d.ts",
package/vapi.ts CHANGED
@@ -423,6 +423,36 @@ export default class Vapi extends VapiEventEmitter {
423
423
  });
424
424
  }
425
425
 
426
+ /**
427
+ * Starts Daily's local (microphone) audio level observer.
428
+ *
429
+ * Only runs when something is listening for 'local-volume-level'. The observer
430
+ * costs an AudioContext plus an AudioWorklet for the lifetime of the call and
431
+ * nothing else in the SDK reads the level, so starting it unconditionally
432
+ * charges every consumer for a feature almost none of them use. Listeners
433
+ * attached after the call is under way should call the public
434
+ * `startLocalAudioLevelObserver()`.
435
+ *
436
+ * Must run after the noise-cancellation processor has settled. Krisp replaces
437
+ * the microphone track, Daily reacts to any local track change by closing the
438
+ * AudioContext its in-flight `audioWorklet.addModule()` is still loading into,
439
+ * and Chrome and Firefox reject that load with "AbortError: Unable to load a
440
+ * worklet's module". Daily answers by stopping the observer for the rest of
441
+ * the call, so losing this race costs the feature, not just console noise.
442
+ * Tracked upstream at https://github.com/daily-co/daily-js/issues/317.
443
+ */
444
+ private async maybeStartLocalAudioLevelObserver(): Promise<void> {
445
+ if (!this.call || this.listenerCount('local-volume-level') === 0) {
446
+ return;
447
+ }
448
+
449
+ try {
450
+ await this.call.startLocalAudioLevelObserver(100);
451
+ } catch (error) {
452
+ this.emit('local-audio-level-observer-error', serializeError(error));
453
+ }
454
+ }
455
+
426
456
  async start(
427
457
  assistant?: CreateAssistantDTO | string,
428
458
  assistantOverrides?: AssistantOverrides,
@@ -835,7 +865,6 @@ export default class Vapi extends VapiEventEmitter {
835
865
 
836
866
  try {
837
867
  this.call.startRemoteParticipantsAudioLevelObserver(100);
838
- this.call.startLocalAudioLevelObserver(100);
839
868
  const audioObserverDuration = Date.now() - audioObserverStartTime;
840
869
  this.emit('call-start-progress', {
841
870
  stage: 'audio-observer-setup',
@@ -905,17 +934,28 @@ export default class Vapi extends VapiEventEmitter {
905
934
  const audioProcessingStartTime = Date.now();
906
935
 
907
936
  try {
908
- this.call
909
- .updateInputSettings({
910
- audio: {
911
- processor: {
912
- type: 'noise-cancellation',
913
- },
937
+ const audioProcessingUpdate = this.call.updateInputSettings({
938
+ audio: {
939
+ processor: {
940
+ type: 'noise-cancellation',
914
941
  },
915
- })
916
- .catch((error) => {
917
- this.emitAudioProcessingError('audio-processing-setup', error);
918
- });
942
+ },
943
+ });
944
+
945
+ audioProcessingUpdate.catch((error) => {
946
+ this.emitAudioProcessingError('audio-processing-setup', error);
947
+ });
948
+
949
+ // The observer waits for this update rather than starting alongside the
950
+ // remote one above, so that Krisp has already swapped the microphone
951
+ // track. See maybeStartLocalAudioLevelObserver().
952
+ //
953
+ // A separate chain, not another link on the one above: EventEmitter
954
+ // rethrows out of emit('error') when a consumer registered no 'error'
955
+ // listener, and whether the observer starts must not hinge on that.
956
+ audioProcessingUpdate
957
+ .catch(() => {})
958
+ .then(() => this.maybeStartLocalAudioLevelObserver());
919
959
 
920
960
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
921
961
  this.emit('call-start-progress', {
@@ -1758,7 +1798,6 @@ export default class Vapi extends VapiEventEmitter {
1758
1798
 
1759
1799
  try {
1760
1800
  this.call.startRemoteParticipantsAudioLevelObserver(100);
1761
- this.call.startLocalAudioLevelObserver(100);
1762
1801
  const audioObserverDuration = Date.now() - audioObserverStartTime;
1763
1802
  this.emit('call-start-progress', {
1764
1803
  stage: 'audio-observer-setup',
@@ -1789,17 +1828,28 @@ export default class Vapi extends VapiEventEmitter {
1789
1828
  const audioProcessingStartTime = Date.now();
1790
1829
 
1791
1830
  try {
1792
- this.call
1793
- .updateInputSettings({
1794
- audio: {
1795
- processor: {
1796
- type: 'noise-cancellation',
1797
- },
1831
+ const audioProcessingUpdate = this.call.updateInputSettings({
1832
+ audio: {
1833
+ processor: {
1834
+ type: 'noise-cancellation',
1798
1835
  },
1799
- })
1800
- .catch((error) => {
1801
- this.emitAudioProcessingError('audio-processing-setup', error);
1802
- });
1836
+ },
1837
+ });
1838
+
1839
+ audioProcessingUpdate.catch((error) => {
1840
+ this.emitAudioProcessingError('audio-processing-setup', error);
1841
+ });
1842
+
1843
+ // The observer waits for this update rather than starting alongside the
1844
+ // remote one above, so that Krisp has already swapped the microphone
1845
+ // track. See maybeStartLocalAudioLevelObserver().
1846
+ //
1847
+ // A separate chain, not another link on the one above: EventEmitter
1848
+ // rethrows out of emit('error') when a consumer registered no 'error'
1849
+ // listener, and whether the observer starts must not hinge on that.
1850
+ audioProcessingUpdate
1851
+ .catch(() => {})
1852
+ .then(() => this.maybeStartLocalAudioLevelObserver());
1803
1853
 
1804
1854
  const audioProcessingDuration = Date.now() - audioProcessingStartTime;
1805
1855
  this.emit('call-start-progress', {