Skip to main content

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?;

Recording a room with StartEgress

This example uses StartEgress with a TemplateSource to record a room layout to an MP4 file. Storage is set once on the request, so every output uses it.

{
"room_name": "my-room",
"template": { "layout": "grid" },
"outputs": [{ "file": { "file_type": "MP4", "filepath": "my-room.mp4" } }],
"storage": {
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket"
}
}
}
lk egress start egress.json
import {
EncodedFileType,
FileOutput,
LiveKitAPI,
Output,
StartEgressRequest,
StorageConfig,
TemplateSource,
} from 'livekit-server-sdk';
const api = new LiveKitAPI();
const info = await api.egress.startEgress(
new StartEgressRequest({
roomName: 'my-room',
source: { case: 'template', value: new TemplateSource({ layout: 'grid' }) },
outputs: [
new Output({
config: {
case: 'file',
value: new FileOutput({ fileType: EncodedFileType.MP4, filepath: 'my-room.mp4' }),
},
}),
],
storage: {
provider: {
case: 's3',
value: { accessKey: '', secret: '', bucket: 'my-bucket', region: '' },
},
},
}),
);
from livekit import api
req = api.StartEgressRequest(
room_name="my-room",
template=api.TemplateSource(layout="grid"),
outputs=[
api.Output(
file=api.FileOutput(
file_type=api.EncodedFileType.MP4,
filepath="my-room.mp4",
)
)
],
storage=api.StorageConfig(
s3=api.S3Upload(bucket="my-bucket", region="", access_key="", secret="")
),
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
lkapi.egress.start_egress(
room_name: 'my-room',
template: LiveKit::Proto::TemplateSource.new(layout: 'grid'),
outputs: LiveKit::Proto::Output.new(
file: LiveKit::Proto::FileOutput.new(
file_type: LiveKit::Proto::EncodedFileType::MP4,
filepath: 'my-room.mp4'
)
),
storage: LiveKit::Proto::StorageConfig.new(
s3: LiveKit::Proto::S3Upload.new(
bucket: 'my-bucket',
region: '',
access_key: '',
secret: ''
)
)
)
import lksdk "github.com/livekit/server-sdk-go/v2"
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartEgress(ctx, &livekit.StartEgressRequest{
RoomName: "my-room",
Source: &livekit.StartEgressRequest_Template{
Template: &livekit.TemplateSource{Layout: "grid"},
},
Outputs: []*livekit.Output{{
Config: &livekit.Output_File{
File: &livekit.FileOutput{
FileType: livekit.EncodedFileType_MP4,
Filepath: "my-room.mp4",
},
},
}},
Storage: &livekit.StorageConfig{
Provider: &livekit.StorageConfig_S3{
S3: &livekit.S3Upload{Bucket: "my-bucket"},
},
},
})
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
val output = LivekitEgress.Output.newBuilder()
.setFile(
LivekitEgress.FileOutput.newBuilder()
.setFileType(LivekitEgress.EncodedFileType.MP4)
.setFilepath("my-room.mp4")
)
.build()
val info = api.egress.startEgress(
roomName = "my-room",
template = LivekitEgress.TemplateSource.newBuilder().setLayout("grid").build(),
outputs = listOf(output),
storage = LivekitEgress.StorageConfig.newBuilder()
.setS3(LivekitEgress.S3Upload.newBuilder().setBucket("my-bucket"))
.build(),
).execute().body()
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let info = api
.egress()
.start_egress(proto::StartEgressRequest {
room_name: "my-room".to_string(),
source: Some(proto::start_egress_request::Source::Template(proto::TemplateSource {
layout: "grid".to_string(),
..Default::default()
})),
outputs: vec![proto::Output {
config: Some(proto::output::Config::File(proto::FileOutput {
file_type: proto::EncodedFileType::Mp4 as i32,
filepath: "my-room.mp4".to_string(),
..Default::default()
})),
..Default::default()
}],
storage: Some(proto::StorageConfig {
provider: Some(proto::storage_config::Provider::S3(proto::S3Upload {
bucket: "my-bucket".to_string(),
..Default::default()
})),
}),
..Default::default()
})
.await?;

Streaming a web page to RTMP

This example uses a WebSource to record an arbitrary URL and stream it to an RTMP destination. A stream output needs no storage, so the request sets none.

{
"web": {
"url": "https://my-page.com/scene",
"await_start_signal": true
},
"outputs": [
{
"stream": {
"protocol": "RTMP",
"urls": ["rtmps://live.example.com/live/stream-key"]
}
}
]
}
lk egress start egress.json
import {
LiveKitAPI,
Output,
StartEgressRequest,
StreamOutput,
StreamProtocol,
WebSource,
} from 'livekit-server-sdk';
const api = new LiveKitAPI();
const info = await api.egress.startEgress(
new StartEgressRequest({
source: {
case: 'web',
value: new WebSource({ url: 'https://my-page.com/scene', awaitStartSignal: true }),
},
outputs: [
new Output({
config: {
case: 'stream',
value: new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: ['rtmps://live.example.com/live/stream-key'],
}),
},
}),
],
}),
);
from livekit import api
req = api.StartEgressRequest(
web=api.WebSource(url="https://my-page.com/scene", await_start_signal=True),
outputs=[
api.Output(
stream=api.StreamOutput(
protocol=api.StreamProtocol.RTMP,
urls=["rtmps://live.example.com/live/stream-key"],
)
)
],
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
lkapi.egress.start_egress(
web: LiveKit::Proto::WebSource.new(
url: 'https://my-page.com/scene',
await_start_signal: true
),
outputs: LiveKit::Proto::Output.new(
stream: LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::RTMP,
urls: ['rtmps://live.example.com/live/stream-key']
)
)
)
import lksdk "github.com/livekit/server-sdk-go/v2"
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartEgress(ctx, &livekit.StartEgressRequest{
Source: &livekit.StartEgressRequest_Web{
Web: &livekit.WebSource{
Url: "https://my-page.com/scene",
AwaitStartSignal: true,
},
},
Outputs: []*livekit.Output{{
Config: &livekit.Output_Stream{
Stream: &livekit.StreamOutput{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{"rtmps://live.example.com/live/stream-key"},
},
},
}},
})
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
val output = LivekitEgress.Output.newBuilder()
.setStream(
LivekitEgress.StreamOutput.newBuilder()
.setProtocol(LivekitEgress.StreamProtocol.RTMP)
.addUrls("rtmps://live.example.com/live/stream-key")
)
.build()
val info = api.egress.startEgress(
roomName = "",
web = LivekitEgress.WebSource.newBuilder()
.setUrl("https://my-page.com/scene")
.setAwaitStartSignal(true)
.build(),
outputs = listOf(output),
).execute().body()
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let info = api
.egress()
.start_egress(proto::StartEgressRequest {
source: Some(proto::start_egress_request::Source::Web(proto::WebSource {
url: "https://my-page.com/scene".to_string(),
await_start_signal: true,
..Default::default()
})),
outputs: vec![proto::Output {
config: Some(proto::output::Config::Stream(proto::StreamOutput {
protocol: proto::StreamProtocol::Rtmp as i32,
urls: vec!["rtmps://live.example.com/live/stream-key".to_string()],
})),
..Default::default()
}],
..Default::default()
})
.await?;

Recording an agent and a caller on separate channels

This example uses a MediaSource with AudioConfig routes to record an audio-only conversation, putting the agent in the left channel and everyone else in the right. Because the request selects no video, the recording is audio only.

This replaces the DUAL_CHANNEL_AGENT mode of the deprecated audio_mixing field. Routes are matched in order and the first match wins, so a track from an agent lands left and every other participant's audio lands right.

{
"room_name": "my-room",
"media": {
"audio": {
"routes": [
{ "participant_kind": "AGENT", "channel": "AUDIO_CHANNEL_LEFT" },
{ "participant_kind": "STANDARD", "channel": "AUDIO_CHANNEL_RIGHT" }
]
}
},
"outputs": [{ "file": { "file_type": "OGG", "filepath": "conversation.ogg" } }],
"storage": { "s3": { "bucket": "my-bucket" } }
}
lk egress start egress.json
import {
AudioChannel,
AudioConfig,
AudioRoute,
EncodedFileType,
FileOutput,
LiveKitAPI,
MediaSource,
Output,
ParticipantInfo_Kind,
StartEgressRequest,
} from 'livekit-server-sdk';
const api = new LiveKitAPI();
const info = await api.egress.startEgress(
new StartEgressRequest({
roomName: 'my-room',
source: {
case: 'media',
value: new MediaSource({
audio: new AudioConfig({
routes: [
new AudioRoute({
match: { case: 'participantKind', value: ParticipantInfo_Kind.AGENT },
channel: AudioChannel.LEFT,
}),
new AudioRoute({
match: { case: 'participantKind', value: ParticipantInfo_Kind.STANDARD },
channel: AudioChannel.RIGHT,
}),
],
}),
}),
},
outputs: [
new Output({
config: {
case: 'file',
value: new FileOutput({ fileType: EncodedFileType.OGG, filepath: 'conversation.ogg' }),
},
}),
],
storage: {
provider: {
case: 's3',
value: { accessKey: '', secret: '', bucket: 'my-bucket', region: '' },
},
},
}),
);
from livekit import api
req = api.StartEgressRequest(
room_name="my-room",
media=api.MediaSource(
audio=api.AudioConfig(
routes=[
api.AudioRoute(
participant_kind=api.ParticipantInfo.Kind.AGENT,
channel=api.AudioChannel.AUDIO_CHANNEL_LEFT,
),
api.AudioRoute(
participant_kind=api.ParticipantInfo.Kind.STANDARD,
channel=api.AudioChannel.AUDIO_CHANNEL_RIGHT,
),
]
)
),
outputs=[
api.Output(
file=api.FileOutput(
file_type=api.EncodedFileType.OGG,
filepath="conversation.ogg",
)
)
],
storage=api.StorageConfig(s3=api.S3Upload(bucket="my-bucket")),
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
lkapi.egress.start_egress(
room_name: 'my-room',
media: LiveKit::Proto::MediaSource.new(
audio: LiveKit::Proto::AudioConfig.new(
routes: [
LiveKit::Proto::AudioRoute.new(
participant_kind: LiveKit::Proto::ParticipantInfo::Kind::AGENT,
channel: LiveKit::Proto::AudioChannel::AUDIO_CHANNEL_LEFT
),
LiveKit::Proto::AudioRoute.new(
participant_kind: LiveKit::Proto::ParticipantInfo::Kind::STANDARD,
channel: LiveKit::Proto::AudioChannel::AUDIO_CHANNEL_RIGHT
)
]
)
),
outputs: LiveKit::Proto::Output.new(
file: LiveKit::Proto::FileOutput.new(
file_type: LiveKit::Proto::EncodedFileType::OGG,
filepath: 'conversation.ogg'
)
),
storage: LiveKit::Proto::StorageConfig.new(
s3: LiveKit::Proto::S3Upload.new(bucket: 'my-bucket')
)
)
import lksdk "github.com/livekit/server-sdk-go/v2"
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartEgress(ctx, &livekit.StartEgressRequest{
RoomName: "my-room",
Source: &livekit.StartEgressRequest_Media{
Media: &livekit.MediaSource{
Audio: &livekit.AudioConfig{
Routes: []*livekit.AudioRoute{
{
Match: &livekit.AudioRoute_ParticipantKind{ParticipantKind: livekit.ParticipantInfo_AGENT},
Channel: livekit.AudioChannel_AUDIO_CHANNEL_LEFT,
},
{
Match: &livekit.AudioRoute_ParticipantKind{ParticipantKind: livekit.ParticipantInfo_STANDARD},
Channel: livekit.AudioChannel_AUDIO_CHANNEL_RIGHT,
},
},
},
},
},
Outputs: []*livekit.Output{{
Config: &livekit.Output_File{
File: &livekit.FileOutput{
FileType: livekit.EncodedFileType_OGG,
Filepath: "conversation.ogg",
},
},
}},
Storage: &livekit.StorageConfig{
Provider: &livekit.StorageConfig_S3{
S3: &livekit.S3Upload{Bucket: "my-bucket"},
},
},
})
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
import livekit.LivekitModels
val api = LiveKitAPI.createClient(host, apiKey, secret)
val audio = LivekitEgress.AudioConfig.newBuilder()
.addRoutes(
LivekitEgress.AudioRoute.newBuilder()
.setParticipantKind(LivekitModels.ParticipantInfo.Kind.AGENT)
.setChannel(LivekitEgress.AudioChannel.AUDIO_CHANNEL_LEFT)
)
.addRoutes(
LivekitEgress.AudioRoute.newBuilder()
.setParticipantKind(LivekitModels.ParticipantInfo.Kind.STANDARD)
.setChannel(LivekitEgress.AudioChannel.AUDIO_CHANNEL_RIGHT)
)
.build()
val output = LivekitEgress.Output.newBuilder()
.setFile(
LivekitEgress.FileOutput.newBuilder()
.setFileType(LivekitEgress.EncodedFileType.OGG)
.setFilepath("conversation.ogg")
)
.build()
val info = api.egress.startEgress(
roomName = "my-room",
media = LivekitEgress.MediaSource.newBuilder().setAudio(audio).build(),
outputs = listOf(output),
storage = LivekitEgress.StorageConfig.newBuilder()
.setS3(LivekitEgress.S3Upload.newBuilder().setBucket("my-bucket"))
.build(),
).execute().body()
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let audio = proto::AudioConfig {
routes: vec![
proto::AudioRoute {
r#match: Some(proto::audio_route::Match::ParticipantKind(
proto::participant_info::Kind::Agent as i32,
)),
channel: proto::AudioChannel::Left as i32,
},
proto::AudioRoute {
r#match: Some(proto::audio_route::Match::ParticipantKind(
proto::participant_info::Kind::Standard as i32,
)),
channel: proto::AudioChannel::Right as i32,
},
],
..Default::default()
};
let info = api
.egress()
.start_egress(proto::StartEgressRequest {
room_name: "my-room".to_string(),
source: Some(proto::start_egress_request::Source::Media(proto::MediaSource {
audio: Some(audio),
..Default::default()
})),
outputs: vec![proto::Output {
config: Some(proto::output::Config::File(proto::FileOutput {
file_type: proto::EncodedFileType::Ogg as i32,
filepath: "conversation.ogg".to_string(),
..Default::default()
})),
..Default::default()
}],
storage: Some(proto::StorageConfig {
provider: Some(proto::storage_config::Provider::S3(proto::S3Upload {
bucket: "my-bucket".to_string(),
..Default::default()
})),
}),
..Default::default()
})
.await?;

Exporting a single track without transcoding

This example exports one track in its native container, using the PASSTHROUGH preset (the StartEgress replacement for the deprecated Track egress). Passthrough captures exactly one track to exactly one file, so leave file_type unset and don't add other outputs. For the full list of constraints, see StartEgress.

{
"room_name": "my-room",
"media": { "video_track_id": "TR_VIDEO_TRACK_ID" },
"preset": "PASSTHROUGH",
"outputs": [{ "file": { "filepath": "my-track" } }],
"storage": {
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket"
}
}
}
lk egress start egress.json
import {
EncodingOptionsPreset,
FileOutput,
LiveKitAPI,
MediaSource,
Output,
StartEgressRequest,
} from 'livekit-server-sdk';
const api = new LiveKitAPI();
const info = await api.egress.startEgress(
new StartEgressRequest({
roomName: 'my-room',
source: { case: 'media', value: new MediaSource({ video: { case: 'videoTrackId', value: 'TR_VIDEO_TRACK_ID' } }) },
encoding: { case: 'preset', value: EncodingOptionsPreset.PASSTHROUGH },
outputs: [
new Output({
config: { case: 'file', value: new FileOutput({ filepath: 'my-track' }) },
}),
],
storage: {
provider: {
case: 's3',
value: { accessKey: '', secret: '', bucket: 'my-bucket', region: '' },
},
},
}),
);
from livekit import api
req = api.StartEgressRequest(
room_name="my-room",
media=api.MediaSource(video_track_id="TR_VIDEO_TRACK_ID"),
preset=api.EncodingOptionsPreset.PASSTHROUGH,
outputs=[api.Output(file=api.FileOutput(filepath="my-track"))],
storage=api.StorageConfig(s3=api.S3Upload(bucket="my-bucket")),
)
async with api.LiveKitAPI() as lkapi:
info = await lkapi.egress.start_egress(req)
require 'livekit'
lkapi = LiveKit::LiveKitAPI.new
lkapi.egress.start_egress(
room_name: 'my-room',
media: LiveKit::Proto::MediaSource.new(video_track_id: 'TR_VIDEO_TRACK_ID'),
preset: LiveKit::Proto::EncodingOptionsPreset::PASSTHROUGH,
outputs: LiveKit::Proto::Output.new(
file: LiveKit::Proto::FileOutput.new(filepath: 'my-track')
),
storage: LiveKit::Proto::StorageConfig.new(
s3: LiveKit::Proto::S3Upload.new(bucket: 'my-bucket')
)
)
import lksdk "github.com/livekit/server-sdk-go/v2"
api, err := lksdk.NewLiveKitAPI()
info, err := api.Egress().StartEgress(ctx, &livekit.StartEgressRequest{
RoomName: "my-room",
Source: &livekit.StartEgressRequest_Media{
Media: &livekit.MediaSource{
Video: &livekit.MediaSource_VideoTrackId{VideoTrackId: "TR_VIDEO_TRACK_ID"},
},
},
Encoding: &livekit.StartEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_PASSTHROUGH,
},
Outputs: []*livekit.Output{{
Config: &livekit.Output_File{
File: &livekit.FileOutput{Filepath: "my-track"},
},
}},
Storage: &livekit.StorageConfig{
Provider: &livekit.StorageConfig_S3{
S3: &livekit.S3Upload{Bucket: "my-bucket"},
},
},
})
import io.livekit.server.LiveKitAPI
import livekit.LivekitEgress
val api = LiveKitAPI.createClient(host, apiKey, secret)
val output = LivekitEgress.Output.newBuilder()
.setFile(LivekitEgress.FileOutput.newBuilder().setFilepath("my-track"))
.build()
val info = api.egress.startEgress(
roomName = "my-room",
media = LivekitEgress.MediaSource.newBuilder()
.setVideoTrackId("TR_VIDEO_TRACK_ID")
.build(),
outputs = listOf(output),
optionsPreset = LivekitEgress.EncodingOptionsPreset.PASSTHROUGH,
storage = LivekitEgress.StorageConfig.newBuilder()
.setS3(LivekitEgress.S3Upload.newBuilder().setBucket("my-bucket"))
.build(),
).execute().body()
use livekit_api::services::LiveKitApi;
use livekit_protocol as proto;
let api = LiveKitApi::with_api_key(host, api_key, api_secret);
let info = api
.egress()
.start_egress(proto::StartEgressRequest {
room_name: "my-room".to_string(),
source: Some(proto::start_egress_request::Source::Media(proto::MediaSource {
video: Some(proto::media_source::Video::VideoTrackId("TR_VIDEO_TRACK_ID".to_string())),
..Default::default()
})),
encoding: Some(proto::start_egress_request::Encoding::Preset(
proto::EncodingOptionsPreset::Passthrough as i32,
)),
outputs: vec![proto::Output {
config: Some(proto::output::Config::File(proto::FileOutput {
filepath: "my-track".to_string(),
..Default::default()
})),
..Default::default()
}],
storage: Some(proto::StorageConfig {
provider: Some(proto::storage_config::Provider::S3(proto::S3Upload {
bucket: "my-bucket".to_string(),
..Default::default()
})),
}),
..Default::default()
})
.await?;

Stop an active egress

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