RoomComposite egress
One common requirement when recording a room is to capture all of the participants and interactions that take place. This can be challenging in a multi-user application, where different users may be joining, leaving, turning their cameras on and off. It may also be desirable for the recording to look as close to the actual application experience as possible, capturing the richness and interactivity of your application.
A RoomComposite
egress uses a web app to create the composited view, rendering the output with an instance of headless Chromium.
In most cases, your existing LiveKit application can be used as a compositing template with few modifications.
Default layouts
We provide a few default compositing layouts that works out of the box. They'll be used by default if a custom template URL is not passed in. These templates are deployed alongside and served by the egress service (source).
While it's a great starting point, you can easily create your own layout using standard web technologies that you are already familiar with.
Layout | Preview |
---|---|
grid | ![]() |
speaker | ![]() |
single-speaker | ![]() |
Additionally, you can use a -light
suffix to change background color to white. i.e. grid-light
.
Starting a RoomComposite
The following example starts a recording for archival to S3. It uses an EncodedFileOutput
for the egress.
const egressClient = new EgressClient('https://my-livekit-host','livekit-api-key','livekit-api-secret');const output = {fileType: EncodedFileType.MP4,filepath: 'livekit-demo/room-composite-test.mp4',s3: {accessKey: 'aws-access-key',secret: 'aws-access-secret',region: 'aws-region',bucket: 'my-bucket'}};const info = await egressClient.startRoomCompositeEgress('my-room', output, {layout: 'speaker',// uncomment to use your own templates// customBaseUrl: 'https://my-template-url.com',});const egressID = info.egressId;
ctx := context.Background()egressClient := lksdk.NewEgressClient("https://my-livekit-host","livekit-api-key","livekit-api-secret",)fileRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "speaker",Output: &livekit.RoomCompositeEgressRequest_File{File: &livekit.EncodedFileOutput{FileType: livekit.EncodedFileType_MP4,Filepath: "livekit-demo/my-room-test.mp4",Output: &livekit.EncodedFileOutput_S3{S3: &livekit.S3Upload{AccessKey: "aws-access-key",Secret: "aws-access-secret",Region: "aws-region",Bucket: "my-bucket",},},},},// uncomment to use your own templates// CustomBaseUrl: "https://my-custom-template.com",}info, err := egressClient.StartRoomCompositeEgress(ctx, fileRequest)egressID := info.EgressId
require 'livekit'const egressClient = LiveKit::EgressServiceClient.new("https://my-livekit-host",api_key: 'key',api_secret: 'secret');info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::EncodedFileOutput.new(file_type: LiveKit::Proto::EncodedFileType::MP4,filepath: "dz-test-recording.mp4",s3: LiveKit::Proto::S3Upload.new(access_key: 'access-key',secret: 'secret',region: 'bucket-region',bucket: 'bucket')),layout: 'speaker',custom_base_url: 'https://my-custom-template.com')puts info
EgressServiceClient client = EgressServiceClient.create("http://example.com","apiKey","secret");LivekitEgress.EncodedFileOutput output = LivekitEgress.EncodedFileOutput.newBuilder().setFileType(LivekitEgress.EncodedFileType.MP4).setFilepath("test-recording.mp4").setS3(LivekitEgress.S3Upload.newBuilder().setAccessKey("access-key").setSecret("secret").setBucket("bucket").setEndpoint("https://my.s3.endpoint").build()).build();Call<LivekitEgress.EgressInfo> call = client.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
Save this as request.json
{"room_name": "my-room","layout": "speaker","file": {"file_type": "MP4","filepath": "livekit-demo/my-room-test.mp4","s3": {"access_key": "aws-access-key","secret": "aws-access-secret","region": "aws-region","bucket": "my-bucket"}}}
% export LIVEKIT_URL=https://my-livekit-host% export LIVEKIT_API_KEY=livekit-api-key% export LIVEKIT_API_SECRET=livekit-api-secret% livekit-cli start-room-composite-egress --request request.jsonEgress started. Egress ID: EG_XXXXXXXXXXXX
fileRequest.Output.File.Output
can be left empty if one ofs3
,azure
, orgcp
is supplied with your config.fileRequest.Output.File.Filepath
can be left empty, and a unique filename will be generated based on the date and room name.
Stream to RTMP
To stream to RTMP, you would use a StreamOutput
.
When multiple RTMP URLs are specified, LiveKit would multi-cast to all of them at the same time.
Stream URLs can be changed even after the egress has started with UpdateStream
const output: StreamOutput = {protocol: StreamProtocol.RTMP,urls: ['rtmp://youtube-url/stream','rtmps://twitch-url/path',],};const info = await egressClient.startRoomCompositeEgress('my-room', output, 'grid');
streamRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "grid",Output: &livekit.RoomCompositeEgressRequest_Stream{Stream: &livekit.StreamOutput{Urls: []string{"rtmp://youtube-url/stream","rtmps://twitch-url/path",},},},}info, err := egressClient.StartRoomCompositeEgress(ctx, fileRequest)
info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::StreamOutput.new(urls: ["rtmp://youtube-url/stream","rtmps://twitch-url/path",]))
LivekitEgress.StreamOutput output = LivekitEgress.StreamOutput.newBuilder().addUrls("rtmp://youtube-url/stream").addUrls("rtmps://twitch-url/path").build();Call<LivekitEgress.EgressInfo> call = client.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
request.json
{"room_name": "my-room","layout": "speaker","stream": {"urls": ["rtmp://youtube-url/stream","rtmps://twitch-url/path"]}}
% livekit-cli start-room-composite-egress --request request.jsonEgress started. Egress ID: EG_XXXXXXXXXXXX
Stream to HLS
As an alternative to generating a single media file, it is possible to have the Egress service generate segments by using the SegmentedFileOutput
output. The Egress service will split the output in media segments of equal duration (6s by default), and generate a manifest listing all the generated segments.
Currently, only HTTP Live Streaming compatible segments (using the MPEG TS file format) and manifests are supported.
If one of s3
, azure
, or gcp
is supplied with the config or request, each segment will be uploaded with an updated manifest as soon as it is generated. This allows playback of the exported media while the export is still ongoing.
const output: SegmentedFileOutput = {filenamePrefix: 'livekit-demo/room-composite-test-',playlistName: 'room-composite-test.m3u8',segmentDuration: 6,protocol: SegmentedFileProtocol.HLS_PROTOCOL,s3: {accessKey: 'aws-access-key',secret: 'aws-access-secret',region: 'aws-region',bucket: 'my-bucket'}};const info = await egressClient.startRoomCompositeEgress('my-room', output, 'grid');
streamRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "grid",Output: &livekit.RoomCompositeEgressRequest_Segments{Segments: &livekit.SegmentedFileOutput{FilenamePrefix: "livekit-demo/my-room-test-",PlaylistName: "my-room-test.m3u8",Output: &livekit.SegmentedFileOutput_S3{S3: &livekit.S3Upload{AccessKey: "aws-access-key",Secret: "aws-access-secret",Region: "aws-region",Bucket: "my-bucket",},},},},}info, err := egressClient.StartRoomCompositeEgress(ctx, fileRequest)
info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::SegmentedFileOutput.new(filename_prefix: "dz-test-recording-",playlist_name: "dz-test-recording.m3u8",s3: LiveKit::Proto::S3Upload.new(access_key: 'access-key',secret: 'secret',region: 'bucket-region',bucket: 'bucket')))
LivekitEgress.SegmentedFileOutput output =LivekitEgress.SegmentedFileOutput.newBuilder().setFilenamePrefix("livekit-demo/room-composite-test-").setPlaylistName("room-composite-test.m3u8").setSegmentDuration(6).setProtocol(LivekitEgress.SegmentedFileProtocol.HLS_PROTOCOL).setS3(LivekitEgress.S3Upload.newBuilder().setAccessKey("access-key").setSecret("secret").setBucket("bucket").setRegion("region").build()).build();Call<LivekitEgress.EgressInfo> call = client.startRoomCompositeEgress("roomName",output);try {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
request.json
{"room_name": "my-room","layout": "speaker","segments": {"filename_prefix": "livekit-demo/my-room-test-","playlist_name": "my-room-test.m3u8","s3": {"access_key": "aws-access-key","secret": "aws-access-secret","region": "aws-region","bucket": "my-bucket"}}}
% livekit-cli start-room-composite-egress --request request.jsonEgress started. Egress ID: EG_XXXXXXXXXXXX
segmentsRequest.Output.Segments.FilenamePrefix
andsegmentsRequest.Output.Segments.PlaylistName
can be left empty, and unique filenames will be generated based on the date and room name.- The playlist file will always be created in the same directory as the segments, as indicated in
segmentsRequest.Output.Segments.FilenamePrefix
, regardless of any potential directory prefix added tosegmentsRequest.Output.Segments.PlaylistName
.
Audio-only composite
If your application is audio-only, you can export a mixed audio file containing audio from all participants in the room.
To start an audio-only composite, pass audio_only=true
when starting an Egress.
const info = await egressClient.startRoomCompositeEgress('my-room',output,'grid',undefined, // EncodingOptionsPreset | EncodingOptionstrue, // audioOnly);
streamRequest := &livekit.RoomCompositeEgressRequest{RoomName: "my-room",Layout: "grid",AudioOnly: true,...}info, err := egressClient.StartRoomCompositeEgress(ctx, fileRequest)
info = egressClient.start_room_composite_egress('room-name',LiveKit::Proto::StreamOutput.new(urls: ["rtmp://youtube-url/stream","rtmps://twitch-url/path",]),audio_only: true)
Call<LivekitEgress.EgressInfo> call = client.startRoomCompositeEgress("roomName",output,"grid", // layoutnull, // encoding options presetnull, // encoding options advancedtrue); // audio onlytry {LivekitEgress.EgressInfo egressInfo = call.execute().body();// handle engress info} catch (IOException e) {// handle error}
request.json
{"room_name": "my-room","layout": "speaker","audio_only": true,...}
% livekit-cli start-room-composite-egress --request request.jsonEgress started. Egress ID: EG_XXXXXXXXXXXX