1
- import { Call , CreateAssistantDTO , CreateSquadDTO , AssistantOverrides } from './api' ;
1
+ import type { ChatCompletionMessageParam } from 'openai/resources' ;
2
+
2
3
import DailyIframe , {
3
- DailyAdvancedConfig ,
4
4
DailyCall ,
5
+ DailyAdvancedConfig ,
6
+ DailyFactoryOptions ,
5
7
DailyEventObjectAppMessage ,
6
8
DailyEventObjectParticipant ,
7
9
DailyEventObjectRemoteParticipantsAudioLevel ,
8
- DailyFactoryOptions ,
9
10
} from '@daily-co/daily-js' ;
10
-
11
- import type { ChatCompletionMessageParam } from 'openai/resources' ;
12
11
import EventEmitter from 'events' ;
12
+
13
+ import {
14
+ Call ,
15
+ CreateSquadDTO ,
16
+ CreateAssistantDTO ,
17
+ AssistantOverrides ,
18
+ } from './api' ;
13
19
import { client } from './client' ;
14
20
15
- function destroyAudioPlayer ( participantId : string ) {
16
- const player = document . querySelector ( `audio[data-participant-id="${ participantId } "]` ) ;
17
- player ?. remove ( ) ;
18
- }
19
- async function startPlayer ( player : HTMLAudioElement , track : any ) {
21
+ async function startAudioPlayer (
22
+ player : HTMLAudioElement ,
23
+ track : MediaStreamTrack ,
24
+ ) {
20
25
player . muted = false ;
21
26
player . autoplay = true ;
22
27
if ( track != null ) {
23
28
player . srcObject = new MediaStream ( [ track ] ) ;
24
29
await player . play ( ) ;
25
30
}
26
31
}
27
- async function buildAudioPlayer ( track : any , participantId : string ) {
32
+
33
+ async function buildAudioPlayer (
34
+ track : MediaStreamTrack ,
35
+ participantId : string ,
36
+ ) {
28
37
const player = document . createElement ( 'audio' ) ;
29
38
player . dataset . participantId = participantId ;
30
39
document . body . appendChild ( player ) ;
31
- await startPlayer ( player , track ) ;
40
+ await startAudioPlayer ( player , track ) ;
32
41
return player ;
33
42
}
43
+
44
+ function destroyAudioPlayer ( participantId : string ) {
45
+ const player = document . querySelector (
46
+ `audio[data-participant-id="${ participantId } "]` ,
47
+ ) ;
48
+ player ?. remove ( ) ;
49
+ }
50
+
34
51
function subscribeToTracks (
35
52
e : DailyEventObjectParticipant ,
36
53
call : DailyCall ,
37
54
isVideoRecordingEnabled ?: boolean ,
55
+ isVideoEnabled ?: boolean ,
38
56
) {
39
57
if ( e . participant . local ) return ;
40
58
41
59
call . updateParticipant ( e . participant . session_id , {
42
60
setSubscribedTracks : {
43
61
audio : true ,
44
- video : isVideoRecordingEnabled ,
62
+ video : isVideoRecordingEnabled || isVideoEnabled ,
45
63
} ,
46
64
} ) ;
47
65
}
@@ -63,7 +81,10 @@ export interface SayMessage {
63
81
endCallAfterSpoken ?: boolean ;
64
82
}
65
83
66
- type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage ;
84
+ type VapiClientToServerMessage =
85
+ | AddMessageMessage
86
+ | ControlMessages
87
+ | SayMessage ;
67
88
68
89
type VapiEventNames =
69
90
| 'call-end'
@@ -72,6 +93,7 @@ type VapiEventNames =
72
93
| 'speech-start'
73
94
| 'speech-end'
74
95
| 'message'
96
+ | 'video'
75
97
| 'error' ;
76
98
77
99
type VapiEventListeners = {
@@ -80,23 +102,36 @@ type VapiEventListeners = {
80
102
'volume-level' : ( volume : number ) => void ;
81
103
'speech-start' : ( ) => void ;
82
104
'speech-end' : ( ) => void ;
105
+ video : ( track : MediaStreamTrack ) => void ;
83
106
message : ( message : any ) => void ;
84
107
error : ( error : any ) => void ;
85
108
} ;
86
109
87
110
class VapiEventEmitter extends EventEmitter {
88
- on < E extends VapiEventNames > ( event : E , listener : VapiEventListeners [ E ] ) : this {
111
+ on < E extends VapiEventNames > (
112
+ event : E ,
113
+ listener : VapiEventListeners [ E ] ,
114
+ ) : this {
89
115
super . on ( event , listener ) ;
90
116
return this ;
91
117
}
92
- once < E extends VapiEventNames > ( event : E , listener : VapiEventListeners [ E ] ) : this {
118
+ once < E extends VapiEventNames > (
119
+ event : E ,
120
+ listener : VapiEventListeners [ E ] ,
121
+ ) : this {
93
122
super . once ( event , listener ) ;
94
123
return this ;
95
124
}
96
- emit < E extends VapiEventNames > ( event : E , ...args : Parameters < VapiEventListeners [ E ] > ) : boolean {
125
+ emit < E extends VapiEventNames > (
126
+ event : E ,
127
+ ...args : Parameters < VapiEventListeners [ E ] >
128
+ ) : boolean {
97
129
return super . emit ( event , ...args ) ;
98
130
}
99
- removeListener < E extends VapiEventNames > ( event : E , listener : VapiEventListeners [ E ] ) : this {
131
+ removeListener < E extends VapiEventNames > (
132
+ event : E ,
133
+ listener : VapiEventListeners [ E ] ,
134
+ ) : this {
100
135
super . removeListener ( event , listener ) ;
101
136
return this ;
102
137
}
@@ -110,20 +145,23 @@ export default class Vapi extends VapiEventEmitter {
110
145
private started : boolean = false ;
111
146
private call : DailyCall | null = null ;
112
147
private speakingTimeout : NodeJS . Timeout | null = null ;
113
- private dailyCallConfig : DailyAdvancedConfig = { }
114
- private dailyCallObject : DailyFactoryOptions = { }
148
+ private dailyCallConfig : DailyAdvancedConfig = { } ;
149
+ private dailyCallObject : DailyFactoryOptions = { } ;
115
150
116
151
constructor (
117
- apiToken : string ,
118
- apiBaseUrl ?: string ,
119
- dailyCallConfig ?: Pick < DailyAdvancedConfig , 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt' > ,
120
- dailyCallObject ?: Pick < DailyFactoryOptions , 'audioSource' >
152
+ apiToken : string ,
153
+ apiBaseUrl ?: string ,
154
+ dailyCallConfig ?: Pick <
155
+ DailyAdvancedConfig ,
156
+ 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt'
157
+ > ,
158
+ dailyCallObject ?: Pick < DailyFactoryOptions , 'audioSource' > ,
121
159
) {
122
160
super ( ) ;
123
161
client . baseUrl = apiBaseUrl ?? 'https://api.vapi.ai' ;
124
162
client . setSecurityData ( apiToken ) ;
125
- this . dailyCallConfig = dailyCallConfig ?? { }
126
- this . dailyCallObject = dailyCallObject ?? { }
163
+ this . dailyCallConfig = dailyCallConfig ?? { } ;
164
+ this . dailyCallObject = dailyCallObject ?? { } ;
127
165
}
128
166
129
167
private cleanup ( ) {
@@ -161,12 +199,17 @@ export default class Vapi extends VapiEventEmitter {
161
199
if ( this . call ) {
162
200
this . cleanup ( ) ;
163
201
}
164
- const isVideoRecordingEnabled = webCall ?. artifactPlan ?. videoRecordingEnabled ?? false ;
202
+
203
+ const isVideoRecordingEnabled =
204
+ webCall ?. artifactPlan ?. videoRecordingEnabled ?? false ;
205
+
206
+ // @ts -expect-error Tavus voice exists
207
+ const isVideoEnabled = webCall . assistant ?. voice ?. provider === 'tavus' ;
165
208
166
209
this . call = DailyIframe . createCallObject ( {
167
210
audioSource : this . dailyCallObject . audioSource ?? true ,
168
- videoSource : isVideoRecordingEnabled ,
169
- dailyConfig : this . dailyCallConfig
211
+ videoSource : this . dailyCallObject . videoSource ?? isVideoRecordingEnabled ,
212
+ dailyConfig : this . dailyCallConfig ,
170
213
} ) ;
171
214
this . call . iframe ( ) ?. style . setProperty ( 'display' , 'none' ) ;
172
215
@@ -197,17 +240,27 @@ export default class Vapi extends VapiEventEmitter {
197
240
this . call . on ( 'track-started' , async ( e ) => {
198
241
if ( ! e || ! e . participant ) return ;
199
242
if ( e . participant ?. local ) return ;
200
- if ( e . track . kind !== 'audio' ) return ;
243
+ if ( e . participant ?. user_name !== 'Vapi Speaker' ) return ;
244
+
245
+ if ( e . track . kind === 'video' ) {
246
+ this . emit ( 'video' , e . track ) ;
247
+ }
201
248
202
- await buildAudioPlayer ( e . track , e . participant . session_id ) ;
249
+ if ( e . track . kind === 'audio' ) {
250
+ await buildAudioPlayer ( e . track , e . participant . session_id ) ;
251
+ }
203
252
204
- if ( e ?. participant ?. user_name !== 'Vapi Speaker' ) return ;
205
253
this . call ?. sendAppMessage ( 'playable' ) ;
206
254
} ) ;
207
255
208
256
this . call . on ( 'participant-joined' , ( e ) => {
209
257
if ( ! e || ! this . call ) return ;
210
- subscribeToTracks ( e , this . call , isVideoRecordingEnabled ) ;
258
+ subscribeToTracks (
259
+ e ,
260
+ this . call ,
261
+ isVideoRecordingEnabled ,
262
+ isVideoEnabled ,
263
+ ) ;
211
264
} ) ;
212
265
213
266
await this . call . join ( {
@@ -231,7 +284,8 @@ export default class Vapi extends VapiEventEmitter {
231
284
this . send ( {
232
285
type : 'control' ,
233
286
control : 'say-first-message' ,
234
- videoRecordingStartDelaySeconds : ( new Date ( ) . getTime ( ) - recordingRequestedTime ) / 1000 ,
287
+ videoRecordingStartDelaySeconds :
288
+ ( new Date ( ) . getTime ( ) - recordingRequestedTime ) / 1000 ,
235
289
} ) ;
236
290
} ) ;
237
291
}
@@ -296,8 +350,13 @@ export default class Vapi extends VapiEventEmitter {
296
350
}
297
351
}
298
352
299
- private handleRemoteParticipantsAudioLevel ( e : DailyEventObjectRemoteParticipantsAudioLevel ) {
300
- const speechLevel = Object . values ( e . participantsAudioLevel ) . reduce ( ( a , b ) => a + b , 0 ) ;
353
+ private handleRemoteParticipantsAudioLevel (
354
+ e : DailyEventObjectRemoteParticipantsAudioLevel ,
355
+ ) {
356
+ const speechLevel = Object . values ( e . participantsAudioLevel ) . reduce (
357
+ ( a , b ) => a + b ,
358
+ 0 ,
359
+ ) ;
301
360
302
361
this . emit ( 'volume-level' , Math . min ( 1 , speechLevel / 0.15 ) ) ;
303
362
@@ -329,25 +388,17 @@ export default class Vapi extends VapiEventEmitter {
329
388
}
330
389
331
390
public setMuted ( mute : boolean ) {
332
- try {
333
- if ( ! this . call ) {
334
- throw new Error ( 'Call object is not available.' ) ;
335
- }
336
- this . call . setLocalAudio ( ! mute ) ;
337
- } catch ( error ) {
338
- throw error ;
391
+ if ( ! this . call ) {
392
+ throw new Error ( 'Call object is not available.' ) ;
339
393
}
394
+ this . call . setLocalAudio ( ! mute ) ;
340
395
}
341
396
342
397
public isMuted ( ) {
343
- try {
344
- if ( ! this . call ) {
345
- return false ;
346
- }
347
- return this . call . localAudio ( ) === false ;
348
- } catch ( error ) {
349
- throw error ;
398
+ if ( ! this . call ) {
399
+ return false ;
350
400
}
401
+ return this . call . localAudio ( ) === false ;
351
402
}
352
403
353
404
public say ( message : string , endCallAfterSpoken ?: boolean ) {
@@ -358,11 +409,15 @@ export default class Vapi extends VapiEventEmitter {
358
409
} ) ;
359
410
}
360
411
361
- public setInputDevicesAsync ( options : Parameters < DailyCall [ 'setInputDevicesAsync' ] > [ 0 ] ) {
412
+ public setInputDevicesAsync (
413
+ options : Parameters < DailyCall [ 'setInputDevicesAsync' ] > [ 0 ] ,
414
+ ) {
362
415
this . call ?. setInputDevicesAsync ( options ) ;
363
416
}
364
417
365
- public setOutputDeviceAsync ( options : Parameters < DailyCall [ 'setOutputDeviceAsync' ] > [ 0 ] ) {
418
+ public setOutputDeviceAsync (
419
+ options : Parameters < DailyCall [ 'setOutputDeviceAsync' ] > [ 0 ] ,
420
+ ) {
366
421
this . call ?. setOutputDeviceAsync ( options ) ;
367
422
}
368
423
0 commit comments