From e0519fbe1bbfd19ccd5ed90fbb97d76e4b1b67c8 Mon Sep 17 00:00:00 2001 From: sagar-vapi <161429577+sagar-vapi@users.noreply.github.com> Date: Sat, 8 Jun 2024 23:12:39 +0530 Subject: [PATCH] Adding support for video recording on daily side (#38) * Adding support for video recording on daily side * fixing variable name and adding stop recording in case of error * reading artifactPlan from call object instead of assistant to handle assistantId case --------- Co-authored-by: Sagar Agarwal --- api.ts | 21 +++++++++++++++++++++ vapi.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/api.ts b/api.ts index 1fd4b82..31089ba 100644 --- a/api.ts +++ b/api.ts @@ -1811,6 +1811,14 @@ export interface AnalysisPlan { structuredDataSchema?: JsonSchema; } +export interface ArtifactPlan { + /** + * This sets whether the video is recorded with Daily. Defaults to false. + * @example true + */ + videoRecordingEnabled?: boolean; +} + export interface CreateAssistantDTO { /** These are the options for the assistant's transcriber. */ transcriber?: DeepgramTranscriber | TalkscriberTranscriber; @@ -1981,6 +1989,7 @@ export interface CreateAssistantDTO { */ serverUrlSecret?: string; analysisPlan?: AnalysisPlan; + artifactPlan?: ArtifactPlan; } export interface Assistant { @@ -2153,6 +2162,7 @@ export interface Assistant { */ serverUrlSecret?: string; analysisPlan?: AnalysisPlan; + artifactPlan?: ArtifactPlan; /** This is the unique identifier for the assistant. */ id: string; /** This is the unique identifier for the org that this assistant belongs to. */ @@ -2339,6 +2349,7 @@ export interface UpdateAssistantDTO { */ serverUrlSecret?: string; analysisPlan?: AnalysisPlan; + artifactPlan?: ArtifactPlan; } export interface AnalysisCostBreakdown { @@ -2394,6 +2405,11 @@ export interface Analysis { successEvaluation?: string; } +export interface Artifact { + /** Video recording url if assistant.artifactPlan.videoRecordingEnabled was set to true */ + videoRecordingUrl?: string; +} + export interface PhoneCallTwilioDetails { statusCallbackEvent?: 'initiated' | 'ringing' | 'answered' | 'completed'; machineDetection?: 'Enable' | 'DetectMessageEnd'; @@ -2598,6 +2614,7 @@ export interface OverrideAssistantDTO { */ serverUrlSecret?: string; analysisPlan?: AnalysisPlan; + artifactPlan?: ArtifactPlan; } export interface SquadMemberDTO { @@ -2803,6 +2820,10 @@ export interface Call { stereoRecordingUrl?: string; /** This is the analysis of the call. Customize the analysis by setting `assistant.analysisPlan`. */ analysis?: Analysis; + /** This is the artifacts related to the call. Customize the analysis by setting `assistant.artifactPlan`. */ + artifact?: Artifact; + /** This stores a copy of assistant.artifactPlan. */ + artifactPlan?: ArtifactPlan; /** These are the messages that were spoken during the call. */ messages?: object[]; /** diff --git a/vapi.ts b/vapi.ts index bd7f441..8acf5ff 100644 --- a/vapi.ts +++ b/vapi.ts @@ -29,13 +29,13 @@ async function buildAudioPlayer(track: any, participantId: string) { await startPlayer(player, track); return player; } -function subscribeToTracks(e: DailyEventObjectParticipant, call: DailyCall) { +function subscribeToTracks(e: DailyEventObjectParticipant, call: DailyCall, isVideoRecordingEnabled?: boolean) { if (e.participant.local) return; call.updateParticipant(e.participant.session_id, { setSubscribedTracks: { audio: true, - video: false, + video: isVideoRecordingEnabled, }, }); } @@ -145,14 +145,21 @@ export default class Vapi extends VapiEventEmitter { if (this.call) { this.cleanup(); } + const isVideoRecordingEnabled = + webCall?.artifactPlan?.videoRecordingEnabled ?? + false; + this.call = DailyIframe.createCallObject({ audioSource: true, - videoSource: false, + videoSource: isVideoRecordingEnabled, }); this.call.iframe()?.style.setProperty('display', 'none'); this.call.on('left-meeting', () => { this.emit('call-end'); + if (isVideoRecordingEnabled) { + this.call?.stopRecording(); + } this.cleanup(); }); @@ -163,6 +170,9 @@ export default class Vapi extends VapiEventEmitter { this.call.on('error', (error: any) => { this.emit('error', error); + if (isVideoRecordingEnabled) { + this.call?.stopRecording(); + } }); this.call.on('camera-error', (error: any) => { @@ -182,7 +192,7 @@ export default class Vapi extends VapiEventEmitter { this.call.on('participant-joined', (e) => { if (!e || !this.call) return; - subscribeToTracks(e, this.call); + subscribeToTracks(e, this.call, isVideoRecordingEnabled); }); await this.call.join({ @@ -190,6 +200,17 @@ export default class Vapi extends VapiEventEmitter { subscribeToTracksAutomatically: false, }); + if (isVideoRecordingEnabled) { + await this.call.startRecording({ + width: 1280, + height: 720, + backgroundColor: '#FF1F2D3D', + layout: { + preset: 'default', + }, + }); + } + this.call.startRemoteParticipantsAudioLevelObserver(100); this.call.on('remote-participants-audio-level', (e) => {