Skip to content
Merged
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
14 changes: 9 additions & 5 deletions publication.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions room.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,25 @@
package lksdk

import (
"cmp"
"context"
"fmt"
"maps"
"reflect"
"sort"
"slices"
"strings"
"sync"
"time"

"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"
Expand Down Expand Up @@ -978,9 +980,8 @@ 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()
activeSpeakers := slices.SortedFunc(maps.Values(speakerMap), func(p1, p2 Participant) int {
return cmp.Compare(p2.AudioLevel(), p1.AudioLevel())
})
r.lock.Lock()
r.activeSpeakers = activeSpeakers
Expand Down
88 changes: 88 additions & 0 deletions room_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
Loading