Recording room composite as HLS
This example records a room composite layout as HLS segments to an S3-compatible bucket.
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 apireq = 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.newoutputs = [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.LiveKitAPIimport livekit.LivekitEgressval 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 credentialscredentials: 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 apicontent = ""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.newcontent = 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.EncodedOutputsimport io.livekit.server.LiveKitAPIimport livekit.LivekitEgressval 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 apirequest = 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.newoutputs = [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.EncodedOutputsimport io.livekit.server.LiveKitAPIimport livekit.LivekitEgressimport livekit.LivekitModelsval 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 outputlk egress update-stream --id <egress-id> --add-urls rtmp://new-server.com/live/stream-key# to remove RTMP outputlk 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 laterstream: 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 outputawait api.egress.updateStream(info.egressId, ['rtmp://new-server.com/live/stream-key']);// to remove RTMP outputawait api.egress.updateStream(info.egressId, [], ['rtmp://new-server.com/live/stream-key']);
from livekit import apirequest = 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 laterstream_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 streamawait 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 streamawait 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.newoutputs = [# a placeholder RTMP output is needed to ensure stream urls can be added to it laterLiveKit::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 streamlkapi.egress.update_stream(info.egress_id, add_output_urls: ["rtmp://new-server.com/live/stream-key"])# remove an output URL from the streamlkapi.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 laterStreamOutputs: []*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 streamapi.Egress().UpdateStream(context.Background(), &livekit.UpdateStreamRequest{EgressId: info.EgressId,AddOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},})// remove an output URL from the streamapi.Egress().UpdateStream(context.Background(), &livekit.UpdateStreamRequest{EgressId: info.EgressId,RemoveOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},})
import io.livekit.server.EncodedOutputsimport io.livekit.server.LiveKitAPIimport livekit.LivekitEgressval api = LiveKitAPI.createClient(host, apiKey, secret)// a placeholder RTMP output is needed to ensure stream urls can be added to it laterval 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 streaminfo = api.egress.updateStream(info!!.egressId,listOf("rtmp://new-server.com/live/stream-key"),).execute().body()// remove an output URL from the streaminfo = 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 laterlet 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 streamapi.egress().update_stream(&info.egress_id, vec!["rtmp://new-server.com/live/stream-key".to_string()], vec![]).await?;// remove an output URL from the streamapi.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.
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 apirequest = 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.newoutput = 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.LiveKitAPIimport livekit.LivekitEgressval 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.