Merge commit '6bd7fac875d9e9009915053d8a590abb372c5679' into feature/livekit-upgrade-1.13.1

This commit is contained in:
2026-06-25 23:54:33 +09:00
291 changed files with 37631 additions and 15381 deletions
+161 -150
View File
@@ -32,194 +32,205 @@ var (
)
func TestAgents(t *testing.T) {
_, finish := setupSingleNodeTest("TestAgents")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, finish := setupSingleNodeTest("TestAgents")
defer finish()
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac3, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac4, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac5, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac6, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
defer ac3.close()
defer ac4.close()
defer ac5.close()
defer ac6.close()
ac1.Run(livekit.JobType_JT_ROOM, "default")
ac2.Run(livekit.JobType_JT_ROOM, "default")
ac3.Run(livekit.JobType_JT_PUBLISHER, "default")
ac4.Run(livekit.JobType_JT_PUBLISHER, "default")
ac5.Run(livekit.JobType_JT_PARTICIPANT, "default")
ac6.Run(livekit.JobType_JT_PARTICIPANT, "default")
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac3, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac4, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac5, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac6, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
defer ac3.close()
defer ac4.close()
defer ac5.close()
defer ac6.close()
ac1.Run(livekit.JobType_JT_ROOM, "default")
ac2.Run(livekit.JobType_JT_ROOM, "default")
ac3.Run(livekit.JobType_JT_PUBLISHER, "default")
ac4.Run(livekit.JobType_JT_PUBLISHER, "default")
ac5.Run(livekit.JobType_JT_PARTICIPANT, "default")
ac6.Run(livekit.JobType_JT_PARTICIPANT, "default")
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 || ac3.registered.Load() != 1 || ac4.registered.Load() != 1 || ac5.registered.Load() != 1 || ac6.registered.Load() != 1 {
return "worker not registered"
}
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 || ac3.registered.Load() != 1 || ac4.registered.Load() != 1 || ac5.registered.Load() != 1 || ac6.registered.Load() != 1 {
return "worker not registered"
}
return ""
}, RegisterTimeout)
return ""
}, RegisterTimeout)
c1 := createRTCClient("c1", defaultServerPort, nil)
c2 := createRTCClient("c2", defaultServerPort, nil)
waitUntilConnected(t, c1, c2)
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("c2", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
// publish 2 tracks
t1, err := c1.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t1.Stop()
t2, err := c1.AddStaticTrack("video/vp8", "video", "webcam")
require.NoError(t, err)
defer t2.Stop()
// publish 2 tracks
t1, err := c1.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t1.Stop()
t2, err := c1.AddStaticTrack("video/vp8", "video", "webcam")
require.NoError(t, err)
defer t2.Stop()
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load()+ac2.roomJobs.Load() != 1 {
return "room job not assigned"
}
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load()+ac2.roomJobs.Load() != 1 {
return "room job not assigned"
}
if ac3.publisherJobs.Load()+ac4.publisherJobs.Load() != 1 {
return fmt.Sprintf("publisher jobs not assigned, ac3: %d, ac4: %d", ac3.publisherJobs.Load(), ac4.publisherJobs.Load())
}
if ac3.publisherJobs.Load()+ac4.publisherJobs.Load() != 1 {
return fmt.Sprintf("publisher jobs not assigned, ac3: %d, ac4: %d", ac3.publisherJobs.Load(), ac4.publisherJobs.Load())
}
if ac5.participantJobs.Load()+ac6.participantJobs.Load() != 2 {
return fmt.Sprintf("participant jobs not assigned, ac5: %d, ac6: %d", ac5.participantJobs.Load(), ac6.participantJobs.Load())
}
if ac5.participantJobs.Load()+ac6.participantJobs.Load() != 2 {
return fmt.Sprintf("participant jobs not assigned, ac5: %d, ac6: %d", ac5.participantJobs.Load(), ac6.participantJobs.Load())
}
return ""
}, 6*time.Second)
return ""
}, 6*time.Second)
// publish 2 tracks
t3, err := c2.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t3.Stop()
t4, err := c2.AddStaticTrack("video/vp8", "video", "webcam")
require.NoError(t, err)
defer t4.Stop()
// publish 2 tracks
t3, err := c2.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t3.Stop()
t4, err := c2.AddStaticTrack("video/vp8", "video", "webcam")
require.NoError(t, err)
defer t4.Stop()
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load()+ac2.roomJobs.Load() != 1 {
return "room job must be assigned 1 time"
}
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load()+ac2.roomJobs.Load() != 1 {
return "room job must be assigned 1 time"
}
if ac3.publisherJobs.Load()+ac4.publisherJobs.Load() != 2 {
return "2 publisher jobs must assigned"
}
if ac3.publisherJobs.Load()+ac4.publisherJobs.Load() != 2 {
return "2 publisher jobs must assigned"
}
if ac5.participantJobs.Load()+ac6.participantJobs.Load() != 2 {
return "2 participant jobs must assigned"
}
if ac5.participantJobs.Load()+ac6.participantJobs.Load() != 2 {
return "2 participant jobs must assigned"
}
return ""
}, AssignJobTimeout)
return ""
}, AssignJobTimeout)
})
}
}
func TestAgentNamespaces(t *testing.T) {
_, finish := setupSingleNodeTest("TestAgentNamespaces")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, finish := setupSingleNodeTest("TestAgentNamespaces")
defer finish()
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
ac1.Run(livekit.JobType_JT_ROOM, "namespace1")
ac2.Run(livekit.JobType_JT_ROOM, "namespace2")
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
ac1.Run(livekit.JobType_JT_ROOM, "namespace1")
ac2.Run(livekit.JobType_JT_ROOM, "namespace2")
_, err = roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: testRoom,
Agents: []*livekit.RoomAgentDispatch{
{},
{
AgentName: "ag",
},
},
})
require.NoError(t, err)
_, err = roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: testRoom,
Agents: []*livekit.RoomAgentDispatch{
{},
{
AgentName: "ag",
},
},
})
require.NoError(t, err)
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 {
return "worker not registered"
}
return ""
}, RegisterTimeout)
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 {
return "worker not registered"
}
return ""
}, RegisterTimeout)
c1 := createRTCClient("c1", defaultServerPort, nil)
waitUntilConnected(t, c1)
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load() != 1 || ac2.roomJobs.Load() != 1 {
return "room job not assigned"
}
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load() != 1 || ac2.roomJobs.Load() != 1 {
return "room job not assigned"
}
job1 := <-ac1.requestedJobs
job2 := <-ac2.requestedJobs
job1 := <-ac1.requestedJobs
job2 := <-ac2.requestedJobs
if job1.Namespace != "namespace1" {
return "namespace is not 'namespace'"
}
if job1.Namespace != "namespace1" {
return "namespace is not 'namespace'"
}
if job2.Namespace != "namespace2" {
return "namespace is not 'namespace2'"
}
if job2.Namespace != "namespace2" {
return "namespace is not 'namespace2'"
}
if job1.Id == job2.Id {
return "job ids are the same"
}
return ""
}, AssignJobTimeout)
if job1.Id == job2.Id {
return "job ids are the same"
}
return ""
}, AssignJobTimeout)
})
}
}
func TestAgentMultiNode(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestAgentMultiNode")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestAgentMultiNode")
defer finish()
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
ac1.Run(livekit.JobType_JT_ROOM, "default")
ac2.Run(livekit.JobType_JT_PUBLISHER, "default")
ac1, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
ac2, err := newAgentClient(agentToken(), defaultServerPort)
require.NoError(t, err)
defer ac1.close()
defer ac2.close()
ac1.Run(livekit.JobType_JT_ROOM, "default")
ac2.Run(livekit.JobType_JT_PUBLISHER, "default")
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 {
return "worker not registered"
}
return ""
}, RegisterTimeout)
testutils.WithTimeout(t, func() string {
if ac1.registered.Load() != 1 || ac2.registered.Load() != 1 {
return "worker not registered"
}
return ""
}, RegisterTimeout)
c1 := createRTCClient("c1", secondServerPort, nil) // Create a room on the second node
waitUntilConnected(t, c1)
c1 := createRTCClient("c1", secondServerPort, testRTCServicePath, nil) // Create a room on the second node
waitUntilConnected(t, c1)
t1, err := c1.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t1.Stop()
t1, err := c1.AddStaticTrack("audio/opus", "audio", "micro")
require.NoError(t, err)
defer t1.Stop()
time.Sleep(time.Second * 10)
time.Sleep(time.Second * 10)
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load() != 1 {
return "room job not assigned"
}
testutils.WithTimeout(t, func() string {
if ac1.roomJobs.Load() != 1 {
return "room job not assigned"
}
if ac2.publisherJobs.Load() != 1 {
return "participant job not assigned"
}
if ac2.publisherJobs.Load() != 1 {
return "participant job not assigned"
}
return ""
}, AssignJobTimeout)
return ""
}, AssignJobTimeout)
})
}
}
func agentToken() string {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
package client
import (
"github.com/frostbyte73/core"
"github.com/livekit/livekit-server/pkg/rtc/datatrack"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"go.uber.org/atomic"
)
type DataTrackRemote struct {
publisherIdentity livekit.ParticipantIdentity
publisherID livekit.ParticipantID
handle uint16
trackID livekit.TrackID
logger logger.Logger
numReceivedPackets atomic.Uint32
closed core.Fuse
}
func NewDataTrackRemote(
publisherIdentity livekit.ParticipantIdentity,
publisherID livekit.ParticipantID,
handle uint16,
trackID livekit.TrackID,
logger logger.Logger,
) *DataTrackRemote {
logger.Infow(
"creating data track remote",
"publisherIdentity", publisherIdentity,
"publisherID", publisherID,
"handle", handle,
"trackID", trackID,
)
return &DataTrackRemote{
publisherIdentity: publisherIdentity,
publisherID: publisherID,
handle: handle,
trackID: trackID,
logger: logger,
}
}
func (d *DataTrackRemote) Close() {
d.logger.Infow(
"closing data track remote",
"publisherIdentity", d.publisherIdentity,
"publisherID", d.publisherID,
"handle", d.handle,
"trackID", d.trackID,
)
d.closed.Break()
}
func (d *DataTrackRemote) Handle() uint16 {
return d.handle
}
func (d *DataTrackRemote) ID() livekit.TrackID {
return d.trackID
}
func (d *DataTrackRemote) PacketReceived(packet *datatrack.Packet) {
if d.closed.IsBroken() {
return
}
valid := len(packet.Payload) != 0
for i := range packet.Payload {
if packet.Payload[i] != byte(255-i) {
valid = false
break
}
}
if valid {
d.numReceivedPackets.Inc()
}
}
func (d *DataTrackRemote) NumReceivedPackets() uint32 {
return d.numReceivedPackets.Load()
}
@@ -0,0 +1,79 @@
// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"context"
"math/rand"
"time"
"github.com/livekit/livekit-server/pkg/rtc/datatrack"
"github.com/livekit/livekit-server/pkg/rtc/types"
"github.com/livekit/protocol/logger"
)
type dataTrackWriter struct {
ctx context.Context
cancel context.CancelFunc
handle uint16
transport types.DataTrackTransport
}
func NewDataTrackWriter(ctx context.Context, handle uint16, transport types.DataTrackTransport) TrackWriter {
ctx, cancel := context.WithCancel(ctx)
return &dataTrackWriter{
ctx: ctx,
cancel: cancel,
handle: handle,
transport: transport,
}
}
func (d *dataTrackWriter) Start() error {
go d.writeFrames()
return nil
}
func (d *dataTrackWriter) Stop() {
d.cancel()
}
func (d *dataTrackWriter) writeFrames() {
seqNum := uint16(0)
frameNum := uint16(0)
for {
select {
case <-d.ctx.Done():
return
default:
packets := datatrack.GenerateRawDataPackets(d.handle, seqNum, frameNum, 1, rand.Intn(2048)+1, 100*time.Millisecond)
for _, packet := range packets {
if err := d.transport.SendDataTrackMessage(packet); err != nil {
logger.Errorw("could not send data track packet", err)
}
}
if len(packets) != 0 {
var lastPacket datatrack.Packet
if err := lastPacket.Unmarshal(packets[len(packets)-1]); err == nil {
seqNum = lastPacket.SequenceNumber + 1
frameNum = lastPacket.FrameNumber + 1
}
}
time.Sleep(100 * time.Millisecond)
}
}
}
+16 -12
View File
@@ -26,13 +26,18 @@ import (
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
"github.com/pion/webrtc/v4/pkg/media/oggreader"
"github.com/livekit/livekit-server/pkg/sfu/mime"
"github.com/livekit/protocol/codecs/mime"
"github.com/livekit/protocol/logger"
)
type TrackWriter interface {
Start() error
Stop()
}
// Writes a file to an RTP track.
// makes it easier to debug and create RTP streams
type TrackWriter struct {
type trackWriter struct {
ctx context.Context
cancel context.CancelFunc
track *webrtc.TrackLocalStaticSample
@@ -45,9 +50,9 @@ type TrackWriter struct {
h264 *h264reader.H264Reader
}
func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, filePath string) *TrackWriter {
func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, filePath string) TrackWriter {
ctx, cancel := context.WithCancel(ctx)
return &TrackWriter{
return &trackWriter{
ctx: ctx,
cancel: cancel,
track: track,
@@ -56,7 +61,7 @@ func NewTrackWriter(ctx context.Context, track *webrtc.TrackLocalStaticSample, f
}
}
func (w *TrackWriter) Start() error {
func (w *trackWriter) Start() error {
if w.filePath == "" {
go w.writeNull()
return nil
@@ -95,11 +100,11 @@ func (w *TrackWriter) Start() error {
return nil
}
func (w *TrackWriter) Stop() {
func (w *trackWriter) Stop() {
w.cancel()
}
func (w *TrackWriter) writeNull() {
func (w *trackWriter) writeNull() {
defer w.onWriteComplete()
sample := media.Sample{Data: []byte{0x0, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond}
h264Sample := media.Sample{Data: []byte{0x00, 0x00, 0x00, 0x01, 0x7, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x8, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x5, 0xff, 0xff, 0xff, 0xff}, Duration: 30 * time.Millisecond}
@@ -117,7 +122,7 @@ func (w *TrackWriter) writeNull() {
}
}
func (w *TrackWriter) writeOgg() {
func (w *trackWriter) writeOgg() {
// Keep track of last granule, the difference is the amount of samples in the buffer
var lastGranule uint64
for {
@@ -150,7 +155,7 @@ func (w *TrackWriter) writeOgg() {
}
}
func (w *TrackWriter) writeVP8() {
func (w *trackWriter) writeVP8() {
// Send our video file frame at a time. Pace our sending such that we send it at the same speed it should be played back as.
// This isn't required since the video is timestamped, but we will such much higher loss if we send all at once.
sleepTime := time.Millisecond * time.Duration((float32(w.ivfheader.TimebaseNumerator)/float32(w.ivfheader.TimebaseDenominator))*1000)
@@ -178,10 +183,9 @@ func (w *TrackWriter) writeVP8() {
}
}
func (w *TrackWriter) writeH264() {
func (w *trackWriter) writeH264() {
// TODO: this is harder
}
func (w *TrackWriter) onWriteComplete() {
func (w *trackWriter) onWriteComplete() {
}
+86 -26
View File
@@ -25,17 +25,18 @@ import (
"github.com/redis/go-redis/v9"
"github.com/twitchtv/twirp"
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/guid"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/routing"
"github.com/livekit/livekit-server/pkg/service"
"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
"github.com/livekit/livekit-server/pkg/testutils"
testclient "github.com/livekit/livekit-server/test/client"
"github.com/livekit/mediatransportutil/pkg/rtcconfig"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/guid"
)
const (
@@ -131,14 +132,30 @@ func waitUntilConnected(t *testing.T, clients ...*testclient.RTCClient) {
wg := sync.WaitGroup{}
for i := range clients {
c := clients[i]
wg.Add(1)
go func() {
defer wg.Done()
err := c.WaitUntilConnected()
wg.Go(func() {
err := c.WaitUntilConnected(5 * time.Second)
if err != nil {
t.Error(err)
}
}()
})
}
wg.Wait()
if t.Failed() {
t.FailNow()
}
}
func ensureNotConnected(t *testing.T, clients ...*testclient.RTCClient) {
logger.Infow("checking if clients connect")
wg := sync.WaitGroup{}
for i := range clients {
c := clients[i]
wg.Go(func() {
err := c.WaitUntilConnected(5 * time.Second)
if err == nil {
t.Error(fmt.Errorf("expected client to not connect: %s", c.ID()))
}
})
}
wg.Wait()
if t.Failed() {
@@ -153,6 +170,7 @@ func createSingleNodeServer(configUpdater func(*config.Config)) *service.Livekit
panic(fmt.Sprintf("could not create config: %v", err))
}
conf.Keys = map[string]string{testApiKey: testApiSecret}
conf.EnableDataTracks = true
if configUpdater != nil {
configUpdater(conf)
}
@@ -183,6 +201,7 @@ func createMultiNodeServer(nodeID string, port uint32) *service.LivekitServer {
conf.RTC.TCPPort = port + 2
conf.Redis.Address = "localhost:6379"
conf.Keys = map[string]string{testApiKey: testApiSecret}
conf.EnableDataTracks = true
currentNode, err := routing.NewLocalNode(conf)
if err != nil {
@@ -200,36 +219,77 @@ func createMultiNodeServer(nodeID string, port uint32) *service.LivekitServer {
return s
}
type testRTCServicePath int
const (
testRTCServicePathv0 testRTCServicePath = iota
testRTCServicePathv0SinglePeerConnection
testRTCServicePathv1
)
func (t testRTCServicePath) String() string {
switch t {
case testRTCServicePathv0:
return "v0"
case testRTCServicePathv0SinglePeerConnection:
return "v0-single-peer-connection"
case testRTCServicePathv1:
return "v1"
default:
return fmt.Sprintf("unknown: %d", t)
}
}
var testRTCServicePaths = []testRTCServicePath{
testRTCServicePathv0,
testRTCServicePathv0SinglePeerConnection,
testRTCServicePathv1,
}
func testRTCServicePathToTestClientOptions(testRTCServicePath testRTCServicePath, opts *testclient.Options) {
if opts == nil {
return
}
switch testRTCServicePath {
case testRTCServicePathv0:
opts.RTCServicePath = "/rtc"
case testRTCServicePathv0SinglePeerConnection:
opts.RTCServicePath = "/rtc"
opts.UseJoinRequestQueryParam = true
case testRTCServicePathv1:
opts.RTCServicePath = "/rtc/v1"
opts.UseJoinRequestQueryParam = true
default:
opts.RTCServicePath = "/rtc"
}
}
// creates a client and runs against server
func createRTCClient(name string, port int, opts *testclient.Options) *testclient.RTCClient {
func createRTCClient(name string, port int, testRTCServicePath testRTCServicePath, opts *testclient.Options) *testclient.RTCClient {
var customizer func(token *auth.AccessToken, grants *auth.VideoGrant)
if opts != nil {
customizer = opts.TokenCustomizer
}
token := joinToken(testRoom, name, customizer)
ws, err := testclient.NewWebSocketConn(fmt.Sprintf("ws://localhost:%d", port), token, opts)
if err != nil {
panic(err)
}
c, err := testclient.NewRTCClient(ws, opts)
if err != nil {
panic(err)
}
go c.Run()
return c
return createRTCClientWithToken(token, port, testRTCServicePath, opts)
}
// creates a client and runs against server
func createRTCClientWithToken(token string, port int, opts *testclient.Options) *testclient.RTCClient {
func createRTCClientWithToken(token string, port int, testRTCServicePath testRTCServicePath, opts *testclient.Options) *testclient.RTCClient {
if opts == nil {
opts = &testclient.Options{
AutoSubscribe: true,
}
}
testRTCServicePathToTestClientOptions(testRTCServicePath, opts)
ws, err := testclient.NewWebSocketConn(fmt.Sprintf("ws://localhost:%d", port), token, opts)
if err != nil {
panic(err)
}
c, err := testclient.NewRTCClient(ws, opts)
c, err := testclient.NewRTCClient(ws, opts.UseJoinRequestQueryParam, opts)
if err != nil {
panic(err)
}
@@ -304,7 +364,7 @@ func listRoomToken() string {
return t
}
func stopWriters(writers ...*testclient.TrackWriter) {
func stopWriters(writers ...testclient.TrackWriter) {
for _, w := range writers {
w.Stop()
}
@@ -61,24 +61,28 @@ func TestMultiNodeUpdateRoomMetadata(t *testing.T) {
})
t.Run("when room has a participant", func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeUpdateRoomMetadata_with_participant")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeUpdateRoomMetadata_with_participant")
defer finish()
c1 := createRTCClient("c1", defaultServerPort, nil)
waitUntilConnected(t, c1)
defer c1.Stop()
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
defer c1.Stop()
_, err := roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: "emptyRoom",
})
require.NoError(t, err)
_, err := roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: "emptyRoom",
})
require.NoError(t, err)
rm, err := roomClient.UpdateRoomMetadata(contextWithToken(adminRoomToken("emptyRoom")), &livekit.UpdateRoomMetadataRequest{
Room: "emptyRoom",
Metadata: "updated metadata",
})
require.NoError(t, err)
require.Equal(t, "updated metadata", rm.Metadata)
rm, err := roomClient.UpdateRoomMetadata(contextWithToken(adminRoomToken("emptyRoom")), &livekit.UpdateRoomMetadataRequest{
Room: "emptyRoom",
Metadata: "updated metadata",
})
require.NoError(t, err)
require.Equal(t, "updated metadata", rm.Metadata)
})
}
})
}
@@ -89,85 +93,97 @@ func TestMultiNodeRemoveParticipant(t *testing.T) {
return
}
_, _, finish := setupMultiNodeTest("TestMultiNodeRemoveParticipant")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeRemoveParticipant")
defer finish()
c1 := createRTCClient("mn_remove_participant", defaultServerPort, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
c1 := createRTCClient("mn_remove_participant", defaultServerPort, testRTCServicePath, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.RemoveParticipant(ctx, &livekit.RoomParticipantIdentity{
Room: testRoom,
Identity: "mn_remove_participant",
})
require.NoError(t, err)
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.RemoveParticipant(ctx, &livekit.RoomParticipantIdentity{
Room: testRoom,
Identity: "mn_remove_participant",
})
require.NoError(t, err)
// participant list doesn't show the participant
listRes, err := roomClient.ListParticipants(ctx, &livekit.ListParticipantsRequest{
Room: testRoom,
})
require.NoError(t, err)
require.Len(t, listRes.Participants, 0)
// participant list doesn't show the participant
listRes, err := roomClient.ListParticipants(ctx, &livekit.ListParticipantsRequest{
Room: testRoom,
})
require.NoError(t, err)
require.Len(t, listRes.Participants, 0)
})
}
}
// update participant metadata
func TestMultiNodeUpdateParticipantMetadata(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeUpdateParticipantMetadata")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeUpdateParticipantMetadata")
defer finish()
c1 := createRTCClient("update_participant_metadata", defaultServerPort, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
c1 := createRTCClient("update_participant_metadata", defaultServerPort, testRTCServicePath, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
ctx := contextWithToken(adminRoomToken(testRoom))
res, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "update_participant_metadata",
Metadata: "the new metadata",
})
require.NoError(t, err)
require.Equal(t, "the new metadata", res.Metadata)
ctx := contextWithToken(adminRoomToken(testRoom))
res, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "update_participant_metadata",
Metadata: "the new metadata",
})
require.NoError(t, err)
require.Equal(t, "the new metadata", res.Metadata)
})
}
}
// admin mute published track
func TestMultiNodeMutePublishedTrack(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeMutePublishedTrack")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeMutePublishedTrack")
defer finish()
identity := "mute_published_track"
c1 := createRTCClient(identity, defaultServerPort, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
identity := "mute_published_track"
c1 := createRTCClient(identity, defaultServerPort, testRTCServicePath, nil)
defer c1.Stop()
waitUntilConnected(t, c1)
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
trackIDs := c1.GetPublishedTrackIDs()
require.NotEmpty(t, trackIDs)
trackIDs := c1.GetPublishedTrackIDs()
require.NotEmpty(t, trackIDs)
ctx := contextWithToken(adminRoomToken(testRoom))
// wait for it to be published before
testutils.WithTimeout(t, func() string {
res, err := roomClient.GetParticipant(ctx, &livekit.RoomParticipantIdentity{
Room: testRoom,
Identity: identity,
ctx := contextWithToken(adminRoomToken(testRoom))
// wait for it to be published before
testutils.WithTimeout(t, func() string {
res, err := roomClient.GetParticipant(ctx, &livekit.RoomParticipantIdentity{
Room: testRoom,
Identity: identity,
})
require.NoError(t, err)
if len(res.Tracks) == 2 {
return ""
} else {
return fmt.Sprintf("expected 2 tracks to be published, actual: %d", len(res.Tracks))
}
})
res, err := roomClient.MutePublishedTrack(ctx, &livekit.MuteRoomTrackRequest{
Room: testRoom,
Identity: identity,
TrackSid: trackIDs[0],
Muted: true,
})
require.NoError(t, err)
require.Equal(t, trackIDs[0], res.Track.Sid)
require.True(t, res.Track.Muted)
})
require.NoError(t, err)
if len(res.Tracks) == 2 {
return ""
} else {
return fmt.Sprintf("expected 2 tracks to be published, actual: %d", len(res.Tracks))
}
})
res, err := roomClient.MutePublishedTrack(ctx, &livekit.MuteRoomTrackRequest{
Room: testRoom,
Identity: identity,
TrackSid: trackIDs[0],
Muted: true,
})
require.NoError(t, err)
require.Equal(t, trackIDs[0], res.Track.Sid)
require.True(t, res.Track.Muted)
}
}
+246 -215
View File
@@ -15,6 +15,7 @@
package test
import (
"fmt"
"testing"
"time"
@@ -33,6 +34,7 @@ func TestMultiNodeRouting(t *testing.T) {
t.SkipNow()
return
}
_, _, finish := setupMultiNodeTest("TestMultiNodeRouting")
defer finish()
@@ -42,35 +44,39 @@ func TestMultiNodeRouting(t *testing.T) {
})
require.NoError(t, err)
// one node connecting to node 1, and another connecting to node 2
c1 := createRTCClient("c1", defaultServerPort, nil)
c2 := createRTCClient("c2", secondServerPort, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
// one node connecting to node 1, and another connecting to node 2
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("c2", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
// c1 publishing, and c2 receiving
t1, err := c1.AddStaticTrack("audio/opus", "audio", "webcam")
require.NoError(t, err)
if t1 != nil {
defer t1.Stop()
// c1 publishing, and c2 receiving
t1, err := c1.AddStaticTrack("audio/opus", "audio", "webcam")
require.NoError(t, err)
if t1 != nil {
defer t1.Stop()
}
testutils.WithTimeout(t, func() string {
if len(c2.SubscribedTracks()) == 0 {
return "c2 received no tracks"
}
if len(c2.SubscribedTracks()[c1.ID()]) != 1 {
return "c2 didn't receive track published by c1"
}
tr1 := c2.SubscribedTracks()[c1.ID()][0]
streamID, _ := rtc.UnpackStreamID(tr1.StreamID())
require.Equal(t, c1.ID(), streamID)
return ""
})
remoteC1 := c2.GetRemoteParticipant(c1.ID())
require.Equal(t, "c1", remoteC1.Name)
require.Equal(t, "metadatac1", remoteC1.Metadata)
})
}
testutils.WithTimeout(t, func() string {
if len(c2.SubscribedTracks()) == 0 {
return "c2 received no tracks"
}
if len(c2.SubscribedTracks()[c1.ID()]) != 1 {
return "c2 didn't receive track published by c1"
}
tr1 := c2.SubscribedTracks()[c1.ID()][0]
streamID, _ := rtc.UnpackStreamID(tr1.StreamID())
require.Equal(t, c1.ID(), streamID)
return ""
})
remoteC1 := c2.GetRemoteParticipant(c1.ID())
require.Equal(t, "c1", remoteC1.Name)
require.Equal(t, "metadatac1", remoteC1.Metadata)
}
func TestConnectWithoutCreation(t *testing.T) {
@@ -82,13 +88,16 @@ func TestConnectWithoutCreation(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestConnectWithoutCreation")
defer finish()
c1 := createRTCClient("c1", defaultServerPort, nil)
waitUntilConnected(t, c1)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
c1.Stop()
c1.Stop()
})
}
}
// testing multiple scenarios rooms
func TestMultinodePublishingUponJoining(t *testing.T) {
if testing.Short() {
t.SkipNow()
@@ -118,30 +127,34 @@ func TestMultinodeReconnectAfterNodeShutdown(t *testing.T) {
return
}
_, s2, finish := setupMultiNodeTest("TestMultinodeReconnectAfterNodeShutdown")
defer finish()
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
_, s2, finish := setupMultiNodeTest("TestMultinodeReconnectAfterNodeShutdown")
defer finish()
// creating room on node 1
_, err := roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: testRoom,
NodeId: s2.Node().Id,
})
require.NoError(t, err)
// creating room on node 1
_, err := roomClient.CreateRoom(contextWithToken(createRoomToken()), &livekit.CreateRoomRequest{
Name: testRoom,
NodeId: s2.Node().Id,
})
require.NoError(t, err)
// one node connecting to node 1, and another connecting to node 2
c1 := createRTCClient("c1", defaultServerPort, nil)
c2 := createRTCClient("c2", secondServerPort, nil)
// one node connecting to node 1, and another connecting to node 2
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("c2", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
stopClients(c1, c2)
waitUntilConnected(t, c1, c2)
stopClients(c1, c2)
// stop s2, and connect to room again
s2.Stop(true)
// stop s2, and connect to room again
s2.Stop(true)
time.Sleep(syncDelay)
time.Sleep(syncDelay)
c3 := createRTCClient("c3", defaultServerPort, nil)
waitUntilConnected(t, c3)
c3 := createRTCClient("c3", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c3)
})
}
}
func TestMultinodeDataPublishing(t *testing.T) {
@@ -154,6 +167,8 @@ func TestMultinodeDataPublishing(t *testing.T) {
defer finish()
scenarioDataPublish(t)
scenarioDataUnlabeledPublish(t)
scenarioDataTracksPublishingUponJoining(t)
}
func TestMultiNodeJoinAfterClose(t *testing.T) {
@@ -185,48 +200,52 @@ func TestMultiNodeRefreshToken(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeJoinAfterClose")
defer finish()
// a participant joining with full permissions
c1 := createRTCClient("c1", defaultServerPort, nil)
waitUntilConnected(t, c1)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
// a participant joining with full permissions
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
// update permissions and metadata
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "c1",
Permission: &livekit.ParticipantPermission{
CanPublish: false,
CanSubscribe: true,
},
Metadata: "metadata",
})
require.NoError(t, err)
// update permissions and metadata
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "c1",
Permission: &livekit.ParticipantPermission{
CanPublish: false,
CanSubscribe: true,
},
Metadata: "metadata",
})
require.NoError(t, err)
testutils.WithTimeout(t, func() string {
if c1.RefreshToken() == "" {
return "did not receive refresh token"
}
// parse token to ensure it's correct
verifier, err := auth.ParseAPIToken(c1.RefreshToken())
require.NoError(t, err)
testutils.WithTimeout(t, func() string {
if c1.RefreshToken() == "" {
return "did not receive refresh token"
}
// parse token to ensure it's correct
verifier, err := auth.ParseAPIToken(c1.RefreshToken())
require.NoError(t, err)
grants, err := verifier.Verify(testApiSecret)
require.NoError(t, err)
_, grants, err := verifier.Verify(testApiSecret)
require.NoError(t, err)
if grants.Metadata != "metadata" {
return "metadata did not match"
}
if *grants.Video.CanPublish {
return "canPublish should be false"
}
if *grants.Video.CanPublishData {
return "canPublishData should be false"
}
if !*grants.Video.CanSubscribe {
return "canSubscribe should be true"
}
return ""
})
if grants.Metadata != "metadata" {
return "metadata did not match"
}
if *grants.Video.CanPublish {
return "canPublish should be false"
}
if *grants.Video.CanPublishData {
return "canPublishData should be false"
}
if !*grants.Video.CanSubscribe {
return "canSubscribe should be true"
}
return ""
})
})
}
}
// ensure that token accurately reflects out of band updates
@@ -239,158 +258,170 @@ func TestMultiNodeUpdateAttributes(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeUpdateAttributes")
defer finish()
c1 := createRTCClient("au1", defaultServerPort, &client.Options{
TokenCustomizer: func(token *auth.AccessToken, grants *auth.VideoGrant) {
token.SetAttributes(map[string]string{
"mykey": "au1",
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("au1", defaultServerPort, testRTCServicePath, &client.Options{
TokenCustomizer: func(token *auth.AccessToken, grants *auth.VideoGrant) {
token.SetAttributes(map[string]string{
"mykey": "au1",
})
},
})
},
})
c2 := createRTCClient("au2", secondServerPort, &client.Options{
TokenCustomizer: func(token *auth.AccessToken, grants *auth.VideoGrant) {
token.SetAttributes(map[string]string{
"mykey": "au2",
c2 := createRTCClient("au2", secondServerPort, testRTCServicePath, &client.Options{
TokenCustomizer: func(token *auth.AccessToken, grants *auth.VideoGrant) {
token.SetAttributes(map[string]string{
"mykey": "au2",
})
grants.SetCanUpdateOwnMetadata(true)
},
})
grants.SetCanUpdateOwnMetadata(true)
},
})
waitUntilConnected(t, c1, c2)
waitUntilConnected(t, c1, c2)
testutils.WithTimeout(t, func() string {
rc2 := c1.GetRemoteParticipant(c2.ID())
rc1 := c2.GetRemoteParticipant(c1.ID())
if rc2 == nil || rc1 == nil {
return "participants could not see each other"
}
if rc1.Attributes == nil || rc1.Attributes["mykey"] != "au1" {
return "rc1's initial attributes are incorrect"
}
if rc2.Attributes == nil || rc2.Attributes["mykey"] != "au2" {
return "rc2's initial attributes are incorrect"
}
return ""
})
testutils.WithTimeout(t, func() string {
rc2 := c1.GetRemoteParticipant(c2.ID())
rc1 := c2.GetRemoteParticipant(c1.ID())
if rc2 == nil || rc1 == nil {
return "participants could not see each other"
}
if rc1.Attributes == nil || rc1.Attributes["mykey"] != "au1" {
return "rc1's initial attributes are incorrect"
}
if rc2.Attributes == nil || rc2.Attributes["mykey"] != "au2" {
return "rc2's initial attributes are incorrect"
}
return ""
})
// this one should not go through
_ = c1.SetAttributes(map[string]string{"mykey": "shouldnotchange"})
_ = c2.SetAttributes(map[string]string{"secondkey": "au2"})
// this one should not go through
_ = c1.SetAttributes(map[string]string{"mykey": "shouldnotchange"})
_ = c2.SetAttributes(map[string]string{"secondkey": "au2"})
// updates using room API should succeed
_, err := roomClient.UpdateParticipant(contextWithToken(adminRoomToken(testRoom)), &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "au1",
Attributes: map[string]string{
"secondkey": "au1",
},
})
require.NoError(t, err)
// updates using room API should succeed
_, err := roomClient.UpdateParticipant(contextWithToken(adminRoomToken(testRoom)), &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "au1",
Attributes: map[string]string{
"secondkey": "au1",
},
})
require.NoError(t, err)
testutils.WithTimeout(t, func() string {
rc1 := c2.GetRemoteParticipant(c1.ID())
rc2 := c1.GetRemoteParticipant(c2.ID())
if rc1.Attributes["secondkey"] != "au1" {
return "au1's attribute update failed"
}
if rc2.Attributes["secondkey"] != "au2" {
return "au2's attribute update failed"
}
if rc1.Attributes["mykey"] != "au1" {
return "au1's mykey should not change"
}
if rc2.Attributes["mykey"] != "au2" {
return "au2's mykey should not change"
}
return ""
})
testutils.WithTimeout(t, func() string {
rc1 := c2.GetRemoteParticipant(c1.ID())
rc2 := c1.GetRemoteParticipant(c2.ID())
if rc1.Attributes["secondkey"] != "au1" {
return "au1's attribute update failed"
}
if rc2.Attributes["secondkey"] != "au2" {
return "au2's attribute update failed"
}
if rc1.Attributes["mykey"] != "au1" {
return "au1's mykey should not change"
}
if rc2.Attributes["mykey"] != "au2" {
return "au2's mykey should not change"
}
return ""
})
})
}
}
func TestMultiNodeRevokePublishPermission(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestMultiNodeRevokePublishPermission")
defer finish()
c1 := createRTCClient("c1", defaultServerPort, nil)
c2 := createRTCClient("c2", secondServerPort, nil)
waitUntilConnected(t, c1, c2)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("c2", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
// c1 publishes a track for c2
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
// c1 publishes a track for c2
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
if len(c2.SubscribedTracks()[c1.ID()]) != 2 {
return "c2 did not receive c1's tracks"
}
return ""
})
testutils.WithTimeout(t, func() string {
if len(c2.SubscribedTracks()[c1.ID()]) != 2 {
return "c2 did not receive c1's tracks"
}
return ""
})
// revoke permission
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "c1",
Permission: &livekit.ParticipantPermission{
CanPublish: false,
CanPublishData: true,
CanSubscribe: true,
},
})
require.NoError(t, err)
// revoke permission
ctx := contextWithToken(adminRoomToken(testRoom))
_, err := roomClient.UpdateParticipant(ctx, &livekit.UpdateParticipantRequest{
Room: testRoom,
Identity: "c1",
Permission: &livekit.ParticipantPermission{
CanPublish: false,
CanPublishData: true,
CanSubscribe: true,
},
})
require.NoError(t, err)
// ensure c1 no longer has track published, c2 no longer see track under C1
testutils.WithTimeout(t, func() string {
if len(c1.GetPublishedTrackIDs()) != 0 {
return "c1 did not unpublish tracks"
}
remoteC1 := c2.GetRemoteParticipant(c1.ID())
if remoteC1 == nil {
return "c2 doesn't know about c1"
}
if len(remoteC1.Tracks) != 0 {
return "c2 still has c1's tracks"
}
return ""
})
// ensure c1 no longer has track published, c2 no longer see track under C1
testutils.WithTimeout(t, func() string {
if len(c1.GetPublishedTrackIDs()) != 0 {
return "c1 did not unpublish tracks"
}
remoteC1 := c2.GetRemoteParticipant(c1.ID())
if remoteC1 == nil {
return "c2 doesn't know about c1"
}
if len(remoteC1.Tracks) != 0 {
return "c2 still has c1's tracks"
}
return ""
})
})
}
}
func TestCloseDisconnectedParticipantOnSignalClose(t *testing.T) {
_, _, finish := setupMultiNodeTest("TestCloseDisconnectedParticipantOnSignalClose")
defer finish()
c1 := createRTCClient("c1", secondServerPort, nil)
waitUntilConnected(t, c1)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("c1", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
c2 := createRTCClient("c2", defaultServerPort, &client.Options{
SignalRequestInterceptor: func(msg *livekit.SignalRequest, next client.SignalRequestHandler) error {
switch msg.Message.(type) {
case *livekit.SignalRequest_Offer, *livekit.SignalRequest_Answer, *livekit.SignalRequest_Leave:
return nil
default:
return next(msg)
}
},
SignalResponseInterceptor: func(msg *livekit.SignalResponse, next client.SignalResponseHandler) error {
switch msg.Message.(type) {
case *livekit.SignalResponse_Offer, *livekit.SignalResponse_Answer:
return nil
default:
return next(msg)
}
},
})
c2 := createRTCClient("c2", defaultServerPort, testRTCServicePath, &client.Options{
SignalRequestInterceptor: func(msg *livekit.SignalRequest, next client.SignalRequestHandler) error {
switch msg.Message.(type) {
case *livekit.SignalRequest_Offer, *livekit.SignalRequest_Answer, *livekit.SignalRequest_Leave:
return nil
default:
return next(msg)
}
},
SignalResponseInterceptor: func(msg *livekit.SignalResponse, next client.SignalResponseHandler) error {
switch msg.Message.(type) {
case *livekit.SignalResponse_Offer, *livekit.SignalResponse_Answer:
return nil
default:
return next(msg)
}
},
})
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) != 1 {
return "c1 did not see c2 join"
}
return ""
})
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) != 1 {
return "c1 did not see c2 join"
}
return ""
})
c2.Stop()
c2.Stop()
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) != 0 {
return "c1 did not see c2 removed"
}
return ""
})
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) != 0 {
return "c1 did not see c2 removed"
}
return ""
})
})
}
}
+266 -119
View File
@@ -31,153 +31,286 @@ import (
// a scenario with lots of clients connecting, publishing, and leaving at random periods
func scenarioPublishingUponJoining(t *testing.T) {
c1 := createRTCClient("puj_1", defaultServerPort, nil)
c2 := createRTCClient("puj_2", secondServerPort, &testclient.Options{AutoSubscribe: true})
c3 := createRTCClient("puj_3", defaultServerPort, &testclient.Options{AutoSubscribe: true})
defer stopClients(c1, c2, c3)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("puj_1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("puj_2", secondServerPort, testRTCServicePath, &testclient.Options{AutoSubscribe: true})
c3 := createRTCClient("puj_3", defaultServerPort, testRTCServicePath, &testclient.Options{AutoSubscribe: true})
defer stopClients(c1, c2, c3)
waitUntilConnected(t, c1, c2, c3)
waitUntilConnected(t, c1, c2, c3)
// c1 and c2 publishing, c3 just receiving
writers := publishTracksForClients(t, c1, c2)
defer stopWriters(writers...)
// c1 and c2 publishing, c3 just receiving
writers := publishTracksForClients(t, c1, c2)
defer stopWriters(writers...)
logger.Infow("waiting to receive tracks from c1 and c2")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
if len(tracks[c1.ID()]) != 2 {
return "did not receive tracks from c1"
}
if len(tracks[c2.ID()]) != 2 {
return "did not receive tracks from c2"
}
return ""
})
logger.Infow("waiting to receive tracks from c1 and c2")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
if len(tracks[c1.ID()]) != 2 {
return "did not receive tracks from c1"
}
if len(tracks[c2.ID()]) != 2 {
return "did not receive tracks from c2"
}
return ""
})
// after a delay, c2 reconnects, then publishing
time.Sleep(syncDelay)
c2.Stop()
// after a delay, c2 reconnects, then publishing
time.Sleep(syncDelay)
c2.Stop()
logger.Infow("waiting for c2 tracks to be gone")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
logger.Infow("waiting for c2 tracks to be gone")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
if len(tracks[c1.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 tracks from c1, actual: %d", len(tracks[c1.ID()]))
}
if len(tracks[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
if len(c1.SubscribedTracks()[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
return ""
})
if len(tracks[c1.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 tracks from c1, actual: %d", len(tracks[c1.ID()]))
}
if len(tracks[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
if len(c1.SubscribedTracks()[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
return ""
})
logger.Infow("c2 reconnecting")
// connect to a diff port
c2 = createRTCClient("puj_2", defaultServerPort, nil)
defer c2.Stop()
waitUntilConnected(t, c2)
writers = publishTracksForClients(t, c2)
defer stopWriters(writers...)
logger.Infow("c2 reconnecting")
// connect to a diff port
c2 = createRTCClient("puj_2", defaultServerPort, testRTCServicePath, nil)
defer c2.Stop()
waitUntilConnected(t, c2)
writers = publishTracksForClients(t, c2)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
// "new c2 tracks should be published again",
if len(tracks[c2.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
if len(c1.SubscribedTracks()[c2.ID()]) != 2 {
return fmt.Sprintf("c1 should be subscribed to 2 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
return ""
})
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedTracks()
// "new c2 tracks should be published again",
if len(tracks[c2.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
if len(c1.SubscribedTracks()[c2.ID()]) != 2 {
return fmt.Sprintf("c1 should be subscribed to 2 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
return ""
})
})
}
}
func scenarioReceiveBeforePublish(t *testing.T) {
c1 := createRTCClient("rbp_1", defaultServerPort, nil)
c2 := createRTCClient("rbp_2", defaultServerPort, nil)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("rbp_1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("rbp_2", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
// c1 publishes
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
// c1 publishes
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
// c2 should see some bytes flowing through
testutils.WithTimeout(t, func() string {
if c2.BytesReceived() > 20 {
return ""
} else {
return fmt.Sprintf("c2 only received %d bytes", c2.BytesReceived())
}
})
// c2 should see some bytes flowing through
testutils.WithTimeout(t, func() string {
if c2.BytesReceived() > 20 {
return ""
} else {
return fmt.Sprintf("c2 only received %d bytes", c2.BytesReceived())
}
})
// now publish on C2
writers = publishTracksForClients(t, c2)
defer stopWriters(writers...)
// now publish on C2
writers = publishTracksForClients(t, c2)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
if len(c1.SubscribedTracks()[c2.ID()]) == 2 {
return ""
} else {
return fmt.Sprintf("expected c1 to receive 2 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
})
testutils.WithTimeout(t, func() string {
if len(c1.SubscribedTracks()[c2.ID()]) == 2 {
return ""
} else {
return fmt.Sprintf("expected c1 to receive 2 tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
})
// now leave, and ensure that it's immediate
c2.Stop()
// now leave, and ensure that it's immediate
c2.Stop()
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) > 0 {
return fmt.Sprintf("expected no remote participants, actual: %v", c1.RemoteParticipants())
}
return ""
})
testutils.WithTimeout(t, func() string {
if len(c1.RemoteParticipants()) > 0 {
return fmt.Sprintf("expected no remote participants, actual: %v", c1.RemoteParticipants())
}
return ""
})
})
}
}
func scenarioDataPublish(t *testing.T) {
c1 := createRTCClient("dp1", defaultServerPort, nil)
c2 := createRTCClient("dp2", secondServerPort, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("scenarioDataPublish/testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("dp1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("dp2", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
payload := "test bytes"
payload := "test bytes"
received := atomic.NewBool(false)
c2.OnDataReceived = func(data []byte, sid string) {
if string(data) == payload && livekit.ParticipantID(sid) == c1.ID() {
received.Store(true)
}
received := atomic.NewBool(false)
c2.OnDataReceived = func(data []byte, sid string) {
if string(data) == payload && livekit.ParticipantID(sid) == c1.ID() {
received.Store(true)
}
}
require.NoError(t, c1.PublishData([]byte(payload), livekit.DataPacket_RELIABLE))
testutils.WithTimeout(t, func() string {
if received.Load() {
return ""
} else {
return "c2 did not receive published data"
}
})
})
}
}
require.NoError(t, c1.PublishData([]byte(payload), livekit.DataPacket_RELIABLE))
func scenarioDataUnlabeledPublish(t *testing.T) {
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("scenarioDataUnlabeledPublish/testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("dp1", defaultServerPort, testRTCServicePath, nil)
c2 := createRTCClient("dp2", secondServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1, c2)
defer stopClients(c1, c2)
testutils.WithTimeout(t, func() string {
if received.Load() {
return ""
} else {
return "c2 did not receive published data"
}
})
payload := "test unlabeled bytes"
received := atomic.NewBool(false)
c2.OnDataReceived = func(data []byte, _sid string) {
if string(data) == payload {
received.Store(true)
}
}
require.NoError(t, c1.PublishDataUnlabeled([]byte(payload)))
testutils.WithTimeout(t, func() string {
if received.Load() {
return ""
} else {
return "c2 did not receive published data unlabeled"
}
})
})
}
}
func scenarioDataTracksPublishingUponJoining(t *testing.T) {
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("scenarioDataTracksPublishingUponJoining/testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("dtpuj_1", defaultServerPort, testRTCServicePath, &testclient.Options{AutoSubscribeDataTrack: true})
c2 := createRTCClient("dtpuj_2", secondServerPort, testRTCServicePath, &testclient.Options{AutoSubscribeDataTrack: true})
c3 := createRTCClient("dtpuj_3", defaultServerPort, testRTCServicePath, &testclient.Options{AutoSubscribeDataTrack: true})
defer stopClients(c1, c2, c3)
waitUntilConnected(t, c1, c2, c3)
// c1 and c2 publishing, c3 just receiving
writers := publishDataTracksForClients(t, c1, c2)
defer stopWriters(writers...)
logger.Infow("waiting to receive data tracks from c1 and c2")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedDataTracks()
if len(tracks[c1.ID()]) != 2 {
return "did not receive data tracks from c1"
}
if len(tracks[c2.ID()]) != 2 {
return "did not receive data tracks from c2"
}
for _, dts := range tracks {
for _, dt := range dts {
if dt.NumReceivedPackets() == 0 {
return fmt.Sprintf("no packets received from %s", dt.ID())
}
}
}
return ""
})
// after a delay, c2 reconnects, then publishing
time.Sleep(syncDelay)
c2.Stop()
logger.Infow("waiting for c2 data tracks to be gone")
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedDataTracks()
if len(tracks[c1.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 data tracks from c1, actual: %d", len(tracks[c1.ID()]))
}
if len(tracks[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 data tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
if len(c1.SubscribedDataTracks()[c2.ID()]) != 0 {
return fmt.Sprintf("c3 should be subscribed to 0 data tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
return ""
})
logger.Infow("c2 reconnecting")
// connect to a diff port
c2 = createRTCClient("dtpuj_2", defaultServerPort, testRTCServicePath, &testclient.Options{AutoSubscribeDataTrack: true})
defer c2.Stop()
waitUntilConnected(t, c2)
writers = publishDataTracksForClients(t, c2)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
tracks := c3.SubscribedDataTracks()
// new c2 data tracks should be published again
if len(tracks[c2.ID()]) != 2 {
return fmt.Sprintf("c3 should be subscribed to 2 data tracks from c2, actual: %d", len(tracks[c2.ID()]))
}
for _, dt := range tracks[c2.ID()] {
if dt.NumReceivedPackets() == 0 {
return fmt.Sprintf("c3 did not receive packets from c2 data track after reconnecting %s", dt.ID())
}
}
if len(c1.SubscribedDataTracks()[c2.ID()]) != 2 {
return fmt.Sprintf("c1 should be subscribed to 2 data tracks from c2, actual: %d", len(c1.SubscribedTracks()[c2.ID()]))
}
for _, dt := range c1.SubscribedDataTracks()[c2.ID()] {
if dt.NumReceivedPackets() == 0 {
return fmt.Sprintf("c1 did not receive packets from c2 data track after reconnecting %s", dt.ID())
}
}
return ""
})
})
}
}
func scenarioJoinClosedRoom(t *testing.T) {
c1 := createRTCClient("jcr1", defaultServerPort, nil)
waitUntilConnected(t, c1)
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("jcr1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
// close room with room client
_, err := roomClient.DeleteRoom(contextWithToken(createRoomToken()), &livekit.DeleteRoomRequest{
Room: testRoom,
})
require.NoError(t, err)
// close room with room client
_, err := roomClient.DeleteRoom(contextWithToken(createRoomToken()), &livekit.DeleteRoomRequest{
Room: testRoom,
})
require.NoError(t, err)
// now join again
c2 := createRTCClient("jcr2", defaultServerPort, nil)
waitUntilConnected(t, c2)
stopClients(c2)
// now join again
c2 := createRTCClient("jcr2", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c2)
stopClients(c2)
})
}
}
// close a room that has been created, but no participant has joined
@@ -194,15 +327,15 @@ func closeNonRTCRoom(t *testing.T) {
require.NoError(t, err)
}
func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []*testclient.TrackWriter {
func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []testclient.TrackWriter {
logger.Infow("publishing tracks for clients")
var writers []*testclient.TrackWriter
var writers []testclient.TrackWriter
for i := range clients {
c := clients[i]
tw, err := c.AddStaticTrack("audio/opus", "audio", "webcam")
require.NoError(t, err)
writers = append(writers, tw)
tw, err = c.AddStaticTrack("video/vp8", "video", "webcam")
require.NoError(t, err)
writers = append(writers, tw)
@@ -210,6 +343,20 @@ func publishTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []*
return writers
}
func publishDataTracksForClients(t *testing.T, clients ...*testclient.RTCClient) []testclient.TrackWriter {
logger.Infow("publishing data tracks for clients")
var writers []testclient.TrackWriter
for i := range clients {
c := clients[i]
for range 2 {
dtw, err := c.PublishDataTrack()
require.NoError(t, err)
writers = append(writers, dtw)
}
}
return writers
}
// Room service tests
func roomServiceListRoom(t *testing.T) {
File diff suppressed because it is too large Load Diff
+72 -68
View File
@@ -25,12 +25,12 @@ import (
"time"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protojson"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/guid"
"github.com/livekit/protocol/utils/protojson"
"github.com/livekit/protocol/webhook"
"github.com/livekit/livekit-server/pkg/config"
@@ -45,78 +45,82 @@ func TestWebhooks(t *testing.T) {
require.NoError(t, err)
defer finish()
c1 := createRTCClient("c1", defaultServerPort, nil)
waitUntilConnected(t, c1)
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventRoomStarted) == nil {
return "did not receive RoomStarted"
}
if ts.GetEvent(webhook.EventParticipantJoined) == nil {
return "did not receive ParticipantJoined"
}
return ""
})
for _, testRTCServicePath := range testRTCServicePaths {
t.Run(fmt.Sprintf("testRTCServicePath=%s", testRTCServicePath.String()), func(t *testing.T) {
c1 := createRTCClient("c1", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c1)
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventRoomStarted) == nil {
return "did not receive RoomStarted"
}
if ts.GetEvent(webhook.EventParticipantJoined) == nil {
return "did not receive ParticipantJoined"
}
return ""
})
// first participant join should have started the room
started := ts.GetEvent(webhook.EventRoomStarted)
require.Equal(t, testRoom, started.Room.Name)
require.NotEmpty(t, started.Id)
require.Greater(t, started.CreatedAt, time.Now().Unix()-100)
require.GreaterOrEqual(t, time.Now().Unix(), started.CreatedAt)
joined := ts.GetEvent(webhook.EventParticipantJoined)
require.Equal(t, "c1", joined.Participant.Identity)
ts.ClearEvents()
// first participant join should have started the room
started := ts.GetEvent(webhook.EventRoomStarted)
require.Equal(t, testRoom, started.Room.Name)
require.NotEmpty(t, started.Id)
require.Greater(t, started.CreatedAt, time.Now().Unix()-100)
require.GreaterOrEqual(t, time.Now().Unix(), started.CreatedAt)
joined := ts.GetEvent(webhook.EventParticipantJoined)
require.Equal(t, "c1", joined.Participant.Identity)
ts.ClearEvents()
// another participant joins
c2 := createRTCClient("c2", defaultServerPort, nil)
waitUntilConnected(t, c2)
defer c2.Stop()
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventParticipantJoined) == nil {
return "did not receive ParticipantJoined"
}
return ""
})
joined = ts.GetEvent(webhook.EventParticipantJoined)
require.Equal(t, "c2", joined.Participant.Identity)
ts.ClearEvents()
// another participant joins
c2 := createRTCClient("c2", defaultServerPort, testRTCServicePath, nil)
waitUntilConnected(t, c2)
defer c2.Stop()
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventParticipantJoined) == nil {
return "did not receive ParticipantJoined"
}
return ""
})
joined = ts.GetEvent(webhook.EventParticipantJoined)
require.Equal(t, "c2", joined.Participant.Identity)
ts.ClearEvents()
// track published
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
ev := ts.GetEvent(webhook.EventTrackPublished)
if ev == nil {
return "did not receive TrackPublished"
}
require.NotNil(t, ev.Track, "TrackPublished did not include trackInfo")
require.Equal(t, string(c1.ID()), ev.Participant.Sid)
return ""
})
ts.ClearEvents()
// track published
writers := publishTracksForClients(t, c1)
defer stopWriters(writers...)
testutils.WithTimeout(t, func() string {
ev := ts.GetEvent(webhook.EventTrackPublished)
if ev == nil {
return "did not receive TrackPublished"
}
require.NotNil(t, ev.Track, "TrackPublished did not include trackInfo")
require.Equal(t, string(c1.ID()), ev.Participant.Sid)
return ""
})
ts.ClearEvents()
// first participant leaves
c1.Stop()
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventParticipantLeft) == nil {
return "did not receive ParticipantLeft"
}
return ""
})
left := ts.GetEvent(webhook.EventParticipantLeft)
require.Equal(t, "c1", left.Participant.Identity)
ts.ClearEvents()
// first participant leaves
c1.Stop()
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventParticipantLeft) == nil {
return "did not receive ParticipantLeft"
}
return ""
})
left := ts.GetEvent(webhook.EventParticipantLeft)
require.Equal(t, "c1", left.Participant.Identity)
ts.ClearEvents()
// room closed
rm := server.RoomManager().GetRoom(context.Background(), testRoom)
rm.Close(types.ParticipantCloseReasonNone)
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventRoomFinished) == nil {
return "did not receive RoomFinished"
}
return ""
})
require.Equal(t, testRoom, ts.GetEvent(webhook.EventRoomFinished).Room.Name)
// room closed
rm := server.RoomManager().GetRoom(context.Background(), testRoom)
rm.Close(types.ParticipantCloseReasonNone)
testutils.WithTimeout(t, func() string {
if ts.GetEvent(webhook.EventRoomFinished) == nil {
return "did not receive RoomFinished"
}
return ""
})
require.Equal(t, testRoom, ts.GetEvent(webhook.EventRoomFinished).Room.Name)
})
}
}
func setupServerWithWebhook() (server *service.LivekitServer, testServer *webhookTestServer, finishFunc func(), err error) {