Skip to main content

Egress examples

Usage examples for Egress APIs to record or livestream a room or individual tracks.

Recording room composite as HLS

This example records a room composite layout as HLS segments to an S3-compatible bucket.

Note

When live_playlist_name is provided, a playlist is generated containing only the last few segments. This can be useful to livestream the recording via HLS.

{
"room_name": "my-room",
"layout": "grid",
"preset": "H264_720P_30",
"custom_base_url": "https://my-custom-template.com",
"audio_only": false,
"segment_outputs": [
{
"filename_prefix": "path/to/my-output",
"playlist_name": "my-output.m3u8",
"live_playlist_name": "my-output-live.m3u8",
"segment_duration": 2,
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket",
"force_path_style": true
}
}
]
}
lk egress start --type room-composite egress.json
import { LiveKitAPI, SegmentedFileOutput, EncodingOptionsPreset } from 'livekit-server-sdk';
const api = new LiveKitAPI();
const outputs = {
segments: new SegmentedFileOutput({
filenamePrefix: 'my-output',
playlistName: 'my-output.m3u8',
livePlaylistName: 'my-output-live.m3u8',
segmentDuration: 2,
output: {
case: 's3',
value: {
accessKey: '',
secret: '',
bucket: '',
region: '',
forcePathStyle: true,
},
},
}),
};
await api.egress.startRoomCompositeEgress('my-room', outputs, {
layout: 'grid',
customBaseUrl: 'https://my-custom-template.com',
encodingOptions: EncodingOptionsPreset.H264_1080P_30,
audioOnly: false,
});
from livekit import api
req = api.RoomCompositeEgressRequest(
room_name="my-room",
layout="speaker",
custom_base_url="http://my-custom-template.com",
preset=api.EncodingOptionsPreset.H264_720P_30,
audio_only=False,
segment_outputs=[api.SegmentedFileOutput(
filename_prefix="my-output",
playlist_name="my-playlist.m3u8",
live_playlist_name="my-live-playlist.m3u8",
segment_duration=2,
s3=api.S3Upload(
bucket="my-bucket",
region="",
access_key="",
secret="",
force_path_style=True,
),
)],
)
async with api.LiveKitAPI() as lkapi:
res = await lkapi.egress.start_room_composite_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
outputs = [
LiveKit::Proto::SegmentedFileOutput.new(
filename_prefix: "my-output",
playlist_name: "my-output.m3u8",
live_playlist_name: "my-output-live.m3u8",
segment_duration: 2,
s3: LiveKit::Proto::S3Upload.new(
access_key: "",
secret: "",
endpoint: "",
region: "",
bucket: "my-bucket",
force_path_style: true,
)
)
]
lkapi.egress.start_room_composite_egress(
'my-room',
outputs,
layout: 'speaker',
custom_base_url: 'https://my-custom-template.com',
preset: LiveKit::Proto::EncodingOptionsPreset::H264_1080P_30,
audio_only: false
)
import lksdk "github.com/livekit/server-sdk-go/v2"
req := &livekit.RoomCompositeEgressRequest{
RoomName: "my-room-to-record",
Layout: "speaker",
AudioOnly: false,
CustomBaseUrl: "https://my-custom-template.com",
Options: &livekit.RoomCompositeEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_30,
},
}
req.SegmentOutputs = []*livekit.SegmentedFileOutput{
{
FilenamePrefix: "my-output",
PlaylistName: "my-output.m3u8",
LivePlaylistName: "my-output-live.m3u8",
SegmentDuration: 2,
Output: &livekit.SegmentedFileOutput_S3{
S3: &livekit.S3Upload{
AccessKey: "",
Secret: "",
Endpoint: "",
Bucket: "",
ForcePathStyle: true,
},
},
},
}
api, err := lksdk.NewLiveKitAPI()
res, err := api.Egress().StartRoomCompositeEgress(context.Background(), req)
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
val segmentOutput = LivekitEgress.SegmentedFileOutput.newBuilder()
.setFilenamePrefix("my-segmented-file")
.setPlaylistName("my-playlist.m3u8")
.setLivePlaylistName("my-live-playlist.m3u8")
.setSegmentDuration(2)
.setS3(
LivekitEgress.S3Upload.newBuilder()
.setBucket("")
.setAccessKey("")
.setSecret("")
.setForcePathStyle(true)
)
.build()
val info = api.egress.startRoomCompositeEgress(
"my-room",
segmentOutput,
layout = "speaker",
optionsPreset = LivekitEgress.EncodingOptionsPreset.H264_720P_30,
customBaseUrl = "https://my-templates.com",
).execute().body()
use livekit_api::services::egress::{EgressOutput, RoomCompositeOptions};
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
use livekit_protocol::segmented_file_output::Output;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let segments = proto::SegmentedFileOutput {
filename_prefix: "my-output".to_string(),
playlist_name: "my-output.m3u8".to_string(),
live_playlist_name: "my-output-live.m3u8".to_string(),
segment_duration: 2,
output: Some(Output::S3(proto::S3Upload {
access_key: "".to_string(),
secret: "".to_string(),
bucket: "my-bucket".to_string(),
force_path_style: true,
..Default::default()
})),
..Default::default()
};
let res = api
.egress()
.start_room_composite_egress(
"my-room",
vec![EgressOutput::Segments(segments)],
RoomCompositeOptions {
layout: "speaker".to_string(),
custom_base_url: "https://my-custom-template.com".to_string(),
..Default::default()
},
)
.await?;

Recording web in portrait

This example records a web page in portrait mode to Google Cloud Storage, streaming to RTMP.

Portrait orientation can be specified by either using a preset option or setting advanced options. Egress automatically resizes the Chrome compositor to your specified resolution. However, keep in mind the following requirements:

  • Chrome has a minimum browser width of 500px.
  • Your application must maintain a portrait layout, even when the browser reports a width larger than typical mobile phones. (for example, 720px width or larger).
{
"url": "https://my-page.com",
"preset": "PORTRAIT_H264_720P_30",
"audio_only": false,
"file_outputs": [
{
"filepath": "my-test-file.mp4",
"gcp": {
"credentials": "{\"type\": \"service_account\", ...}",
"bucket": "my-bucket"
}
}
],
"stream_outputs": [
{
"protocol": "RTMP",
"urls": ["rtmps://my-rtmp-server.com/live/stream-key"]
}
]
}
lk egress start --type web egress.json
import * as fs from 'fs';
import { LiveKitAPI, EncodedFileOutput, GCPUpload, StreamOutput, StreamProtocol, EncodingOptionsPreset } from 'livekit-server-sdk';
const api = new LiveKitAPI();
const content = fs.readFileSync('/path/to/credentials.json');
const outputs = {
file: new EncodedFileOutput({
filepath: 'my-recording.mp4',
output: {
case: 'gcp',
value: new GCPUpload({
// credentials need to be a JSON encoded string containing credentials
credentials: content.toString(),
bucket: 'my-bucket',
}),
},
}),
stream: new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: ['rtmp://example.com/live/stream-key'],
}),
};
await api.egress.startWebEgress('https://my-site.com', outputs, {
encodingOptions: EncodingOptionsPreset.PORTRAIT_H264_1080P_30,
audioOnly: false,
});
from livekit import api
content = ""
with open("/path/to/credentials.json", "r") as f:
content = f.read()
file_output = api.EncodedFileOutput(
filepath="myfile.mp4",
gcp=api.GCPUpload(
credentials=content,
bucket="my-bucket",
),
)
req = api.WebEgressRequest(
url="https://my-site.com",
preset=api.EncodingOptionsPreset.PORTRAIT_H264_1080P_30,
audio_only=False,
file_outputs=[file_output],
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.RTMP,
urls=["rtmp://myserver.com/live/stream-key"],
)],
)
async with api.LiveKitAPI() as lkapi:
res = await lkapi.egress.start_web_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
content = File.read("/path/to/credentials.json")
outputs = [
LiveKit::Proto::EncodedFileOutput.new(
filepath: "myfile.mp4",
gcp: LiveKit::Proto::GCPUpload.new(
credentials: content,
bucket: "my-bucket"
)
),
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::RTMP,
urls: ["rtmp://myserver.com/live/stream-key"]
)
]
lkapi.egress.start_web_egress(
'https://my-website.com',
outputs,
preset: LiveKit::Proto::EncodingOptionsPreset::PORTRAIT_H264_1080P_30,
audio_only: false
)
import lksdk "github.com/livekit/server-sdk-go/v2"
credentialsJson, err := os.ReadFile("/path/to/credentials.json")
if err != nil {
panic(err.Error())
}
req := &livekit.WebEgressRequest{
Url: "https://my-website.com",
AudioOnly: false,
Options: &livekit.WebEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_30,
},
}
req.FileOutputs = []*livekit.EncodedFileOutput{
{
Filepath: "myfile.mp4",
Output: &livekit.EncodedFileOutput_Gcp{
Gcp: &livekit.GCPUpload{
Credentials: string(credentialsJson),
Bucket: "my-bucket",
},
},
},
}
req.StreamOutputs = []*livekit.StreamOutput{
{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{"rtmp://myserver.com/live/stream-key"},
},
}
api, err := lksdk.NewLiveKitAPI()
res, err := api.Egress().StartWebEgress(context.Background(), req)
import io.livekit.server.EncodedOutputs
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
// We recommend using Google's auth library (google-auth-library-oauth2-http) to load the credentials file.
val credentials = GoogleCredentials.fromStream(FileInputStream("/path/to/credentials.json"))
val fileOutput = LivekitEgress.EncodedFileOutput.newBuilder()
.setFilepath("myfile.mp4")
.setGcp(
LivekitEgress.GCPUpload.newBuilder()
.setBucket("my-bucket")
.setCredentials(credentials.toString())
)
.build()
val streamOutput = LivekitEgress.StreamOutput.newBuilder()
.setProtocol(LivekitEgress.StreamProtocol.RTMP)
.addUrls("rtmps://myserver.com/live/stream-key")
.build()
val outputs = EncodedOutputs(fileOutput, streamOutput, null, null)
val info = api.egress.startWebEgress(
"https://my-site.com",
outputs,
optionsPreset = LivekitEgress.EncodingOptionsPreset.PORTRAIT_H264_720P_30,
audioOnly = false,
videoOnly = false,
awaitStartSignal = true,
).execute().body()
use livekit_api::services::egress::{EgressOutput, WebOptions};
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
use livekit_protocol::{encoded_file_output, stream_output};
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let credentials = std::fs::read_to_string("/path/to/credentials.json")?;
let file = proto::EncodedFileOutput {
filepath: "myfile.mp4".to_string(),
output: Some(encoded_file_output::Output::Gcp(proto::GcpUpload {
credentials,
bucket: "my-bucket".to_string(),
..Default::default()
})),
..Default::default()
};
let stream = proto::StreamOutput {
protocol: proto::StreamProtocol::Rtmp as i32,
urls: vec!["rtmp://myserver.com/live/stream-key".to_string()],
};
let res = api
.egress()
.start_web_egress(
"https://my-site.com",
vec![EgressOutput::File(file), EgressOutput::Stream(stream)],
WebOptions::default(),
)
.await?;

SRT streaming with thumbnails

This example streams an individual participant to an SRT server, generating thumbnails every 5 seconds. Thumbnails are stored in Azure Blob Storage.

{
"room_name": "my-room",
"identity": "participant-to-record",
"screen_share": false,
"advanced": {
"width": 1280,
"height": 720,
"framerate": 30,
"audioCodec": "AAC",
"audioBitrate": 128,
"videoCodec": "H264_HIGH",
"videoBitrate": 5000,
"keyFrameInterval": 2
},
"stream_outputs": [
{
"protocol": "SRT",
"urls": ["srt://my-srt-server.com:9999"]
}
],
"image_outputs": [
{
"capture_interval": 5,
"width": 1280,
"height": 720,
"filename_prefix": "{room_name}/{publisher_identity}",
"filename_suffix": "IMAGE_SUFFIX_TIMESTAMP",
"disable_manifest": true,
"azure": {
"account_name": "my-account",
"account_key": "my-key",
"container_name": "my-container"
}
}
]
}
lk egress start --type participant egress.json
import { LiveKitAPI, StreamOutput, StreamProtocol, ImageOutput, ImageFileSuffix, AudioCodec, VideoCodec, EncodingOptions } from 'livekit-server-sdk';
const api = new LiveKitAPI();
const outputs = {
stream: new StreamOutput({
protocol: StreamProtocol.SRT,
urls: ['srt://my-srt-server.com:9999'],
}),
images: new ImageOutput({
captureInterval: 5,
width: 1280,
height: 720,
filenamePrefix: '{room_name}/{publisher_identity}',
filenameSuffix: ImageFileSuffix.IMAGE_SUFFIX_TIMESTAMP,
output: {
case: 'azure',
value: {
accountName: 'azure-account-name',
accountKey: 'azure-account-key',
containerName: 'azure-container',
},
},
}),
};
const info = await api.egress.startParticipantEgress('my-room', 'participant-to-record', outputs, {
screenShare: false,
encodingOptions: new EncodingOptions({
width: 1280,
height: 720,
framerate: 30,
audioCodec: AudioCodec.AAC,
audioBitrate: 128,
videoCodec: VideoCodec.H264_HIGH,
videoBitrate: 5000,
keyFrameInterval: 2,
}),
});
from livekit import api
request = api.ParticipantEgressRequest(
room_name="my-room",
identity="publisher-to-record",
screen_share=False,
advanced=api.EncodingOptions(
width=1280,
height=720,
framerate=30,
audio_codec=api.AudioCodec.AAC,
audio_bitrate=128,
video_codec=api.VideoCodec.H264_HIGH,
video_bitrate=5000,
key_frame_interval=2,
),
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.SRT,
urls=["srt://my-srt-server:9999"],
)],
image_outputs=[api.ImageOutput(
capture_interval=5,
width=1280,
height=720,
filename_prefix="{room_name}/{publisher_identity}",
filename_suffix=api.ImageFileSuffix.IMAGE_SUFFIX_TIMESTAMP,
azure=api.AzureBlobUpload(
account_name="my-azure-account",
account_key="my-azure-key",
container_name="my-azure-container",
),
)],
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_participant_egress(request)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
outputs = [
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::SRT,
urls: ["srt://my-srt-server:9999"],
),
LiveKit::Proto::ImageOutput.new(
capture_interval: 5,
width: 1280,
height: 720,
filename_prefix: "{room_name}/{publisher_identity}",
filename_suffix: LiveKit::Proto::ImageFileSuffix::IMAGE_SUFFIX_TIMESTAMP,
azure: LiveKit::Proto::AzureBlobUpload.new(
account_name: "account-name",
account_key: "account-key",
container_name: "container-name",
)
)
]
info = lkapi.egress.start_participant_egress(
'room-name',
'publisher-identity',
outputs,
screen_share: false,
advanced: LiveKit::Proto::EncodingOptions.new(
width: 1280,
height: 720,
framerate: 30,
audio_codec: LiveKit::Proto::AudioCodec::AAC,
audio_bitrate: 128,
video_codec: LiveKit::Proto::VideoCodec::H264_HIGH,
video_bitrate: 5000,
key_frame_interval: 2,
)
)
import lksdk "github.com/livekit/server-sdk-go/v2"
req := &livekit.ParticipantEgressRequest{
RoomName: "my-room",
Identity: "participant-to-record",
ScreenShare: false,
Options: &livekit.ParticipantEgressRequest_Advanced{
Advanced: &livekit.EncodingOptions{
Width: 1280,
Height: 720,
Framerate: 30,
AudioCodec: livekit.AudioCodec_AAC,
AudioBitrate: 128,
VideoCodec: livekit.VideoCodec_H264_HIGH,
VideoBitrate: 5000,
KeyFrameInterval: 2,
},
},
StreamOutputs: []*livekit.StreamOutput{{
Protocol: livekit.StreamProtocol_SRT,
Urls: []string{"srt://my-srt-host:9999"},
}},
ImageOutputs: []*livekit.ImageOutput{{
CaptureInterval: 5,
Width: 1280,
Height: 720,
FilenamePrefix: "{room_name}/{publisher_identity}",
FilenameSuffix: livekit.ImageFileSuffix_IMAGE_SUFFIX_TIMESTAMP,
DisableManifest: true,
Output: &livekit.ImageOutput_Azure{
Azure: &livekit.AzureBlobUpload{
AccountName: "my-account-name",
AccountKey: "my-account-key",
ContainerName: "my-container",
},
},
}},
}
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartParticipantEgress(context.Background(), req)
import io.livekit.server.EncodedOutputs
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
import livekit.LivekitModels
val api = LiveKitAPI.createClient(host, apiKey, secret)
val streamOutput = LivekitEgress.StreamOutput.newBuilder()
.setProtocol(LivekitEgress.StreamProtocol.SRT)
.addUrls("srt://my-srt-server:9999")
.build()
val imageOutput = LivekitEgress.ImageOutput.newBuilder()
.setCaptureInterval(5)
.setWidth(1280)
.setHeight(720)
.setFilenamePrefix("{room_name}/{publisher_identity}")
.setFilenameSuffix(LivekitEgress.ImageFileSuffix.IMAGE_SUFFIX_TIMESTAMP)
.setAzure(
LivekitEgress.AzureBlobUpload.newBuilder()
.setAccountName("")
.setAccountKey("")
.setContainerName("")
)
.build()
val outputs = EncodedOutputs(null, streamOutput, null, imageOutput)
val encodingOptions = LivekitEgress.EncodingOptions.newBuilder()
.setWidth(1280)
.setHeight(720)
.setFramerate(30)
.setAudioCodec(LivekitModels.AudioCodec.AAC)
.setAudioBitrate(128)
.setVideoCodec(LivekitModels.VideoCodec.H264_HIGH)
.setVideoBitrate(5000)
.setKeyFrameInterval(2.0)
.build()
val info = api.egress.startParticipantEgress(
"my-room",
"publisher-to-record",
outputs,
screenShare = false,
optionsAdvanced = encodingOptions,
).execute().body()
use livekit_api::services::egress::encoding::EncodingOptions;
use livekit_api::services::egress::{EgressOutput, ParticipantEgressOptions};
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
use livekit_protocol::image_output;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let stream = proto::StreamOutput {
protocol: proto::StreamProtocol::Srt as i32,
urls: vec!["srt://my-srt-server:9999".to_string()],
};
let images = proto::ImageOutput {
capture_interval: 5,
width: 1280,
height: 720,
filename_prefix: "{room_name}/{publisher_identity}".to_string(),
filename_suffix: proto::ImageFileSuffix::ImageSuffixTimestamp as i32,
disable_manifest: true,
output: Some(image_output::Output::Azure(proto::AzureBlobUpload {
account_name: "my-account-name".to_string(),
account_key: "my-account-key".to_string(),
container_name: "my-container".to_string(),
})),
..Default::default()
};
let info = api
.egress()
.start_participant_egress(
"my-room",
"participant-to-record",
vec![EgressOutput::Stream(stream), EgressOutput::Image(images)],
ParticipantEgressOptions {
screenshare: false,
encoding: EncodingOptions {
width: 1280,
height: 720,
framerate: 30,
audio_codec: proto::AudioCodec::Aac,
audio_bitrate: 128,
video_codec: proto::VideoCodec::H264High,
video_bitrate: 5000,
keyframe_interval: 2.0,
..EncodingOptions::default()
},
},
)
.await?;

Adding RTMP to track composite egress

Create a TrackComposite Egress recorded as HLS segments, with RTMP output added later.

{
"room_name": "my-room",
"audio_track_id": "TR_AUDIO_ID",
"video_track_id": "TR_VIDEO_ID",
"stream_outputs": [
{
"protocol": "RTMP",
"urls": []
}
],
"segment_outputs": [
{
"filename_prefix": "path/to/my-output",
"playlist_name": "my-output.m3u8",
"segment_duration": 2,
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket"
}
}
]
}
lk egress start --type track-composite egress.json
# later, to add a RTMP output
lk egress update-stream --id <egress-id> --add-urls rtmp://new-server.com/live/stream-key
# to remove RTMP output
lk egress update-stream --id <egress-id> --remove-urls rtmp://new-server.com/live/stream-key
import { LiveKitAPI, StreamOutput, StreamProtocol, SegmentedFileOutput, EncodingOptionsPreset } from 'livekit-server-sdk';
const api = new LiveKitAPI();
const outputs = {
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
stream: new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: [],
}),
segments: new SegmentedFileOutput({
filenamePrefix: 'my-output',
playlistName: 'my-output.m3u8',
segmentDuration: 2,
output: {
case: 's3',
value: {
accessKey: '',
secret: '',
bucket: '',
region: '',
forcePathStyle: true,
},
},
}),
};
const info = await api.egress.startTrackCompositeEgress('my-room', outputs, {
videoTrackId: 'TR_VIDEO_TRACK_ID',
audioTrackId: 'TR_AUDIO_TRACK_ID',
encodingOptions: EncodingOptionsPreset.H264_720P_30,
});
// later, to add RTMP output
await api.egress.updateStream(info.egressId, ['rtmp://new-server.com/live/stream-key']);
// to remove RTMP output
await api.egress.updateStream(info.egressId, [], ['rtmp://new-server.com/live/stream-key']);
from livekit import api
request = api.TrackCompositeEgressRequest(
room_name="my-room",
audio_track_id="TR_AUDIO_TRACK_ID",
video_track_id="TR_VIDEO_TRACK_ID",
preset=api.EncodingOptionsPreset.H264_720P_30,
# a placeholder RTMP output is needed to ensure stream urls can be added to it later
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.RTMP,
urls=[],
)],
segment_outputs=[api.SegmentedFileOutput(
filename_prefix="my-output",
playlist_name="my-playlist.m3u8",
live_playlist_name="my-live-playlist.m3u8",
segment_duration=2,
s3=api.S3Upload(
bucket="my-bucket",
region="",
access_key="",
secret="",
force_path_style=True,
),
)],
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_track_composite_egress(request)
# add new output URL to the stream
await lkapi.egress.update_stream(api.UpdateStreamRequest(
egress_id=info.egress_id,
add_output_urls=["rtmp://new-server.com/live/stream-key"],
))
# remove an output URL from the stream
await lkapi.egress.update_stream(api.UpdateStreamRequest(
egress_id=info.egress_id,
remove_output_urls=["rtmp://new-server.com/live/stream-key"],
))
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
outputs = [
# a placeholder RTMP output is needed to ensure stream urls can be added to it later
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::RTMP,
urls: [],
),
LiveKit::Proto::SegmentedFileOutput.new(
filename_prefix: "my-output",
playlist_name: "my-output.m3u8",
segment_duration: 2,
s3: LiveKit::Proto::S3Upload.new(
access_key: "",
secret: "",
endpoint: "",
region: "",
bucket: "my-bucket",
force_path_style: true,
)
)
]
info = lkapi.egress.start_track_composite_egress(
'room-name',
outputs,
audio_track_id: 'TR_AUDIO_TRACK_ID',
video_track_id: 'TR_VIDEO_TRACK_ID',
preset: LiveKit::Proto::EncodingOptionsPreset::H264_1080P_30,
)
# add new output URL to the stream
lkapi.egress.update_stream(info.egress_id, add_output_urls: ["rtmp://new-server.com/live/stream-key"])
# remove an output URL from the stream
lkapi.egress.update_stream(info.egress_id, remove_output_urls: ["rtmp://new-server.com/live/stream-key"])
import lksdk "github.com/livekit/server-sdk-go/v2"
req := &livekit.TrackCompositeEgressRequest{
RoomName: "my-room",
VideoTrackId: "TR_VIDEO_TRACK_ID",
AudioTrackId: "TR_AUDIO_TRACK_ID",
Options: &livekit.TrackCompositeEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_H264_720P_30,
},
SegmentOutputs: []*livekit.SegmentedFileOutput{{
FilenamePrefix: "my-output",
PlaylistName: "my-output.m3u8",
SegmentDuration: 2,
Output: &livekit.SegmentedFileOutput_S3{
S3: &livekit.S3Upload{
AccessKey: "",
Secret: "",
Endpoint: "",
Bucket: "",
ForcePathStyle: true,
},
},
}},
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
StreamOutputs: []*livekit.StreamOutput{{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{},
}},
}
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartTrackCompositeEgress(context.Background(), req)
// add new output URL to the stream
api.Egress().UpdateStream(context.Background(), &livekit.UpdateStreamRequest{
EgressId: info.EgressId,
AddOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},
})
// remove an output URL from the stream
api.Egress().UpdateStream(context.Background(), &livekit.UpdateStreamRequest{
EgressId: info.EgressId,
RemoveOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},
})
import io.livekit.server.EncodedOutputs
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
val streamOutput = LivekitEgress.StreamOutput.newBuilder()
.setProtocol(LivekitEgress.StreamProtocol.RTMP)
.build()
val segmentOutput = LivekitEgress.SegmentedFileOutput.newBuilder()
.setFilenamePrefix("my-hls-file")
.setPlaylistName("my-playlist.m3u8")
.setLivePlaylistName("my-live-playlist.m3u8")
.setSegmentDuration(2)
.setS3(
LivekitEgress.S3Upload.newBuilder()
.setBucket("")
.setAccessKey("")
.setSecret("")
.setForcePathStyle(true)
)
.build()
val outputs = EncodedOutputs(null, streamOutput, segmentOutput, null)
var info = api.egress.startTrackCompositeEgress(
"my-room",
outputs,
"TR_AUDIO_TRACK_ID",
"TR_VIDEO_TRACK_ID",
optionsPreset = LivekitEgress.EncodingOptionsPreset.H264_1080P_30,
).execute().body()
// add new output URL to the stream
info = api.egress.updateStream(
info!!.egressId,
listOf("rtmp://new-server.com/live/stream-key"),
).execute().body()
// remove an output URL from the stream
info = api.egress.updateStream(
info!!.egressId,
emptyList(),
listOf("rtmp://new-server.com/live/stream-key"),
).execute().body()
use livekit_api::services::egress::{EgressOutput, TrackCompositeOptions};
use livekit_api::services::egress::encoding::H264_720P_30;
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
use livekit_protocol::segmented_file_output::Output;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
let stream = proto::StreamOutput {
protocol: proto::StreamProtocol::Rtmp as i32,
urls: vec![],
};
let segments = proto::SegmentedFileOutput {
filename_prefix: "my-output".to_string(),
playlist_name: "my-output.m3u8".to_string(),
segment_duration: 2,
output: Some(Output::S3(proto::S3Upload {
access_key: "".to_string(),
secret: "".to_string(),
bucket: "my-bucket".to_string(),
force_path_style: true,
..Default::default()
})),
..Default::default()
};
let info = api
.egress()
.start_track_composite_egress(
"my-room",
vec![EgressOutput::Stream(stream), EgressOutput::Segments(segments)],
TrackCompositeOptions {
audio_track_id: "TR_AUDIO_TRACK_ID".to_string(),
video_track_id: "TR_VIDEO_TRACK_ID".to_string(),
encoding: H264_720P_30,
},
)
.await?;
// add new output URL to the stream
api.egress()
.update_stream(&info.egress_id, vec!["rtmp://new-server.com/live/stream-key".to_string()], vec![])
.await?;
// remove an output URL from the stream
api.egress()
.update_stream(&info.egress_id, vec![], vec!["rtmp://new-server.com/live/stream-key".to_string()])
.await?;

Exporting individual tracks without transcoding

Export video tracks to Azure Blob Storage without transcoding.

Separate video and audio tracks

Video and audio tracks must be exported separately using Track Egress.

{
"room_name": "my-room",
"track_id": "TR_TRACK_ID",
"filepath": "{room_name}/{track_id}",
"azure": {
"account_name": "my-account",
"account_key": "my-key",
"container_name": "my-container"
}
}
lk egress start --type track egress.json
import { LiveKitAPI, DirectFileOutput } from 'livekit-server-sdk';
const api = new LiveKitAPI();
const output = new DirectFileOutput({
filepath: '{room_name}/{track_id}',
output: {
case: 'azure',
value: {
accountName: 'account-name',
accountKey: 'account-key',
containerName: 'container-name',
},
},
});
const info = await api.egress.startTrackEgress('my-room', output, 'TR_TRACK_ID');
from livekit import api
request = api.TrackEgressRequest(
room_name="my-room",
track_id="TR_TRACK_ID",
file=api.DirectFileOutput(
filepath="{room_name}/{track_id}",
azure=api.AzureBlobUpload(
account_name="ACCOUNT_NAME",
account_key="ACCOUNT_KEY",
container_name="CONTAINER_NAME",
),
),
)
async with api.LiveKitAPI() as lkapi:
egress_info = await lkapi.egress.start_track_egress(request)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
output = LiveKit::Proto::DirectFileOutput.new(
filepath: "{room_name}/{track_id}",
azure: LiveKit::Proto::AzureBlobUpload.new(
account_name: "account",
account_key: "account-key",
container_name: "container"
)
)
lkapi.egress.start_track_egress("my-room", output, "TR_TRACK_ID")
import lksdk "github.com/livekit/server-sdk-go/v2"
req := &livekit.TrackEgressRequest{
RoomName: "my-room",
TrackId: "TR_TRACK_ID",
Output: &livekit.TrackEgressRequest_File{
File: &livekit.DirectFileOutput{
Filepath: "{room_name}/{track_id}",
Output: &livekit.DirectFileOutput_Azure{
Azure: &livekit.AzureBlobUpload{
AccountName: "",
AccountKey: "",
ContainerName: "",
},
},
},
},
}
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartTrackEgress(context.Background(), req)
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
val fileOutput = LivekitEgress.DirectFileOutput.newBuilder()
.setFilepath("{room_name}/{track_id}")
.setAzure(
LivekitEgress.AzureBlobUpload.newBuilder()
.setAccountName("")
.setAccountKey("")
.setContainerName("")
)
.build()
val info = api.egress.startTrackEgress("my-room", fileOutput, "TR_TRACK_ID").execute().body()
use livekit_api::services::egress::TrackEgressOutput;
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
use livekit_protocol::direct_file_output::Output;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let output = proto::DirectFileOutput {
filepath: "{room_name}/{track_id}".to_string(),
output: Some(Output::Azure(proto::AzureBlobUpload {
account_name: "account".to_string(),
account_key: "account-key".to_string(),
container_name: "container".to_string(),
})),
..Default::default()
};
let info = api
.egress()
.start_track_egress("my-room", TrackEgressOutput::File(Box::new(output)), "TR_TRACK_ID")
.await?;

Stop an active egress

To stop an active egress, see the API reference for StopEgress for examples.