Introduction
LiveKit Egress gives you a powerful and consistent set of APIs to export any room or individual tracks from a LiveKit session.
It supports recording to a MP4 file or HLS segments, as well as exporting to livestreaming services like YouTube Live, Twitch, and Facebook via RTMP(s).
For LiveKit Cloud customers, Egress is ready to use with your project without additional configuration. When self-hosting LiveKit, Egress is a separate component that needs to be deployed.
Egress Types
Room Composite Egress
Export an entire room's video and/or audio using a web layout rendered by Chrome.
Example use case: recording a meeting for team members to watch later.
Track Composite Egress
Sync and export up to one audio and one video track. Will transcode and mux.
Example use case: exporting audio+video from many cameras at once during a production, for use in additional post-production.
Track Egress
Export individual tracks directly. Video tracks are not transcoded.
Example use case: streaming an audio track to a captioning service via websocket.
Supported Outputs
Egress Type | MP4 File | OGG File | WebM File | HLS (TS SEGMENTS) | RTMP(s) Stream | WebSocket Stream |
---|---|---|---|---|---|---|
Room Composite | ✅ | ✅ | ✅ | ✅ | ||
Web | ✅ | ✅ | ✅ | ✅ | ||
Track Composite | ✅ | ✅ | ✅ | ✅ | ||
Track | ✅ | ✅ | ✅ | ✅ |
Files can be uploaded to any S3 compatible storage, Azure, GCP, or Ali OSS.
Note that a very long-running Egress may hit our Egress time limits.
Multi-output
Egress is optimized to transcode once while sending output to multiple destinations. For example, from the same Egress you may simultaneously
- stream to multiple RTMP endpoints
- record as HLS
- record as MP4
Streaming
RTMP streams can be added and removed on the fly using the UpdateStream API.
Streaming to Mux
To start a Mux stream, all you need is your stream key. You can then use mux://<stream_key>
as
a url
in your StreamOutput
.
Choosing RTMP ingest endpoints
RTMP streams do not perform well over long distances. Some stream providers include a region or location as part of your stream url, while others might use region-based routing.
- When self-hosting, choose stream endpoints that are close to where your Egress servers are deployed.
- With Cloud Egress, we will route your egress request to a server closest to your rtmp endpoints. If streaming to multiple destinations, it is best to choose endpoints which are close to one another.
Adding streams to non-streaming egress
In order to use the UpdateStream API, your initial request will need to contain a StreamOutput. This means if you want
to start a file recording first and then add streams afterwards, the initial request will need to contain
a StreamOutput
with protocol
set to RTMP and urls
left empty.
Websocket streams
The Egress service will send pings to your websocket server every 30 seconds. Your server should both respond to these pings, and send pings of its own to avoid timeouts and other abnormal closures.
File/Segment outputs
Filename templating
The below templates can be used in request filename/filepath parameters:
Egress Type | {room_id} | {room_name} | {time} | {publisher_identity} | {track_id} | {track_type} | {track_source} |
---|---|---|---|---|---|---|---|
Room Composite | ✅ | ✅ | ✅ | ||||
Web | ✅ | ||||||
Track Composite | ✅ | ✅ | ✅ | ✅ | |||
Track | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
- If no filename is provided with a request, one will be generated in the form of
"{room_name}-{time}"
. - If your filename ends with a
/
, a file will be generated in that directory. - If your filename is missing an extension or includes the wrong extension, the correct one will be added.
Examples:
Request filename | Output filename |
---|---|
"" | testroom-2022-10-04T011306.mp4 |
"livekit-recordings/" | livekit-recordings/testroom-2022-10-04T011306.mp4 |
"{room_name}/{time}" | testroom/2022-10-04T011306.mp4 |
"{room_id}-{publisher_identity}.mp4" | 10719607-f7b0-4d82-afe1-06b77e91fe12-david.mp4 |
"{track_type}-{track_source}-{track_id}" | audio-microphone-TR_SKasdXCVgHsei.ogg |
S3
Egress supports any S3-compatible storage provider. When endpoint
is left empty, it'll use AWS's regional endpoints.
region
is required when endpoint
is not set.
const output = {fileType: EncodedFileType.MP4,filepath: 'livekit-demo/room-composite-test.mp4',s3: {accessKey: 'aws-access-key',secret: 'aws-access-secret',bucket: 'my-bucket',endpoint: 'https://my.s3.endpoint',}};await egressClient.startRoomCompositeEgress('my-room', output);
fileRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "speaker",Output: &livekit.RoomCompositeEgressRequest_File{File: &livekit.EncodedFileOutput{FileType: livekit.EncodedFileType_MP4,Filepath: "livekit-demo/my-room-test.mp4",Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{AccessKey: "aws-access-key",Secret: "aws-access-secret",Bucket: "my-bucket",Endpoint: "https://my.s3.endpoint",},},},},}egressClient.StartRoomCompositeEgress(ctx, fileRequest)
info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::EncodedFileOutput.new(file_type: LiveKit::Proto::EncodedFileType::MP4,filepath: "dz-test-recording.mp4",s3: LiveKit::Proto::S3Upload.new(access_key: 'access-key',secret: 'secret',bucket: 'bucket',endpoint: 'https://my.s3.endpoint')))
LivekitEgress.EncodedFileOutput output = LivekitEgress.EncodedFileOutput.newBuilder().setFileType(LivekitEgress.EncodedFileType.MP4).setFilepath("test-recording.mp4").setS3(LivekitEgress.S3Upload.newBuilder().setAccessKey("access-key").setSecret("secret").setBucket("bucket").setEndpoint("https://my.s3.endpoint").build()).build();Call<LivekitEgress.EgressInfo> call = egressClient.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
Google Cloud Storage
For Egress to upload to Google Cloud Storage, you'll need to provide credentials in JSON.
This can be obtained by first creating a service account
that has permissions to create storage objects (i.e. Storage Object Creator
).
Then create a key for that account and export as a JSON file.
We'll refer to this file as credentials.json
.
import * as fs from 'fs';const content = fs.readFileSync('/path/to/credentials.json')const output = {fileType: EncodedFileType.MP4,filepath: 'livekit-demo/room-composite-test.mp4',gcp: {// credentials need to be a JSON encoded string containing credentialscredentials: content.toString(),bucket: 'my-bucket'}};await egressClient.startRoomCompositeEgress('my-room', output);
credentialsJson, err := os.ReadFile("/path/to/credentials.json")if err != nil {panic(err.Error())}fileRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "speaker",Output: &livekit.RoomCompositeEgressRequest_File{File: &livekit.EncodedFileOutput{FileType: livekit.EncodedFileType_MP4,Filepath: "livekit-demo/my-room-test.mp4",Output: &livekit.EncodedFileOutput_Gcp{Gcp: &livekit.GCPUpload{Credentials: string(credentialsJson),Bucket: "my-bucket",},},},},}egressClient.StartRoomCompositeEgress(ctx, fileRequest)
content = File.read("/path/to/credentials.json")info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::EncodedFileOutput.new(file_type: LiveKit::Proto::EncodedFileType::MP4,filepath: "dz-test-recording.mp4",gcp: LiveKit::Proto::GCPUpload.new(credentials: content,bucket: 'my-bucket')))
byte[] credentials;try {credentials = Files.readAllBytes(new File("/path/to/credentials.json").toPath());} catch (IOException e) {// handle exceptioncredentials = new byte[0];}LivekitEgress.EncodedFileOutput output = LivekitEgress.EncodedFileOutput.newBuilder().setFileType(LivekitEgress.EncodedFileType.MP4).setFilepath("test-recording.mp4").setGcp(LivekitEgress.GCPUpload.newBuilder().setCredentials(ByteString.copyFrom(credentials)).build()).build();Call<LivekitEgress.EgressInfo> call = egressClient.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
Azure
In order to upload to Azure Blob Storage, you'll need the account's shared access key.
const output = {fileType: EncodedFileType.MP4,filepath: 'livekit-demo/room-composite-test.mp4',azure: {accountName: 'my-account',accountKey: 'shared-access-key',containerName: 'my-container',}};await egressClient.startRoomCompositeEgress('my-room', output);
fileRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "speaker",Output: &livekit.RoomCompositeEgressRequest_File{File: &livekit.EncodedFileOutput{FileType: livekit.EncodedFileType_MP4,Filepath: "livekit-demo/my-room-test.mp4",Output: &livekit.EncodedFileOutput_Azure{Azure: &livekit.AzureBlobUpload{AccountName: "my-account",AccountKey: "shared-access-key",ContainerName: "my-container",},},},},}egressClient.StartRoomCompositeEgress(ctx, fileRequest)
info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::EncodedFileOutput.new(file_type: LiveKit::Proto::EncodedFileType::MP4,filepath: "dz-test-recording.mp4",azure: LiveKit::Proto::AzureBlobUpload.new(accountName: 'my-account',accountKey: 'shared-access-key',containerName: 'my-container')))
LivekitEgress.EncodedFileOutput output = LivekitEgress.EncodedFileOutput.newBuilder().setFileType(LivekitEgress.EncodedFileType.MP4).setFilepath("test-recording.mp4").setAzure(LivekitEgress.AzureBlobUpload.newBuilder().setAccountName("my-account").setAccountKey("shared-access-key").setContainerName("my-container").build()).build();Call<LivekitEgress.EgressInfo> call = egressClient.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
Service Architecture
Depending on your request type, the egress service will either launch a web template in Chrome and connect to the room (room composite requests), or it will use the sdk directly (track and track composite requests). It uses GStreamer to encode, and can output to a file or to one or more streams.
API
All RPC definitions and options can be found here.
The Egress API exists within our server SDKs and CLI
You can also use curl
to interact with Egress APIs. To do so, you can POST to:
https://<your-livekit-host>/twirp/livekit.Egress/<MethodName>
Requests to Egress service need roomRecord
permission in the access token.
For example:
% curl -X POST https://<your-livekit-host>/twirp/livekit.Egress/StartRoomCompositeEgress \-H "Authorization: Bearer <access-token>" \-H 'Content-Type: application/json' \-d '{"room_name": "your-room", "segments": {"filename_prefix": "your-hls-playlist.m3u8", "s3": {"access_key": "<key>", "secret": "<secret>", "bucket": "<bucket>", "region": "<bucket-region>"}}}'{"egress_id":"EG_MU4QwhXUhWf9","room_id":"<room-id>","room_name":"your-room","status":"EGRESS_STARTING"...}
StartRoomCompositeEgress
Starts a new Composite Recording using a web browser as the rendering engine.
Parameter | Type | Required | Description |
---|---|---|---|
room_name | string | yes | name of room to record |
layout | string | layout parameter that is passed to the template | |
audio_only | bool | true if resulting output should only contain audio | |
video_only | bool | true if resulting output should only contain video | |
custom_base_url | string | URL to the page that would composite tracks, uses embedded templates if left blank | |
file_outputs | EncodedFileOutput[] | output to MP4 file. currently only supports a single entry | |
segment_outputs | SegmentedFileOutput[] | output to HLS segments. currently only supports a single entry | |
stream_outputs | StreamOutput[] | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs | |
preset | EncodingOptionsPreset | encoding preset to use. only one of preset or advanced could be set | |
advanced | EncodingOptions | advanced encoding options. only one of preset or advanced could be set |
StartTrackCompositeEgress
Starts a new Track Composite
Parameter | Type | Required | Description |
---|---|---|---|
room_name | string | yes | name of room to record |
audio_track_id | string | ID of audio track to composite | |
video_track_id | string | ID of video track to composite | |
file_outputs | EncodedFileOutput[] | output to MP4 file. currently only supports a single entry | |
segment_outputs | SegmentedFileOutput[] | output to HLS segments. currently only supports a single entry | |
stream_outputs | StreamOutput[] | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs | |
preset | EncodingOptionsPreset | encoding preset to use. only one of preset or advanced could be set | |
advanced | EncodingOptions | advanced encoding options. only one of preset or advanced could be set |
StartTrackEgress
Starts a new Track Egress
Parameter | Type | Required | Description |
---|---|---|---|
room_name | string | yes | name of room to record |
track_id | string | layout parameter that is passed to the template | |
file | DirectFileOutput | only one of file or websocket_url can be set | |
websocket_url | string | url to websocket to receive audio output. only one of file or websocket_url can be set |
StartWebEgress
Starts a new Web Egress
Parameter | Type | Required | Description |
---|---|---|---|
url | string | yes | URL of the web page to record |
audio_only | bool | true if resulting output should only contain audio | |
video_only | bool | true if resulting output should only contain video | |
file_outputs | EncodedFileOutput[] | output to MP4 file. currently only supports a single entry | |
segment_outputs | SegmentedFileOutput[] | output to HLS segments. currently only supports a single entry | |
stream_outputs | StreamOutput[] | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs | |
preset | EncodingOptionsPreset | encoding preset to use. only one of preset or advanced could be set | |
advanced | EncodingOptions | advanced encoding options. only one of preset or advanced could be set |
UpdateLayout
Used to change the web layout on an active RoomCompositeEgress.
Parameter | Type | Required | Description |
---|---|---|---|
egress_id | string | yes | Egress ID to update |
layout | string | yes | layout to update to |
const info = await egressClient.updateLayout(egressID, 'grid-light');
info, err := egressClient.UpdateLayout(ctx, &livekit.UpdateLayoutRequest{EgressId: egressID,Layout: "grid-light",})
egressClient.update_layout('egress-id', 'grid-dark')
try {egressClient.updateLayout("egressId", "grid-light").execute();} catch (IOException e) {// handle exception}
livekit-cli update-layout --id EG_XXXXXXXXXXXX --layout grid-light
UpdateStream
Used to add or remove stream urls from an active stream
Note: you can only add outputs to an Egress that was started with stream_outputs
set.
Parameter | Type | Required | Description |
---|---|---|---|
egress_id | string | yes | Egress ID to update |
add_output_urls | string[] | URLs to add to the egress as output destinations | |
remove_output_urls | string[] | URLs to remove from the egress |
const output = {protocol: StreamProtocol.RTMP,urls: ['rtmp://live.twitch.tv/app/<stream-key>']};var info = await egressClient.startRoomCompositeEgress('my-room', 'speaker', output);const streamEgressID = info.egressId;info = await egressClient.updateStream(streamEgressID,['rtmp://a.rtmp.youtube.com/live2/stream-key']);
streamRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "speaker",Output: &livekit.RoomCompositeEgressRequest_Stream{Stream: &livekit.StreamOutput{Protocol: livekit.StreamProtocol_RTMP,Urls: []string{"rtmp://live.twitch.tv/app/<stream-key>"},},},}info, err := egressClient.StartRoomCompositeEgress(ctx, streamRequest)streamEgressID := info.EgressIdinfo, err = egressClient.UpdateStream(ctx, &livekit.UpdateStreamRequest{EgressId: streamEgressID,AddOutputUrls: []string{"rtmp://a.rtmp.youtube.com/live2/<stream-key>"}})
# to add streamsegressClient.update_stream('egress-id',add_output_urls: ['rtmp://new-url'],remove_output_urls: ['rtmp://old-url'])
try {egressClient.updateStream("egressId",Collections.singletonList("rtmp://new-url"),Collections.singletonList("rtmp://old-url")).execute();} catch (IOException e) {// handle exception}
livekit-cli update-stream \--id EG_XXXXXXXXXXXX \--add-urls "rtmp://a.rtmp.youtube.com/live2/stream-key"
ListEgress
Used to list active egress. Does not include completed egress.
const res = await egressClient.listEgress();
res, err := egressClient.ListEgress(ctx, &livekit.ListEgressRequest{})
# to list egress on myroomegressClient.list_egress(room_name: 'myroom')# to list all egressesegressClient.list_egress()
try {List<LivekitEgress.EgressInfo> egressInfos = egressClient.listEgress().execute().body();} catch (IOException e) {// handle exception}
livekit-cli list-egress
StopEgress
Stops an active egress.
const info = await egressClient.stopEgress(egressID);
info, err := egressClient.StopEgress(ctx, &livekit.StopEgressRequest{EgressId: egressID,})
egressClient.stop_egress('egress-id')
try {egressClient.stopEgress("egressId").execute();} catch (IOException e) {// handle exception}
livekit-cli stop-egress --id EG_XXXXXXXXXXXX
Types
EncodedFileOutput
Field | Type | Description |
---|---|---|
filepath | string | default {room_name}-{time} |
disable_manifest | bool | by default, Egress outputs a {filepath}.json with metadata of the file |
s3 | S3Upload | set if uploading to S3 compatible storage. only one storage output can be set |
gcp | GCPUpload | set if uploading to GCP |
azure | AzureBlobUpload | set if uploading to Azure |
aliOSS | AliOSSUpload | set if uploading to AliOSS |
DirectFileOutput
Field | Type | Description |
---|---|---|
filepath | string | default {track_id}-{time} |
disable_manifest | bool | by default, Egress outputs a {filepath}.json with metadata of the file |
s3 | S3Upload | set if uploading to S3 compatible storage. only one storage output can be set |
gcp | GCPUpload | set if uploading to GCP |
azure | AzureBlobUpload | set if uploading to Azure |
aliOSS | AliOSSUpload | set if uploading to AliOSS |
SegmentedFileOutput
Field | Type | Description |
---|---|---|
filename_prefix | string | prefix used in each segment (include any paths here) |
playlist_name | string | name of the m3u8 playlist. when empty, matches filename_prefix |
segment_duration | uint32 | length of each segment (defaults to 4s) |
filename_suffix | SegmentedFileSuffix | INDEX (1, 2, 3) or TIMESTAMP (in UTC) |
disable_manifest | bool | |
s3 | S3Upload | set if uploading to S3 compatible storage. only one storage output can be set |
gcp | GCPUpload | set if uploading to GCP |
azure | AzureBlobUpload | set if uploading to Azure |
aliOSS | AliOSSUpload | set if uploading to AliOSS |
StreamOutput
Field | Type | Description |
---|---|---|
protocol | SreamProtocol | (optional) only RTMP is supported |
urls | string[] | list of URLs to send stream to |
S3Upload
Field | Type | Description |
---|---|---|
access_key | string | |
secret | string | S3 secret key |
bucket | string | destination bucket |
region | string | region of the S3 bucket (optional) |
endpoint | string | URL to use for S3 (optional) |
force_path_style | bool | leave bucket in the path and never to sub-domain (optional) |
metadata | map<string, string> | metadata key/value pairs to store (optional) |
tagging | string | (optional) |
GCPUpload
Field | Type | Description |
---|---|---|
credentials | string | Contents of credentials.json |
bucket | string | destination bucket |
AzureBlobUpload
Field | Type | Description |
---|---|---|
account_name | string | |
account_key | string | |
container_name | string | destination container |
AliOSSUpload
Field | Type | Description |
---|---|---|
access_key | string | |
secret | string | |
bucket | string | |
region | string | |
endpoint | string |
EncodingOptions
Field | Type | Description |
---|---|---|
width | int32 | |
height | int32 | |
depth | int32 | default 24 |
framerate | int32 | default 30 |
audio_codec | AudioCodec | default AAC |
audio_bitrate | int32 | 128 |
audio_frequency | int32 | 44100 |
video_codec | VideoCodec | default H264_MAIN |
video_bitrate | int32 | default 4500 |
key_frame_interval | int32 | default 4s |
EncodingOptionsPreset
Enum, valid values:
- H264_720P_30: 0
- H264_720P_60: 1
- H264_1080P_30: 2
- H264_1080P_60: 3
- PORTRAIT_H264_720P_30: 4
- PORTRAIT_H264_720P_60: 5
- PORTRAIT_H264_1080P_30: 6
- PORTRAIT_H264_1080P_60: 7
Deployment
See the deployment docs for more info on deployment, config, and autoscaling.