Building Embedded Video & Audio Calling with LiveKit Cloud, Next.js, and Laravel
We needed video and audio calls directly inside the product—not another third-party Zoom link. This is how we compared LiveKit with Twilio, Daily, Agora, and Zoom, and then wired LiveKit Cloud into a Next.js client and a Laravel API.
Most products do not have a video problem. They have a meeting-link problem.
A customer books a session in your app. You paste a Zoom or Google Meet URL into an email. They leave your product, wait in someone else’s lobby, and when the call ends, you still do not own the recording, the attendance data, or the next click. That is fine for an internal standup. It is a leak if calling is part of the core product.
We closed that leak with LiveKit Cloud: a Next.js client for the call UI, our existing Laravel API for users and permissions, and LiveKit for the media layer. This post covers both the architectural decision and a working map of the integration—enough for another developer to follow the same shape in their own stack.
"A third-party meeting link is a meeting in someone else’s product. Embedded video is a feature of yours."
What This Post Covers
Why product teams embed video calling instead of sending users to Zoom or Meet.
How LiveKit compares with Daily, Agora, Twilio, Vonage, Amazon Chime, Zoom, and self-hosted SFUs.
Cloud vs. self-hosted LiveKit and why we started on Cloud.
The core code terms you will encounter: SFU, adaptive streaming, dynacast, rooms, and tokens.
The architecture and actual code snippets used to join a call, handle audio vs. video, support guests, and manage recordings.
How LiveKit bills participant-minutes (e.g., a 1-minute call with 5 people equals 5 WebRTC minutes).
Why Embed Video Calling Instead of a Zoom Link?
The fastest way to ship "video" is a third-party URL. Users already know Zoom, Meet, and Teams, and support teams know how to troubleshoot them. However, the hidden cost of that convenience shows up later:
You lose the session: Notes, files, billing, and the next user action now live in another app under another brand.
You lose access control: Your API already knows who paid, who is the host, and who is a guest. A shared meeting link does not.
You lose the recording: It sits in the vendor’s history instead of next to the customer’s record in your database.
You lose conversion: Opening Zoom, signing in, and waiting for the host introduces friction and drop-offs.
An embedded video and audio call keeps the user inside the product. They are already logged in, the UI matches the rest of the app, and when the call ends, they remain on your page.
Building that from raw WebRTC requires managing media servers, TURN relays, reconnects, and recordings. We didn't want to become a telecom company. We wanted native calling in Next.js, with our existing backend maintaining strict control over who can join. That is the exact gap a platform like LiveKit fills—and it is the kind of robust feature our custom software development services are built for.
LiveKit vs Zoom, Twilio, Daily, Agora, and the Rest
The 2026 shortlist for putting video calling into an app is straightforward. We scored platforms based on escape hatches, developer experience, cost shapes for 1:1 and small groups, and whether recordings could land in storage we already own.
Daily.co: A pleasure to integrate, billing roughly $0.004 per participant-minute after a free bucket. If the goal is a call on screen by lunch, it wins. It is a weaker escape hatch if you later want the media server under your own roof.
Agora: Features a serious global mesh, especially in APAC. Audio looks cheap, but HD and Full-HD rates climb quickly. The stack is proprietary—fine if you are buying coverage, less fine if you want an open protocol.
Twilio Video & Vonage Video: The familiar CPaaS path. Video is a separate product and a separate bill. Vonage (the old TokBox lineage) is a closer enterprise drop-in at around $0.0041 per minute. Both are closed SFUs.
Amazon Chime SDK: Attractive if everything else is already on AWS at roughly $0.0017 per attendee-minute with one invoice and a clearer HIPAA story, though developer experience lags specialists.
Zoom Video SDK:
Embeds Zoom directly. Fine if you want Zoom's brand and large-session muscle, but you are building on their product rather than an open protocol.
mediasoup, Janus, or Jitsi: Puts you directly in the media-server business. A valid choice if you want to run an SRE rotation for one-way audio at 2 a.m., but not what we needed this quarter.
Why LiveKit Won
We picked LiveKit because its trade-offs matched a product team rather than a telecom team:
Cloud and self-hosted speak the same protocol: We could ship on *.livekit.cloud without a vendor-only client API. If data residency or unit economics later demand it, the same Next.js code can point at a server we operate.
The React kit is a starting point, not a prison: Stock components got a conference on screen quickly, allowing us to replace the chrome without rewriting WebRTC.
Grants matched the product: An audio call is a publish-source list. Guests are a different identity, but hosts and guests join the same room cleanly.
Recordings land in our bucket: Cloud egress writes a grid layout straight to the S3 bucket we already use.
The cost shape fits 1:1 and small groups: Connection minutes range between $0.0004–$0.0005 plus bandwidth as long as HD send and recording are monitored.
Cloud LiveKit vs. Running Your Own Server
LiveKit is an open-source realtime media server with client SDKs for web and mobile. You can run it in two ways:
Self-hosted: You operate livekit-server on Kubernetes or VMs, plus TURN for firewalls. At very high volume, this beats any vendor invoice. If you already scale infrastructure this way, our DevOps services run Kubernetes workloads day-to-day.
LiveKit Cloud
: A managed project WebSocket URL, API keys, a global edge network, and dashboards. You pay a monthly plan plus usage and skip managing the SFU yourself.
How LiveKit Works: SFU, Adaptive Streaming, and Dynacast
What is an SFU? A Selective Forwarding Unit is a media server in the middle of a call. Instead of every participant uploading multiple streams in a group call, each person uploads once, and the server forwards streams to everyone else.
What is adaptive streaming? The client matches video quality to the size of the video tile on screen. Small thumbnails do not request 720p, saving significant bandwidth.
What is dynacast
: The publisher and SFU stop encoding quality layers that no active subscriber is watching.
Turn both options on in your room options; they are the difference between a clean demo and an unmanageable bandwidth bill.
The Architecture
: Next.js, Your API, and LiveKit Cloud
The product creates a session and a unique room name (e.g., call-{uuid}).
When a participant is authorized, the API signs a JWT and returns { token, server_url, room_name, identity }.
Next.js passes the token and URL into LiveKit’s LiveKitRoom component.
WebRTC handles media over wss://...livekit.cloud. Audio and video never transit your application servers.
Integration Code Snippets
1. Install the SDKs
JSON
"@livekit/components-react": "^2.9.21",
"@livekit/components-styles": "^1.2.0",
"livekit-client": "^2.19.2"
2. Keep Credentials on the Server (config/livekit.php)
PHP
return [
'url' => env('LIVEKIT_URL'),
'api_key' => env('LIVEKIT_API_KEY'),
'api_secret' => env('LIVEKIT_API_SECRET'),
'token_ttl' => (int) env('LIVEKIT_TOKEN_TTL', 3600),
];
3. Fetch Token on the Client (app/services/calls.ts)
TypeScript
export type LiveKitTokenResponse = {
token: string;
server_url: string;
room_name: string;
identity: string;
};
export async function fetchCallToken(callId: string) {
const response = await apiClient.get<{
success: boolean;
data: LiveKitTokenResponse;
}>(`calls/${callId}/token`);
return response.data.data;
}
4. Create Room and Mint Token (CallService.php)
PHP
$roomName = 'call-' . Str::uuid()->toString();
$call = VideoCall::query()->create([
'room_name' => $roomName,
'status' => CallStatus::Ringing,
'mode' => $mode, // video | audio
]);
$identity = 'user-' . $actor->id;
$videoGrants = [];
if ($call->mode === CallMode::Audio) {
$videoGrants['canPublishSources'] = [
'microphone', 'screen_share', 'screen_share_audio',
];
}
$token = $this->liveKitTokenService->createToken(
$call->room_name,
$identity,
$actor->name ?? 'Participant',
$videoGrants
);
5. Connect the Next.js Room (VideoSession.tsx)
TypeScript
const roomKey = callId ?? token.slice(0, 12);
<LiveKitRoom
key={roomKey}
token={token}
serverUrl={serverUrl}
connect
video={false}
audio={false}
connectOptions={{ autoSubscribe: true }}
options={{ adaptiveStream: true, dynacast: true }}
onDisconnected={onDisconnected}
data-lk-theme="default"
>
<PublishLocalTracks mode={callMode} />
<CallConference onEndCall={endCall} />
</LiveKitRoom>
What LiveKit Cloud Costs
LiveKit bills by participant-minutes (one person connected for one minute). For instance, a 1-minute call with 5 people accounts for 5 WebRTC minutes.
Build ($0/mo): Development tier, no card required.
Ship (from $50/mo): Production use, email support, includes ~150,000 WebRTC minutes (then ~$0.0005/min).
Scale (from $500/mo): Region pinning, HIPAA BAA, includes ~1.5 million minutes (then ~$0.0004/min).
Bandwidth:
Billed separately around $0.10–$0.12 per GB after your included bucket.
Closing
Embedded video calling is not a quick widget you drop in after lunch, but it shouldn't require running a dedicated media cluster yourself. LiveKit Cloud strikes the ideal middle ground: managed WebRTC infrastructure, secure JWT tokens minted via your API, and fully customizable React components.
Originally posted by Omax Tech
Top comments (0)