Skip to main content
RapidDev - Software Development Agency
App Featurescommunication-social18 min read

How to Add Video Calls to Your App (Copy-Paste Prompts Included)

Video calling needs a WebRTC SFU service (Daily.co or Livekit Cloud), a Supabase Edge Function that generates meeting tokens, and a video grid UI that handles the NotAllowedError when users deny camera permission. With v0 or Lovable you can ship working 1-to-many calls in 6-12 hours. Daily.co free tier covers 10,000 participant-minutes/mo ($0/mo for small apps); usage-based pricing kicks in at $0.00099/participant-minute after that.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Advanced

Category

communication-social

Build with AI

6-12 hours with Lovable or v0

Custom build

4-8 weeks custom dev

Running cost

$0/mo under 10,000 participant-minutes, $20-80/mo at 1,000 users

Works on

Web

Everything it takes to ship Video Calls — parts, prompts, and real costs.

TL;DR

Video calling needs a WebRTC SFU service (Daily.co or Livekit Cloud), a Supabase Edge Function that generates meeting tokens, and a video grid UI that handles the NotAllowedError when users deny camera permission. With v0 or Lovable you can ship working 1-to-many calls in 6-12 hours. Daily.co free tier covers 10,000 participant-minutes/mo ($0/mo for small apps); usage-based pricing kicks in at $0.00099/participant-minute after that.

What Video Calls Actually Require to Build

Video calling is the most technically complex communication feature on this list. You are not just rendering a UI — you are negotiating a live peer-to-peer media connection through firewalls, managing microphone and camera permissions in the browser, and handling the participant grid resizing as users join and leave. Building WebRTC from scratch takes 4-8 weeks. The practical approach is to use a Selective Forwarding Unit (SFU) service — Daily.co or Livekit — that handles ICE negotiation, STUN/TURN servers, and codec selection for you. Your code becomes: generate a room token on the server, load the SDK in the client, and render the video tiles. The product decisions are about the UX around the call — permission handling, the controls bar, screen sharing, and the participant grid layout.

What users consider table stakes in 2026

  • One-click join with no plugin installation — entirely browser-based via WebRTC
  • Camera and microphone permission prompt handled gracefully with a clear explanation before the browser dialog appears
  • Mute and camera-off controls always visible and always responsive — these are the safety controls users reach for first
  • Participant video grid that reconfigures dynamically as people join and leave without requiring a page refresh
  • Screen sharing option accessible without leaving the call
  • Connection quality indicator so users know whether degraded quality is on their end or a network issue
  • Ability to end or leave the call and return to the app — the back-navigation should not drop you into a broken state

Anatomy of the Feature

Seven components make up a production video call feature. The SDK handles the hardest parts — your code lives in the room creation API and the video grid UI. Camera permission errors are where first builds get stuck.

Layers:UIDataBackendService

WebRTC peer connection layer

Backend

Daily.co JavaScript SDK (@daily-co/daily-js) or Livekit Client SDK (livekit-client) handles all ICE negotiation, STUN/TURN server traversal, codec selection, and bitrate adaptation automatically. You call DailyIframe.createCallObject() or new Room() — the SDK takes it from there.

Note: Do not attempt to implement WebRTC ICE negotiation manually. The STUN/TURN infrastructure alone would take weeks to harden for cross-network reliability. Daily.co and Livekit have already solved this.

Room creation API

Backend

A Supabase Edge Function calls the Daily.co Rooms API (POST /v1/rooms) with the DAILY_API_KEY stored in Supabase Secrets. The API returns a room URL and meeting token that expires in 60 minutes. The Edge Function inserts the room record into the call_rooms Supabase table and returns the token to the client.

Note: Never expose the Daily.co API key in client-side code. The Edge Function is the only place the key should be used. Store it via the Supabase Cloud tab Secrets panel.

Video and audio tile grid

UI

A CSS Grid layout where each tile is a video element fed by the SDK's participant hooks. Daily.co provides useParticipant() React hooks; Livekit provides @livekit/components-react with pre-built VideoTrack and AudioTrack components. The grid uses repeat(auto-fill, minmax(300px, 1fr)) to reflow automatically as participants join.

Note: A fixed 2-column grid breaks at 5+ participants. Use auto-fill with a min-width so the layout adapts. Test with at least 4 browser tabs open on the published URL.

Call controls bar

UI

A shadcn/ui Button row fixed at the bottom of the call view: mute microphone toggle, camera on/off toggle, share screen button, and end call button. State is managed via Daily's useLocalParticipant() hook or Livekit's useLocalParticipant hook — the SDK is the source of truth for whether the mic is actually muted.

Note: Always read mute state from the SDK, not from local React state. Optimistic UI updates that do not sync with the actual media track state cause the button to show 'muted' while audio is still broadcasting.

Screen sharing

Service

navigator.mediaDevices.getDisplayMedia() triggers the browser's native screen picker. Both Daily.co and Livekit wrap this in their SDK so screen share appears in the participant grid automatically without additional configuration.

Note: getDisplayMedia requires HTTPS. It throws NotSupportedError on HTTP and inside iframes without the allow='display-capture' attribute. Vercel and Lovable's published URLs both use HTTPS by default.

Call invite and room link

Data

A Supabase call_rooms table stores the Daily.co room URL, the host user ID, creation time, and expiry time. Participants join by receiving the room URL via in-app messaging or email. Any authenticated user who has the room ID can SELECT the room URL via RLS.

Note: Set expires_at to now() + interval '60 minutes' to match the Daily.co token expiry. A cleanup job or Edge Function can mark expired rooms as closed.

Permission error handler

UI

getUserMedia throws NotAllowedError when the user denies camera or microphone permission. This must be caught and handled with an informative modal that shows browser-specific instructions for re-enabling permissions — the generic 'something went wrong' error loses users.

Note: Chrome and Firefox show permissions in different locations (address bar lock icon vs. site settings). Include a screenshot-based guide or a link to the browser's settings URL for the specific denied device.

The data model

One table tracks call rooms — host, status, and expiry. Run this in the Supabase SQL editor.

schema.sql
1create table public.call_rooms (
2 id uuid primary key default gen_random_uuid(),
3 room_url text not null,
4 room_name text not null,
5 host_user_id uuid references auth.users(id) on delete cascade not null,
6 status text not null default 'waiting' check (status in ('waiting', 'active', 'ended')),
7 created_at timestamptz not null default now(),
8 expires_at timestamptz not null default (now() + interval '60 minutes')
9);
10
11alter table public.call_rooms enable row level security;
12
13-- Authenticated users can create rooms
14create policy "host can insert"
15 on public.call_rooms for insert
16 to authenticated
17 with check (auth.uid() = host_user_id);
18
19-- Any authenticated user can read a room if they have the room id
20create policy "authenticated can select"
21 on public.call_rooms for select
22 to authenticated
23 using (true);
24
25-- Host can update room status
26create policy "host can update status"
27 on public.call_rooms for update
28 using (auth.uid() = host_user_id);
29
30create index call_rooms_host_idx
31 on public.call_rooms (host_user_id, created_at desc);
32
33create index call_rooms_status_idx
34 on public.call_rooms (status)
35 where status != 'ended';

Heads up: The status index filtered to non-ended rooms keeps the active rooms query fast. The expires_at column lets you build a scheduled Edge Function or pg_cron job that marks rooms as 'ended' when the Daily.co token expires.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Best UI, Next.js ecosystemFit for this feature:

Best path for video calls: Next.js App Router's natural client/server boundary maps cleanly to the Daily.co pattern — server-side room creation in an API route, client-side SDK in a 'use client' component.

Step by step

  1. 1Prompt v0 with the spec below to generate the VideoCallPage, ParticipantGrid, and CallControls components plus the /api/create-room route
  2. 2Open the Vars panel and add DAILY_API_KEY (server-only) and NEXT_PUBLIC_SUPABASE_URL with the anon key
  3. 3Run the SQL schema from this page in the Supabase SQL editor
  4. 4Deploy to Vercel — camera access requires HTTPS; the v0 sandbox preview cannot access camera or microphone
  5. 5Open the deployed URL on two browser tabs and test the full call flow: join, mute, camera toggle, screen share, and leave
Paste into v0
Build a video call feature for a Next.js 14 App Router app using the Daily.co SDK (@daily-co/daily-js). API route /api/create-room (POST, server-side): call Daily.co Rooms API with DAILY_API_KEY; create a room with exp set to 60 minutes from now; return { roomUrl, token }. VideoCallPage (client component — 'use client'): on mount, fetch /api/create-room to get roomUrl and token; call DailyIframe.createCallObject() and join(roomUrl, { token, startVideoOff: true }). ParticipantGrid (client component): use useParticipants() to get participant list; render CSS grid with repeat(auto-fill, minmax(300px, 1fr)); each tile renders a video element with participant.videoTrack as srcObject; show display name overlay in bottom-left of each tile. CallControls (client component): fixed bar at bottom with four shadcn/ui icon buttons — toggle mic (useLocalParticipant().isMicMuted), toggle camera (useLocalParticipant().isCamMuted), share screen (call.startScreenShare()), end call (call.leave() then router.push('/')). Participant count badge in header. Permission error handler: wrap getUserMedia in try/catch; on NotAllowedError show a shadcn/ui Dialog with instructions for Chrome and Firefox to re-enable permissions. Screen share falls back with a toast if getDisplayMedia is unavailable. Wire track.onended on screen share tracks to reset the share button state when user stops sharing from the browser UI. Include TypeScript types for DailyParticipant.

Where this path bites

  • Supabase is not auto-provisioned — create the call_rooms table manually and add the Daily.co API key to Vercel environment variables
  • Camera access is blocked in the v0 sandbox preview — deploy to Vercel to test any video functionality
  • The Daily.co free tier covers 10,000 participant-minutes/mo; higher volumes require a paid Daily.co plan

Third-party services you'll need

Video calls require a WebRTC SFU service — you choose between Daily.co and Livekit Cloud. Both offer generous free tiers for early-stage apps.

ServiceWhat it doesFree tierPaid from
Daily.coWebRTC SFU + rooms API + STUN/TURN infrastructure; handles all media routing between participantsFree: 10,000 participant-minutes/mo (approx 167 one-hour calls)Pay-as-you-go $0.00099/participant-minute after free tier (approx)
Livekit CloudAlternative WebRTC SFU with open-source self-hosted option; React components included in @livekit/components-reactFree: 50,000 minutes/mo (more generous than Daily.co free tier)$0.001/minute after free tier (approx)
SupabaseEdge Function for room token generation, call_rooms table for room trackingFree tier sufficient for room management at low volumePro $25/mo for production uptime

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

Daily.co free tier covers approximately 167 one-hour calls per month. Supabase free tier handles room storage. Zero cost for light usage.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Camera shows a black screen in Lovable preview

Symptom: getUserMedia is blocked by the sandbox attribute on the Lovable preview iframe. The browser's permission prompt never fires and the camera constraint fails silently — the video element renders but stays black. This is not a bug in the generated code; it is a security restriction of the preview environment.

Fix: Always test video calls on the published Lovable URL. Add a visible banner in the call UI during development: 'Camera preview not available in the editor — publish to test.' The camera works correctly on the published HTTPS URL on any real device.

HTTPS required for screen sharing

Symptom: getDisplayMedia() throws NotSupportedError on any non-HTTPS origin, including http://localhost and any iframe without the allow='display-capture' attribute. The screen share button appears to do nothing — no error message, no browser dialog, just silence.

Fix: Deploy to a real HTTPS URL before testing screen sharing. Vercel and Lovable's published URLs both use HTTPS by default. For local development, localhost is a browser exception — screen sharing works on localhost but not on a local network IP like 192.168.x.x.

Daily.co token fails to generate — 401 from Edge Function

Symptom: The Edge Function calls the Daily.co Rooms API but the DAILY_API_KEY is not present in Supabase Secrets. Daily.co returns a 401 Unauthorized response. The Edge Function either returns an error to the client or fails silently, and the video page stays on the loading state indefinitely.

Fix: Add DAILY_API_KEY to Supabase Secrets via the Cloud tab before deploying the Edge Function. Verify it is set by checking the Supabase Edge Function logs — the log will show '401' if the key is missing. The key is found in the Daily.co dashboard under Developers.

Participant grid breaks at 5 or more users

Symptom: AI generates a fixed 2-column CSS grid. When a 5th participant joins, their video tile overflows the viewport and is cut off or wraps to a third row that breaks the layout. On mobile, 2 columns is already too many for small screens.

Fix: Replace the fixed grid with: display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)). This reflows dynamically as participants join. Test with at least 4 browser tabs open simultaneously on the published HTTPS URL.

Screen share button stays active after user stops sharing

Symptom: When a user stops screen sharing from the browser's native UI (the 'Stop sharing' button Chrome shows at the bottom of the screen), the MediaStreamTrack.onended event fires — but the SDK's track.onended event is not wired in the generated code. The in-app share button continues to show the active sharing state.

Fix: Subscribe to the track's ended event in the SDK. In Daily.co: listen to the screen-share-stopped event on the call object and reset the share button state. In Livekit: subscribe to TrackEvent.Ended on the local screen share track.

Best practices

1

Name the SDK explicitly in your prompt — Daily.co or Livekit — never let the AI choose generically, or it may attempt native WebRTC implementation

2

Set camera OFF by default when joining (startVideoOff: true) — users prefer to control when they appear on camera

3

Always read mic and camera state from the SDK hooks, not from local React state — the SDK is the source of truth for what the hardware is actually doing

4

Test with 4 browser tabs on the published URL before launch — the 4-participant case almost always reveals grid layout and audio feedback issues

5

Wire track.onended on screen share tracks so the in-app button resets when the user stops sharing from the browser UI

6

Add a 'Camera not available in editor' banner in development to save debugging time when camera appears black

7

Store the Daily.co API key only in Supabase Secrets or Vercel environment variables — never in client-side code or committed to the repository

When You Need Custom Development

Daily.co and Livekit Cloud handle 99% of video call use cases. A few requirements genuinely push beyond what managed SFU services provide:

  • You need HIPAA-compliant video calls with a Business Associate Agreement (BAA) — Daily.co and Livekit Cloud offer BAAs only on enterprise plans
  • You need server-side recording and transcription stored in your own infrastructure rather than a third-party cloud
  • You need a custom TURN server to relay media for users behind restrictive corporate firewalls that block the default Daily.co and Livekit TURN endpoints
  • You need to integrate AI features into the media pipeline — real-time transcription, noise cancellation, or speaker diarisation — which requires access to the raw audio stream before it reaches the SFU

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Can I add video calls to a Lovable app?

Yes — Lovable can scaffold the Daily.co Edge Function for room creation and the video grid component in a single prompt. The important limitation is that camera access is blocked in the Lovable editor preview. You must publish the app and test video calls on the published HTTPS URL. Build it in Lovable; test it outside Lovable.

What is the cheapest way to add video calling to a web app?

Daily.co free tier covers 10,000 participant-minutes per month — roughly 167 one-hour calls. Livekit Cloud free tier covers 50,000 minutes. For a small app, the total cost is $0/mo. You only need to pay when usage consistently exceeds the free tier, at which point Daily.co charges $0.00099/participant-minute (approximately $0.06 per one-hour call).

Do I need to build WebRTC from scratch?

No — and you should not try. WebRTC ICE negotiation, STUN/TURN server infrastructure, and codec selection are not problems you want to solve while building a product. Daily.co and Livekit handle all of it. Your code is: generate a room token on the server, call the SDK on the client, render the video tiles. The entire integration is 100-200 lines of code.

Can users share their screen during a video call?

Yes — both Daily.co and Livekit support screen sharing through the standard browser getDisplayMedia() API. The SDK wraps it so the shared screen appears as a participant tile in the grid automatically. Screen sharing requires HTTPS and does not work in the Lovable preview iframe or on http:// URLs.

Why does the camera show a black screen in the Lovable editor?

The Lovable editor preview runs inside a sandboxed iframe that blocks camera and microphone access for security. This is not a bug in your code. The camera works correctly on the published Lovable URL. Always test video features on the published URL, not in the editor. Add a visible banner in the call UI that says so during development.

How many participants can join a video call?

Daily.co supports up to 1,000 participants in a single session on paid plans; the free tier is intended for small group calls. Livekit Cloud supports similar scale. For most apps (team calls, tutoring, 1-to-1 consultations) you will never need more than 10-20 participants and the free tier is more than sufficient.

Does video calling work on mobile browsers?

Yes — WebRTC is supported on iOS Safari 14.1+ and Android Chrome. iOS requires the user to explicitly grant camera and microphone permissions via the browser's prompt. The camera and mic permission UI on iOS is different from desktop — include instructions for iOS in your permission error handler. Screen sharing is not available on iOS Safari as of 2026.

How do I record video calls?

Daily.co offers a cloud recording API that saves MP4 files to your Daily.co storage or an S3-compatible bucket. Enable it by passing the start_cloud_recording property when creating the room. Livekit has an Egress service for the same purpose. Both require a paid plan. Client-side recording via the MediaRecorder API is possible but produces lower quality and puts the recording burden on the participant's device.

RapidDev

Need this feature production-ready?

RapidDev builds video calls into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.