diff --git a/README.md b/README.md index 564bae095..35939b083 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ vapi.say("Our time's up, goodbye!", true) ## Recording -When video recording is enabled on the call's artifact plan, Vapi starts a recording automatically when the call begins. Recording start is asynchronous: success is signaled by the `recording-started` event and failure by the `recording-error` event. +When video recording is enabled on the call's artifact plan, Vapi starts a recording automatically when the call begins. When the server provides a meeting token for the call (`call.transport.callToken`), the SDK joins with it and Daily auto-starts the cloud recording, which is more reliable on weak networks. Otherwise the SDK starts the recording from the client after joining. Recording start is asynchronous: success is signaled by the `recording-started` event and failure by the `recording-error` event. A failed recording start is not retried automatically. Use `startRecording()` and `stopRecording()` to control the recording yourself. For example, to retry after a failure: diff --git a/example/src/App.tsx b/example/src/App.tsx index a1179d258..eaa2a51b6 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import Vapi from '@vapi-ai/web'; const VAPI_PUBLIC_KEY = import.meta.env.VITE_VAPI_PUBLIC_KEY; @@ -30,6 +30,9 @@ function App() { const [networkTestRunning, setNetworkTestRunning] = useState(false); const [networkTestResults, setNetworkTestResults] = useState(null); const [simulateFailure, setSimulateFailure] = useState('none'); + const [videoRecordingEnabled, setVideoRecordingEnabled] = useState(false); + const [localVideoTrack, setLocalVideoTrack] = useState(null); + const localVideoRef = useRef(null); useEffect(() => { // Check for stored webCall on component mount @@ -61,7 +64,17 @@ function App() { setConnected(false); setAssistantIsSpeaking(false); setVolumeLevel(0); - addMessage('system', 'Call ended - webCall data preserved for reconnection'); + setLocalVideoTrack(null); + addMessage('system', 'Call ended'); + }); + + // Tracks the local camera so the UI can show what's being recorded when + // video recording is enabled on the call. + vapi.on('daily-participant-updated', (participant) => { + if (!participant.local) return; + const video = participant.tracks?.video; + const track = video?.persistentTrack ?? video?.track ?? null; + setLocalVideoTrack(video?.state === 'playable' ? track : null); }); vapi.on('speech-start', () => { @@ -147,6 +160,15 @@ function App() { }; }, [vapi]); + // Attach the local camera track to the preview element whenever it changes. + useEffect(() => { + if (localVideoRef.current) { + localVideoRef.current.srcObject = localVideoTrack + ? new MediaStream([localVideoTrack]) + : null; + } + }, [localVideoTrack]); + const addMessage = (type: 'user' | 'assistant' | 'system', content: string) => { setMessages(prev => [...prev, { time: new Date().toLocaleTimeString(), @@ -203,6 +225,9 @@ function App() { firstMessage: "Hello! I'm your AI assistant. How can I help you today?", endCallMessage: "Thank you for the conversation. Goodbye!", endCallPhrases: ["goodbye", "bye", "end call", "hang up"], + artifactPlan: { + videoRecordingEnabled, + }, // Max call duration (in seconds) - 10 minutes maxDurationSeconds: 600 @@ -216,7 +241,8 @@ function App() { webCallUrl: (webCall as any).webCallUrl, id: webCall.id, artifactPlan: webCall.artifactPlan, - assistant: webCall.assistant + assistant: webCall.assistant, + transport: webCall.transport, }; localStorage.setItem('vapi-webcall', JSON.stringify(webCallToStore)); setStoredWebCall(webCallToStore); @@ -230,7 +256,19 @@ function App() { }; const stopCall = () => { + // Leaves the call without ending it server-side. With + // roomDeleteOnUserLeaveEnabled: false the call stays alive, so you can + // rejoin it with the Reconnect button. + vapi.stop(); + addMessage('system', 'Left the call - it stays alive for reconnection'); + }; + + const endCall = () => { + // Ends the Vapi call for everyone; reconnection is not possible after this. vapi.end(); + localStorage.removeItem('vapi-webcall'); + setStoredWebCall(null); + addMessage('system', 'Ended the call - stored call data cleared'); }; const reconnectCall = async () => { @@ -445,6 +483,56 @@ function App() { )} + {/* Local Camera Preview */} + {localVideoTrack && ( +
+
+ )} + + {/* Call Settings */} +
+

⚙️ Call Settings

+ +

+ When enabled, your camera turns on and the call is recorded to video. Applies to the next + call you start. +

+
+ {/* Failure Simulation Controls */}
)} - + + - +