LiveKit
"LiveKit: real-time video/audio, WebRTC, rooms, tracks, screen sharing, recording, React components"
LiveKit is an open-source WebRTC infrastructure platform for building real-time video and audio applications. Unlike video hosting services that handle pre-recorded content, LiveKit focuses on live, interactive communication — video calls, live streams, screen shares, and audio rooms. The architecture revolves around rooms, participants, and tracks. A room is a session where participants connect. Each participant publishes tracks (camera, microphone, screen) and subscribes to others' tracks. LiveKit handles the hard parts of WebRTC: SFU routing, bandwidth estimation, simulcast, and codec negotiation. Your server generates access tokens that control who can join which room and what permissions they have. Use the React SDK for UI, the server SDK for room management, and webhooks for lifecycle events. ## Key Points - Generate short-lived access tokens (1 hour or less) with the minimum required permissions per participant role. - Use the `emptyTimeout` room option to auto-close rooms that are no longer in use, preventing resource leaks. - Pass `identity` consistently across sessions so LiveKit can track reconnections properly. - Use `contentHint: "detail"` for screen sharing presentations and `contentHint: "motion"` for video content. - Implement the `onDisconnected` callback to handle network drops gracefully and attempt reconnection. - Use reliable data messages for chat and signaling; use unreliable for high-frequency updates like cursor positions. - Store room metadata as JSON for structured data that all participants and your server can access. - Use egress to S3 or compatible storage rather than recording client-side for reliability and quality. - Do not expose your API key and secret on the client side; generate tokens on the server only. - Do not create rooms without `emptyTimeout`; orphaned rooms consume server resources indefinitely. - Do not subscribe to all tracks when building an audio-only experience; filter by `Track.Source` to reduce bandwidth. - Do not use `canPublish: true` for audience members in broadcast scenarios; it wastes resources and creates security risks. ## Quick Example ``` LIVEKIT_URL=wss://your-project.livekit.cloud LIVEKIT_API_KEY=your-api-key LIVEKIT_API_SECRET=your-api-secret ```
skilldb get video-services-skills/LiveKitFull skill: 445 linesLiveKit
Core Philosophy
LiveKit is an open-source WebRTC infrastructure platform for building real-time video and audio applications. Unlike video hosting services that handle pre-recorded content, LiveKit focuses on live, interactive communication — video calls, live streams, screen shares, and audio rooms. The architecture revolves around rooms, participants, and tracks. A room is a session where participants connect. Each participant publishes tracks (camera, microphone, screen) and subscribes to others' tracks. LiveKit handles the hard parts of WebRTC: SFU routing, bandwidth estimation, simulcast, and codec negotiation. Your server generates access tokens that control who can join which room and what permissions they have. Use the React SDK for UI, the server SDK for room management, and webhooks for lifecycle events.
Setup
Install the server SDK and React components:
// Server-side
// "@livekit/server-sdk": "^2.0.0"
// Client-side
// "@livekit/components-react": "^2.0.0"
// "livekit-client": "^2.0.0"
Configure credentials:
import { AccessToken, RoomServiceClient } from "livekit-server-sdk";
const LIVEKIT_URL = process.env.LIVEKIT_URL!; // wss://your-project.livekit.cloud
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY!;
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET!;
const roomService = new RoomServiceClient(
LIVEKIT_URL,
LIVEKIT_API_KEY,
LIVEKIT_API_SECRET
);
Environment variables required:
LIVEKIT_URL=wss://your-project.livekit.cloud
LIVEKIT_API_KEY=your-api-key
LIVEKIT_API_SECRET=your-api-secret
Key Techniques
Generating Access Tokens
async function createRoomToken(
roomName: string,
participantName: string,
participantIdentity: string,
options: {
canPublish?: boolean;
canSubscribe?: boolean;
canPublishData?: boolean;
ttlSeconds?: number;
} = {}
) {
const {
canPublish = true,
canSubscribe = true,
canPublishData = true,
ttlSeconds = 3600,
} = options;
const token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
identity: participantIdentity,
name: participantName,
ttl: ttlSeconds,
});
token.addGrant({
room: roomName,
roomJoin: true,
canPublish,
canSubscribe,
canPublishData,
});
return token.toJwt();
}
// Generate a view-only token (audience member)
async function createViewerToken(roomName: string, identity: string) {
return createRoomToken(roomName, identity, identity, {
canPublish: false,
canSubscribe: true,
canPublishData: false,
});
}
Room Management (Server-Side)
async function manageRooms() {
// Create a room
const room = await roomService.createRoom({
name: "meeting-abc-123",
emptyTimeout: 300, // close after 5 min empty
maxParticipants: 50,
metadata: JSON.stringify({ createdBy: "user-456", type: "meeting" }),
});
// List all active rooms
const rooms = await roomService.listRooms();
// Get participants in a room
const participants = await roomService.listParticipants("meeting-abc-123");
// Remove a participant
await roomService.removeParticipant("meeting-abc-123", "participant-identity");
// Update room metadata
await roomService.updateRoomMetadata(
"meeting-abc-123",
JSON.stringify({ status: "in-progress" })
);
// Delete a room
await roomService.deleteRoom("meeting-abc-123");
return { room, rooms, participants };
}
// Mute a participant's track from the server
async function muteParticipant(roomName: string, identity: string) {
const participant = await roomService.getParticipant(roomName, identity);
const audioTrack = participant.tracks.find(
(t) => t.source === "MICROPHONE"
);
if (audioTrack?.sid) {
await roomService.mutePublishedTrack(roomName, identity, audioTrack.sid, true);
}
}
React Video Conference Component
"use client";
import {
LiveKitRoom,
VideoConference,
RoomAudioRenderer,
ControlBar,
GridLayout,
ParticipantTile,
useTracks,
} from "@livekit/components-react";
import { Track } from "livekit-client";
import "@livekit/components-styles";
interface MeetingRoomProps {
token: string;
serverUrl: string;
onDisconnected?: () => void;
}
function MeetingRoom({ token, serverUrl, onDisconnected }: MeetingRoomProps) {
return (
<LiveKitRoom
token={token}
serverUrl={serverUrl}
connect={true}
video={true}
audio={true}
onDisconnected={onDisconnected}
data-lk-theme="default"
style={{ height: "100vh" }}
>
<VideoConference />
</LiveKitRoom>
);
}
Custom Video Layout
"use client";
import {
LiveKitRoom,
GridLayout,
ParticipantTile,
RoomAudioRenderer,
ControlBar,
useTracks,
useParticipants,
useLocalParticipant,
} from "@livekit/components-react";
import { Track } from "livekit-client";
function CustomVideoGrid() {
const tracks = useTracks(
[
{ source: Track.Source.Camera, withPlaceholder: true },
{ source: Track.Source.ScreenShare, withPlaceholder: false },
],
{ onlySubscribed: false }
);
return (
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
<div style={{ flex: 1 }}>
<GridLayout tracks={tracks} style={{ height: "100%" }}>
<ParticipantTile />
</GridLayout>
</div>
<RoomAudioRenderer />
<ControlBar
controls={{
camera: true,
microphone: true,
screenShare: true,
leave: true,
chat: true,
}}
/>
</div>
);
}
Screen Sharing
"use client";
import { useLocalParticipant } from "@livekit/components-react";
import { useState } from "react";
function ScreenShareButton() {
const { localParticipant } = useLocalParticipant();
const [isSharing, setIsSharing] = useState(false);
const toggleScreenShare = async () => {
if (isSharing) {
await localParticipant.setScreenShareEnabled(false);
setIsSharing(false);
} else {
await localParticipant.setScreenShareEnabled(true, {
audio: true, // Share system audio too
resolution: { width: 1920, height: 1080, frameRate: 30 },
contentHint: "detail", // Optimize for text/slides
});
setIsSharing(true);
}
};
return (
<button onClick={toggleScreenShare}>
{isSharing ? "Stop Sharing" : "Share Screen"}
</button>
);
}
Data Messages Between Participants
"use client";
import { useLocalParticipant, useRoomContext } from "@livekit/components-react";
import { DataPacket_Kind, RoomEvent } from "livekit-client";
import { useEffect, useState, useCallback } from "react";
interface ChatMessage {
sender: string;
text: string;
timestamp: number;
}
function useDataChannel() {
const room = useRoomContext();
const { localParticipant } = useLocalParticipant();
const [messages, setMessages] = useState<ChatMessage[]>([]);
useEffect(() => {
const handleDataReceived = (
payload: Uint8Array,
participant: any,
) => {
const decoded = new TextDecoder().decode(payload);
const message: ChatMessage = JSON.parse(decoded);
setMessages((prev) => [...prev, message]);
};
room.on(RoomEvent.DataReceived, handleDataReceived);
return () => {
room.off(RoomEvent.DataReceived, handleDataReceived);
};
}, [room]);
const sendMessage = useCallback(
async (text: string) => {
const message: ChatMessage = {
sender: localParticipant.identity,
text,
timestamp: Date.now(),
};
const encoded = new TextEncoder().encode(JSON.stringify(message));
await localParticipant.publishData(encoded, {
reliable: true,
});
setMessages((prev) => [...prev, message]);
},
[localParticipant]
);
return { messages, sendMessage };
}
Recording and Egress
import { EgressClient, EncodedFileOutput, SegmentedFileOutput } from "livekit-server-sdk";
const egressClient = new EgressClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
async function startRoomRecording(roomName: string) {
const output: EncodedFileOutput = {
fileType: 0, // MP4
filepath: `recordings/${roomName}-{time}.mp4`,
s3: {
accessKey: process.env.AWS_ACCESS_KEY_ID!,
secret: process.env.AWS_SECRET_ACCESS_KEY!,
region: "us-east-1",
bucket: "my-recordings-bucket",
},
};
const egress = await egressClient.startRoomCompositeEgress(roomName, {
file: output,
}, {
layout: "grid",
audioOnly: false,
});
return { egressId: egress.egressId };
}
async function startHlsStream(roomName: string) {
const output: SegmentedFileOutput = {
filenamePrefix: `live/${roomName}/segment`,
playlistName: "index.m3u8",
segmentDuration: 4,
s3: {
accessKey: process.env.AWS_ACCESS_KEY_ID!,
secret: process.env.AWS_SECRET_ACCESS_KEY!,
region: "us-east-1",
bucket: "my-live-bucket",
},
};
const egress = await egressClient.startRoomCompositeEgress(roomName, {
segments: output,
});
return { egressId: egress.egressId };
}
async function stopRecording(egressId: string) {
await egressClient.stopEgress(egressId);
}
Webhook Handling
import { WebhookReceiver } from "livekit-server-sdk";
import type { NextApiRequest, NextApiResponse } from "next";
const webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const authHeader = req.headers.authorization as string;
const body = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
let event;
try {
event = await webhookReceiver.receive(body, authHeader);
} catch {
return res.status(401).json({ error: "Invalid webhook" });
}
switch (event.event) {
case "room_started":
console.log(`Room created: ${event.room?.name}`);
break;
case "room_finished":
console.log(`Room closed: ${event.room?.name}`);
break;
case "participant_joined":
console.log(`${event.participant?.identity} joined ${event.room?.name}`);
break;
case "participant_left":
console.log(`${event.participant?.identity} left ${event.room?.name}`);
break;
case "track_published":
console.log(`Track published: ${event.track?.source}`);
break;
case "egress_ended":
console.log(`Recording finished: ${event.egressInfo?.egressId}`);
break;
}
res.status(200).json({ ok: true });
}
Best Practices
- Generate short-lived access tokens (1 hour or less) with the minimum required permissions per participant role.
- Use the
emptyTimeoutroom option to auto-close rooms that are no longer in use, preventing resource leaks. - Pass
identityconsistently across sessions so LiveKit can track reconnections properly. - Use
contentHint: "detail"for screen sharing presentations andcontentHint: "motion"for video content. - Implement the
onDisconnectedcallback to handle network drops gracefully and attempt reconnection. - Use reliable data messages for chat and signaling; use unreliable for high-frequency updates like cursor positions.
- Store room metadata as JSON for structured data that all participants and your server can access.
- Use egress to S3 or compatible storage rather than recording client-side for reliability and quality.
Anti-Patterns
- Do not expose your API key and secret on the client side; generate tokens on the server only.
- Do not create rooms without
emptyTimeout; orphaned rooms consume server resources indefinitely. - Do not subscribe to all tracks when building an audio-only experience; filter by
Track.Sourceto reduce bandwidth. - Do not use
canPublish: truefor audience members in broadcast scenarios; it wastes resources and creates security risks. - Do not poll for participant lists from the server; use webhooks or client-side events for real-time updates.
- Do not ignore the
onDisconnectedevent; users need feedback and reconnection logic when the connection drops. - Do not use client-side recording as the primary capture method; server-side egress is more reliable and captures all participants.
- Do not hardcode room names; generate unique names to avoid collisions between concurrent sessions.
Install this skill directly: skilldb add video-services-skills
Related Skills
Amazon IVS
"Amazon Interactive Video Service: low-latency live streaming, real-time stages, chat, stream recording, channel management, viewer analytics"
Api.video
"api.video: video hosting API, upload, live streaming, player customization, analytics, webhooks"
Bunny Stream
"Bunny.net Stream: video hosting, HLS delivery, direct uploads, video collections, thumbnail generation, token authentication, webhook events"
Cloudflare Stream
"Cloudflare Stream: video hosting, adaptive streaming, direct upload, watermarks, signed URLs, Workers integration"
Mux Video
"Mux: video hosting, HLS streaming, upload API, playback URLs, thumbnails, analytics, Mux Player, Next.js integration"
Vimeo OTT
"Vimeo OTT API: video-on-demand platform, subscription management, customer auth, content libraries, embed players, analytics, webhook events"