Server-driven connection quality detection (#167)

This commit is contained in:
David Zhao
2021-11-03 21:05:20 -07:00
committed by GitHub
parent 862a212b93
commit aa9534b7fb
14 changed files with 818 additions and 81 deletions

9
go.mod
View File

@@ -7,7 +7,7 @@ require (
github.com/c9s/goprocinfo v0.0.0-20210130143923-c95fcf8c64a8
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/gammazero/workerpool v1.1.2
github.com/go-logr/logr v1.1.0
github.com/go-logr/logr v1.2.0
github.com/go-logr/zapr v1.1.0
github.com/go-redis/redis/v8 v8.11.3
github.com/google/subcommands v1.2.0 // indirect
@@ -15,7 +15,7 @@ require (
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/golang-lru v0.5.4
github.com/jxskiss/base62 v0.0.0-20191017122030-4f11678b909b
github.com/livekit/protocol v0.9.12-0.20211102204637-f3bd2c316e7b
github.com/livekit/protocol v0.10.0
github.com/magefile/mage v1.11.0
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
github.com/mitchellh/go-homedir v1.1.0
@@ -42,11 +42,8 @@ require (
go.uber.org/zap v1.19.1
golang.org/x/mod v0.5.1 // indirect
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
golang.org/x/tools v0.1.7 // indirect
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
replace github.com/pion/ion-sfu => github.com/livekit/ion-sfu v1.20.14
// replace github.com/livekit/protocol => ../protocol
replace github.com/pion/ion-sfu => github.com/livekit/ion-sfu v1.20.16

24
go.sum
View File

@@ -112,11 +112,13 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.1.0 h1:nAbevmWlS2Ic4m4+/An5NXkaGqlqpbBgdcuThZxnZyI=
github.com/go-logr/logr v1.1.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v1.1.0 h1:rZHor2gcVGCG11UlKl+WUsfCMOOi2k/mTCDKDK6zZws=
github.com/go-logr/zapr v1.1.0/go.mod h1:YShqdLLTU346TNVu8Tvwe3bOo6gc75oZ1joeE+1lYdQ=
github.com/go-logr/zerologr v1.2.0 h1:oS1fjSSEHwpv8Lam3SNmPTLTUw6V4DoB2ZzryqrkMB0=
github.com/go-logr/zerologr v1.2.0/go.mod h1:O82obOiXzyxiBNgAMRT1m+XbOvY8K18Kf6XhT52oqoc=
github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8=
github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -248,10 +250,10 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/shortuuid/v3 v3.0.6 h1:pr15YQyvhiSX/qPxncFtqk+v4xLEpOZObbsY/mKrcvA=
github.com/lithammer/shortuuid/v3 v3.0.6/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/livekit/ion-sfu v1.20.14 h1:/eCaRUIa8KxyaTdjOQFJJJGDrjIVClL14BkYO2NqVSw=
github.com/livekit/ion-sfu v1.20.14/go.mod h1:Nmf1dro+y5vr0laNSJSrYDMDYrjERcEWVbWyenEWZ0A=
github.com/livekit/protocol v0.9.12-0.20211102204637-f3bd2c316e7b h1:jdos5NJ+yOn6AdOLrGp6IeGit+hJyN6W9XtAP5wC9YY=
github.com/livekit/protocol v0.9.12-0.20211102204637-f3bd2c316e7b/go.mod h1:7ir9zSlgnrPQoGGNv4f8U/c9QrWh+ogC9B5xVbJNedM=
github.com/livekit/ion-sfu v1.20.16 h1:B4+z0sf4t3zZSXFIwHive8malNn6Vje+7b1OW4ETDOM=
github.com/livekit/ion-sfu v1.20.16/go.mod h1:sUjL3tZRROs3NjCm6ZLT+IsisdYVRtxfq4OhVFHVd/A=
github.com/livekit/protocol v0.10.0 h1:s2zf1+G1Tcx6UKIf8mbRzbQ4ELdyS0mlLGsFkTVT5Aw=
github.com/livekit/protocol v0.10.0/go.mod h1:7ir9zSlgnrPQoGGNv4f8U/c9QrWh+ogC9B5xVbJNedM=
github.com/lucsky/cuid v1.0.2 h1:z4XlExeoderxoPj2/dxKOyPxe9RCOu7yNq9/XWxIUMQ=
github.com/lucsky/cuid v1.0.2/go.mod h1:QaaJqckboimOmhRSJXSx/+IT+VTfxfPGSo/6mfgUfmE=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
@@ -418,9 +420,10 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -615,14 +618,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -664,7 +665,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=

View File

@@ -28,6 +28,10 @@ var (
{Type: webrtc.TypeRTCPFBNACK, Parameter: "pli"}}
)
const (
lostUpdateDelta = time.Second
)
// MediaTrack represents a WebRTC track that needs to be forwarded
// Implements the PublishedTrack interface
type MediaTrack struct {
@@ -38,6 +42,7 @@ type MediaTrack struct {
muted utils.AtomicFlag
numUpTracks uint32
simulcasted utils.AtomicFlag
buffer *buffer.Buffer
// channel to send RTCP packets to the source
lock sync.RWMutex
@@ -48,6 +53,14 @@ type MediaTrack struct {
receiver sfu.Receiver
lastPLI time.Time
// track audio fraction lost
fracLostLock sync.Mutex
maxDownFracLost uint8
maxDownFracLostTs time.Time
currentUpFracLost uint32
maxUpFracLost uint8
maxUpFracLostTs time.Time
onClose func()
}
@@ -62,6 +75,7 @@ type MediaTrackParams struct {
ReceiverConfig ReceiverConfig
AudioConfig config.AudioConfig
Stats *stats.RoomStatsReporter
Logger logger.Logger
}
func NewMediaTrack(track *webrtc.TrackRemote, params MediaTrackParams) *MediaTrack {
@@ -130,6 +144,10 @@ func (t *MediaTrack) IsSubscriber(subId string) bool {
return t.subscribedTracks[subId] != nil
}
func (t *MediaTrack) PublishLossPercentage() uint32 {
return FixedPointToPercent(uint8(atomic.LoadUint32(&t.currentUpFracLost)))
}
// AddSubscriber subscribes sub to current mediaTrack
func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
if !sub.CanSubscribe() {
@@ -241,7 +259,7 @@ func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
if sender == nil {
return
}
logger.Debugw("removing peerconnection track",
t.params.Logger.Debugw("removing peerconnection track",
"track", t.ID(),
"pIDs", []string{t.params.ParticipantID, sub.ID()},
"participant", sub.Identity(),
@@ -252,15 +270,18 @@ func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
return
}
if _, ok := err.(*rtcerr.InvalidStateError); !ok {
logger.Warnw("could not remove remoteTrack from forwarder", err,
t.params.Logger.Warnw("could not remove remoteTrack from forwarder", err,
"participant", sub.Identity(), "pID", sub.ID())
}
}
sub.RemoveSubscribedTrack(t.params.ParticipantID, subTrack)
sub.RemoveSubscribedTrack(subTrack)
sub.Negotiate()
}()
})
if t.Kind() == livekit.TrackType_AUDIO {
downTrack.AddReceiverReportListener(t.handleMaxLossFeedback)
}
t.subscribedTracks[sub.ID()] = subTrack
subTrack.SetPublisherMuted(t.IsMuted())
@@ -268,7 +289,7 @@ func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
t.receiver.AddDownTrack(downTrack, t.shouldStartWithBestQuality())
// since sub will lock, run it in a goroutine to avoid deadlocks
go func() {
sub.AddSubscribedTrack(t.params.ParticipantID, subTrack)
sub.AddSubscribedTrack(subTrack)
sub.Negotiate()
}()
@@ -276,8 +297,24 @@ func (t *MediaTrack) AddSubscriber(sub types.Participant) error {
return nil
}
func (t *MediaTrack) NumUpTracks() uint32 {
return atomic.LoadUint32(&t.numUpTracks)
func (t *MediaTrack) NumUpTracks() (uint32, uint32) {
numRegistered := atomic.LoadUint32(&t.numUpTracks)
numPublishing := uint32(0)
if t.simulcasted.Get() {
t.lock.RLock()
if t.receiver != nil {
for i := int32(0); i < 3; i++ {
if t.receiver.HasSpatialLayer(i) {
numPublishing += 1
}
}
}
t.lock.RUnlock()
} else {
numPublishing = 1
}
return numPublishing, numRegistered
}
// AddReceiver adds a new RTP receiver to the track
@@ -286,13 +323,7 @@ func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.Tra
defer t.lock.Unlock()
buff, rtcpReader := t.params.BufferFactory.GetBufferPair(uint32(track.SSRC()))
buff.OnFeedback(func(fb []rtcp.Packet) {
if t.params.Stats != nil {
t.params.Stats.Incoming.HandleRTCP(fb)
}
// feedback for the source RTCP
t.params.RTCPChan <- fb
})
buff.OnFeedback(t.handlePublisherFeedback)
if t.Kind() == livekit.TrackType_AUDIO {
t.audioLevel = NewAudioLevel(t.params.AudioConfig.ActiveLevel, t.params.AudioConfig.MinPercentile)
@@ -310,7 +341,7 @@ func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.Tra
rtcpReader.OnPacket(func(bytes []byte) {
pkts, err := rtcp.Unmarshal(bytes)
if err != nil {
logger.Errorw("could not unmarshal RTCP", err)
t.params.Logger.Errorw("could not unmarshal RTCP", err)
return
}
@@ -341,16 +372,17 @@ func (t *MediaTrack) AddReceiver(receiver *webrtc.RTPReceiver, track *webrtc.Tra
onclose()
}
})
t.receiver.OnFractionLostFB(func(lost uint8) {
buff.SetLastFractionLostReport(lost)
})
t.params.Stats.AddPublishedTrack(t.Kind().String())
if t.Kind() == livekit.TrackType_AUDIO {
t.buffer = buff
}
}
t.receiver.AddUpTrack(track, buff, t.shouldStartWithBestQuality())
// when RID is set, track is simulcasted
// TODO: how does this work with FF, SSRC based simulcast?
t.simulcasted.TrySet(track.RID() != "")
atomic.AddUint32(&t.numUpTracks, 1)
if atomic.LoadUint32(&t.numUpTracks) > 1 {
t.simulcasted.TrySet(true)
}
buff.Bind(receiver.GetParameters(), buffer.Options{
MaxBitRate: t.params.ReceiverConfig.maxBitrate,
@@ -369,7 +401,7 @@ func (t *MediaTrack) RemoveSubscriber(participantId string) {
}
func (t *MediaTrack) RemoveAllSubscribers() {
logger.Debugw("removing all subscribers", "track", t.ID())
t.params.Logger.Debugw("removing all subscribers", "track", t.ID())
t.lock.Lock()
defer t.lock.Unlock()
for _, subTrack := range t.subscribedTracks {
@@ -449,7 +481,7 @@ func (t *MediaTrack) sendDownTrackBindingReports(sub types.Participant) {
i := 0
for {
if err := sub.SubscriberPC().WriteRTCP(batch); err != nil {
logger.Errorw("could not write RTCP", err)
t.params.Logger.Errorw("could not write RTCP", err)
return
}
if i > 5 {
@@ -461,6 +493,72 @@ func (t *MediaTrack) sendDownTrackBindingReports(sub types.Participant) {
}()
}
func (t *MediaTrack) handlePublisherFeedback(packets []rtcp.Packet) {
var maxLost uint8
var hasSenderReport bool
for _, p := range packets {
switch pkt := p.(type) {
case *rtcp.SenderReport:
for _, rr := range pkt.Reports {
if rr.FractionLost > maxLost {
maxLost = rr.FractionLost
}
hasSenderReport = true
}
}
}
if hasSenderReport {
t.fracLostLock.Lock()
if maxLost > t.maxUpFracLost {
t.maxUpFracLost = maxLost
}
now := time.Now()
if now.Sub(t.maxUpFracLostTs) > lostUpdateDelta {
atomic.StoreUint32(&t.currentUpFracLost, uint32(t.maxUpFracLost))
t.maxUpFracLost = 0
t.maxUpFracLostTs = now
}
t.fracLostLock.Unlock()
}
if t.params.Stats != nil {
t.params.Stats.Incoming.HandleRTCP(packets)
}
// also look for sender reports
// feedback for the source RTCP
t.params.RTCPChan <- packets
}
// handles max loss for audio packets
func (t *MediaTrack) handleMaxLossFeedback(_ *sfu.DownTrack, report *rtcp.ReceiverReport) {
var (
shouldUpdate bool
maxLost uint8
)
t.fracLostLock.Lock()
for _, rr := range report.Reports {
if t.maxDownFracLost < rr.FractionLost {
t.maxDownFracLost = rr.FractionLost
}
}
now := time.Now()
if now.Sub(t.maxDownFracLostTs) > lostUpdateDelta {
shouldUpdate = true
maxLost = t.maxDownFracLost
t.maxDownFracLost = 0
t.maxDownFracLostTs = now
}
t.fracLostLock.Unlock()
if shouldUpdate && t.buffer != nil {
// ok to access buffer since receivers are added before subscribers
t.buffer.SetLastFractionLostReport(maxLost)
}
}
func (t *MediaTrack) DebugInfo() map[string]interface{} {
info := map[string]interface{}{
"ID": t.ID(),

View File

@@ -78,6 +78,8 @@ type ParticipantImpl struct {
publishedTracks map[string]types.PublishedTrack
// client intended to publish, yet to be reconciled
pendingTracks map[string]*livekit.TrackInfo
// keep track of other publishers identities that we are subscribed to
subscribedTo sync.Map // string => struct{}
lock sync.RWMutex
once sync.Once
@@ -600,6 +602,14 @@ func (p *ParticipantImpl) SendRoomUpdate(room *livekit.Room) error {
})
}
func (p *ParticipantImpl) SendConnectionQualityUpdate(update *livekit.ConnectionQualityUpdate) error {
return p.writeMessage(&livekit.SignalResponse{
Message: &livekit.SignalResponse_ConnectionQuality{
ConnectionQuality: update,
},
})
}
func (p *ParticipantImpl) SetTrackMuted(trackId string, muted bool, fromAdmin bool) {
isPending := false
p.lock.RLock()
@@ -664,6 +674,68 @@ func (p *ParticipantImpl) GetAudioLevel() (level uint8, active bool) {
return
}
func (p *ParticipantImpl) GetConnectionQuality() livekit.ConnectionQuality {
// avg loss across all tracks, weigh published the same as subscribed
var pubLoss, subLoss uint32
var reducedQualityPub bool
var reducedQualitySub bool
p.lock.RLock()
defer p.lock.RUnlock()
for _, pubTrack := range p.publishedTracks {
if pubTrack.IsMuted() {
continue
}
pubLoss += pubTrack.PublishLossPercentage()
publishing, registered := pubTrack.NumUpTracks()
if registered > 0 && publishing != registered {
reducedQualityPub = true
}
}
numTracks := uint32(len(p.publishedTracks))
if numTracks > 0 {
pubLoss /= numTracks
}
for _, subTrack := range p.subscribedTracks {
if subTrack.IsMuted() {
continue
}
if subTrack.DownTrack().TargetSpatialLayer() < subTrack.DownTrack().MaxSpatialLayer() {
reducedQualitySub = true
}
subLoss += subTrack.SubscribeLossPercentage()
}
numTracks = uint32(len(p.subscribedTracks))
if numTracks > 0 {
subLoss /= numTracks
}
avgLoss := (pubLoss + subLoss) / 2
if avgLoss >= 4 {
return livekit.ConnectionQuality_POOR
} else if avgLoss <= 2 && !reducedQualityPub && !reducedQualitySub {
return livekit.ConnectionQuality_EXCELLENT
}
return livekit.ConnectionQuality_GOOD
}
func (p *ParticipantImpl) IsSubscribedTo(identity string) bool {
_, ok := p.subscribedTo.Load(identity)
return ok
}
func (p *ParticipantImpl) GetSubscribedParticipants() []string {
var identities []string
p.subscribedTo.Range(func(key, _ interface{}) bool {
if identity, ok := key.(string); ok {
identities = append(identities, identity)
}
return true
})
return identities
}
func (p *ParticipantImpl) CanPublish() bool {
return p.permission == nil || p.permission.CanPublish
}
@@ -717,21 +789,32 @@ func (p *ParticipantImpl) GetSubscribedTracks() []types.SubscribedTrack {
}
// AddSubscribedTrack adds a track to the participant's subscribed list
func (p *ParticipantImpl) AddSubscribedTrack(pubId string, subTrack types.SubscribedTrack) {
p.params.Logger.Debugw("added subscribedTrack", "pIDs", []string{pubId, p.ID()},
func (p *ParticipantImpl) AddSubscribedTrack(subTrack types.SubscribedTrack) {
p.params.Logger.Debugw("added subscribedTrack", "publisher", subTrack.PublisherIdentity(),
"participant", p.Identity(), "track", subTrack.ID())
p.lock.Lock()
p.subscribedTracks[subTrack.ID()] = subTrack
p.lock.Unlock()
p.subscribedTo.Store(subTrack.PublisherIdentity(), struct{}{})
}
// RemoveSubscribedTrack removes a track to the participant's subscribed list
func (p *ParticipantImpl) RemoveSubscribedTrack(pubId string, subTrack types.SubscribedTrack) {
p.params.Logger.Debugw("removed subscribedTrack", "pIDs", []string{pubId, p.ID()},
func (p *ParticipantImpl) RemoveSubscribedTrack(subTrack types.SubscribedTrack) {
p.params.Logger.Debugw("removed subscribedTrack", "publisher", subTrack.PublisherIdentity(),
"participant", p.Identity(), "track", subTrack.ID())
p.lock.Lock()
delete(p.subscribedTracks, subTrack.ID())
// remove from subscribed map
numRemaining := 0
for _, st := range p.subscribedTracks {
if st.PublisherIdentity() == subTrack.PublisherIdentity() {
numRemaining++
}
}
p.lock.Unlock()
if numRemaining == 0 {
p.subscribedTo.Delete(subTrack.PublisherIdentity())
}
}
func (p *ParticipantImpl) sendIceCandidate(c *webrtc.ICECandidate, target livekit.SignalTarget) {
@@ -856,6 +939,7 @@ func (p *ParticipantImpl) onMediaTrack(track *webrtc.TrackRemote, rtpReceiver *w
ReceiverConfig: p.params.Config.Receiver,
AudioConfig: p.params.AudioConfig,
Stats: p.params.Stats,
Logger: p.params.Logger,
})
// add to published and clean up pending

View File

@@ -243,6 +243,34 @@ func TestMuteSetting(t *testing.T) {
})
}
func TestConnectionQuality(t *testing.T) {
testPublishedTrack := func(loss, numPublishing, numRegistered uint32) *typesfakes.FakePublishedTrack {
t := &typesfakes.FakePublishedTrack{}
t.PublishLossPercentageReturns(loss)
t.NumUpTracksReturns(numPublishing, numRegistered)
return t
}
// TODO: this test is rather limited since we cannot mock DownTrack's Target & Max spatial layers
// to improve this after split
t.Run("smooth sailing", func(t *testing.T) {
p := newParticipantForTest("test")
p.publishedTracks["video"] = testPublishedTrack(1, 3, 3)
p.publishedTracks["audio"] = testPublishedTrack(0, 1, 1)
require.Equal(t, livekit.ConnectionQuality_EXCELLENT, p.GetConnectionQuality())
})
t.Run("reduced publishing", func(t *testing.T) {
p := newParticipantForTest("test")
p.publishedTracks["video"] = testPublishedTrack(3, 2, 3)
p.publishedTracks["audio"] = testPublishedTrack(3, 1, 1)
require.Equal(t, livekit.ConnectionQuality_GOOD, p.GetConnectionQuality())
})
}
func newParticipantForTest(identity string) *ParticipantImpl {
conf, _ := config.NewConfig("", nil)
// disable mux, it doesn't play too well with unit test

View File

@@ -62,7 +62,7 @@ func NewRoom(room *livekit.Room, config WebRTCConfig, audioConfig *config.AudioC
Logger: logger.Logger(logger.GetLogger().WithValues("room", room.Name)),
config: config,
audioConfig: audioConfig,
statsReporter: stats.NewRoomStatsReporter(room.Name),
statsReporter: stats.NewRoomStatsReporter(),
participants: make(map[string]types.Participant),
participantOpts: make(map[string]*ParticipantOptions),
bufferFactory: buffer.NewBufferFactory(config.Receiver.packetBufferSize, logr.Logger{}),
@@ -76,6 +76,7 @@ func NewRoom(room *livekit.Room, config WebRTCConfig, audioConfig *config.AudioC
}
r.statsReporter.RoomStarted()
go r.audioUpdateWorker()
go r.connectionQualityWorker()
return r
}
@@ -703,6 +704,47 @@ func (r *Room) audioUpdateWorker() {
}
}
func (r *Room) connectionQualityWorker() {
// send updates to only users that are subscribed to each other
for {
if r.IsClosed() {
return
}
participants := r.GetParticipants()
connectionInfos := make(map[string]*livekit.ConnectionQualityInfo, len(participants))
for _, p := range participants {
connectionInfos[p.Identity()] = &livekit.ConnectionQualityInfo{
ParticipantSid: p.ID(),
Quality: p.GetConnectionQuality(),
}
}
for _, op := range participants {
update := &livekit.ConnectionQualityUpdate{}
// send to user itself
if info, ok := connectionInfos[op.Identity()]; ok {
update.Updates = append(update.Updates, info)
}
// send to other participants its subscribed to
for _, identity := range op.GetSubscribedParticipants() {
if info, ok := connectionInfos[identity]; ok {
update.Updates = append(update.Updates, info)
}
}
if err := op.SendConnectionQualityUpdate(update); err != nil {
r.Logger.Warnw("could not send connection quality update", err,
"participant", op.Identity())
}
}
time.Sleep(time.Second * 5)
}
}
func (r *Room) DebugInfo() map[string]interface{} {
info := map[string]interface{}{
"Name": r.Room.Name,

View File

@@ -19,7 +19,8 @@ type SubscribedTrack struct {
publisherIdentity string
subMuted utils.AtomicFlag
pubMuted utils.AtomicFlag
debouncer func(func())
debouncer func(func())
}
func NewSubscribedTrack(publisherIdentity string, dt *sfu.DownTrack) *SubscribedTrack {
@@ -42,6 +43,10 @@ func (t *SubscribedTrack) DownTrack() *sfu.DownTrack {
return t.dt
}
func (t *SubscribedTrack) SubscribeLossPercentage() uint32 {
return FixedPointToPercent(t.DownTrack().CurrentMaxLossFraction())
}
// has subscriber indicated it wants to mute this track
func (t *SubscribedTrack) IsMuted() bool {
return t.subMuted.Get()

View File

@@ -53,8 +53,13 @@ type Participant interface {
SendSpeakerUpdate(speakers []*livekit.SpeakerInfo) error
SendDataPacket(packet *livekit.DataPacket) error
SendRoomUpdate(room *livekit.Room) error
SendConnectionQualityUpdate(update *livekit.ConnectionQualityUpdate) error
SetTrackMuted(trackId string, muted bool, fromAdmin bool)
GetAudioLevel() (level uint8, active bool)
GetConnectionQuality() livekit.ConnectionQuality
IsSubscribedTo(identity string) bool
// returns list of participant identities that the current participant is subscribed to
GetSubscribedParticipants() []string
// permissions
@@ -78,9 +83,8 @@ type Participant interface {
OnClose(func(Participant))
// package methods
AddSubscribedTrack(participantId string, st SubscribedTrack)
RemoveSubscribedTrack(participantId string, st SubscribedTrack)
AddSubscribedTrack(st SubscribedTrack)
RemoveSubscribedTrack(st SubscribedTrack)
SubscriberPC() *webrtc.PeerConnection
DebugInfo() map[string]interface{}
@@ -104,6 +108,9 @@ type PublishedTrack interface {
RemoveAllSubscribers()
// returns quality information that's appropriate for width & height
GetQualityForDimension(width, height uint32) livekit.VideoQuality
// returns number of uptracks that are publishing, registered
NumUpTracks() (uint32, uint32)
PublishLossPercentage() uint32
ToProto() *livekit.TrackInfo
// callbacks
@@ -118,6 +125,7 @@ type SubscribedTrack interface {
IsMuted() bool
SetPublisherMuted(muted bool)
UpdateSubscriberSettings(enabled bool, quality livekit.VideoQuality)
SubscribeLossPercentage() uint32
}
// interface for properties of webrtc.TrackRemote

View File

@@ -25,11 +25,10 @@ type FakeParticipant struct {
addICECandidateReturnsOnCall map[int]struct {
result1 error
}
AddSubscribedTrackStub func(string, types.SubscribedTrack)
AddSubscribedTrackStub func(types.SubscribedTrack)
addSubscribedTrackMutex sync.RWMutex
addSubscribedTrackArgsForCall []struct {
arg1 string
arg2 types.SubscribedTrack
arg1 types.SubscribedTrack
}
AddSubscriberStub func(types.Participant) (int, error)
addSubscriberMutex sync.RWMutex
@@ -121,6 +120,16 @@ type FakeParticipant struct {
result1 uint8
result2 bool
}
GetConnectionQualityStub func() livekit.ConnectionQuality
getConnectionQualityMutex sync.RWMutex
getConnectionQualityArgsForCall []struct {
}
getConnectionQualityReturns struct {
result1 livekit.ConnectionQuality
}
getConnectionQualityReturnsOnCall map[int]struct {
result1 livekit.ConnectionQuality
}
GetPublishedTrackStub func(string) types.PublishedTrack
getPublishedTrackMutex sync.RWMutex
getPublishedTrackArgsForCall []struct {
@@ -152,6 +161,16 @@ type FakeParticipant struct {
getResponseSinkReturnsOnCall map[int]struct {
result1 routing.MessageSink
}
GetSubscribedParticipantsStub func() []string
getSubscribedParticipantsMutex sync.RWMutex
getSubscribedParticipantsArgsForCall []struct {
}
getSubscribedParticipantsReturns struct {
result1 []string
}
getSubscribedParticipantsReturnsOnCall map[int]struct {
result1 []string
}
GetSubscribedTrackStub func(string) types.SubscribedTrack
getSubscribedTrackMutex sync.RWMutex
getSubscribedTrackArgsForCall []struct {
@@ -247,6 +266,17 @@ type FakeParticipant struct {
isReadyReturnsOnCall map[int]struct {
result1 bool
}
IsSubscribedToStub func(string) bool
isSubscribedToMutex sync.RWMutex
isSubscribedToArgsForCall []struct {
arg1 string
}
isSubscribedToReturns struct {
result1 bool
}
isSubscribedToReturnsOnCall map[int]struct {
result1 bool
}
NegotiateStub func()
negotiateMutex sync.RWMutex
negotiateArgsForCall []struct {
@@ -301,17 +331,27 @@ type FakeParticipant struct {
rTCPChanReturnsOnCall map[int]struct {
result1 chan []rtcp.Packet
}
RemoveSubscribedTrackStub func(string, types.SubscribedTrack)
RemoveSubscribedTrackStub func(types.SubscribedTrack)
removeSubscribedTrackMutex sync.RWMutex
removeSubscribedTrackArgsForCall []struct {
arg1 string
arg2 types.SubscribedTrack
arg1 types.SubscribedTrack
}
RemoveSubscriberStub func(string)
removeSubscriberMutex sync.RWMutex
removeSubscriberArgsForCall []struct {
arg1 string
}
SendConnectionQualityUpdateStub func(*livekit.ConnectionQualityUpdate) error
sendConnectionQualityUpdateMutex sync.RWMutex
sendConnectionQualityUpdateArgsForCall []struct {
arg1 *livekit.ConnectionQualityUpdate
}
sendConnectionQualityUpdateReturns struct {
result1 error
}
sendConnectionQualityUpdateReturnsOnCall map[int]struct {
result1 error
}
SendDataPacketStub func(*livekit.DataPacket) error
sendDataPacketMutex sync.RWMutex
sendDataPacketArgsForCall []struct {
@@ -502,17 +542,16 @@ func (fake *FakeParticipant) AddICECandidateReturnsOnCall(i int, result1 error)
}{result1}
}
func (fake *FakeParticipant) AddSubscribedTrack(arg1 string, arg2 types.SubscribedTrack) {
func (fake *FakeParticipant) AddSubscribedTrack(arg1 types.SubscribedTrack) {
fake.addSubscribedTrackMutex.Lock()
fake.addSubscribedTrackArgsForCall = append(fake.addSubscribedTrackArgsForCall, struct {
arg1 string
arg2 types.SubscribedTrack
}{arg1, arg2})
arg1 types.SubscribedTrack
}{arg1})
stub := fake.AddSubscribedTrackStub
fake.recordInvocation("AddSubscribedTrack", []interface{}{arg1, arg2})
fake.recordInvocation("AddSubscribedTrack", []interface{}{arg1})
fake.addSubscribedTrackMutex.Unlock()
if stub != nil {
fake.AddSubscribedTrackStub(arg1, arg2)
fake.AddSubscribedTrackStub(arg1)
}
}
@@ -522,17 +561,17 @@ func (fake *FakeParticipant) AddSubscribedTrackCallCount() int {
return len(fake.addSubscribedTrackArgsForCall)
}
func (fake *FakeParticipant) AddSubscribedTrackCalls(stub func(string, types.SubscribedTrack)) {
func (fake *FakeParticipant) AddSubscribedTrackCalls(stub func(types.SubscribedTrack)) {
fake.addSubscribedTrackMutex.Lock()
defer fake.addSubscribedTrackMutex.Unlock()
fake.AddSubscribedTrackStub = stub
}
func (fake *FakeParticipant) AddSubscribedTrackArgsForCall(i int) (string, types.SubscribedTrack) {
func (fake *FakeParticipant) AddSubscribedTrackArgsForCall(i int) types.SubscribedTrack {
fake.addSubscribedTrackMutex.RLock()
defer fake.addSubscribedTrackMutex.RUnlock()
argsForCall := fake.addSubscribedTrackArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
return argsForCall.arg1
}
func (fake *FakeParticipant) AddSubscriber(arg1 types.Participant) (int, error) {
@@ -1005,6 +1044,59 @@ func (fake *FakeParticipant) GetAudioLevelReturnsOnCall(i int, result1 uint8, re
}{result1, result2}
}
func (fake *FakeParticipant) GetConnectionQuality() livekit.ConnectionQuality {
fake.getConnectionQualityMutex.Lock()
ret, specificReturn := fake.getConnectionQualityReturnsOnCall[len(fake.getConnectionQualityArgsForCall)]
fake.getConnectionQualityArgsForCall = append(fake.getConnectionQualityArgsForCall, struct {
}{})
stub := fake.GetConnectionQualityStub
fakeReturns := fake.getConnectionQualityReturns
fake.recordInvocation("GetConnectionQuality", []interface{}{})
fake.getConnectionQualityMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeParticipant) GetConnectionQualityCallCount() int {
fake.getConnectionQualityMutex.RLock()
defer fake.getConnectionQualityMutex.RUnlock()
return len(fake.getConnectionQualityArgsForCall)
}
func (fake *FakeParticipant) GetConnectionQualityCalls(stub func() livekit.ConnectionQuality) {
fake.getConnectionQualityMutex.Lock()
defer fake.getConnectionQualityMutex.Unlock()
fake.GetConnectionQualityStub = stub
}
func (fake *FakeParticipant) GetConnectionQualityReturns(result1 livekit.ConnectionQuality) {
fake.getConnectionQualityMutex.Lock()
defer fake.getConnectionQualityMutex.Unlock()
fake.GetConnectionQualityStub = nil
fake.getConnectionQualityReturns = struct {
result1 livekit.ConnectionQuality
}{result1}
}
func (fake *FakeParticipant) GetConnectionQualityReturnsOnCall(i int, result1 livekit.ConnectionQuality) {
fake.getConnectionQualityMutex.Lock()
defer fake.getConnectionQualityMutex.Unlock()
fake.GetConnectionQualityStub = nil
if fake.getConnectionQualityReturnsOnCall == nil {
fake.getConnectionQualityReturnsOnCall = make(map[int]struct {
result1 livekit.ConnectionQuality
})
}
fake.getConnectionQualityReturnsOnCall[i] = struct {
result1 livekit.ConnectionQuality
}{result1}
}
func (fake *FakeParticipant) GetPublishedTrack(arg1 string) types.PublishedTrack {
fake.getPublishedTrackMutex.Lock()
ret, specificReturn := fake.getPublishedTrackReturnsOnCall[len(fake.getPublishedTrackArgsForCall)]
@@ -1172,6 +1264,59 @@ func (fake *FakeParticipant) GetResponseSinkReturnsOnCall(i int, result1 routing
}{result1}
}
func (fake *FakeParticipant) GetSubscribedParticipants() []string {
fake.getSubscribedParticipantsMutex.Lock()
ret, specificReturn := fake.getSubscribedParticipantsReturnsOnCall[len(fake.getSubscribedParticipantsArgsForCall)]
fake.getSubscribedParticipantsArgsForCall = append(fake.getSubscribedParticipantsArgsForCall, struct {
}{})
stub := fake.GetSubscribedParticipantsStub
fakeReturns := fake.getSubscribedParticipantsReturns
fake.recordInvocation("GetSubscribedParticipants", []interface{}{})
fake.getSubscribedParticipantsMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeParticipant) GetSubscribedParticipantsCallCount() int {
fake.getSubscribedParticipantsMutex.RLock()
defer fake.getSubscribedParticipantsMutex.RUnlock()
return len(fake.getSubscribedParticipantsArgsForCall)
}
func (fake *FakeParticipant) GetSubscribedParticipantsCalls(stub func() []string) {
fake.getSubscribedParticipantsMutex.Lock()
defer fake.getSubscribedParticipantsMutex.Unlock()
fake.GetSubscribedParticipantsStub = stub
}
func (fake *FakeParticipant) GetSubscribedParticipantsReturns(result1 []string) {
fake.getSubscribedParticipantsMutex.Lock()
defer fake.getSubscribedParticipantsMutex.Unlock()
fake.GetSubscribedParticipantsStub = nil
fake.getSubscribedParticipantsReturns = struct {
result1 []string
}{result1}
}
func (fake *FakeParticipant) GetSubscribedParticipantsReturnsOnCall(i int, result1 []string) {
fake.getSubscribedParticipantsMutex.Lock()
defer fake.getSubscribedParticipantsMutex.Unlock()
fake.GetSubscribedParticipantsStub = nil
if fake.getSubscribedParticipantsReturnsOnCall == nil {
fake.getSubscribedParticipantsReturnsOnCall = make(map[int]struct {
result1 []string
})
}
fake.getSubscribedParticipantsReturnsOnCall[i] = struct {
result1 []string
}{result1}
}
func (fake *FakeParticipant) GetSubscribedTrack(arg1 string) types.SubscribedTrack {
fake.getSubscribedTrackMutex.Lock()
ret, specificReturn := fake.getSubscribedTrackReturnsOnCall[len(fake.getSubscribedTrackArgsForCall)]
@@ -1676,6 +1821,67 @@ func (fake *FakeParticipant) IsReadyReturnsOnCall(i int, result1 bool) {
}{result1}
}
func (fake *FakeParticipant) IsSubscribedTo(arg1 string) bool {
fake.isSubscribedToMutex.Lock()
ret, specificReturn := fake.isSubscribedToReturnsOnCall[len(fake.isSubscribedToArgsForCall)]
fake.isSubscribedToArgsForCall = append(fake.isSubscribedToArgsForCall, struct {
arg1 string
}{arg1})
stub := fake.IsSubscribedToStub
fakeReturns := fake.isSubscribedToReturns
fake.recordInvocation("IsSubscribedTo", []interface{}{arg1})
fake.isSubscribedToMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeParticipant) IsSubscribedToCallCount() int {
fake.isSubscribedToMutex.RLock()
defer fake.isSubscribedToMutex.RUnlock()
return len(fake.isSubscribedToArgsForCall)
}
func (fake *FakeParticipant) IsSubscribedToCalls(stub func(string) bool) {
fake.isSubscribedToMutex.Lock()
defer fake.isSubscribedToMutex.Unlock()
fake.IsSubscribedToStub = stub
}
func (fake *FakeParticipant) IsSubscribedToArgsForCall(i int) string {
fake.isSubscribedToMutex.RLock()
defer fake.isSubscribedToMutex.RUnlock()
argsForCall := fake.isSubscribedToArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeParticipant) IsSubscribedToReturns(result1 bool) {
fake.isSubscribedToMutex.Lock()
defer fake.isSubscribedToMutex.Unlock()
fake.IsSubscribedToStub = nil
fake.isSubscribedToReturns = struct {
result1 bool
}{result1}
}
func (fake *FakeParticipant) IsSubscribedToReturnsOnCall(i int, result1 bool) {
fake.isSubscribedToMutex.Lock()
defer fake.isSubscribedToMutex.Unlock()
fake.IsSubscribedToStub = nil
if fake.isSubscribedToReturnsOnCall == nil {
fake.isSubscribedToReturnsOnCall = make(map[int]struct {
result1 bool
})
}
fake.isSubscribedToReturnsOnCall[i] = struct {
result1 bool
}{result1}
}
func (fake *FakeParticipant) Negotiate() {
fake.negotiateMutex.Lock()
fake.negotiateArgsForCall = append(fake.negotiateArgsForCall, struct {
@@ -1998,17 +2204,16 @@ func (fake *FakeParticipant) RTCPChanReturnsOnCall(i int, result1 chan []rtcp.Pa
}{result1}
}
func (fake *FakeParticipant) RemoveSubscribedTrack(arg1 string, arg2 types.SubscribedTrack) {
func (fake *FakeParticipant) RemoveSubscribedTrack(arg1 types.SubscribedTrack) {
fake.removeSubscribedTrackMutex.Lock()
fake.removeSubscribedTrackArgsForCall = append(fake.removeSubscribedTrackArgsForCall, struct {
arg1 string
arg2 types.SubscribedTrack
}{arg1, arg2})
arg1 types.SubscribedTrack
}{arg1})
stub := fake.RemoveSubscribedTrackStub
fake.recordInvocation("RemoveSubscribedTrack", []interface{}{arg1, arg2})
fake.recordInvocation("RemoveSubscribedTrack", []interface{}{arg1})
fake.removeSubscribedTrackMutex.Unlock()
if stub != nil {
fake.RemoveSubscribedTrackStub(arg1, arg2)
fake.RemoveSubscribedTrackStub(arg1)
}
}
@@ -2018,17 +2223,17 @@ func (fake *FakeParticipant) RemoveSubscribedTrackCallCount() int {
return len(fake.removeSubscribedTrackArgsForCall)
}
func (fake *FakeParticipant) RemoveSubscribedTrackCalls(stub func(string, types.SubscribedTrack)) {
func (fake *FakeParticipant) RemoveSubscribedTrackCalls(stub func(types.SubscribedTrack)) {
fake.removeSubscribedTrackMutex.Lock()
defer fake.removeSubscribedTrackMutex.Unlock()
fake.RemoveSubscribedTrackStub = stub
}
func (fake *FakeParticipant) RemoveSubscribedTrackArgsForCall(i int) (string, types.SubscribedTrack) {
func (fake *FakeParticipant) RemoveSubscribedTrackArgsForCall(i int) types.SubscribedTrack {
fake.removeSubscribedTrackMutex.RLock()
defer fake.removeSubscribedTrackMutex.RUnlock()
argsForCall := fake.removeSubscribedTrackArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
return argsForCall.arg1
}
func (fake *FakeParticipant) RemoveSubscriber(arg1 string) {
@@ -2063,6 +2268,67 @@ func (fake *FakeParticipant) RemoveSubscriberArgsForCall(i int) string {
return argsForCall.arg1
}
func (fake *FakeParticipant) SendConnectionQualityUpdate(arg1 *livekit.ConnectionQualityUpdate) error {
fake.sendConnectionQualityUpdateMutex.Lock()
ret, specificReturn := fake.sendConnectionQualityUpdateReturnsOnCall[len(fake.sendConnectionQualityUpdateArgsForCall)]
fake.sendConnectionQualityUpdateArgsForCall = append(fake.sendConnectionQualityUpdateArgsForCall, struct {
arg1 *livekit.ConnectionQualityUpdate
}{arg1})
stub := fake.SendConnectionQualityUpdateStub
fakeReturns := fake.sendConnectionQualityUpdateReturns
fake.recordInvocation("SendConnectionQualityUpdate", []interface{}{arg1})
fake.sendConnectionQualityUpdateMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeParticipant) SendConnectionQualityUpdateCallCount() int {
fake.sendConnectionQualityUpdateMutex.RLock()
defer fake.sendConnectionQualityUpdateMutex.RUnlock()
return len(fake.sendConnectionQualityUpdateArgsForCall)
}
func (fake *FakeParticipant) SendConnectionQualityUpdateCalls(stub func(*livekit.ConnectionQualityUpdate) error) {
fake.sendConnectionQualityUpdateMutex.Lock()
defer fake.sendConnectionQualityUpdateMutex.Unlock()
fake.SendConnectionQualityUpdateStub = stub
}
func (fake *FakeParticipant) SendConnectionQualityUpdateArgsForCall(i int) *livekit.ConnectionQualityUpdate {
fake.sendConnectionQualityUpdateMutex.RLock()
defer fake.sendConnectionQualityUpdateMutex.RUnlock()
argsForCall := fake.sendConnectionQualityUpdateArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeParticipant) SendConnectionQualityUpdateReturns(result1 error) {
fake.sendConnectionQualityUpdateMutex.Lock()
defer fake.sendConnectionQualityUpdateMutex.Unlock()
fake.SendConnectionQualityUpdateStub = nil
fake.sendConnectionQualityUpdateReturns = struct {
result1 error
}{result1}
}
func (fake *FakeParticipant) SendConnectionQualityUpdateReturnsOnCall(i int, result1 error) {
fake.sendConnectionQualityUpdateMutex.Lock()
defer fake.sendConnectionQualityUpdateMutex.Unlock()
fake.SendConnectionQualityUpdateStub = nil
if fake.sendConnectionQualityUpdateReturnsOnCall == nil {
fake.sendConnectionQualityUpdateReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.sendConnectionQualityUpdateReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *FakeParticipant) SendDataPacket(arg1 *livekit.DataPacket) error {
fake.sendDataPacketMutex.Lock()
ret, specificReturn := fake.sendDataPacketReturnsOnCall[len(fake.sendDataPacketArgsForCall)]
@@ -2782,12 +3048,16 @@ func (fake *FakeParticipant) Invocations() map[string][][]interface{} {
defer fake.debugInfoMutex.RUnlock()
fake.getAudioLevelMutex.RLock()
defer fake.getAudioLevelMutex.RUnlock()
fake.getConnectionQualityMutex.RLock()
defer fake.getConnectionQualityMutex.RUnlock()
fake.getPublishedTrackMutex.RLock()
defer fake.getPublishedTrackMutex.RUnlock()
fake.getPublishedTracksMutex.RLock()
defer fake.getPublishedTracksMutex.RUnlock()
fake.getResponseSinkMutex.RLock()
defer fake.getResponseSinkMutex.RUnlock()
fake.getSubscribedParticipantsMutex.RLock()
defer fake.getSubscribedParticipantsMutex.RUnlock()
fake.getSubscribedTrackMutex.RLock()
defer fake.getSubscribedTrackMutex.RUnlock()
fake.getSubscribedTracksMutex.RLock()
@@ -2806,6 +3076,8 @@ func (fake *FakeParticipant) Invocations() map[string][][]interface{} {
defer fake.identityMutex.RUnlock()
fake.isReadyMutex.RLock()
defer fake.isReadyMutex.RUnlock()
fake.isSubscribedToMutex.RLock()
defer fake.isSubscribedToMutex.RUnlock()
fake.negotiateMutex.RLock()
defer fake.negotiateMutex.RUnlock()
fake.onCloseMutex.RLock()
@@ -2828,6 +3100,8 @@ func (fake *FakeParticipant) Invocations() map[string][][]interface{} {
defer fake.removeSubscribedTrackMutex.RUnlock()
fake.removeSubscriberMutex.RLock()
defer fake.removeSubscriberMutex.RUnlock()
fake.sendConnectionQualityUpdateMutex.RLock()
defer fake.sendConnectionQualityUpdateMutex.RUnlock()
fake.sendDataPacketMutex.RLock()
defer fake.sendDataPacketMutex.RUnlock()
fake.sendJoinResponseMutex.RLock()

View File

@@ -83,11 +83,33 @@ type FakePublishedTrack struct {
nameReturnsOnCall map[int]struct {
result1 string
}
NumUpTracksStub func() (uint32, uint32)
numUpTracksMutex sync.RWMutex
numUpTracksArgsForCall []struct {
}
numUpTracksReturns struct {
result1 uint32
result2 uint32
}
numUpTracksReturnsOnCall map[int]struct {
result1 uint32
result2 uint32
}
OnCloseStub func(func())
onCloseMutex sync.RWMutex
onCloseArgsForCall []struct {
arg1 func()
}
PublishLossPercentageStub func() uint32
publishLossPercentageMutex sync.RWMutex
publishLossPercentageArgsForCall []struct {
}
publishLossPercentageReturns struct {
result1 uint32
}
publishLossPercentageReturnsOnCall map[int]struct {
result1 uint32
}
RemoveAllSubscribersStub func()
removeAllSubscribersMutex sync.RWMutex
removeAllSubscribersArgsForCall []struct {
@@ -536,6 +558,62 @@ func (fake *FakePublishedTrack) NameReturnsOnCall(i int, result1 string) {
}{result1}
}
func (fake *FakePublishedTrack) NumUpTracks() (uint32, uint32) {
fake.numUpTracksMutex.Lock()
ret, specificReturn := fake.numUpTracksReturnsOnCall[len(fake.numUpTracksArgsForCall)]
fake.numUpTracksArgsForCall = append(fake.numUpTracksArgsForCall, struct {
}{})
stub := fake.NumUpTracksStub
fakeReturns := fake.numUpTracksReturns
fake.recordInvocation("NumUpTracks", []interface{}{})
fake.numUpTracksMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}
func (fake *FakePublishedTrack) NumUpTracksCallCount() int {
fake.numUpTracksMutex.RLock()
defer fake.numUpTracksMutex.RUnlock()
return len(fake.numUpTracksArgsForCall)
}
func (fake *FakePublishedTrack) NumUpTracksCalls(stub func() (uint32, uint32)) {
fake.numUpTracksMutex.Lock()
defer fake.numUpTracksMutex.Unlock()
fake.NumUpTracksStub = stub
}
func (fake *FakePublishedTrack) NumUpTracksReturns(result1 uint32, result2 uint32) {
fake.numUpTracksMutex.Lock()
defer fake.numUpTracksMutex.Unlock()
fake.NumUpTracksStub = nil
fake.numUpTracksReturns = struct {
result1 uint32
result2 uint32
}{result1, result2}
}
func (fake *FakePublishedTrack) NumUpTracksReturnsOnCall(i int, result1 uint32, result2 uint32) {
fake.numUpTracksMutex.Lock()
defer fake.numUpTracksMutex.Unlock()
fake.NumUpTracksStub = nil
if fake.numUpTracksReturnsOnCall == nil {
fake.numUpTracksReturnsOnCall = make(map[int]struct {
result1 uint32
result2 uint32
})
}
fake.numUpTracksReturnsOnCall[i] = struct {
result1 uint32
result2 uint32
}{result1, result2}
}
func (fake *FakePublishedTrack) OnClose(arg1 func()) {
fake.onCloseMutex.Lock()
fake.onCloseArgsForCall = append(fake.onCloseArgsForCall, struct {
@@ -568,6 +646,59 @@ func (fake *FakePublishedTrack) OnCloseArgsForCall(i int) func() {
return argsForCall.arg1
}
func (fake *FakePublishedTrack) PublishLossPercentage() uint32 {
fake.publishLossPercentageMutex.Lock()
ret, specificReturn := fake.publishLossPercentageReturnsOnCall[len(fake.publishLossPercentageArgsForCall)]
fake.publishLossPercentageArgsForCall = append(fake.publishLossPercentageArgsForCall, struct {
}{})
stub := fake.PublishLossPercentageStub
fakeReturns := fake.publishLossPercentageReturns
fake.recordInvocation("PublishLossPercentage", []interface{}{})
fake.publishLossPercentageMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakePublishedTrack) PublishLossPercentageCallCount() int {
fake.publishLossPercentageMutex.RLock()
defer fake.publishLossPercentageMutex.RUnlock()
return len(fake.publishLossPercentageArgsForCall)
}
func (fake *FakePublishedTrack) PublishLossPercentageCalls(stub func() uint32) {
fake.publishLossPercentageMutex.Lock()
defer fake.publishLossPercentageMutex.Unlock()
fake.PublishLossPercentageStub = stub
}
func (fake *FakePublishedTrack) PublishLossPercentageReturns(result1 uint32) {
fake.publishLossPercentageMutex.Lock()
defer fake.publishLossPercentageMutex.Unlock()
fake.PublishLossPercentageStub = nil
fake.publishLossPercentageReturns = struct {
result1 uint32
}{result1}
}
func (fake *FakePublishedTrack) PublishLossPercentageReturnsOnCall(i int, result1 uint32) {
fake.publishLossPercentageMutex.Lock()
defer fake.publishLossPercentageMutex.Unlock()
fake.PublishLossPercentageStub = nil
if fake.publishLossPercentageReturnsOnCall == nil {
fake.publishLossPercentageReturnsOnCall = make(map[int]struct {
result1 uint32
})
}
fake.publishLossPercentageReturnsOnCall[i] = struct {
result1 uint32
}{result1}
}
func (fake *FakePublishedTrack) RemoveAllSubscribers() {
fake.removeAllSubscribersMutex.Lock()
fake.removeAllSubscribersArgsForCall = append(fake.removeAllSubscribersArgsForCall, struct {
@@ -856,8 +987,12 @@ func (fake *FakePublishedTrack) Invocations() map[string][][]interface{} {
defer fake.kindMutex.RUnlock()
fake.nameMutex.RLock()
defer fake.nameMutex.RUnlock()
fake.numUpTracksMutex.RLock()
defer fake.numUpTracksMutex.RUnlock()
fake.onCloseMutex.RLock()
defer fake.onCloseMutex.RUnlock()
fake.publishLossPercentageMutex.RLock()
defer fake.publishLossPercentageMutex.RUnlock()
fake.removeAllSubscribersMutex.RLock()
defer fake.removeAllSubscribersMutex.RUnlock()
fake.removeSubscriberMutex.RLock()

View File

@@ -55,6 +55,16 @@ type FakeSubscribedTrack struct {
setPublisherMutedArgsForCall []struct {
arg1 bool
}
SubscribeLossPercentageStub func() uint32
subscribeLossPercentageMutex sync.RWMutex
subscribeLossPercentageArgsForCall []struct {
}
subscribeLossPercentageReturns struct {
result1 uint32
}
subscribeLossPercentageReturnsOnCall map[int]struct {
result1 uint32
}
UpdateSubscriberSettingsStub func(bool, livekit.VideoQuality)
updateSubscriberSettingsMutex sync.RWMutex
updateSubscriberSettingsArgsForCall []struct {
@@ -309,6 +319,59 @@ func (fake *FakeSubscribedTrack) SetPublisherMutedArgsForCall(i int) bool {
return argsForCall.arg1
}
func (fake *FakeSubscribedTrack) SubscribeLossPercentage() uint32 {
fake.subscribeLossPercentageMutex.Lock()
ret, specificReturn := fake.subscribeLossPercentageReturnsOnCall[len(fake.subscribeLossPercentageArgsForCall)]
fake.subscribeLossPercentageArgsForCall = append(fake.subscribeLossPercentageArgsForCall, struct {
}{})
stub := fake.SubscribeLossPercentageStub
fakeReturns := fake.subscribeLossPercentageReturns
fake.recordInvocation("SubscribeLossPercentage", []interface{}{})
fake.subscribeLossPercentageMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeSubscribedTrack) SubscribeLossPercentageCallCount() int {
fake.subscribeLossPercentageMutex.RLock()
defer fake.subscribeLossPercentageMutex.RUnlock()
return len(fake.subscribeLossPercentageArgsForCall)
}
func (fake *FakeSubscribedTrack) SubscribeLossPercentageCalls(stub func() uint32) {
fake.subscribeLossPercentageMutex.Lock()
defer fake.subscribeLossPercentageMutex.Unlock()
fake.SubscribeLossPercentageStub = stub
}
func (fake *FakeSubscribedTrack) SubscribeLossPercentageReturns(result1 uint32) {
fake.subscribeLossPercentageMutex.Lock()
defer fake.subscribeLossPercentageMutex.Unlock()
fake.SubscribeLossPercentageStub = nil
fake.subscribeLossPercentageReturns = struct {
result1 uint32
}{result1}
}
func (fake *FakeSubscribedTrack) SubscribeLossPercentageReturnsOnCall(i int, result1 uint32) {
fake.subscribeLossPercentageMutex.Lock()
defer fake.subscribeLossPercentageMutex.Unlock()
fake.SubscribeLossPercentageStub = nil
if fake.subscribeLossPercentageReturnsOnCall == nil {
fake.subscribeLossPercentageReturnsOnCall = make(map[int]struct {
result1 uint32
})
}
fake.subscribeLossPercentageReturnsOnCall[i] = struct {
result1 uint32
}{result1}
}
func (fake *FakeSubscribedTrack) UpdateSubscriberSettings(arg1 bool, arg2 livekit.VideoQuality) {
fake.updateSubscriberSettingsMutex.Lock()
fake.updateSubscriberSettingsArgsForCall = append(fake.updateSubscriberSettingsArgsForCall, struct {
@@ -355,6 +418,8 @@ func (fake *FakeSubscribedTrack) Invocations() map[string][][]interface{} {
defer fake.publisherIdentityMutex.RUnlock()
fake.setPublisherMutedMutex.RLock()
defer fake.setPublisherMutedMutex.RUnlock()
fake.subscribeLossPercentageMutex.RLock()
defer fake.subscribeLossPercentageMutex.RUnlock()
fake.updateSubscriberSettingsMutex.RLock()
defer fake.updateSubscriberSettingsMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}

View File

@@ -44,6 +44,11 @@ func UnpackDataTrackLabel(packed string) (peerId string, trackId string, label s
return
}
// converts a fixed point number to the number part of %
func FixedPointToPercent(frac uint8) uint32 {
return (uint32(frac) * 100) >> 8
}
func ToProtoParticipants(participants []types.Participant) []*livekit.ParticipantInfo {
infos := make([]*livekit.ParticipantInfo, 0, len(participants))
for _, op := range participants {

View File

@@ -53,7 +53,6 @@ func initPacketStats() {
}
type PacketStats struct {
roomName string
direction string // incoming or outgoing
PacketBytes uint64 `json:"packetBytes"`
@@ -63,9 +62,8 @@ type PacketStats struct {
FIRTotal uint64 `json:"firTotal"`
}
func newPacketStats(room, direction string) *PacketStats {
func newPacketStats(direction string) *PacketStats {
return &PacketStats{
roomName: room,
direction: direction,
}
}
@@ -121,7 +119,6 @@ func (s *PacketStats) HandleRTCP(pkts []rtcp.Packet) {
func (s PacketStats) Copy() *PacketStats {
return &PacketStats{
roomName: s.roomName,
direction: s.direction,
PacketBytes: atomic.LoadUint64(&s.PacketBytes),
PacketTotal: atomic.LoadUint64(&s.PacketTotal),

View File

@@ -60,11 +60,10 @@ type RoomStatsReporter struct {
Outgoing *PacketStats
}
func NewRoomStatsReporter(roomName string) *RoomStatsReporter {
func NewRoomStatsReporter() *RoomStatsReporter {
return &RoomStatsReporter{
roomName: roomName,
Incoming: newPacketStats(roomName, "incoming"),
Outgoing: newPacketStats(roomName, "outgoing"),
Incoming: newPacketStats("incoming"),
Outgoing: newPacketStats("outgoing"),
}
}