@vapi-ai/web 2.5.2 → 2.6.2
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 +51 -2
- package/dist/vapi.js +140 -2
- package/package.json +5 -5
- package/vapi.ts +181 -15
package/dist/vapi.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
2
|
-
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
|
|
2
|
+
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyEventObjectNoPayload, DailyEventObjectRecordingError, DailyEventObjectRecordingStarted, DailyEventObjectRecordingStopped, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides, CreateWorkflowDTO, WorkflowOverrides } from './api';
|
|
5
5
|
export interface EndCallMessage {
|
|
@@ -23,7 +23,7 @@ export interface SayMessage {
|
|
|
23
23
|
interruptAssistantEnabled?: boolean;
|
|
24
24
|
}
|
|
25
25
|
type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage | EndCallMessage;
|
|
26
|
-
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'camera-error' | 'network-quality-change' | 'network-connection' | 'daily-participant-updated' | 'call-start-progress' | 'call-start-success' | 'call-start-failed';
|
|
26
|
+
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'local-volume-level' | 'local-audio-level-observer-error' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'camera-error' | 'network-quality-change' | 'network-connection' | 'daily-participant-updated' | 'call-start-progress' | 'call-start-success' | 'call-start-failed' | 'recording-started' | 'recording-stopped' | 'recording-stats' | 'recording-error' | 'recording-upload-completed';
|
|
27
27
|
interface CallStartProgressEvent {
|
|
28
28
|
stage: string;
|
|
29
29
|
status: 'started' | 'completed' | 'failed';
|
|
@@ -48,6 +48,8 @@ type VapiEventListeners = {
|
|
|
48
48
|
'call-end': () => void;
|
|
49
49
|
'call-start': () => void;
|
|
50
50
|
'volume-level': (volume: number) => void;
|
|
51
|
+
'local-volume-level': (volume: number) => void;
|
|
52
|
+
'local-audio-level-observer-error': (error: any) => void;
|
|
51
53
|
'speech-start': () => void;
|
|
52
54
|
'speech-end': () => void;
|
|
53
55
|
video: (track: MediaStreamTrack) => void;
|
|
@@ -60,6 +62,11 @@ type VapiEventListeners = {
|
|
|
60
62
|
'call-start-progress': (event: CallStartProgressEvent) => void;
|
|
61
63
|
'call-start-success': (event: CallStartSuccessEvent) => void;
|
|
62
64
|
'call-start-failed': (event: CallStartFailedEvent) => void;
|
|
65
|
+
'recording-started': (event: DailyEventObjectRecordingStarted) => void;
|
|
66
|
+
'recording-stopped': (event: DailyEventObjectRecordingStopped) => void;
|
|
67
|
+
'recording-stats': (event: DailyEventObjectNoPayload) => void;
|
|
68
|
+
'recording-error': (event: DailyEventObjectRecordingError) => void;
|
|
69
|
+
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
63
70
|
};
|
|
64
71
|
type StartCallOptions = {
|
|
65
72
|
/**
|
|
@@ -120,9 +127,21 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
120
127
|
private cleanup;
|
|
121
128
|
private isMobileDevice;
|
|
122
129
|
private sleep;
|
|
130
|
+
/**
|
|
131
|
+
* Reports a failed audio processing update on the SDK's `error` event.
|
|
132
|
+
*
|
|
133
|
+
* Daily applies input settings inside its remotely loaded call machine, so a
|
|
134
|
+
* failure - Krisp's `KrispInitError: Canceled`, for example - arrives as a
|
|
135
|
+
* rejected promise, which a synchronous try/catch around
|
|
136
|
+
* `updateInputSettings()` cannot see. Without this the rejection escapes to
|
|
137
|
+
* the page as an unhandled rejection instead of reaching the listeners
|
|
138
|
+
* consumers register. Audio processing is non-critical, so the call continues.
|
|
139
|
+
*/
|
|
140
|
+
private emitAudioProcessingError;
|
|
123
141
|
start(assistant?: CreateAssistantDTO | string, assistantOverrides?: AssistantOverrides, squad?: CreateSquadDTO | string, workflow?: CreateWorkflowDTO | string, workflowOverrides?: WorkflowOverrides, options?: StartCallOptions): Promise<Call | null>;
|
|
124
142
|
private onAppMessage;
|
|
125
143
|
private handleRemoteParticipantsAudioLevel;
|
|
144
|
+
private handleLocalAudioLevel;
|
|
126
145
|
/**
|
|
127
146
|
* Stops the call by destroying the Daily call object.
|
|
128
147
|
*
|
|
@@ -149,6 +168,36 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
149
168
|
increaseMicLevel(gain: number): Promise<void>;
|
|
150
169
|
setOutputDeviceAsync(options: Parameters<DailyCall['setOutputDeviceAsync']>[0]): void;
|
|
151
170
|
getDailyCallObject(): DailyCall | null;
|
|
171
|
+
/**
|
|
172
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
173
|
+
*
|
|
174
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
175
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
176
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
177
|
+
*
|
|
178
|
+
* Returns 0 when there is no active call.
|
|
179
|
+
*/
|
|
180
|
+
getLocalAudioLevel(): number;
|
|
181
|
+
/**
|
|
182
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
183
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
184
|
+
* interval or restart it if it was stopped.
|
|
185
|
+
*
|
|
186
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
187
|
+
* microphone audio level (0 to 1).
|
|
188
|
+
*
|
|
189
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
190
|
+
*/
|
|
191
|
+
startLocalAudioLevelObserver(interval?: number): Promise<void>;
|
|
192
|
+
/**
|
|
193
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
194
|
+
* events will no longer be emitted until the observer is started again.
|
|
195
|
+
*/
|
|
196
|
+
stopLocalAudioLevelObserver(): void;
|
|
197
|
+
/**
|
|
198
|
+
* Returns whether the local audio level observer is currently running.
|
|
199
|
+
*/
|
|
200
|
+
isLocalAudioLevelObserverRunning(): boolean;
|
|
152
201
|
startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings): void;
|
|
153
202
|
stopScreenSharing(): void;
|
|
154
203
|
/**
|
package/dist/vapi.js
CHANGED
|
@@ -141,6 +141,24 @@ class Vapi extends VapiEventEmitter {
|
|
|
141
141
|
async sleep(ms) {
|
|
142
142
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Reports a failed audio processing update on the SDK's `error` event.
|
|
146
|
+
*
|
|
147
|
+
* Daily applies input settings inside its remotely loaded call machine, so a
|
|
148
|
+
* failure - Krisp's `KrispInitError: Canceled`, for example - arrives as a
|
|
149
|
+
* rejected promise, which a synchronous try/catch around
|
|
150
|
+
* `updateInputSettings()` cannot see. Without this the rejection escapes to
|
|
151
|
+
* the page as an unhandled rejection instead of reaching the listeners
|
|
152
|
+
* consumers register. Audio processing is non-critical, so the call continues.
|
|
153
|
+
*/
|
|
154
|
+
emitAudioProcessingError(stage, error) {
|
|
155
|
+
this.emit('error', {
|
|
156
|
+
type: `${stage}-error`,
|
|
157
|
+
stage,
|
|
158
|
+
error: serializeError(error),
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
144
162
|
async start(assistant, assistantOverrides, squad, workflow, workflowOverrides, options) {
|
|
145
163
|
const startTime = Date.now();
|
|
146
164
|
// Input validation with detailed error messages
|
|
@@ -300,6 +318,26 @@ class Vapi extends VapiEventEmitter {
|
|
|
300
318
|
this.call.on('network-connection', (event) => {
|
|
301
319
|
this.emit('network-connection', event);
|
|
302
320
|
});
|
|
321
|
+
this.call.on('recording-started', (event) => {
|
|
322
|
+
if (event)
|
|
323
|
+
this.emit('recording-started', event);
|
|
324
|
+
});
|
|
325
|
+
this.call.on('recording-stopped', (event) => {
|
|
326
|
+
if (event)
|
|
327
|
+
this.emit('recording-stopped', event);
|
|
328
|
+
});
|
|
329
|
+
this.call.on('recording-stats', (event) => {
|
|
330
|
+
if (event)
|
|
331
|
+
this.emit('recording-stats', event);
|
|
332
|
+
});
|
|
333
|
+
this.call.on('recording-error', (event) => {
|
|
334
|
+
if (event)
|
|
335
|
+
this.emit('recording-error', event);
|
|
336
|
+
});
|
|
337
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
338
|
+
if (event)
|
|
339
|
+
this.emit('recording-upload-completed', event);
|
|
340
|
+
});
|
|
303
341
|
this.call.on('track-started', async (e) => {
|
|
304
342
|
if (!e || !e.participant) {
|
|
305
343
|
return;
|
|
@@ -479,6 +517,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
479
517
|
const audioObserverStartTime = Date.now();
|
|
480
518
|
try {
|
|
481
519
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
520
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
482
521
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
483
522
|
this.emit('call-start-progress', {
|
|
484
523
|
stage: 'audio-observer-setup',
|
|
@@ -509,9 +548,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
509
548
|
if (e)
|
|
510
549
|
this.handleRemoteParticipantsAudioLevel(e);
|
|
511
550
|
});
|
|
551
|
+
this.call.on('local-audio-level', (e) => {
|
|
552
|
+
if (e)
|
|
553
|
+
this.handleLocalAudioLevel(e);
|
|
554
|
+
});
|
|
512
555
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
513
556
|
this.call.on('nonfatal-error', (e) => {
|
|
514
557
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
558
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
559
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
560
|
+
}
|
|
515
561
|
if (e?.type === 'audio-processor-error') {
|
|
516
562
|
this.call
|
|
517
563
|
?.updateInputSettings({
|
|
@@ -523,6 +569,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
523
569
|
})
|
|
524
570
|
.then(() => {
|
|
525
571
|
(0, daily_guards_1.safeSetLocalAudio)(this.call, true);
|
|
572
|
+
})
|
|
573
|
+
.catch((error) => {
|
|
574
|
+
this.emitAudioProcessingError('audio-processor-recovery', error);
|
|
526
575
|
});
|
|
527
576
|
}
|
|
528
577
|
});
|
|
@@ -534,12 +583,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
534
583
|
});
|
|
535
584
|
const audioProcessingStartTime = Date.now();
|
|
536
585
|
try {
|
|
537
|
-
this.call
|
|
586
|
+
this.call
|
|
587
|
+
.updateInputSettings({
|
|
538
588
|
audio: {
|
|
539
589
|
processor: {
|
|
540
590
|
type: 'noise-cancellation',
|
|
541
591
|
},
|
|
542
592
|
},
|
|
593
|
+
})
|
|
594
|
+
.catch((error) => {
|
|
595
|
+
this.emitAudioProcessingError('audio-processing-setup', error);
|
|
543
596
|
});
|
|
544
597
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
545
598
|
this.emit('call-start-progress', {
|
|
@@ -653,6 +706,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
653
706
|
this.speakingTimeout = null;
|
|
654
707
|
}, 1000);
|
|
655
708
|
}
|
|
709
|
+
handleLocalAudioLevel(e) {
|
|
710
|
+
this.emit('local-volume-level', e.audioLevel);
|
|
711
|
+
}
|
|
656
712
|
/**
|
|
657
713
|
* Stops the call by destroying the Daily call object.
|
|
658
714
|
*
|
|
@@ -733,6 +789,53 @@ class Vapi extends VapiEventEmitter {
|
|
|
733
789
|
getDailyCallObject() {
|
|
734
790
|
return this.call;
|
|
735
791
|
}
|
|
792
|
+
/**
|
|
793
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
794
|
+
*
|
|
795
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
796
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
797
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
798
|
+
*
|
|
799
|
+
* Returns 0 when there is no active call.
|
|
800
|
+
*/
|
|
801
|
+
getLocalAudioLevel() {
|
|
802
|
+
if (!this.call) {
|
|
803
|
+
return 0;
|
|
804
|
+
}
|
|
805
|
+
return this.call.getLocalAudioLevel();
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
809
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
810
|
+
* interval or restart it if it was stopped.
|
|
811
|
+
*
|
|
812
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
813
|
+
* microphone audio level (0 to 1).
|
|
814
|
+
*
|
|
815
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
816
|
+
*/
|
|
817
|
+
async startLocalAudioLevelObserver(interval) {
|
|
818
|
+
if (!this.call) {
|
|
819
|
+
throw new Error('Call object is not available.');
|
|
820
|
+
}
|
|
821
|
+
await this.call.startLocalAudioLevelObserver(interval);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
825
|
+
* events will no longer be emitted until the observer is started again.
|
|
826
|
+
*/
|
|
827
|
+
stopLocalAudioLevelObserver() {
|
|
828
|
+
this.call?.stopLocalAudioLevelObserver();
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Returns whether the local audio level observer is currently running.
|
|
832
|
+
*/
|
|
833
|
+
isLocalAudioLevelObserverRunning() {
|
|
834
|
+
if (!this.call) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
return this.call.isLocalAudioLevelObserverRunning();
|
|
838
|
+
}
|
|
736
839
|
startScreenSharing(displayMediaOptions, screenVideoSendSettings) {
|
|
737
840
|
this.call?.startScreenShare({
|
|
738
841
|
displayMediaOptions,
|
|
@@ -849,6 +952,26 @@ class Vapi extends VapiEventEmitter {
|
|
|
849
952
|
this.call.on('network-connection', (event) => {
|
|
850
953
|
this.emit('network-connection', event);
|
|
851
954
|
});
|
|
955
|
+
this.call.on('recording-started', (event) => {
|
|
956
|
+
if (event)
|
|
957
|
+
this.emit('recording-started', event);
|
|
958
|
+
});
|
|
959
|
+
this.call.on('recording-stopped', (event) => {
|
|
960
|
+
if (event)
|
|
961
|
+
this.emit('recording-stopped', event);
|
|
962
|
+
});
|
|
963
|
+
this.call.on('recording-stats', (event) => {
|
|
964
|
+
if (event)
|
|
965
|
+
this.emit('recording-stats', event);
|
|
966
|
+
});
|
|
967
|
+
this.call.on('recording-error', (event) => {
|
|
968
|
+
if (event)
|
|
969
|
+
this.emit('recording-error', event);
|
|
970
|
+
});
|
|
971
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
972
|
+
if (event)
|
|
973
|
+
this.emit('recording-upload-completed', event);
|
|
974
|
+
});
|
|
852
975
|
this.call.on('track-started', async (e) => {
|
|
853
976
|
if (!e || !e.participant) {
|
|
854
977
|
return;
|
|
@@ -888,9 +1011,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
888
1011
|
if (e)
|
|
889
1012
|
this.handleRemoteParticipantsAudioLevel(e);
|
|
890
1013
|
});
|
|
1014
|
+
this.call.on('local-audio-level', (e) => {
|
|
1015
|
+
if (e)
|
|
1016
|
+
this.handleLocalAudioLevel(e);
|
|
1017
|
+
});
|
|
891
1018
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
892
1019
|
this.call.on('nonfatal-error', (e) => {
|
|
893
1020
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
1021
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
1022
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
1023
|
+
}
|
|
894
1024
|
if (e?.type === 'audio-processor-error') {
|
|
895
1025
|
this.call
|
|
896
1026
|
?.updateInputSettings({
|
|
@@ -902,6 +1032,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
902
1032
|
})
|
|
903
1033
|
.then(() => {
|
|
904
1034
|
(0, daily_guards_1.safeSetLocalAudio)(this.call, true);
|
|
1035
|
+
})
|
|
1036
|
+
.catch((error) => {
|
|
1037
|
+
this.emitAudioProcessingError('audio-processor-recovery', error);
|
|
905
1038
|
});
|
|
906
1039
|
}
|
|
907
1040
|
});
|
|
@@ -1021,6 +1154,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
1021
1154
|
const audioObserverStartTime = Date.now();
|
|
1022
1155
|
try {
|
|
1023
1156
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
1157
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
1024
1158
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
1025
1159
|
this.emit('call-start-progress', {
|
|
1026
1160
|
stage: 'audio-observer-setup',
|
|
@@ -1049,12 +1183,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
1049
1183
|
});
|
|
1050
1184
|
const audioProcessingStartTime = Date.now();
|
|
1051
1185
|
try {
|
|
1052
|
-
this.call
|
|
1186
|
+
this.call
|
|
1187
|
+
.updateInputSettings({
|
|
1053
1188
|
audio: {
|
|
1054
1189
|
processor: {
|
|
1055
1190
|
type: 'noise-cancellation',
|
|
1056
1191
|
},
|
|
1057
1192
|
},
|
|
1193
|
+
})
|
|
1194
|
+
.catch((error) => {
|
|
1195
|
+
this.emitAudioProcessingError('audio-processing-setup', error);
|
|
1058
1196
|
});
|
|
1059
1197
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
1060
1198
|
this.emit('call-start-progress', {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vapi-ai/web",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/vapi.js",
|
|
6
6
|
"types": "dist/vapi.d.ts",
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/VapiAI/web.git"
|
|
22
|
+
"url": "git+https://github.com/VapiAI/client-sdk-web.git"
|
|
23
23
|
},
|
|
24
24
|
"author": "vapi",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/VapiAI/web/issues"
|
|
27
|
+
"url": "https://github.com/VapiAI/client-sdk-web/issues"
|
|
28
28
|
},
|
|
29
|
-
"homepage": "https://github.com/VapiAI/web#readme",
|
|
29
|
+
"homepage": "https://github.com/VapiAI/client-sdk-web#readme",
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@daily-co/daily-js": "^0.
|
|
34
|
+
"@daily-co/daily-js": "^0.87.0",
|
|
35
35
|
"events": "^3.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
package/vapi.ts
CHANGED
|
@@ -5,7 +5,12 @@ import DailyIframe, {
|
|
|
5
5
|
DailyAdvancedConfig,
|
|
6
6
|
DailyFactoryOptions,
|
|
7
7
|
DailyEventObjectAppMessage,
|
|
8
|
+
DailyEventObjectLocalAudioLevel,
|
|
9
|
+
DailyEventObjectNoPayload,
|
|
8
10
|
DailyEventObjectParticipant,
|
|
11
|
+
DailyEventObjectRecordingError,
|
|
12
|
+
DailyEventObjectRecordingStarted,
|
|
13
|
+
DailyEventObjectRecordingStopped,
|
|
9
14
|
DailyEventObjectRemoteParticipantsAudioLevel,
|
|
10
15
|
DailyParticipant,
|
|
11
16
|
DailyVideoSendSettings,
|
|
@@ -62,6 +67,8 @@ type VapiEventNames =
|
|
|
62
67
|
| 'call-end'
|
|
63
68
|
| 'call-start'
|
|
64
69
|
| 'volume-level'
|
|
70
|
+
| 'local-volume-level'
|
|
71
|
+
| 'local-audio-level-observer-error'
|
|
65
72
|
| 'speech-start'
|
|
66
73
|
| 'speech-end'
|
|
67
74
|
| 'message'
|
|
@@ -73,7 +80,12 @@ type VapiEventNames =
|
|
|
73
80
|
| 'daily-participant-updated'
|
|
74
81
|
| 'call-start-progress'
|
|
75
82
|
| 'call-start-success'
|
|
76
|
-
| 'call-start-failed'
|
|
83
|
+
| 'call-start-failed'
|
|
84
|
+
| 'recording-started'
|
|
85
|
+
| 'recording-stopped'
|
|
86
|
+
| 'recording-stats'
|
|
87
|
+
| 'recording-error'
|
|
88
|
+
| 'recording-upload-completed';
|
|
77
89
|
|
|
78
90
|
interface CallStartProgressEvent {
|
|
79
91
|
stage: string;
|
|
@@ -169,6 +181,8 @@ type VapiEventListeners = {
|
|
|
169
181
|
'call-end': () => void;
|
|
170
182
|
'call-start': () => void;
|
|
171
183
|
'volume-level': (volume: number) => void;
|
|
184
|
+
'local-volume-level': (volume: number) => void;
|
|
185
|
+
'local-audio-level-observer-error': (error: any) => void;
|
|
172
186
|
'speech-start': () => void;
|
|
173
187
|
'speech-end': () => void;
|
|
174
188
|
video: (track: MediaStreamTrack) => void;
|
|
@@ -181,6 +195,11 @@ type VapiEventListeners = {
|
|
|
181
195
|
'call-start-progress': (event: CallStartProgressEvent) => void;
|
|
182
196
|
'call-start-success': (event: CallStartSuccessEvent) => void;
|
|
183
197
|
'call-start-failed': (event: CallStartFailedEvent) => void;
|
|
198
|
+
'recording-started': (event: DailyEventObjectRecordingStarted) => void;
|
|
199
|
+
'recording-stopped': (event: DailyEventObjectRecordingStopped) => void;
|
|
200
|
+
'recording-stats': (event: DailyEventObjectNoPayload) => void;
|
|
201
|
+
'recording-error': (event: DailyEventObjectRecordingError) => void;
|
|
202
|
+
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
184
203
|
};
|
|
185
204
|
|
|
186
205
|
type StartCallOptions = {
|
|
@@ -349,6 +368,28 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
349
368
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
350
369
|
}
|
|
351
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Reports a failed audio processing update on the SDK's `error` event.
|
|
373
|
+
*
|
|
374
|
+
* Daily applies input settings inside its remotely loaded call machine, so a
|
|
375
|
+
* failure - Krisp's `KrispInitError: Canceled`, for example - arrives as a
|
|
376
|
+
* rejected promise, which a synchronous try/catch around
|
|
377
|
+
* `updateInputSettings()` cannot see. Without this the rejection escapes to
|
|
378
|
+
* the page as an unhandled rejection instead of reaching the listeners
|
|
379
|
+
* consumers register. Audio processing is non-critical, so the call continues.
|
|
380
|
+
*/
|
|
381
|
+
private emitAudioProcessingError(
|
|
382
|
+
stage: 'audio-processing-setup' | 'audio-processor-recovery',
|
|
383
|
+
error: unknown,
|
|
384
|
+
) {
|
|
385
|
+
this.emit('error', {
|
|
386
|
+
type: `${stage}-error`,
|
|
387
|
+
stage,
|
|
388
|
+
error: serializeError(error),
|
|
389
|
+
timestamp: new Date().toISOString(),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
352
393
|
async start(
|
|
353
394
|
assistant?: CreateAssistantDTO | string,
|
|
354
395
|
assistantOverrides?: AssistantOverrides,
|
|
@@ -539,6 +580,26 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
539
580
|
this.emit('network-connection', event);
|
|
540
581
|
});
|
|
541
582
|
|
|
583
|
+
this.call.on('recording-started', (event) => {
|
|
584
|
+
if (event) this.emit('recording-started', event);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
this.call.on('recording-stopped', (event) => {
|
|
588
|
+
if (event) this.emit('recording-stopped', event);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
this.call.on('recording-stats', (event) => {
|
|
592
|
+
if (event) this.emit('recording-stats', event);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
this.call.on('recording-error', (event) => {
|
|
596
|
+
if (event) this.emit('recording-error', event);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
600
|
+
if (event) this.emit('recording-upload-completed', event);
|
|
601
|
+
});
|
|
602
|
+
|
|
542
603
|
this.call.on('track-started', async (e) => {
|
|
543
604
|
if (!e || !e.participant) {
|
|
544
605
|
return;
|
|
@@ -736,6 +797,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
736
797
|
|
|
737
798
|
try {
|
|
738
799
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
800
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
739
801
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
740
802
|
this.emit('call-start-progress', {
|
|
741
803
|
stage: 'audio-observer-setup',
|
|
@@ -766,10 +828,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
766
828
|
if (e) this.handleRemoteParticipantsAudioLevel(e);
|
|
767
829
|
});
|
|
768
830
|
|
|
831
|
+
this.call.on('local-audio-level', (e) => {
|
|
832
|
+
if (e) this.handleLocalAudioLevel(e);
|
|
833
|
+
});
|
|
834
|
+
|
|
769
835
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
770
836
|
|
|
771
837
|
this.call.on('nonfatal-error', (e) => {
|
|
772
838
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
839
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
840
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
841
|
+
}
|
|
773
842
|
if (e?.type === 'audio-processor-error') {
|
|
774
843
|
this.call
|
|
775
844
|
?.updateInputSettings({
|
|
@@ -781,6 +850,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
781
850
|
})
|
|
782
851
|
.then(() => {
|
|
783
852
|
safeSetLocalAudio(this.call, true);
|
|
853
|
+
})
|
|
854
|
+
.catch((error) => {
|
|
855
|
+
this.emitAudioProcessingError('audio-processor-recovery', error);
|
|
784
856
|
});
|
|
785
857
|
}
|
|
786
858
|
});
|
|
@@ -795,14 +867,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
795
867
|
const audioProcessingStartTime = Date.now();
|
|
796
868
|
|
|
797
869
|
try {
|
|
798
|
-
this.call
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
870
|
+
this.call
|
|
871
|
+
.updateInputSettings({
|
|
872
|
+
audio: {
|
|
873
|
+
processor: {
|
|
874
|
+
type: 'noise-cancellation',
|
|
875
|
+
},
|
|
802
876
|
},
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
877
|
+
})
|
|
878
|
+
.catch((error) => {
|
|
879
|
+
this.emitAudioProcessingError('audio-processing-setup', error);
|
|
880
|
+
});
|
|
881
|
+
|
|
806
882
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
807
883
|
this.emit('call-start-progress', {
|
|
808
884
|
stage: 'audio-processing-setup',
|
|
@@ -927,6 +1003,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
927
1003
|
}, 1000);
|
|
928
1004
|
}
|
|
929
1005
|
|
|
1006
|
+
private handleLocalAudioLevel(e: DailyEventObjectLocalAudioLevel) {
|
|
1007
|
+
this.emit('local-volume-level', e.audioLevel);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
930
1010
|
/**
|
|
931
1011
|
* Stops the call by destroying the Daily call object.
|
|
932
1012
|
*
|
|
@@ -1027,6 +1107,57 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1027
1107
|
return this.call;
|
|
1028
1108
|
}
|
|
1029
1109
|
|
|
1110
|
+
/**
|
|
1111
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
1112
|
+
*
|
|
1113
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
1114
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
1115
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
1116
|
+
*
|
|
1117
|
+
* Returns 0 when there is no active call.
|
|
1118
|
+
*/
|
|
1119
|
+
public getLocalAudioLevel(): number {
|
|
1120
|
+
if (!this.call) {
|
|
1121
|
+
return 0;
|
|
1122
|
+
}
|
|
1123
|
+
return this.call.getLocalAudioLevel();
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
1128
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
1129
|
+
* interval or restart it if it was stopped.
|
|
1130
|
+
*
|
|
1131
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
1132
|
+
* microphone audio level (0 to 1).
|
|
1133
|
+
*
|
|
1134
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
1135
|
+
*/
|
|
1136
|
+
public async startLocalAudioLevelObserver(interval?: number): Promise<void> {
|
|
1137
|
+
if (!this.call) {
|
|
1138
|
+
throw new Error('Call object is not available.');
|
|
1139
|
+
}
|
|
1140
|
+
await this.call.startLocalAudioLevelObserver(interval);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
1145
|
+
* events will no longer be emitted until the observer is started again.
|
|
1146
|
+
*/
|
|
1147
|
+
public stopLocalAudioLevelObserver(): void {
|
|
1148
|
+
this.call?.stopLocalAudioLevelObserver();
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* Returns whether the local audio level observer is currently running.
|
|
1153
|
+
*/
|
|
1154
|
+
public isLocalAudioLevelObserverRunning(): boolean {
|
|
1155
|
+
if (!this.call) {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
return this.call.isLocalAudioLevelObserverRunning();
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1030
1161
|
public startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings) {
|
|
1031
1162
|
this.call?.startScreenShare({
|
|
1032
1163
|
displayMediaOptions,
|
|
@@ -1161,6 +1292,26 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1161
1292
|
this.emit('network-connection', event);
|
|
1162
1293
|
});
|
|
1163
1294
|
|
|
1295
|
+
this.call.on('recording-started', (event) => {
|
|
1296
|
+
if (event) this.emit('recording-started', event);
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
this.call.on('recording-stopped', (event) => {
|
|
1300
|
+
if (event) this.emit('recording-stopped', event);
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
this.call.on('recording-stats', (event) => {
|
|
1304
|
+
if (event) this.emit('recording-stats', event);
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
this.call.on('recording-error', (event) => {
|
|
1308
|
+
if (event) this.emit('recording-error', event);
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
1312
|
+
if (event) this.emit('recording-upload-completed', event);
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1164
1315
|
this.call.on('track-started', async (e) => {
|
|
1165
1316
|
if (!e || !e.participant) {
|
|
1166
1317
|
return;
|
|
@@ -1208,10 +1359,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1208
1359
|
if (e) this.handleRemoteParticipantsAudioLevel(e);
|
|
1209
1360
|
});
|
|
1210
1361
|
|
|
1362
|
+
this.call.on('local-audio-level', (e) => {
|
|
1363
|
+
if (e) this.handleLocalAudioLevel(e);
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1211
1366
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
1212
1367
|
|
|
1213
1368
|
this.call.on('nonfatal-error', (e) => {
|
|
1214
1369
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
1370
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
1371
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
1372
|
+
}
|
|
1215
1373
|
if (e?.type === 'audio-processor-error') {
|
|
1216
1374
|
this.call
|
|
1217
1375
|
?.updateInputSettings({
|
|
@@ -1223,6 +1381,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1223
1381
|
})
|
|
1224
1382
|
.then(() => {
|
|
1225
1383
|
safeSetLocalAudio(this.call, true);
|
|
1384
|
+
})
|
|
1385
|
+
.catch((error) => {
|
|
1386
|
+
this.emitAudioProcessingError('audio-processor-recovery', error);
|
|
1226
1387
|
});
|
|
1227
1388
|
}
|
|
1228
1389
|
});
|
|
@@ -1353,6 +1514,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1353
1514
|
|
|
1354
1515
|
try {
|
|
1355
1516
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
1517
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
1356
1518
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
1357
1519
|
this.emit('call-start-progress', {
|
|
1358
1520
|
stage: 'audio-observer-setup',
|
|
@@ -1383,14 +1545,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1383
1545
|
const audioProcessingStartTime = Date.now();
|
|
1384
1546
|
|
|
1385
1547
|
try {
|
|
1386
|
-
this.call
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1548
|
+
this.call
|
|
1549
|
+
.updateInputSettings({
|
|
1550
|
+
audio: {
|
|
1551
|
+
processor: {
|
|
1552
|
+
type: 'noise-cancellation',
|
|
1553
|
+
},
|
|
1390
1554
|
},
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1555
|
+
})
|
|
1556
|
+
.catch((error) => {
|
|
1557
|
+
this.emitAudioProcessingError('audio-processing-setup', error);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1394
1560
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
1395
1561
|
this.emit('call-start-progress', {
|
|
1396
1562
|
stage: 'audio-processing-setup',
|