Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 29 additions & 15 deletions cmd/lk/egress.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,18 @@ const (
)

var (
egressStartDescription = `Initiates a new egress of the chosen TYPE:
- "room-composite" composes multiple participant tracks into a single output stream
- "participant" captures a single participant
- "track" captures a single track without transcoding
- "track-composite" captures an audio and a video track
- "web" captures any website, with a lifecycle detached from LiveKit rooms

REQUEST_JSON is one of:
- ` + reflect.TypeFor[livekit.RoomCompositeEgressRequest]().Name() + `
- ` + reflect.TypeFor[livekit.ParticipantEgressRequest]().Name() + `
- ` + reflect.TypeFor[livekit.TrackEgressRequest]().Name() + `
- ` + reflect.TypeFor[livekit.TrackCompositeEgressRequest]().Name() + `
- ` + reflect.TypeFor[livekit.WebEgressRequest]().Name() + `

egressStartDescription = `Initiates a new egress.

REQUEST_JSON is a ` + reflect.TypeFor[livekit.StartEgressRequest]().Name() + `, whose source is a
layout template, a web page, or media tracks from a room.

TYPE selects a deprecated per-type request instead:
- "room-composite" composes multiple participant tracks into a single output stream, as ` + reflect.TypeFor[livekit.RoomCompositeEgressRequest]().Name() + `
- "participant" captures a single participant, as ` + reflect.TypeFor[livekit.ParticipantEgressRequest]().Name() + `
- "track" captures a single track without transcoding, as ` + reflect.TypeFor[livekit.TrackEgressRequest]().Name() + `
- "track-composite" captures an audio and a video track, as ` + reflect.TypeFor[livekit.TrackCompositeEgressRequest]().Name() + `
- "web" captures any website, with a lifecycle detached from LiveKit rooms, as ` + reflect.TypeFor[livekit.WebEgressRequest]().Name() + `

See cmd/livekit-cli/examples`
)

Expand All @@ -83,7 +81,6 @@ var (
&cli.StringFlag{
Name: "type",
Usage: "Specify `TYPE` of egress (see above)",
Value: string(EgressTypeRoomComposite),
},
},
ArgsUsage: "REQUEST_JSON",
Expand Down Expand Up @@ -397,6 +394,8 @@ func createEgressClient(ctx context.Context, cmd *cli.Command) (context.Context,

func handleEgressStart(ctx context.Context, cmd *cli.Command) error {
switch cmd.String("type") {
case "":
return startEgress(ctx, cmd)
case string(EgressTypeRoomComposite):
return startRoomCompositeEgress(ctx, cmd)
case string(EgressTypeWeb):
Expand Down Expand Up @@ -563,6 +562,21 @@ func _deprecatedStartTrackEgress(ctx context.Context, cmd *cli.Command) error {
return nil
}

func startEgress(ctx context.Context, cmd *cli.Command) error {
req, err := ReadRequestArg[livekit.StartEgressRequest](cmd)
if err != nil {
return err
}

info, err := egressClient.StartEgress(ctx, req)
if err != nil {
return err
}

printInfo(info)
return nil
}

func unmarshalEgressRequest(cmd *cli.Command, req proto.Message) error {
reqBytes, err := os.ReadFile(cmd.String("request"))
if err != nil {
Expand Down
56 changes: 52 additions & 4 deletions cmd/lk/egress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ import (
lksdk "github.com/livekit/server-sdk-go/v2"
)

// fakeEgressService implements livekit.Egress. Only ListEgress is exercised
// by the egress list tests; the other RPCs return empty results.
// fakeEgressService implements livekit.Egress. Only ListEgress and StartEgress
// are exercised by the egress tests; the other RPCs return empty results.
type fakeEgressService struct {
listRequests []*livekit.ListEgressRequest
listResponses []*livekit.ListEgressResponse
listErr error
startRequests []*livekit.StartEgressRequest
}

func (f *fakeEgressService) ListEgress(_ context.Context, req *livekit.ListEgressRequest) (*livekit.ListEgressResponse, error) {
Expand All @@ -50,8 +51,9 @@ func (f *fakeEgressService) ListEgress(_ context.Context, req *livekit.ListEgres
return f.listResponses[idx], nil
}

func (f *fakeEgressService) StartEgress(_ context.Context, _ *livekit.StartEgressRequest) (*livekit.EgressInfo, error) {
return nil, nil
func (f *fakeEgressService) StartEgress(_ context.Context, req *livekit.StartEgressRequest) (*livekit.EgressInfo, error) {
f.startRequests = append(f.startRequests, req)
return &livekit.EgressInfo{EgressId: "EG_test"}, nil
}
func (f *fakeEgressService) StartRoomCompositeEgress(_ context.Context, _ *livekit.RoomCompositeEgressRequest) (*livekit.EgressInfo, error) {
return nil, nil
Expand Down Expand Up @@ -427,3 +429,49 @@ func TestListEgress_JSONOrdering_ByID(t *testing.T) {

assert.Equal(t, []string{"EG_C", "EG_A", "EG_B"}, extractEgressIDs(t, buf.Bytes()))
}

func buildEgressStartCommand(t *testing.T, egressType, request string) *cli.Command {
t.Helper()
var captured *cli.Command
app := &cli.Command{
Name: "test",
Flags: []cli.Flag{&cli.StringFlag{Name: "type"}},
Action: func(_ context.Context, cmd *cli.Command) error {
captured = cmd
return nil
},
}

args := []string{"test"}
if egressType != "" {
args = append(args, "--type", egressType)
}
args = append(args, request)

require.NoError(t, app.Run(context.Background(), args))
require.NotNil(t, captured)
return captured
}

func TestEgressStart_Media(t *testing.T) {
svc := &fakeEgressService{}
setupFakeEgressClient(t, svc)

cmd := buildEgressStartCommand(t, "", `{
"room_name": "my-room",
"media": { "video_track_id": "TR_XXXXXXXXXXXX" },
"outputs": [{ "file": { "filepath": "my-track.mp4" } }]
}`)
require.NoError(t, handleEgressStart(context.Background(), cmd))

require.Len(t, svc.startRequests, 1)
req := svc.startRequests[0]
assert.Equal(t, "my-room", req.GetRoomName())
assert.Equal(t, "TR_XXXXXXXXXXXX", req.GetMedia().GetVideoTrackId())
require.Len(t, req.GetOutputs(), 1)
assert.Equal(t, "my-track.mp4", req.GetOutputs()[0].GetFile().GetFilepath())
}

func TestEgressStart_UnknownType(t *testing.T) {
require.Error(t, handleEgressStart(context.Background(), buildEgressStartCommand(t, "nonsense", "{}")))
}
13 changes: 13 additions & 0 deletions cmd/lk/examples/media-egress-v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"room_name": "my-room",
"media": {
"video_track_id": "TR_XXXXXXXXXXXX"
},
"outputs": [
{
"file": {
"filepath": "my-track.mp4"
}
}
]
}
21 changes: 21 additions & 0 deletions cmd/lk/examples/template-egress-v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"room_name": "my-room",
"template": {
"layout": "speaker-dark"
},
"outputs": [
{
"file": {
"filepath": "livekit-demo/my-room-test.mp4"
}
}
],
"storage": {
"s3": {
"access_key": "aws-access-key",
"secret": "aws-access-secret",
"region": "aws-region",
"bucket": "my-bucket"
}
}
}
20 changes: 20 additions & 0 deletions cmd/lk/examples/web-egress-v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"web": {
"url": "https://docs.livekit.io/server/egress"
},
"outputs": [
{
"file": {
"filepath": "livekit-demo/my-web-test.mp4"
}
}
],
"storage": {
"s3": {
"access_key": "aws-access-key",
"secret": "aws-access-secret",
"region": "aws-region",
"bucket": "my-bucket"
}
}
}
Loading
Loading