name: livekit-ingress
description: Import RTMP, WHIP, and media streams into LiveKit rooms from OBS, encoders, and external sources
argument-hint: ""
allowed-tools: Read, Write, Bash(lk, npm install, pip install), Glob, Grep
LiveKit Ingress (Streaming Input)
Import external streams into LiveKit: $ARGUMENTS
Expert Knowledge
You are a LiveKit ingress specialist with expertise in:
- RTMP streaming from OBS/encoders
- WHIP protocol integration
- Simulcast and transcoding
- URL/file ingestion
- Broadcasting workflows
Ingress Overview
| Input Type | Protocol | Transcoding | Use Case |
|---|---|---|---|
| RTMP | Real-Time Messaging Protocol | Required | OBS, hardware encoders |
| WHIP | WebRTC-HTTP Ingestion Protocol | Optional | Low-latency WebRTC |
| URL | HTTP/HTTPS | Required | Pre-recorded content |
Creating Ingress
RTMP Ingress
import { IngressClient, IngressInput } from 'livekit-server-sdk';
const ingressClient = new IngressClient(
process.env.LIVEKIT_URL!,
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
// Create RTMP ingress
const ingress = await ingressClient.createIngress(IngressInput.RTMP_INPUT, {
name: 'obs-stream',
roomName: 'live-room',
participantIdentity: 'broadcaster',
participantName: 'Live Stream',
// Optional: video encoding options
video: {
source: 'SCREEN_SHARE', // or 'CAMERA'
layers: [
{ width: 1920, height: 1080, bitrate: 3000000, quality: 90 },
{ width: 1280, height: 720, bitrate: 1500000, quality: 85 },
{ width: 640, height: 480, bitrate: 600000, quality: 80 },
],
},
audio: {
source: 'MICROPHONE',
preset: 'OPUS_STEREO_96KBPS',
},
});
console.log('RTMP URL:', ingress.url);
console.log('Stream Key:', ingress.streamKey);
Python
from livekit.api import LiveKitAPI
from livekit.protocol import ingress as ingress_proto
api = LiveKitAPI()
# Create RTMP ingress
ingress = await api.ingress.create_ingress(
ingress_proto.CreateIngressRequest(
input_type=ingress_proto.IngressInput.RTMP_INPUT,
name="obs-stream",
room_name="live-room",
participant_identity="broadcaster",
participant_name="Live Stream",
video=ingress_proto.IngressVideoOptions(
source=ingress_proto.TrackSource.SCREEN_SHARE,
layers=[
ingress_proto.VideoLayer(width=1920, height=1080, bitrate=3000000),
ingress_proto.VideoLayer(width=1280, height=720, bitrate=1500000),
ingress_proto.VideoLayer(width=640, height=480, bitrate=600000),
],
),
audio=ingress_proto.IngressAudioOptions(
source=ingress_proto.TrackSource.MICROPHONE,
preset=ingress_proto.IngressAudioEncodingPreset.OPUS_STEREO_96KBPS,
),
)
)
print(f"RTMP URL: {ingress.url}")
print(f"Stream Key: {ingress.stream_key}")
WHIP Ingress
Low-latency WebRTC ingestion:
const ingress = await ingressClient.createIngress(IngressInput.WHIP_INPUT, {
name: 'webrtc-stream',
roomName: 'live-room',
participantIdentity: 'webrtc-broadcaster',
enableTranscoding: false, // Direct passthrough for lower latency
});
console.log('WHIP URL:', ingress.url);
// Use this URL in WHIP-compatible software
URL Ingress
Import from HTTP/HTTPS sources:
const ingress = await ingressClient.createIngress(IngressInput.URL_INPUT, {
name: 'video-file',
roomName: 'playback-room',
participantIdentity: 'video-player',
url: 'https://example.com/video.mp4',
});
OBS Studio Configuration
- Open OBS → Settings → Stream
- Service: Custom
- Server:
rtmp://your-livekit-ingress.com/live - Stream Key:
<stream-key-from-api>
Recommended OBS Settings
Output Settings:
├── Video Bitrate: 3000-6000 Kbps
├── Encoder: x264 or NVENC
├── Audio Bitrate: 128-320 Kbps
├── Keyframe Interval: 2 seconds
Video Settings:
├── Base Resolution: 1920x1080
├── Output Resolution: 1920x1080
└── FPS: 30 or 60
FFmpeg Streaming
# Stream file to RTMP ingress
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -b:v 3000k \
-c:a aac -b:a 128k \
-f flv "rtmp://ingress-url/live/stream-key"
# Stream webcam
ffmpeg -f avfoundation -i "0:0" \
-c:v libx264 -preset veryfast -b:v 2000k \
-c:a aac -b:a 128k \
-f flv "rtmp://ingress-url/live/stream-key"
# Loop video continuously
ffmpeg -stream_loop -1 -re -i input.mp4 \
-c:v libx264 -b:v 3000k \
-c:a aac -b:a 128k \
-f flv "rtmp://ingress-url/live/stream-key"
Simulcast Layers
Configure multiple quality levels:
const ingress = await ingressClient.createIngress(IngressInput.RTMP_INPUT, {
name: 'simulcast-stream',
roomName: 'live-room',
participantIdentity: 'broadcaster',
video: {
source: 'CAMERA',
layers: [
// High quality
{ width: 1920, height: 1080, bitrate: 4000000, quality: 90 },
// Medium quality
{ width: 1280, height: 720, bitrate: 1500000, quality: 85 },
// Low quality
{ width: 640, height: 360, bitrate: 500000, quality: 80 },
],
},
});
Audio Presets
// Available audio presets
const audioPresets = [
'OPUS_STEREO_96KBPS', // Default, good quality
'OPUS_MONO_64KBPS', // Low bandwidth
'OPUS_STEREO_128KBPS', // High quality
];
const ingress = await ingressClient.createIngress(IngressInput.RTMP_INPUT, {
...config,
audio: {
source: 'MICROPHONE',
preset: 'OPUS_STEREO_128KBPS',
},
});
Managing Ingress
// List all ingresses
const ingresses = await ingressClient.listIngress({
roomName: 'live-room', // Optional filter
});
// Get ingress info
const ingress = await ingressClient.getIngress(ingressId);
// Update ingress
const updated = await ingressClient.updateIngress({
ingressId: ingress.ingressId,
name: 'Updated Stream Name',
roomName: 'new-room',
});
// Delete ingress
await ingressClient.deleteIngress(ingressId);
Ingress State
import { IngressState } from 'livekit-server-sdk';
// Check ingress status
const ingress = await ingressClient.getIngress(ingressId);
switch (ingress.state) {
case IngressState.ENDPOINT_INACTIVE:
console.log('Waiting for stream...');
break;
case IngressState.ENDPOINT_BUFFERING:
console.log('Buffering stream...');
break;
case IngressState.ENDPOINT_PUBLISHING:
console.log('Stream is live!');
break;
case IngressState.ENDPOINT_ERROR:
console.log('Stream error:', ingress.error);
break;
}
Webhooks
Listen for ingress events:
// webhook handler
app.post('/livekit-webhook', (req, res) => {
const event = req.body;
switch (event.event) {
case 'ingress_started':
console.log('Ingress started:', event.ingressInfo);
break;
case 'ingress_ended':
console.log('Ingress ended:', event.ingressInfo);
break;
}
res.sendStatus(200);
});
CLI Commands
# Create RTMP ingress
lk ingress create \
--room my-room \
--identity broadcaster \
--name "My Stream" \
--input-type rtmp
# Create WHIP ingress
lk ingress create \
--room my-room \
--identity broadcaster \
--input-type whip
# List ingresses
lk ingress list
# Get ingress info
lk ingress info <ingress-id>
# Delete ingress
lk ingress delete <ingress-id>
Complete Example
import { IngressClient, IngressInput, IngressState } from 'livekit-server-sdk';
class BroadcastService {
private ingress: IngressClient;
constructor() {
this.ingress = new IngressClient(
process.env.LIVEKIT_URL!,
process.env.LIVEKIT_API_KEY!,
process.env.LIVEKIT_API_SECRET!
);
}
async createBroadcast(
broadcasterId: string,
roomName: string
): Promise<{ rtmpUrl: string; streamKey: string }> {
const ingress = await this.ingress.createIngress(IngressInput.RTMP_INPUT, {
name: `${broadcasterId}-stream`,
roomName,
participantIdentity: broadcasterId,
participantName: 'Broadcaster',
video: {
source: 'CAMERA',
layers: [
{ width: 1920, height: 1080, bitrate: 4000000, quality: 90 },
{ width: 1280, height: 720, bitrate: 1500000, quality: 85 },
{ width: 640, height: 360, bitrate: 500000, quality: 80 },
],
},
audio: {
source: 'MICROPHONE',
preset: 'OPUS_STEREO_96KBPS',
},
});
return {
rtmpUrl: ingress.url,
streamKey: ingress.streamKey,
};
}
async getBroadcastStatus(ingressId: string): Promise<{
isLive: boolean;
error?: string;
}> {
const ingress = await this.ingress.getIngress(ingressId);
return {
isLive: ingress.state === IngressState.ENDPOINT_PUBLISHING,
error: ingress.error,
};
}
async endBroadcast(ingressId: string): Promise<void> {
await this.ingress.deleteIngress(ingressId);
}
async listActiveBroadcasts(roomName?: string) {
const ingresses = await this.ingress.listIngress({ roomName });
return ingresses.filter(
(i) => i.state === IngressState.ENDPOINT_PUBLISHING
);
}
}
export const broadcastService = new BroadcastService();
Best Practices
- Use simulcast: Multiple quality layers for adaptive streaming
- Set keyframe interval: 2 seconds for reliable switching
- Monitor ingress state: Handle errors gracefully
- Use WHIP for low latency: When sub-second latency is needed
- Validate stream settings: Check OBS/encoder configuration
Deliverables
For: $ARGUMENTS
Provide:
- Ingress type selection
- Encoding configuration
- Simulcast layer setup
- Client/encoder configuration
- State monitoring
- Management endpoints