From 9b1d2bb6d12484de45f4c04bd41e82b93ccc65af Mon Sep 17 00:00:00 2001 From: Luke Shiels <47612057+skve@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:40:45 -0800 Subject: [PATCH 1/2] Fix intro message cut-off issue --- vapi.ts | 110 +++++++++++++++----------------------------------------- 1 file changed, 28 insertions(+), 82 deletions(-) diff --git a/vapi.ts b/vapi.ts index ff3814566..ce9350ea3 100644 --- a/vapi.ts +++ b/vapi.ts @@ -10,12 +10,7 @@ import DailyIframe, { } from '@daily-co/daily-js'; import EventEmitter from 'events'; -import { - Call, - CreateSquadDTO, - CreateAssistantDTO, - AssistantOverrides, -} from './api'; +import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides } from './api'; import { client } from './client'; export interface AddMessageMessage { @@ -35,10 +30,7 @@ export interface SayMessage { endCallAfterSpoken?: boolean; } -type VapiClientToServerMessage = - | AddMessageMessage - | ControlMessages - | SayMessage; +type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage; type VapiEventNames = | 'call-end' @@ -61,10 +53,7 @@ type VapiEventListeners = { error: (error: any) => void; }; -async function startAudioPlayer( - player: HTMLAudioElement, - track: MediaStreamTrack, -) { +async function startAudioPlayer(player: HTMLAudioElement, track: MediaStreamTrack) { player.muted = false; player.autoplay = true; if (track != null) { @@ -73,10 +62,7 @@ async function startAudioPlayer( } } -async function buildAudioPlayer( - track: MediaStreamTrack, - participantId: string, -) { +async function buildAudioPlayer(track: MediaStreamTrack, participantId: string) { const player = document.createElement('audio'); player.dataset.participantId = participantId; document.body.appendChild(player); @@ -85,9 +71,7 @@ async function buildAudioPlayer( } function destroyAudioPlayer(participantId: string) { - const player = document.querySelector( - `audio[data-participant-id="${participantId}"]`, - ); + const player = document.querySelector(`audio[data-participant-id="${participantId}"]`); player?.remove(); } @@ -108,30 +92,18 @@ function subscribeToTracks( } class VapiEventEmitter extends EventEmitter { - on( - event: E, - listener: VapiEventListeners[E], - ): this { + on(event: E, listener: VapiEventListeners[E]): this { super.on(event, listener); return this; } - once( - event: E, - listener: VapiEventListeners[E], - ): this { + once(event: E, listener: VapiEventListeners[E]): this { super.once(event, listener); return this; } - emit( - event: E, - ...args: Parameters - ): boolean { + emit(event: E, ...args: Parameters): boolean { return super.emit(event, ...args); } - removeListener( - event: E, - listener: VapiEventListeners[E], - ): this { + removeListener(event: E, listener: VapiEventListeners[E]): this { super.removeListener(event, listener); return this; } @@ -147,14 +119,12 @@ export default class Vapi extends VapiEventEmitter { private speakingTimeout: NodeJS.Timeout | null = null; private dailyCallConfig: DailyAdvancedConfig = {}; private dailyCallObject: DailyFactoryOptions = {}; + private introRequested: boolean = false; constructor( apiToken: string, apiBaseUrl?: string, - dailyCallConfig?: Pick< - DailyAdvancedConfig, - 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt' - >, + dailyCallConfig?: Pick, dailyCallObject?: Pick, ) { super(); @@ -176,9 +146,7 @@ export default class Vapi extends VapiEventEmitter { return false; } const userAgent = navigator.userAgent; - return /android|iphone|ipad|ipod|iemobile|blackberry|bada/i.test( - userAgent.toLowerCase(), - ); + return /android|iphone|ipad|ipod|iemobile|blackberry|bada/i.test(userAgent.toLowerCase()); } private async sleep(ms: number) { @@ -214,8 +182,7 @@ export default class Vapi extends VapiEventEmitter { this.cleanup(); } - const isVideoRecordingEnabled = - webCall?.artifactPlan?.videoRecordingEnabled ?? false; + const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false; const isVideoEnabled = webCall.transport?.assistantVideoEnabled ?? false; @@ -268,19 +235,18 @@ export default class Vapi extends VapiEventEmitter { this.call.on('participant-joined', (e) => { if (!e || !this.call) return; - subscribeToTracks( - e, - this.call, - isVideoRecordingEnabled, - isVideoEnabled, - ); - }); - // Allow mobile devices to finish processing the microphone permissions - // request before joining the call and playing the assistant's audio - if (this.isMobileDevice()) { - await this.sleep(1000); - } + if (!this.introRequested && !e.participant.local) { + this.introRequested = true; + + this.send({ + type: 'control', + control: 'say-first-message', + }); + } + + subscribeToTracks(e, this.call, isVideoRecordingEnabled, isVideoEnabled); + }); await this.call.join({ // @ts-expect-error This exists @@ -289,8 +255,6 @@ export default class Vapi extends VapiEventEmitter { }); if (isVideoRecordingEnabled) { - const recordingRequestedTime = new Date().getTime(); - this.call.startRecording({ width: 1280, height: 720, @@ -299,15 +263,6 @@ export default class Vapi extends VapiEventEmitter { preset: 'default', }, }); - - this.call.on('recording-started', () => { - this.send({ - type: 'control', - control: 'say-first-message', - videoRecordingStartDelaySeconds: - (new Date().getTime() - recordingRequestedTime) / 1000, - }); - }); } this.call.startRemoteParticipantsAudioLevelObserver(100); @@ -370,13 +325,8 @@ export default class Vapi extends VapiEventEmitter { } } - private handleRemoteParticipantsAudioLevel( - e: DailyEventObjectRemoteParticipantsAudioLevel, - ) { - const speechLevel = Object.values(e.participantsAudioLevel).reduce( - (a, b) => a + b, - 0, - ); + private handleRemoteParticipantsAudioLevel(e: DailyEventObjectRemoteParticipantsAudioLevel) { + const speechLevel = Object.values(e.participantsAudioLevel).reduce((a, b) => a + b, 0); this.emit('volume-level', Math.min(1, speechLevel / 0.15)); @@ -429,15 +379,11 @@ export default class Vapi extends VapiEventEmitter { }); } - public setInputDevicesAsync( - options: Parameters[0], - ) { + public setInputDevicesAsync(options: Parameters[0]) { this.call?.setInputDevicesAsync(options); } - public setOutputDeviceAsync( - options: Parameters[0], - ) { + public setOutputDeviceAsync(options: Parameters[0]) { this.call?.setOutputDeviceAsync(options); } From 97eea99ca1add46c8aa7b8e6e03e675c7ae58391 Mon Sep 17 00:00:00 2001 From: Luke Shiels <47612057+skve@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:45:37 -0800 Subject: [PATCH 2/2] Refactor vapi.ts for improved readability and consistency by formatting multi-line imports and function parameters. Removed unused mobile device detection and sleep functions. No functional changes made. Signed-off-by: Luke Shiels <47612057+skve@users.noreply.github.com> --- vapi.ts | 83 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/vapi.ts b/vapi.ts index ce9350ea3..d565fa7ba 100644 --- a/vapi.ts +++ b/vapi.ts @@ -10,7 +10,12 @@ import DailyIframe, { } from '@daily-co/daily-js'; import EventEmitter from 'events'; -import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides } from './api'; +import { + Call, + CreateSquadDTO, + CreateAssistantDTO, + AssistantOverrides, +} from './api'; import { client } from './client'; export interface AddMessageMessage { @@ -30,7 +35,10 @@ export interface SayMessage { endCallAfterSpoken?: boolean; } -type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage; +type VapiClientToServerMessage = + | AddMessageMessage + | ControlMessages + | SayMessage; type VapiEventNames = | 'call-end' @@ -53,7 +61,10 @@ type VapiEventListeners = { error: (error: any) => void; }; -async function startAudioPlayer(player: HTMLAudioElement, track: MediaStreamTrack) { +async function startAudioPlayer( + player: HTMLAudioElement, + track: MediaStreamTrack, +) { player.muted = false; player.autoplay = true; if (track != null) { @@ -62,7 +73,10 @@ async function startAudioPlayer(player: HTMLAudioElement, track: MediaStreamTrac } } -async function buildAudioPlayer(track: MediaStreamTrack, participantId: string) { +async function buildAudioPlayer( + track: MediaStreamTrack, + participantId: string, +) { const player = document.createElement('audio'); player.dataset.participantId = participantId; document.body.appendChild(player); @@ -71,7 +85,9 @@ async function buildAudioPlayer(track: MediaStreamTrack, participantId: string) } function destroyAudioPlayer(participantId: string) { - const player = document.querySelector(`audio[data-participant-id="${participantId}"]`); + const player = document.querySelector( + `audio[data-participant-id="${participantId}"]`, + ); player?.remove(); } @@ -92,18 +108,30 @@ function subscribeToTracks( } class VapiEventEmitter extends EventEmitter { - on(event: E, listener: VapiEventListeners[E]): this { + on( + event: E, + listener: VapiEventListeners[E], + ): this { super.on(event, listener); return this; } - once(event: E, listener: VapiEventListeners[E]): this { + once( + event: E, + listener: VapiEventListeners[E], + ): this { super.once(event, listener); return this; } - emit(event: E, ...args: Parameters): boolean { + emit( + event: E, + ...args: Parameters + ): boolean { return super.emit(event, ...args); } - removeListener(event: E, listener: VapiEventListeners[E]): this { + removeListener( + event: E, + listener: VapiEventListeners[E], + ): this { super.removeListener(event, listener); return this; } @@ -124,7 +152,10 @@ export default class Vapi extends VapiEventEmitter { constructor( apiToken: string, apiBaseUrl?: string, - dailyCallConfig?: Pick, + dailyCallConfig?: Pick< + DailyAdvancedConfig, + 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt' + >, dailyCallObject?: Pick, ) { super(); @@ -141,18 +172,6 @@ export default class Vapi extends VapiEventEmitter { this.speakingTimeout = null; } - private isMobileDevice() { - if (typeof navigator === 'undefined') { - return false; - } - const userAgent = navigator.userAgent; - return /android|iphone|ipad|ipod|iemobile|blackberry|bada/i.test(userAgent.toLowerCase()); - } - - private async sleep(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - async start( assistant?: CreateAssistantDTO | string, assistantOverrides?: AssistantOverrides, @@ -182,7 +201,8 @@ export default class Vapi extends VapiEventEmitter { this.cleanup(); } - const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false; + const isVideoRecordingEnabled = + webCall?.artifactPlan?.videoRecordingEnabled ?? false; const isVideoEnabled = webCall.transport?.assistantVideoEnabled ?? false; @@ -325,8 +345,13 @@ export default class Vapi extends VapiEventEmitter { } } - private handleRemoteParticipantsAudioLevel(e: DailyEventObjectRemoteParticipantsAudioLevel) { - const speechLevel = Object.values(e.participantsAudioLevel).reduce((a, b) => a + b, 0); + private handleRemoteParticipantsAudioLevel( + e: DailyEventObjectRemoteParticipantsAudioLevel, + ) { + const speechLevel = Object.values(e.participantsAudioLevel).reduce( + (a, b) => a + b, + 0, + ); this.emit('volume-level', Math.min(1, speechLevel / 0.15)); @@ -379,11 +404,15 @@ export default class Vapi extends VapiEventEmitter { }); } - public setInputDevicesAsync(options: Parameters[0]) { + public setInputDevicesAsync( + options: Parameters[0], + ) { this.call?.setInputDevicesAsync(options); } - public setOutputDeviceAsync(options: Parameters[0]) { + public setOutputDeviceAsync( + options: Parameters[0], + ) { this.call?.setOutputDeviceAsync(options); }