From 2a074df7f7c5d091124e6576e0383c520a8f8f89 Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Feb 2026 10:13:24 -0800 Subject: [PATCH 1/4] WiP --- publication.go | 14 +++++++++----- room.go | 13 ++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/publication.go b/publication.go index 63a48878..159470ec 100644 --- a/publication.go +++ b/publication.go @@ -16,12 +16,12 @@ package lksdk import ( "errors" + "maps" + "slices" "strings" "sync" "time" - "golang.org/x/exp/maps" - "github.com/pion/rtcp" "github.com/pion/webrtc/v4" "go.uber.org/atomic" @@ -339,7 +339,7 @@ func (p *LocalTrackPublication) TrackLocal() webrtc.TrackLocal { func (p *LocalTrackPublication) TrackLocalForSimulcast() []*LocalTrack { p.lock.RLock() defer p.lock.RUnlock() - return maps.Values(p.simulcastTracks) + return slices.Collect(maps.Values(p.simulcastTracks)) } // GetSimulcastTrack returns the simulcast track for a specific quality level. @@ -431,7 +431,7 @@ func (p *LocalTrackPublication) setBackupCodecTracksForSimulcast(st []*LocalTrac func (p *LocalTrackPublication) getBackupCodecTrack() (TrackLocalWithCodec, []*LocalTrack) { p.lock.RLock() defer p.lock.RUnlock() - return p.backupCodecTrack, maps.Values(p.backupCodecTracksForSimulcast) + return p.backupCodecTrack, slices.Collect(maps.Values(p.backupCodecTracksForSimulcast)) } func (p *LocalTrackPublication) setBackupCodecPublished() { @@ -509,7 +509,11 @@ func (p *LocalTrackPublication) setPublishingCodecsQuality(subscribedCodecs []*l mainTrack := backupCodecTrack if len(backupCodecTracksForSimulcast) > 0 { - mainTrack = maps.Values(backupCodecTracksForSimulcast)[0] + iter := maps.Values(backupCodecTracksForSimulcast) + iter(func(l *LocalTrack) bool { + mainTrack = l + return false + }) } if mainTrack == nil || !strings.HasSuffix(strings.ToLower(mainTrack.Codec().MimeType), subscribedCodec.Codec) { p.log.Warnw("subscriber requested backup codec but no track found", nil, "trackID", p.SID(), "codec", subscribedCodec.Codec) diff --git a/room.go b/room.go index 5d5eb192..4618c53c 100644 --- a/room.go +++ b/room.go @@ -15,10 +15,12 @@ package lksdk import ( + "cmp" "context" "fmt" + "maps" "reflect" - "sort" + "slices" "strings" "sync" "time" @@ -26,12 +28,12 @@ import ( "github.com/pion/interceptor" "github.com/pion/rtcp" "github.com/pion/webrtc/v4" - "golang.org/x/exp/maps" "golang.org/x/mod/semver" "google.golang.org/protobuf/proto" protoLogger "github.com/livekit/protocol/logger" protosignalling "github.com/livekit/protocol/signalling" + "github.com/livekit/server-sdk-go/v2/signalling" "github.com/livekit/mediatransportutil/pkg/pacer" @@ -978,10 +980,11 @@ func (r *Room) OnSpeakersChanged(speakerUpdates []*livekit.SpeakerInfo) { } } - activeSpeakers := maps.Values(speakerMap) - sort.Slice(activeSpeakers, func(i, j int) bool { - return activeSpeakers[i].AudioLevel() > activeSpeakers[j].AudioLevel() + activeSpeakersIter := maps.Values(speakerMap) + slices.SortedFunc(activeSpeakersIter, func(p1, p2 Participant) int { + return cmp.Compare(p1.AudioLevel(), p2.AudioLevel()) }) + activeSpeakers := slices.Collect(activeSpeakersIter) r.lock.Lock() r.activeSpeakers = activeSpeakers r.lock.Unlock() From 588e5ee3b83b681dec8ff7118110d5ab8b031aad Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Feb 2026 10:22:57 -0800 Subject: [PATCH 2/4] Fix --- room.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/room.go b/room.go index 4618c53c..1f179f47 100644 --- a/room.go +++ b/room.go @@ -980,11 +980,9 @@ func (r *Room) OnSpeakersChanged(speakerUpdates []*livekit.SpeakerInfo) { } } - activeSpeakersIter := maps.Values(speakerMap) - slices.SortedFunc(activeSpeakersIter, func(p1, p2 Participant) int { + activeSpeakers := slices.SortedFunc(maps.Values(speakerMap), func(p1, p2 Participant) int { return cmp.Compare(p1.AudioLevel(), p2.AudioLevel()) }) - activeSpeakers := slices.Collect(activeSpeakersIter) r.lock.Lock() r.activeSpeakers = activeSpeakers r.lock.Unlock() From 50528f45f8fea5245cb113918354a614808be8be Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Feb 2026 10:28:35 -0800 Subject: [PATCH 3/4] Add unit test --- go.mod | 2 +- room.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index c3e67962..7b7110cf 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/twitchtv/twirp v8.1.3+incompatible go.uber.org/atomic v1.11.0 golang.org/x/crypto v0.48.0 - golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a + golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect google.golang.org/protobuf v1.36.11 ) diff --git a/room.go b/room.go index 1f179f47..5ce20a4c 100644 --- a/room.go +++ b/room.go @@ -981,7 +981,7 @@ func (r *Room) OnSpeakersChanged(speakerUpdates []*livekit.SpeakerInfo) { } activeSpeakers := slices.SortedFunc(maps.Values(speakerMap), func(p1, p2 Participant) int { - return cmp.Compare(p1.AudioLevel(), p2.AudioLevel()) + return cmp.Compare(p2.AudioLevel(), p1.AudioLevel()) }) r.lock.Lock() r.activeSpeakers = activeSpeakers From c62fe73f1e5ee8da9ffc4aa1d9c22d70e66e3cdd Mon Sep 17 00:00:00 2001 From: Benjamin Pracht Date: Fri, 27 Feb 2026 10:30:06 -0800 Subject: [PATCH 4/4] Add unit test --- room_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 room_test.go diff --git a/room_test.go b/room_test.go new file mode 100644 index 00000000..d78fa87d --- /dev/null +++ b/room_test.go @@ -0,0 +1,88 @@ +package lksdk + +import ( + "testing" + + "github.com/livekit/protocol/livekit" + "github.com/stretchr/testify/require" +) + +func TestOnSpeakersChanged(t *testing.T) { + room := NewRoom(nil) + + // Set up the local participant with a SID. + room.LocalParticipant.updateInfo(&livekit.ParticipantInfo{ + Sid: "local", + Identity: "local-identity", + }) + + // Add three remote participants. + room.addRemoteParticipant(&livekit.ParticipantInfo{ + Sid: "remote-1", + Identity: "remote-1-identity", + }, false) + room.addRemoteParticipant(&livekit.ParticipantInfo{ + Sid: "remote-2", + Identity: "remote-2-identity", + }, false) + room.addRemoteParticipant(&livekit.ParticipantInfo{ + Sid: "remote-3", + Identity: "remote-3-identity", + }, false) + + t.Run("active speakers sorted by audio level descending", func(t *testing.T) { + room.OnSpeakersChanged([]*livekit.SpeakerInfo{ + {Sid: "remote-2", Level: 0.8, Active: true}, + {Sid: "local", Level: 0.5, Active: true}, + {Sid: "remote-1", Level: 0.2, Active: true}, + {Sid: "remote-3", Level: 0.9, Active: true}, + }) + + speakers := room.ActiveSpeakers() + require.Len(t, speakers, 4) + require.Equal(t, float32(0.9), speakers[0].AudioLevel()) + require.Equal(t, float32(0.8), speakers[1].AudioLevel()) + require.Equal(t, float32(0.5), speakers[2].AudioLevel()) + require.Equal(t, float32(0.2), speakers[3].AudioLevel()) + + require.Equal(t, "remote-3", speakers[0].SID()) + require.Equal(t, "remote-2", speakers[1].SID()) + require.Equal(t, "local", speakers[2].SID()) + require.Equal(t, "remote-1", speakers[3].SID()) + }) + + t.Run("inactive speaker removed from list", func(t *testing.T) { + room.OnSpeakersChanged([]*livekit.SpeakerInfo{ + {Sid: "remote-2", Level: 0, Active: false}, + }) + + speakers := room.ActiveSpeakers() + require.Len(t, speakers, 3) + for _, s := range speakers { + require.NotEqual(t, "remote-2", s.SID()) + } + }) + + t.Run("updated levels re-sort speakers", func(t *testing.T) { + // remote-1 was 0.2, now bump it to 1.0 so it becomes the loudest. + room.OnSpeakersChanged([]*livekit.SpeakerInfo{ + {Sid: "remote-1", Level: 1.0, Active: true}, + }) + + speakers := room.ActiveSpeakers() + require.Len(t, speakers, 3) + require.Equal(t, "remote-1", speakers[0].SID()) + require.Equal(t, "remote-3", speakers[1].SID()) + require.Equal(t, "local", speakers[2].SID()) + }) + + t.Run("unknown participant is ignored", func(t *testing.T) { + room.OnSpeakersChanged([]*livekit.SpeakerInfo{ + {Sid: "unknown", Level: 0.5, Active: true}, + }) + + // List should be unchanged from the previous subtest. + speakers := room.ActiveSpeakers() + require.Len(t, speakers, 3) + }) +}