Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
LiveKit
https://github.com/livekit/livekit
Admin
End-to-end stack for WebRTC. SFU media server and SDKs.
Tokens:
15,998
Snippets:
75
Trust Score:
9.3
Update:
2 months ago
Context
Skills
Chat
Benchmark
67.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# LiveKit Server LiveKit Server is a high-performance, scalable WebRTC Selective Forwarding Unit (SFU) written in Go. It enables real-time audio, video, and data communication between participants in virtual rooms, handling the complexity of WebRTC connections, media routing, and participant management. The server supports advanced features including simulcast, adaptive bitrate streaming, end-to-end encryption, recording/egress, ingress (RTMP/WHIP), and SIP integration. The architecture follows a distributed design where multiple server nodes can work together using Redis for coordination. It provides a comprehensive API for room management, participant control, and media operations. LiveKit Server handles WebRTC signaling via WebSocket connections, manages peer connections for publishers and subscribers, and routes media packets between participants efficiently using a modern SFU approach. ## Server Startup and Configuration The server is started via the `livekit-server` CLI command with YAML configuration support. ```bash # Start server with config file livekit-server --config /path/to/config.yaml # Start in development mode (auto-generates dev keys) livekit-server --dev # Start with inline configuration via environment variable export LIVEKIT_CONFIG=' port: 7880 rtc: port_range_start: 50000 port_range_end: 60000 keys: APIKey123: secretKey456 ' livekit-server # Generate API key/secret pair livekit-server generate-keys # Output: API Key: APImykey123 # API Secret: mysecret456... # List available ports livekit-server ports ``` ## Configuration File Structure ```yaml # config.yaml - LiveKit Server Configuration port: 7880 bind_addresses: - "" rtc: port_range_start: 50000 port_range_end: 60000 tcp_port: 7881 stun_servers: - stun:stun.l.google.com:19302 use_external_ip: true redis: address: localhost:6379 password: "" db: 0 keys: APIKey123: YourSecretKey456 # API Key: Secret mapping room: auto_create: true empty_timeout: 300 # seconds max_participants: 100 logging: level: info # debug, info, warn, error json: false limit: max_metadata_size: 65536 max_room_name_length: 128 max_participant_name_length: 128 ``` ## Room Service API The Room Service provides server-side room and participant management via Twirp RPC. ### CreateRoom Creates a new room or returns existing room configuration. ```go // Go SDK example import ( "context" lksdk "github.com/livekit/server-sdk-go/v2" "github.com/livekit/protocol/livekit" ) func createRoom() { roomClient := lksdk.NewRoomServiceClient( "https://your-livekit-host", "APIKey123", "YourSecretKey456", ) room, err := roomClient.CreateRoom(context.Background(), &livekit.CreateRoomRequest{ Name: "my-room", EmptyTimeout: 600, // 10 minutes MaxParticipants: 50, Metadata: `{"topic": "weekly standup"}`, Egress: &livekit.RoomEgress{ Room: &livekit.RoomCompositeEgressRequest{ FileOutputs: []*livekit.EncodedFileOutput{{ FileType: livekit.EncodedFileType_MP4, Filepath: "recordings/{room_name}-{time}.mp4", }}, }, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Room created: %s (SID: %s)\n", room.Name, room.Sid) } ``` ```bash # curl example using livekit-cli livekit-cli create-room \ --url https://your-livekit-host \ --api-key APIKey123 \ --api-secret YourSecretKey456 \ --name my-room \ --empty-timeout 600 ``` ### ListRooms Lists all active rooms or specific rooms by name. ```go func listRooms() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) // List all rooms res, err := roomClient.ListRooms(context.Background(), &livekit.ListRoomsRequest{}) if err != nil { log.Fatal(err) } for _, room := range res.Rooms { fmt.Printf("Room: %s, Participants: %d, Created: %v\n", room.Name, room.NumParticipants, room.CreationTime) } // List specific rooms res, _ = roomClient.ListRooms(context.Background(), &livekit.ListRoomsRequest{ Names: []string{"room1", "room2"}, }) } ``` ### DeleteRoom Removes a room and disconnects all participants. ```go func deleteRoom() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) _, err := roomClient.DeleteRoom(context.Background(), &livekit.DeleteRoomRequest{ Room: "my-room", }) if err != nil { log.Fatal(err) } fmt.Println("Room deleted successfully") } ``` ### ListParticipants Lists all participants in a room. ```go func listParticipants() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) res, err := roomClient.ListParticipants(context.Background(), &livekit.ListParticipantsRequest{ Room: "my-room", }) if err != nil { log.Fatal(err) } for _, p := range res.Participants { fmt.Printf("Participant: %s (SID: %s), Tracks: %d\n", p.Identity, p.Sid, len(p.Tracks)) for _, track := range p.Tracks { fmt.Printf(" Track: %s, Type: %s, Muted: %v\n", track.Sid, track.Type, track.Muted) } } } ``` ### UpdateParticipant Updates participant metadata, name, or permissions. ```go func updateParticipant() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) participant, err := roomClient.UpdateParticipant(context.Background(), &livekit.UpdateParticipantRequest{ Room: "my-room", Identity: "user123", Metadata: `{"role": "moderator", "displayName": "John Doe"}`, Permission: &livekit.ParticipantPermission{ CanPublish: true, CanSubscribe: true, CanPublishData: true, CanPublishSources: []livekit.TrackSource{ livekit.TrackSource_CAMERA, livekit.TrackSource_MICROPHONE, livekit.TrackSource_SCREEN_SHARE, }, }, Attributes: map[string]string{ "team": "engineering", "timezone": "UTC-5", }, }) if err != nil { log.Fatal(err) } fmt.Printf("Updated participant: %s\n", participant.Identity) } ``` ### RemoveParticipant Removes a participant from a room. ```go func removeParticipant() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) _, err := roomClient.RemoveParticipant(context.Background(), &livekit.RoomParticipantIdentity{ Room: "my-room", Identity: "user123", }) if err != nil { log.Fatal(err) } fmt.Println("Participant removed") } ``` ### MutePublishedTrack Server-side mute/unmute of participant tracks. ```go func muteTrack() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) res, err := roomClient.MutePublishedTrack(context.Background(), &livekit.MuteRoomTrackRequest{ Room: "my-room", Identity: "user123", TrackSid: "TR_camera123", Muted: true, // true to mute, false to unmute }) if err != nil { log.Fatal(err) } fmt.Printf("Track muted: %v\n", res.Track.Muted) } ``` ### SendData Sends data messages to participants in a room. ```go func sendData() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) // Send to all participants _, err := roomClient.SendData(context.Background(), &livekit.SendDataRequest{ Room: "my-room", Data: []byte(`{"type": "announcement", "message": "Meeting starts in 5 minutes"}`), Kind: livekit.DataPacket_RELIABLE, // or LOSSY for real-time updates }) // Send to specific participants _, err = roomClient.SendData(context.Background(), &livekit.SendDataRequest{ Room: "my-room", Data: []byte(`{"type": "private", "message": "Hello!"}`), Kind: livekit.DataPacket_RELIABLE, DestinationIdentities: []string{"user456", "user789"}, }) // Send with topic for filtering _, err = roomClient.SendData(context.Background(), &livekit.SendDataRequest{ Room: "my-room", Data: []byte(`{"emoji": "thumbs_up"}`), Kind: livekit.DataPacket_LOSSY, Topic: "reactions", }) } ``` ### UpdateRoomMetadata Updates room-level metadata. ```go func updateRoomMetadata() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) room, err := roomClient.UpdateRoomMetadata(context.Background(), &livekit.UpdateRoomMetadataRequest{ Room: "my-room", Metadata: `{"topic": "Q4 Planning", "recording": true, "locked": false}`, }) if err != nil { log.Fatal(err) } fmt.Printf("Room metadata updated: %s\n", room.Metadata) } ``` ### MoveParticipant Moves a participant to another room. ```go func moveParticipant() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) _, err := roomClient.MoveParticipant(context.Background(), &livekit.MoveParticipantRequest{ Room: "source-room", Identity: "user123", DestinationRoom: "breakout-room-1", }) if err != nil { log.Fatal(err) } fmt.Println("Participant moved to breakout room") } ``` ## WebSocket Signaling Connection Clients connect via WebSocket for real-time signaling. ```javascript // JavaScript client connection example const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // JWT access token // Connect to LiveKit server const ws = new WebSocket(`wss://your-livekit-host/rtc?access_token=${token}&auto_subscribe=true`); ws.onopen = () => { console.log("Connected to LiveKit signaling"); }; ws.onmessage = (event) => { // Handle SignalResponse messages (offer, answer, trickle ICE, etc.) const response = parseSignalResponse(event.data); if (response.join) { console.log("Joined room:", response.join.room.name); console.log("Participant SID:", response.join.participant.sid); } if (response.offer) { // Handle SDP offer from server handleOffer(response.offer); } if (response.trickle) { // Handle ICE candidate handleIceCandidate(response.trickle); } }; // Send SDP answer function sendAnswer(sdp) { ws.send(encodeSignalRequest({ answer: { sdp: sdp.sdp, type: sdp.type } })); } // Send ICE candidate function sendIceCandidate(candidate, target) { ws.send(encodeSignalRequest({ trickle: { candidateInit: JSON.stringify(candidate), target: target // 0 = publisher, 1 = subscriber } })); } ``` ## Access Token Generation Tokens are JWTs that grant room access and permissions. ```go import ( "time" "github.com/livekit/protocol/auth" ) func generateToken() string { apiKey := "APIKey123" apiSecret := "YourSecretKey456" // Create access token at := auth.NewAccessToken(apiKey, apiSecret) grant := &auth.VideoGrant{ RoomJoin: true, Room: "my-room", CanPublish: boolPtr(true), CanSubscribe: boolPtr(true), CanPublishData: boolPtr(true), CanPublishSources: []string{ "camera", "microphone", "screen_share", "screen_share_audio", }, CanUpdateOwnMetadata: boolPtr(true), } at.AddGrant(grant). SetIdentity("user123"). SetName("John Doe"). SetValidFor(24 * time.Hour). SetMetadata(`{"role": "presenter"}`) token, err := at.ToJWT() if err != nil { log.Fatal(err) } return token } func boolPtr(b bool) *bool { return &b } ``` ```bash # Generate token using livekit-cli livekit-cli create-token \ --api-key APIKey123 \ --api-secret YourSecretKey456 \ --room my-room \ --identity user123 \ --name "John Doe" \ --valid-for 24h \ --can-publish \ --can-subscribe ``` ## Participant Implementation The ParticipantImpl manages WebRTC connections and track publishing/subscribing. ```go // Internal participant creation (server-side) import ( "github.com/livekit/livekit-server/pkg/rtc" "github.com/livekit/protocol/livekit" ) // Participant parameters for creation params := rtc.ParticipantParams{ Identity: "user123", Name: "John Doe", SID: livekit.ParticipantID("PA_abc123"), ProtocolVersion: 12, Grants: &auth.ClaimGrants{ Video: &auth.VideoGrant{ RoomJoin: true, Room: "my-room", CanPublish: boolPtr(true), CanSubscribe: boolPtr(true), }, }, Config: webrtcConfig, Sink: messageSink, AudioConfig: audioConfig, VideoConfig: videoConfig, AdaptiveStream: true, Telemetry: telemetryService, } participant, err := rtc.NewParticipant(params) if err != nil { log.Fatal(err) } // Key participant methods: // participant.ID() - Get participant SID // participant.Identity() - Get participant identity // participant.State() - Get connection state (JOINING, JOINED, ACTIVE, DISCONNECTED) // participant.IsPublisher() - Check if participant has published tracks // participant.GetPublishedTracks() - Get all published tracks // participant.Close(sendLeave, reason, isExpectedToResume) - Close participant connection ``` ## Track Management Publishing and subscribing to media tracks. ```go // Handle track publish request (server-side) func handleAddTrack(p *rtc.ParticipantImpl, req *livekit.AddTrackRequest) { // Validate permissions if !p.CanPublishSource(req.Source) { log.Warn("no permission to publish", "source", req.Source) return } // Add track for publishing p.AddTrack(req) } // Track subscription management func manageSubscriptions(p *rtc.ParticipantImpl) { // Get subscribed tracks subscribedTracks := p.SubscriptionManager.GetSubscribedTracks() for _, track := range subscribedTracks { fmt.Printf("Subscribed to: %s from %s\n", track.ID(), track.PublisherID()) } // Update subscription settings p.UpdateSubscription(&livekit.UpdateTrackSettings{ TrackSids: []string{"TR_video123"}, Disabled: false, Quality: livekit.VideoQuality_HIGH, Width: 1920, Height: 1080, Fps: 30, }) } ``` ```javascript // Client-side track publishing example async function publishTracks(room) { // Publish camera const videoTrack = await room.localParticipant.setCameraEnabled(true); console.log("Published video:", videoTrack.trackSid); // Publish microphone const audioTrack = await room.localParticipant.setMicrophoneEnabled(true); console.log("Published audio:", audioTrack.trackSid); // Publish screen share with audio const screenTracks = await room.localParticipant.setScreenShareEnabled(true, { audio: true, video: { resolution: { width: 1920, height: 1080, frameRate: 30 } } }); // Set track mute state await videoTrack.mute(); await videoTrack.unmute(); // Update track dimensions (for simulcast) await room.localParticipant.publishTrack(videoTrack, { simulcast: true, videoCodec: 'vp8', videoSimulcastLayers: [ { width: 640, height: 360, bitrate: 500000 }, { width: 1280, height: 720, bitrate: 1500000 }, { width: 1920, height: 1080, bitrate: 3000000 }, ], }); } ``` ## Webhooks LiveKit sends webhooks for room and participant events. ```go import ( "net/http" "github.com/livekit/protocol/livekit" "github.com/livekit/protocol/webhook" ) func handleWebhook(w http.ResponseWriter, r *http.Request) { apiKey := "APIKey123" apiSecret := "YourSecretKey456" // Verify and parse webhook provider := webhook.NewDefaultNotifierProvider(apiKey, apiSecret) event, err := webhook.ReceiveWebhookEvent(r, provider) if err != nil { http.Error(w, "invalid webhook", http.StatusUnauthorized) return } switch event.Event { case "room_started": fmt.Printf("Room started: %s\n", event.Room.Name) case "room_finished": fmt.Printf("Room finished: %s, duration: %ds\n", event.Room.Name, event.Room.NumParticipants) case "participant_joined": fmt.Printf("Participant joined: %s in room %s\n", event.Participant.Identity, event.Room.Name) case "participant_left": fmt.Printf("Participant left: %s\n", event.Participant.Identity) case "track_published": fmt.Printf("Track published: %s by %s\n", event.Track.Sid, event.Participant.Identity) case "track_unpublished": fmt.Printf("Track unpublished: %s\n", event.Track.Sid) case "egress_started": fmt.Printf("Egress started: %s\n", event.EgressInfo.EgressId) case "egress_ended": fmt.Printf("Egress ended: %s, status: %s\n", event.EgressInfo.EgressId, event.EgressInfo.Status) } w.WriteHeader(http.StatusOK) } ``` ## Egress (Recording/Streaming) Record rooms or stream to external platforms. ```go import ( lksdk "github.com/livekit/server-sdk-go/v2" "github.com/livekit/protocol/livekit" ) func startRoomCompositeEgress() { egressClient := lksdk.NewEgressClient(host, apiKey, apiSecret) // Record room to file info, err := egressClient.StartRoomCompositeEgress(context.Background(), &livekit.RoomCompositeEgressRequest{ RoomName: "my-room", Layout: "speaker", AudioOnly: false, VideoOnly: false, CustomBaseUrl: "https://my-templates.com/layout", FileOutputs: []*livekit.EncodedFileOutput{{ FileType: livekit.EncodedFileType_MP4, Filepath: "recordings/{room_name}/{time}.mp4", S3: &livekit.S3Upload{ AccessKey: "AWS_ACCESS_KEY", Secret: "AWS_SECRET_KEY", Bucket: "my-recordings", Region: "us-east-1", }, }}, StreamOutputs: []*livekit.StreamOutput{{ Protocol: livekit.StreamProtocol_RTMP, Urls: []string{"rtmp://live.twitch.tv/app/stream_key"}, }}, }) if err != nil { log.Fatal(err) } fmt.Printf("Egress started: %s\n", info.EgressId) } func startTrackEgress() { egressClient := lksdk.NewEgressClient(host, apiKey, apiSecret) // Record individual track info, err := egressClient.StartTrackEgress(context.Background(), &livekit.TrackEgressRequest{ RoomName: "my-room", TrackId: "TR_video123", Output: &livekit.TrackEgressRequest_File{ File: &livekit.DirectFileOutput{ Filepath: "tracks/{track_id}.webm", }, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Track egress started: %s\n", info.EgressId) } func stopEgress(egressId string) { egressClient := lksdk.NewEgressClient(host, apiKey, apiSecret) info, err := egressClient.StopEgress(context.Background(), &livekit.StopEgressRequest{ EgressId: egressId, }) if err != nil { log.Fatal(err) } fmt.Printf("Egress stopped: %s, Status: %s\n", info.EgressId, info.Status) } ``` ## Ingress (External Streams) Bring external media streams into LiveKit rooms. ```go import ( lksdk "github.com/livekit/server-sdk-go/v2" "github.com/livekit/protocol/livekit" ) func createRTMPIngress() { ingressClient := lksdk.NewIngressClient(host, apiKey, apiSecret) info, err := ingressClient.CreateIngress(context.Background(), &livekit.CreateIngressRequest{ InputType: livekit.IngressInput_RTMP_INPUT, Name: "OBS Stream", RoomName: "my-room", ParticipantIdentity: "broadcaster", ParticipantName: "Main Camera", Video: &livekit.IngressVideoOptions{ Source: livekit.TrackSource_CAMERA, EncodingOptions: &livekit.IngressVideoOptions_Preset{ Preset: livekit.IngressVideoEncodingPreset_H264_1080P_30, }, }, Audio: &livekit.IngressAudioOptions{ Source: livekit.TrackSource_MICROPHONE, EncodingOptions: &livekit.IngressAudioOptions_Preset{ Preset: livekit.IngressAudioEncodingPreset_OPUS_STEREO_96KBPS, }, }, }) if err != nil { log.Fatal(err) } fmt.Printf("Ingress created: %s\n", info.IngressId) fmt.Printf("RTMP URL: %s\n", info.Url) fmt.Printf("Stream Key: %s\n", info.StreamKey) } func createWHIPIngress() { ingressClient := lksdk.NewIngressClient(host, apiKey, apiSecret) info, err := ingressClient.CreateIngress(context.Background(), &livekit.CreateIngressRequest{ InputType: livekit.IngressInput_WHIP_INPUT, Name: "WebRTC Ingress", RoomName: "my-room", ParticipantIdentity: "whip-source", BypassTranscoding: true, // Forward WebRTC directly without transcoding }) if err != nil { log.Fatal(err) } fmt.Printf("WHIP URL: %s\n", info.Url) } ``` ## Data Channels and RPC Real-time data communication between participants. ```go // Server-side RPC call func performRPC() { roomClient := lksdk.NewRoomServiceClient(host, apiKey, apiSecret) res, err := roomClient.PerformRpc(context.Background(), &livekit.PerformRpcRequest{ Room: "my-room", DestinationIdentity: "agent-123", Method: "processMessage", Payload: `{"text": "Hello, can you help?", "language": "en"}`, ResponseTimeoutMs: 5000, }) if err != nil { log.Fatal(err) } fmt.Printf("RPC response: %s\n", res.Payload) } ``` ```javascript // Client-side data publishing async function sendData(room) { // Send reliable data (guaranteed delivery) const encoder = new TextEncoder(); await room.localParticipant.publishData( encoder.encode(JSON.stringify({ type: 'chat', message: 'Hello!' })), { reliable: true } ); // Send unreliable data (low latency, may be lost) await room.localParticipant.publishData( encoder.encode(JSON.stringify({ type: 'cursor', x: 100, y: 200 })), { reliable: false } ); // Send to specific participants await room.localParticipant.publishData( encoder.encode(JSON.stringify({ type: 'private', to: 'user456' })), { reliable: true, destinationIdentities: ['user456'] } ); // Register RPC handler room.localParticipant.registerRpcMethod('greet', async (data) => { const request = JSON.parse(data.payload); return JSON.stringify({ message: `Hello, ${request.name}!` }); }); // Call RPC on another participant const response = await room.localParticipant.performRpc({ destinationIdentity: 'agent-123', method: 'processMessage', payload: JSON.stringify({ text: 'Help me with this' }), responseTimeoutMs: 5000, }); } ``` ## Connection Quality and Metrics Monitor participant connection quality. ```go // Get connection quality info func monitorQuality(p *rtc.ParticipantImpl) { qualityInfo := p.GetConnectionQuality() fmt.Printf("Participant: %s\n", qualityInfo.ParticipantSid) fmt.Printf("Quality: %s\n", qualityInfo.Quality) // EXCELLENT, GOOD, POOR, LOST fmt.Printf("Score: %.2f\n", qualityInfo.Score) // 0-5 MOS score } ``` ```javascript // Client-side quality monitoring room.on('connectionQualityChanged', (quality, participant) => { console.log(`${participant.identity} quality: ${quality}`); // quality: 'excellent' | 'good' | 'poor' | 'lost' }); room.localParticipant.on('trackPublished', (publication) => { publication.track?.on('statistics', (stats) => { console.log('Track stats:', { bytesSent: stats.bytesSent, packetsSent: stats.packetsSent, packetsLost: stats.packetsLost, jitter: stats.jitter, roundTripTime: stats.roundTripTime, }); }); }); ``` ## Summary LiveKit Server provides a complete real-time communication infrastructure with support for multi-party video/audio conferencing, screen sharing, data channels, recording, and streaming. The primary use cases include video conferencing applications, live streaming platforms, interactive classrooms, telehealth solutions, collaborative workspaces, and gaming applications requiring real-time media. Integration typically involves deploying the server (standalone or clustered with Redis), generating access tokens for client authentication, connecting clients via WebSocket for signaling, and using the Room Service API for server-side control. The server handles WebRTC complexity including ICE negotiation, DTLS/SRTP encryption, simulcast/SVC for adaptive quality, and efficient media routing. For production deployments, consider using Redis for multi-node coordination, configuring TURN servers for NAT traversal, setting up webhooks for event notifications, and integrating egress/ingress services for recording and external stream input.