Introduction
LiveKit Ingress lets you import video from another source into a LiveKit room. While WebRTC is a versatile and scalable transport protocol for both media ingestion and delivery, some applications require integrating with existing workflows or equipment that do not support WebRTC. Perhaps your users want to publish video from OBS Studio or a dedicated hardware device, or maybe they want to stream the content of media file hosted on a HTTP server to a room. LiveKit Ingress makes these integrations easy.
LiveKit Ingress can automatically transcode the source media to ensure compatibility with LiveKit clients. It can publish multiple layers with Simulcast. The parameters of the different video layers can be defined at ingress creation time. Presets are provided to make encoding settings configuration easy. The optional ability to provide custom encoding parameters enables more specialized use cases.
For LiveKit Cloud customers, Ingress is ready to use with your project without additional configuration. When self-hosting LiveKit, Ingress is deployed as a separate service.
Supported Sources
- RTMP/RTMPS
- WHIP
- Media files fetched from any HTTP server. The following media formats are supported:
- HTTP Live Streaming (HLS)
- ISO MPEG-4 (MP4)
- Apple Quicktime (MOV)
- Matroska (MKV/WEBM)
- OGG audio
- MP3 audio
- M4A audio
Workflow
WHIP / RTMP
A typical push Ingress goes like this:
- Your app creates an Ingress with
CreateIngress
API, which returns a URL and stream key of the Ingress - Your user copies and pastes the URL and key into your streaming workflow
- Your user starts their stream
- The Ingress Service starts transcoding their stream
- The Ingress Service joins the LiveKit room and publishes the transcoded media for other Participants
- When the stream source disconnects from the Ingress service, the Ingress Service participant leaves the room.
- The Ingress remains valid, in a disconnected state, allowing it to be reused with the same stream key
URL Input
When pulling media from a HTTP server, Ingress has a slightly different lifecycle: it will start immediately after calling CreateIngress.
- Your app creates an Ingress with
CreateIngress
API - The Ingress Service starts fetching the file and transcoding it
- The Ingress Service joins the LiveKit room and publishes the transcoded media for other Participants
- When the file is completely consumed, or if
DeleteIngress
is called, the Ingress Service participant leaves the room.
API
CreateIngress
WHIP / RTMP example
To provision an Ingress with the Ingress Service, use the CreateIngress API. It returns an IngressInfo
object that describes the created Ingress, along with connection settings. These parameters can also be
queried at any time using the ListIngress
API
Create a file at ingress.json
with the following contents:
{"input_type": 0 for RTMP, 1 for WHIP"name": "Name of the Ingress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant","bypass_transcoding": true // for WHIP ingress only, disables transcoding and simulcast}
Then create the Ingress using livekit-cli:
% export LIVEKIT_URL=https://my-livekit-host% export LIVEKIT_API_KEY=livekit-api-key% export LIVEKIT_API_SECRET=livekit-api-secret% livekit-cli create-ingress --request ingress.json
import { IngressClient, IngressInfo, IngressInput } from 'livekit-server-sdk';const livekitHost = 'https://my-livekit-host';const ingressClient = new IngressClient(livekitHost, 'api-key', 'secret-key');const ingress = {name: 'my-ingress',roomName: 'my-room',participantIdentity: 'my-participant',participantName: 'My Participant',// for WHIP ingress only, disables transcoding and simulcastbypassTranscoding: false,};// Use IngressInput.WHIP_INPUT to create a WHIP endpointawait ingressClient.createIngress(IngressInput.RTMP_INPUT, ingress);
ctx := context.Background()ingressClient := lksdk.NewIngressClient("https://my-livekit-host","livekit-api-key","livekit-api-secret",)ingressRequest := &livekit.CreateIngressRequest{InputType: livekit.IngressInput_RTMP_INPUT, // Or livekit.IngressInput_WHIP_INPUTName: "my-ingress",RoomName: "my-room",ParticipantIdentity: "my-participant",ParticipantName: "My Participant",// for WHIP ingress only, disables transcoding and simulcastBypassTranscoding: false,}info, err := ingressClient.CreateIngress(ctx, ingressRequest)ingressID := info.IngressId
ingressClient = LiveKit::IngressServiceClient.new(url, api_key: "yourkey", api_secret: "yoursecret")info = ingressClient.create_ingress(:RTMP_INPUT, # Or WHIP_INPUTname: "my-ingress",room_name: "my-room",participant_identity: "my-participant",participant_name: "My Participant",)puts info.ingress_id
URL Input example
With URL Input, Ingress will begin immediately after CreateIngress
is called. URL_INPUT Ingress cannot be re-used.
Create a file at ingress.json
with the following contents:
{"input_type": "URL_INPUT", // or 2"name": "Name of the Ingress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant","url": "HTTP(S) url to the file"}
Then create the Ingress using livekit-cli:
% export LIVEKIT_URL=https://my-livekit-host% export LIVEKIT_API_KEY=livekit-api-key% export LIVEKIT_API_SECRET=livekit-api-secret% livekit-cli create-ingress --request ingress.json
import { IngressClient, IngressInfo, IngressInput } from 'livekit-server-sdk';const livekitHost = 'https://my-livekit-host';const ingressClient = new IngressClient(livekitHost, 'api-key', 'secret-key');const ingress = {name: 'my-ingress',roomName: 'my-room',participantIdentity: 'my-participant',participantName: 'My Participant',url: 'https://domain.com/video.m3u8'};// Use IngressInput.WHIP_INPUT to create a WHIP endpoint, or IngressInput.URL_INPUT to pull media from a HTTP server.await ingressClient.createIngress(IngressInput.URL_INPUT, ingress);
ctx := context.Background()ingressClient := lksdk.NewIngressClient("https://my-livekit-host","livekit-api-key","livekit-api-secret",)ingressRequest := &livekit.CreateIngressRequest{InputType: livekit.IngressInput_URL_INPUT,Name: "my-ingress",RoomName: "my-room",ParticipantIdentity: "my-participant",ParticipantName: "My Participant",Url: "https://domain.com/video.m3u8",}info, err := ingressClient.CreateIngress(ctx, ingressRequest)ingressID := info.IngressId
ingressClient = LiveKit::IngressServiceClient.new(url, api_key: "yourkey", api_secret: "yoursecret")info = ingressClient.create_ingress(:URL_INPUT,name: "my-ingress",room_name: "my-room",participant_identity: "my-participant",participant_name: "My Participant",url: "https://domain.com/video.m3u8",)puts info.ingress_id
ListIngress
% livekit-cli list-ingress
The optional --room
parameter allows restricting the output to the Ingress' associtated to a given room.
await ingressClient.listIngress('my-room');
The roomName
parameter can be left empty to list all Ingress.
listRequest := &livekit.ListIngressRequest{RoomName: "my-room", // Optional parameter to restrict the list to only one room. Leave empty to list all Ingress.}infoArray, err := ingressClient.ListIngress(ctx, listRequest)
puts ingressClient.list_ingress(# optionalroom_name: "my-room")
UpdateIngress
The Ingress configuration can be updated using the UpdateIngress
API. This enables
the ability to re-use the same Ingress URL to publish to different rooms. Only reusable Ingresses,
such as RTMP or WHIP, can be updated.
Create a file at ingress.json
with the fields to be updated.
{"ingress_id": "Ingress ID of the Ingress to update","name": "Name of the Ingress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant"}
The only required field is ingress_id
. Non provided fields are left unchanged.
% livekit-cli update-ingress --request ingress.json
The optional --room
option allows to restrict the output to the Ingress associtated to a given room.
const update = {name: 'my-other-ingress',roomName: 'my-other-room',participantIdentity: 'my-other-participant',participantName: 'My Other Participant'};await ingressClient.updateIngress(ingressID, update);
Parameters left empty in the update object are left unchanged.
updateRequest := &livekit.UpdateIngressRequest{IngressId: "ingressID", // required parameter indicating what Ingress to updateName: "my-other-ingress",RoomName: "my-other-room",ParticipantIdentity: "my-other-participant",ParticipantName: "My Other Participant",}info, err := ingressClient.UpdateIngress(ctx, updateRequest)
Non specified fields are left unchanged.
# only specified fields are updated, all fields are optionalputs ingressClient.update_ingress("ingress-id",name: "ingress-name",room_name: "my-room",participant_identity: "my-participant",participant_name: "My Participant",audio: LiveKit::Proto::IngressAudioOptions.new(...),video: LiveKit::Proto::IngressVideoOptions.new(...),)
DeleteIngress
An Ingress can be reused multiple times. When not needed anymore, it can be deleted using the DeleteIngress
API:
% livekit-cli delete-ingress --id 'ingress_id'
await ingressClient.deleteIngress('ingress_id');
deleteRequest := &livekit.DeleteIngressRequest{IngressId: "ingress_id",}info, err := ingressClient.DeleteIngress(ctx, deleteRequest)
puts ingressClient.delete_ingress("ingress-id")
Using video presets
By default, the Ingress service transcodes the media being received using settings suitable for most use cases. These settings enable video simulcast to ensure media can be consumed by all viewers. In some situations however, adjusting these settings may be desirable to match source content or the viewer conditions better. For this purpose, LiveKit Ingress defines several presets, both for audio and video. Presets define both the characteristics of the media (codec, dimesions, framerate, channel count, sample rate) and the bitrate. For video, a single preset defines the full set of simulcast layers.
A preset can be chosen at Ingress creation time from the constants in the Ingress protocol definition:
Create a file at ingress.json
with the following contents:
{"name": "Name of the egress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant""video": {"name": "track name","source": "SCREEN_SHARE","preset": "Video preset enum value"},"audio": {"name": "track name","source": "SCREEN_SHARE_AUDIO","preset": "Audio preset enum value"}}
Then create the Ingress using livekit-cli:
% livekit-cli create-ingress --request ingress.json
const ingress = {name: 'my-ingress',roomName: 'my-room',participantIdentity: 'my-participant',participantName: 'My Participant',video: {source: TrackSource.SCREEN_SHARE,preset: IngressVideoEncodingPreset.H264_1080P_30FPS_3_LAYERS},audio: {source: TrackSource.SCREEN_SHARE_AUDIO,preset: IngressAudioEncodingPreset.OPUS_MONO_64KBS}};await ingressClient.createIngress(IngressInput.RTMP_INPUT, ingress);
ingressRequest := &livekit.CreateIngressRequest{Name: "my-ingress",RoomName: "my-room",ParticipantIdentity: "my-participant",ParticipantName: "My Participant",Video: &livekit.IngressVideoOptions{EncodingOptions: &livekit.IngressVideoOptions_Preset{Preset: livekit.IngressVideoEncodingPreset_H264_1080P_30FPS_3_LAYERS,},},Audio: &livekit.IngressAudioOptions{EncodingOptions: &livekit.IngressAudioOptions_Preset{Preset: livekit.IngressAudioEncodingPreset_OPUS_MONO_64KBS,},},}info, err := ingressClient.CreateIngress(ctx, ingressRequest)ingressID := info.IngressId
video_options = LiveKit::Proto::IngressVideoOptions.new(name: "track name",source: :SCREEN_SHARE,preset: :H264_1080P_30FPS_3_LAYERS)audio_options = LiveKit::Proto::IngressAudioOptions.new(name: "track name",source: :SCREEN_SHARE_AUDIO,preset: :OPUS_STEREO_96KBPS)info = ingressClient.create_ingress(:RTMP_INPUT,name: 'dz-test',room_name: 'davids-room',participant_identity: 'ingress',video: video_options,audio: audio_options,)puts info.ingress_id
Custom settings
For specialized use cases, it is also possible to specify fully custom encoding parameters. In this case, all video layers need to be defined if simulcast is desired.
Create a file at ingress.json
with the following contents:
{"name": "Name of the egress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant","video": {"options": {"video_codec": "video codec ID from the [VideoCodec enum](https://github.com/livekit/protocol/blob/main/livekit_models.proto)","frame_rate": "desired framerate in frame per second","layers": [{"quality": "ID for one of the LOW, MEDIUM or HIGH VideoQualitu definitions","witdh": "width of the layer in pixels","height": "height of the layer in pixels","bitrate": "video bitrate for the layer in bit per second"}]}},"audio": {"options": {"audio_codec": "audio codec ID from the [AudioCodec enum](https://github.com/livekit/protocol/blob/main/livekit_models.proto)","bitrate": "audio bitrate for the layer in bit per second","channels": "audio channel count, 1 for mono, 2 for stereo","disable_dtx": "wether to disable the [DTX feature](https://www.rfc-editor.org/rfc/rfc6716#section-2.1.9) for the OPUS codec"}}}
Then create the Ingress using livekit-cli:
livekit-cli create-ingress --request ingress.json
const ingress = {name: 'my-ingress',roomName: 'my-room',participantIdentity: 'my-participant',participantName: 'My Participant',video: {options: {video_codec: VideoCodec.H264_BASELINE,frame_rate: 30,layers: [{quality: VideoQuality.HIGH,width: 1920,height: 1080,bitrate: 4500000,}]}},audio: {options: {audio_codec: AudioCodec.OPUS,bitrate: 64000,channels: 1,}}};await ingressClient.createIngress(IngressInput.RTMP_INPUT, ingress);
ingressRequest := &livekit.CreateIngressRequest{Name: "my-ingress",RoomName: "my-room:",ParticipantIdentity: "my-participant",ParticipantName: "My Participant",Video: &livekit.IngressVideoOptions{EncodingOptions: &livekit.IngressVideoOptions_Options{Options: &livekit.IngressVideoEncodingOptions{VideoCodec: livekit.VideoCodec_H264_BASELINE,FrameRate: 30,Layers: []*livekit.VideoLayer{&livekit.VideoLayer{Quality: livekit.VideoQuality_HIGH,Width: 1920,Height: 1080,Bitrate: 4_500_000,},},},},},Audio: &livekit.IngressAudioOptions{EncodingOptions: &livekit.IngressAudioOptions_Options{Options: &livekit.IngressAudioEncodingOptions{AudioCodec: livekit.AudioCodec_OPUS,Bitrate: 64_000,Channels: 1,},},},}info, err := ingressClient.CreateIngress(ctx, ingressRequest)ingressID := info.IngressId
video_encoding_opts = LiveKit::Proto::IngressVideoEncodingOptions.new(frame_rate: 30,)# add layers as arrayvideo_encoding_opts.layers += [LiveKit::Proto::VideoLayer.new(quality: :HIGH,width: 1920,height: 1080,bitrate: 4_500_000,)]video_options = LiveKit::Proto::IngressVideoOptions.new(name: "track name",source: :SCREEN_SHARE,options: video_encoding_opts,)audio_options = LiveKit::Proto::IngressAudioOptions.new(name: "track name",source: :SCREEN_SHARE_AUDIO,options: LiveKit::Proto::IngressAudioEncodingOptions.new(bitrate: 64000,disable_dtx: true,channels: 1,))info = ingressClient.create_ingress(:RTMP_INPUT,name: 'dz-test',room_name: 'davids-room',participant_identity: 'ingress',video: video_options,audio: audio_options,)puts info.ingress_id
Bypass transcoding for WHIP sessions
For WHIP ingress sessions, the Ingress service allows disabling the transcoding step on the server. This means that the audio and video media sent by the WHIP encoder will be forwarded unmodified to the LiveKit clients. This decreases the end to end latency between the media encoder and the viewers. When using this mode, it is the responsibility of the entity setting the encoder parameters to ensure that these settings are compatible with all the clients who will consume the media, and achieve the right trade offs between quality and reach for clients with variable connection quality. Simulcast is not supported when bypassing transcoding.
This mode is enabled using the bypass_transcoding
parameter in the create ingress request:
Create a file at ingress.json
with the following contents:
{"input_type": 1 (WHIP only)"name": "Name of the egress goes here","room_name": "Name of the room to connect to","participant_identity": "Unique identity for the room participant the Ingress service will connect as","participant_name": "Name displayed in the room for the participant","bypass_transcoding": true}
Then create the Ingress using livekit-cli:
livekit-cli create-ingress --request ingress.json
const ingress = {name: 'my-ingress',roomName: 'my-room',participantIdentity: 'my-participant',participantName: 'My Participant',bypassTranscoding: true};await ingressClient.createIngress(IngressInput.WHIP_INPUT, ingress);
ingressRequest := &livekit.CreateIngressRequest{InputType: livekit.IngressInput_WHIP_INPUTName: "my-ingress",RoomName: "my-room:",ParticipantIdentity: "my-participant",ParticipantName: "My Participant",BypassTranscoding: true,}info, err := ingressClient.CreateIngress(ctx, ingressRequest)ingressID := info.IngressId
video_options = LiveKit::Proto::IngressVideoOptions.new(name: "track name",source: :SCREEN_SHARE,)audio_options = LiveKit::Proto::IngressAudioOptions.new(name: "track name",source: :SCREEN_SHARE_AUDIO,)info = ingressClient.create_ingress(:WHIP_INPUT,name: 'dz-test',room_name: 'davids-room',participant_identity: 'ingress',bypass_transcoding: true,video: video_options,audio: audio_options,)puts info.ingress_id
Service architecture
Livekit Ingress exposes public RTMP and WHIP endpoints streamers can connect to. On initial handshake, the Ingress service validates the incoming request and retrieves the corresponding Ingress metadata, including what LiveKit room the stream belongs to. The Ingress server then sets up a GStreamer based media processing pipeline to transcode the incoming media to a format compatible with LiveKit WebRTC clients, publishes the resulting media to the LiveKit room.