Merge branch 'master' into duan/add-video-frame-caching

This commit is contained in:
CloudWebRTC
2026-06-29 11:25:28 +08:00
committed by GitHub
40 changed files with 2727 additions and 185 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ import (
type AgentService interface {
HandleConnection(context.Context, agent.SignalConn, agent.WorkerRegistration)
DrainConnections(time.Duration)
DrainConnections(time.Duration, bool)
}
type TestServer struct {
@@ -140,7 +140,7 @@ func (h *TestServer) SimulateAgentWorker(opts ...SimulatedWorkerOption) *AgentWo
}
func (h *TestServer) Close() {
h.DrainConnections(1)
h.DrainConnections(1, false)
}
var _ agent.SignalConn = (*AgentWorker)(nil)
+39
View File
@@ -91,6 +91,8 @@ type Config struct {
EnableDataTracks bool `yaml:"enable_data_tracks,omitempty"`
EnableParticipantDataBlob bool `yaml:"enable_participant_data_blob,omitempty"`
API APIConfig `yaml:"api,omitempty"`
}
@@ -280,6 +282,8 @@ type RegionConfig struct {
Lon float64 `yaml:"lon,omitempty"`
}
// ---------------------------------
type LimitConfig struct {
NumTracks int32 `yaml:"num_tracks,omitempty"`
BytesPerSec float32 `yaml:"bytes_per_sec,omitempty"`
@@ -291,6 +295,9 @@ type LimitConfig struct {
MaxRoomNameLength int `yaml:"max_room_name_length,omitempty"`
MaxParticipantIdentityLength int `yaml:"max_participant_identity_length,omitempty"`
MaxParticipantNameLength int `yaml:"max_participant_name_length,omitempty"`
MaxDataBlobKeyLength int `yaml:"max_data_blob_key_length,omitempty"`
MaxDataBlobSize uint32 `yaml:"max_data_blobs_size,omitempty"`
}
func (l LimitConfig) CheckRoomNameLength(name string) bool {
@@ -321,6 +328,36 @@ func (l LimitConfig) CheckAttributesSize(attributes map[string]string) bool {
return uint32(total) <= l.MaxAttributesSize
}
func (l LimitConfig) CheckDataBlobKeyLength(key string) bool {
return l.MaxDataBlobKeyLength == 0 || len(key) <= l.MaxDataBlobKeyLength
}
func (l LimitConfig) CheckDataBlobsSize(dataBlobs []*livekit.DataBlob) bool {
if l.MaxDataBlobSize == 0 {
return true
}
total := 0
for _, dataBlob := range dataBlobs {
total += len(dataBlob.GetKey().String()) + len(dataBlob.Contents)
}
return uint32(total) <= l.MaxDataBlobSize
}
func (l LimitConfig) CanAddDataBlob(dataBlobs []*livekit.DataBlob, toAdd *livekit.DataBlob) bool {
if l.MaxDataBlobSize == 0 {
return true
}
total := 0
for _, dataBlob := range dataBlobs {
total += len(dataBlob.Key.String()) + len(dataBlob.Contents)
}
return uint32(total+len(toAdd.GetKey().String())+len(toAdd.Contents)) <= l.MaxDataBlobSize
}
// ---------------------------------
type IngressConfig struct {
RTMPBaseURL string `yaml:"rtmp_base_url,omitempty"`
WHIPBaseURL string `yaml:"whip_base_url,omitempty"`
@@ -443,6 +480,8 @@ var DefaultConfig = Config{
MaxRoomNameLength: 256,
MaxParticipantIdentityLength: 256,
MaxParticipantNameLength: 256,
MaxDataBlobKeyLength: 256,
MaxDataBlobSize: 64000,
},
Logging: LoggingConfig{
PionLevel: "error",
+19 -10
View File
@@ -225,8 +225,10 @@ type ParticipantParams struct {
EnableRTPStreamRestartDetection bool
ForceBackupCodecPolicySimulcast bool
DisableTransceiverReuseForE2EE bool
EnableParticipantDataBlob bool
EnableStartAtDesiredQuality bool
VideoFrameCachingDuration time.Duration
MigrationWaitDuration time.Duration
VideoFrameCachingDuration time.Duration
}
type ParticipantImpl struct {
@@ -335,6 +337,8 @@ type ParticipantImpl struct {
rpcLock sync.Mutex
rpcPendingAcks map[string]*utils.DataChannelRpcPendingAckHandler
rpcPendingResponses map[string]*utils.DataChannelRpcPendingResponseHandler
dataBlob *ParticipantDataBlob
}
func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) {
@@ -373,6 +377,9 @@ func NewParticipant(params ParticipantParams) (*ParticipantImpl, error) {
telemetryGuard: &telemetry.ReferenceGuard{},
nextSubscribedDataTrackHandle: uint16(rand.Intn(256)),
requireBroadcast: params.Grants.Metadata != "" || len(params.Grants.Attributes) != 0,
dataBlob: NewParticipantDataBlob(ParticipantDataBlobParams{
Logger: params.Logger,
}),
}
p.setupSignalling()
@@ -912,6 +919,7 @@ func (p *ParticipantImpl) ToProtoWithVersion() (*livekit.ParticipantInfo, utils.
KindDetails: grants.GetKindDetails(),
DisconnectReason: p.CloseReason().ToDisconnectReason(),
ClientProtocol: clientProtocol,
Capabilities: p.params.ClientInfo.GetCapabilities(),
}
p.lock.RUnlock()
@@ -1340,9 +1348,7 @@ func (p *ParticipantImpl) AddTrack(req *livekit.AddTrackRequest) {
return
}
p.pendingTracksLock.Lock()
ti := p.addPendingTrackLocked(req)
p.pendingTracksLock.Unlock()
ti := p.addPendingTrack(req)
if ti == nil {
return
}
@@ -1568,7 +1574,7 @@ func (p *ParticipantImpl) setupMigrationTimerLocked() {
// to try and succeed. If not, close the subscriber peer connection
// and help the remote side to narrow down its ICE candidate pool.
//
p.migrationTimer = time.AfterFunc(migrationWaitDuration, func() {
p.migrationTimer = time.AfterFunc(max(p.params.MigrationWaitDuration, migrationWaitDuration), func() {
p.clearMigrationTimer()
if p.IsClosed() || p.IsDisconnected() {
@@ -2852,7 +2858,10 @@ func (p *ParticipantImpl) onSubscribedAudioCodecChange(
return p.sendSubscribedAudioCodecUpdate(subscribedAudioCodecUpdate)
}
func (p *ParticipantImpl) addPendingTrackLocked(req *livekit.AddTrackRequest) *livekit.TrackInfo {
func (p *ParticipantImpl) addPendingTrack(req *livekit.AddTrackRequest) *livekit.TrackInfo {
p.pendingTracksLock.Lock()
defer p.pendingTracksLock.Unlock()
if req.Sid != "" {
track := p.GetPublishedTrack(livekit.TrackID(req.Sid))
if track == nil {
@@ -3249,11 +3258,11 @@ func (p *ParticipantImpl) mediaTrackReceived(
mt = p.addMediaTrack(signalCid, ti)
newTrack = true
// if the addTrackRequest is sent before participant active then it means the client tries to publish
// before fully connected, in this case we only record the time when the participant is active since
// if the addTrackRequest is sent before publisher peer connection is established, then it means the client tries to publish
// before fully connected, in this case we only record the time when publisher peer connection is established since
// we want this metric to represent the time cost by publishing.
if activeAt := p.lastActiveAt.Load(); activeAt != nil && createdAt.Before(*activeAt) {
createdAt = *activeAt
if connectedAt := p.TransportManager.PublisherFirstConnectedAt(); !connectedAt.IsZero() && createdAt.Before(connectedAt) {
createdAt = connectedAt
}
pubTime = time.Since(createdAt)
p.dirty.Store(true)
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2026 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 rtc
import (
"sync"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
type ParticipantDataBlobParams struct {
Logger logger.Logger
}
type ParticipantDataBlob struct {
params ParticipantDataBlobParams
lock sync.Mutex
blobs map[string]*livekit.DataBlob
}
func NewParticipantDataBlob(params ParticipantDataBlobParams) *ParticipantDataBlob {
return &ParticipantDataBlob{
params: params,
blobs: make(map[string]*livekit.DataBlob),
}
}
func (p *ParticipantDataBlob) Add(db *livekit.DataBlob) {
p.lock.Lock()
defer p.lock.Unlock()
if db.Key == nil {
return
}
p.blobs[db.Key.String()] = db
}
func (p *ParticipantDataBlob) Delete(dbKey *livekit.DataBlobKey) {
p.lock.Lock()
defer p.lock.Unlock()
if dbKey == nil {
return
}
delete(p.blobs, dbKey.String())
}
func (p *ParticipantDataBlob) Get(dbKey *livekit.DataBlobKey) *livekit.DataBlob {
p.lock.Lock()
defer p.lock.Unlock()
if dbKey == nil {
return nil
}
db, ok := p.blobs[dbKey.String()]
if !ok {
return nil
}
return db
}
func (p *ParticipantDataBlob) GetAll() []*livekit.DataBlob {
p.lock.Lock()
defer p.lock.Unlock()
all := make([]*livekit.DataBlob, 0, len(p.blobs))
for _, db := range p.blobs {
all = append(all, db)
}
return all
}
// -------------------------------
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2026 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 rtc
import (
"github.com/livekit/livekit-server/pkg/rtc/types"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
func (p *ParticipantImpl) HandleStoreDataBlobRequest(req *livekit.StoreDataBlobRequest) {
if !p.params.EnableParticipantDataBlob {
p.pubLogger.Warnw("data blob not enabled", nil, "req", logger.Proto(req))
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_NOT_ALLOWED,
Message: "data blob not enabled",
})
return
}
if req.Blob == nil || req.Blob.Key == nil || len(req.Blob.Key.String()) == 0 || !p.params.LimitConfig.CheckDataBlobKeyLength(req.Blob.Key.String()) {
p.pubLogger.Warnw("data blob is invalid", nil, "req", logger.Proto(req))
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_INVALID_REQUEST,
Message: "data blob is invalid",
})
return
}
if len(req.Blob.Contents) == 0 {
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_INVALID_REQUEST,
Message: "data blob is empty",
})
return
}
if !p.params.LimitConfig.CanAddDataBlob(p.dataBlob.GetAll(), req.Blob) {
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_LIMIT_EXCEEDED,
Message: "async attribute definition exceeds limit",
})
return
}
p.AddDataBlob(req.Blob)
p.listener().OnStoreDataBlob(p, req.Blob)
p.sendStoreDataBlobResponse(req.RequestId, req.Blob.Key)
}
func (p *ParticipantImpl) HandleGetDataBlobRequest(req *livekit.GetDataBlobRequest) {
if req.Key == nil {
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_INVALID_REQUEST,
Message: "data blob key is required",
})
return
}
p.listener().OnGetDataBlob(p, req)
}
func (p *ParticipantImpl) AddDataBlob(dataBlob *livekit.DataBlob) {
p.dataBlob.Add(dataBlob)
}
func (p *ParticipantImpl) GetDataBlob(key *livekit.DataBlobKey) *livekit.DataBlob {
return p.dataBlob.Get(key)
}
func (p *ParticipantImpl) ProcessGetDataBlobRequest(req *livekit.GetDataBlobRequest, publisher types.Participant) {
if publisher == nil {
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_NOT_FOUND,
Message: "participant not found",
})
return
}
dataBlob := publisher.GetDataBlob(req.Key)
if dataBlob == nil {
p.sendRequestResponse(&livekit.RequestResponse{
RequestId: req.RequestId,
Reason: livekit.RequestResponse_NOT_FOUND,
Message: "data blob not found",
})
return
}
p.sendGetDataBlobResponse(req.RequestId, dataBlob)
}
func (p *ParticipantImpl) GetAllDataBlob() []*livekit.DataBlob {
return p.dataBlob.GetAll()
}
@@ -0,0 +1,300 @@
// Copyright 2026 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 rtc
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/livekit-server/pkg/config"
"github.com/livekit/livekit-server/pkg/routing/routingfakes"
"github.com/livekit/livekit-server/pkg/rtc/types/typesfakes"
)
func newParticipantWithDataBlob(t *testing.T, enabled bool, maxKeyLength int, maxSize uint32) *ParticipantImpl {
t.Helper()
p := newParticipantForTest("test")
p.params.EnableParticipantDataBlob = enabled
p.params.LimitConfig = config.LimitConfig{
MaxDataBlobKeyLength: maxKeyLength,
MaxDataBlobSize: maxSize,
}
return p
}
func lastRequestResponse(t *testing.T, sink *routingfakes.FakeMessageSink, idx int) *livekit.RequestResponse {
t.Helper()
msg := sink.WriteMessageArgsForCall(idx).(*livekit.SignalResponse)
rr, ok := msg.Message.(*livekit.SignalResponse_RequestResponse)
require.True(t, ok, "expected SignalResponse_RequestResponse, got %T", msg.Message)
return rr.RequestResponse
}
func TestHandleStoreDataBlobRequest(t *testing.T) {
t.Run("returns NOT_ALLOWED when feature not enabled", func(t *testing.T) {
p := newParticipantWithDataBlob(t, false, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
req := &livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: genericKey("blob-1"),
Contents: []byte("def"),
},
}
p.HandleStoreDataBlobRequest(req)
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_NOT_ALLOWED, rr.Reason)
require.Empty(t, p.dataBlob.GetAll())
})
t.Run("returns INVALID_REQUEST when blob is nil", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
require.Empty(t, p.dataBlob.GetAll())
})
t.Run("returns INVALID_REQUEST when key is nil", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Contents: []byte("def"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
})
t.Run("returns INVALID_REQUEST when key has no oneof set", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: &livekit.DataBlobKey{},
Contents: []byte("def"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
})
t.Run("returns INVALID_REQUEST when key exceeds length limit", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 5, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: genericKey(strings.Repeat("a", 64)),
Contents: []byte("def"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
})
t.Run("returns INVALID_REQUEST when contents is empty", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: genericKey("blob-1"),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
require.Empty(t, p.dataBlob.GetAll())
})
t.Run("returns LIMIT_EXCEEDED when adding would breach the limit", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 16)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
Blob: &livekit.DataBlob{
Key: genericKey("blob-1"),
Contents: []byte(strings.Repeat("x", 32)),
},
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_LIMIT_EXCEEDED, rr.Reason)
require.Empty(t, p.dataBlob.GetAll())
})
t.Run("stores a valid blob, notifies listener, and sends response", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
listener := p.params.ParticipantListener.(*typesfakes.FakeLocalParticipantListener)
key := genericKey("blob-1")
contents := []byte("definition-bytes")
blob := &livekit.DataBlob{Key: key, Contents: contents}
p.HandleStoreDataBlobRequest(&livekit.StoreDataBlobRequest{
RequestId: 42,
Blob: blob,
})
require.Equal(t, 1, sink.WriteMessageCallCount())
msg := sink.WriteMessageArgsForCall(0).(*livekit.SignalResponse)
response, ok := msg.Message.(*livekit.SignalResponse_StoreDataBlobResponse)
require.True(t, ok, "expected SignalResponse_StoreDataBlobResponse, got %T", msg.Message)
require.Equal(t, uint32(42), response.StoreDataBlobResponse.RequestId)
require.Equal(t, key, response.StoreDataBlobResponse.Key)
stored := p.dataBlob.Get(key)
require.NotNil(t, stored)
require.Equal(t, contents, stored.Contents)
require.Equal(t, 1, listener.OnStoreDataBlobCallCount())
gotParticipant, gotBlob := listener.OnStoreDataBlobArgsForCall(0)
require.Equal(t, p, gotParticipant)
require.Equal(t, blob, gotBlob)
})
}
func TestHandleGetDataBlobRequest(t *testing.T) {
t.Run("returns INVALID_REQUEST when key is missing", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.HandleGetDataBlobRequest(&livekit.GetDataBlobRequest{
ParticipantIdentity: "other",
})
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_INVALID_REQUEST, rr.Reason)
})
t.Run("forwards request to listener when key is provided", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
listener := p.params.ParticipantListener.(*typesfakes.FakeLocalParticipantListener)
req := &livekit.GetDataBlobRequest{
ParticipantIdentity: "other",
Key: genericKey("blob-1"),
}
p.HandleGetDataBlobRequest(req)
require.Equal(t, 1, listener.OnGetDataBlobCallCount())
gotParticipant, gotReq := listener.OnGetDataBlobArgsForCall(0)
require.Equal(t, p, gotParticipant)
require.Equal(t, req, gotReq)
})
}
func TestGetDataBlob(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
key := genericKey("blob-1")
require.Nil(t, p.GetDataBlob(key))
blob := &livekit.DataBlob{
Key: key,
Contents: []byte("definition"),
}
p.dataBlob.Add(blob)
got := p.GetDataBlob(key)
require.NotNil(t, got)
require.Equal(t, key.String(), got.Key.String())
require.Equal(t, []byte("definition"), got.Contents)
}
func TestProcessGetDataBlobRequest(t *testing.T) {
t.Run("returns NOT_FOUND when publisher is nil", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
p.ProcessGetDataBlobRequest(&livekit.GetDataBlobRequest{
Key: genericKey("blob-1"),
}, nil)
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_NOT_FOUND, rr.Reason)
require.Contains(t, rr.Message, "participant")
})
t.Run("returns NOT_FOUND when publisher has no matching blob", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
publisher := &typesfakes.FakeParticipant{}
publisher.GetDataBlobReturns(nil)
req := &livekit.GetDataBlobRequest{
Key: genericKey("blob-1"),
}
p.ProcessGetDataBlobRequest(req, publisher)
require.Equal(t, 1, publisher.GetDataBlobCallCount())
require.Equal(t, req.Key, publisher.GetDataBlobArgsForCall(0))
require.Equal(t, 1, sink.WriteMessageCallCount())
rr := lastRequestResponse(t, sink, 0)
require.Equal(t, livekit.RequestResponse_NOT_FOUND, rr.Reason)
})
t.Run("sends blob response when publisher has a matching blob", func(t *testing.T) {
p := newParticipantWithDataBlob(t, true, 0, 0)
sink := p.params.Sink.(*routingfakes.FakeMessageSink)
key := genericKey("blob-1")
blob := &livekit.DataBlob{
Key: key,
Contents: []byte("definition-bytes"),
}
publisher := &typesfakes.FakeParticipant{}
publisher.GetDataBlobReturns(blob)
p.ProcessGetDataBlobRequest(&livekit.GetDataBlobRequest{
RequestId: 42,
Key: key,
}, publisher)
require.Equal(t, 1, sink.WriteMessageCallCount())
msg := sink.WriteMessageArgsForCall(0).(*livekit.SignalResponse)
response, ok := msg.Message.(*livekit.SignalResponse_GetDataBlobResponse)
require.True(t, ok, "expected SignalResponse_GetDataBlobResponse, got %T", msg.Message)
require.Equal(t, uint32(42), response.GetDataBlobResponse.RequestId)
require.Equal(t, blob, response.GetDataBlobResponse.Blob)
})
}
+175
View File
@@ -0,0 +1,175 @@
// Copyright 2026 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 rtc
import (
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/require"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
)
func newTestDataBlob() *ParticipantDataBlob {
return NewParticipantDataBlob(ParticipantDataBlobParams{
Logger: logger.GetLogger(),
})
}
func genericKey(name string) *livekit.DataBlobKey {
return &livekit.DataBlobKey{
Key: &livekit.DataBlobKey_Generic{
Generic: name,
},
}
}
func TestParticipantDataBlob_AddAndGet(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
contents := []byte("definition-bytes")
a.Add(&livekit.DataBlob{Key: key, Contents: contents})
got := a.Get(key)
require.NotNil(t, got)
require.Equal(t, key.String(), got.Key.String())
require.Equal(t, contents, got.Contents)
}
func TestParticipantDataBlob_AddOverwrites(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v1")})
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v2")})
got := a.Get(key)
require.NotNil(t, got)
require.Equal(t, []byte("v2"), got.Contents)
require.Len(t, a.GetAll(), 1)
}
func TestParticipantDataBlob_DistinctKeys(t *testing.T) {
a := newTestDataBlob()
key1 := genericKey("blob-1")
key2 := genericKey("blob-2")
a.Add(&livekit.DataBlob{Key: key1, Contents: []byte("c1")})
a.Add(&livekit.DataBlob{Key: key2, Contents: []byte("c2")})
got1 := a.Get(key1)
require.NotNil(t, got1)
require.Equal(t, []byte("c1"), got1.Contents)
got2 := a.Get(key2)
require.NotNil(t, got2)
require.Equal(t, []byte("c2"), got2.Contents)
require.Len(t, a.GetAll(), 2)
}
func TestParticipantDataBlob_Delete(t *testing.T) {
a := newTestDataBlob()
key := genericKey("blob-1")
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("definition")})
a.Delete(key)
require.Nil(t, a.Get(key))
require.Empty(t, a.GetAll())
// deleting a non-existent key is a no-op
a.Delete(key)
require.Empty(t, a.GetAll())
}
func TestParticipantDataBlob_NilKey(t *testing.T) {
a := newTestDataBlob()
// nil key should be silently ignored, not panic
a.Add(&livekit.DataBlob{Contents: []byte("definition")})
require.Empty(t, a.GetAll())
require.Nil(t, a.Get(nil))
a.Delete(nil)
require.Empty(t, a.GetAll())
}
func TestParticipantDataBlob_GetMissing(t *testing.T) {
a := newTestDataBlob()
require.Nil(t, a.Get(genericKey("missing")))
}
func TestParticipantDataBlob_GetAllContents(t *testing.T) {
a := newTestDataBlob()
key1 := genericKey("blob-1")
key2 := genericKey("blob-2")
a.Add(&livekit.DataBlob{Key: key1, Contents: []byte("def-1")})
a.Add(&livekit.DataBlob{Key: key2, Contents: []byte("def-2")})
all := a.GetAll()
require.Len(t, all, 2)
for _, db := range all {
switch key := db.Key.Key.(type) {
case *livekit.DataBlobKey_Generic:
switch key.Generic {
case "blob-1":
require.Equal(t, []byte("def-1"), db.Contents)
case "blob-2":
require.Equal(t, []byte("def-2"), db.Contents)
default:
require.Fail(t, "unexpected key", key.Generic)
}
default:
require.Fail(t, "unexpected key type", "Generic")
}
}
}
func TestParticipantDataBlob_ConcurrentAccess(t *testing.T) {
a := newTestDataBlob()
const numGoroutines = 16
const opsPerGoroutine = 100
var wg sync.WaitGroup
wg.Add(numGoroutines)
for g := 0; g < numGoroutines; g++ {
go func(g int) {
defer wg.Done()
for i := 0; i < opsPerGoroutine; i++ {
key := genericKey(fmt.Sprintf("blob-%d", g%8))
a.Add(&livekit.DataBlob{Key: key, Contents: []byte("v")})
_ = a.Get(key)
_ = a.GetAll()
if i%3 == 0 {
a.Delete(key)
}
}
}(g)
}
wg.Wait()
}
+14
View File
@@ -368,3 +368,17 @@ func (p *ParticipantImpl) SendDataTrackSubscriberHandles(handles map[uint32]*liv
SubHandles: handles,
}))
}
func (p *ParticipantImpl) sendStoreDataBlobResponse(requestId uint32, key *livekit.DataBlobKey) error {
return p.signaller.WriteMessage(p.signalling.SignalStoreDataBlobResponse(&livekit.StoreDataBlobResponse{
RequestId: requestId,
Key: key,
}))
}
func (p *ParticipantImpl) sendGetDataBlobResponse(requestId uint32, dataBlob *livekit.DataBlob) error {
return p.signaller.WriteMessage(p.signalling.SignalGetDataBlobResponse(&livekit.GetDataBlobResponse{
RequestId: requestId,
Blob: dataBlob,
}))
}
+12
View File
@@ -1384,6 +1384,11 @@ func (r *Room) onUpdateDataSubscriptions(participant types.LocalParticipant, req
}
}
func (r *Room) onGetDataBlob(participant types.LocalParticipant, req *livekit.GetDataBlobRequest) {
publisher := r.GetParticipant(livekit.ParticipantIdentity(req.ParticipantIdentity))
participant.ProcessGetDataBlobRequest(req, publisher)
}
func (r *Room) onLeave(p types.LocalParticipant, reason types.ParticipantCloseReason) {
r.RemoveParticipant(p.Identity(), p.ID(), reason)
}
@@ -1996,6 +2001,13 @@ func (l *localParticipantListener) OnUpdateDataSubscriptions(p types.LocalPartic
l.room.onUpdateDataSubscriptions(p, req)
}
func (l *localParticipantListener) OnStoreDataBlob(_p types.LocalParticipant, _dataBlob *livekit.DataBlob) {
}
func (l *localParticipantListener) OnGetDataBlob(p types.LocalParticipant, req *livekit.GetDataBlobRequest) {
l.room.onGetDataBlob(p, req)
}
func (l *localParticipantListener) OnSyncState(p types.LocalParticipant, state *livekit.SyncState) error {
return l.room.onSyncState(p, state)
}
+2
View File
@@ -62,4 +62,6 @@ type ParticipantSignalling interface {
SignalPublishDataTrackResponse(publishDataTrackResponse *livekit.PublishDataTrackResponse) proto.Message
SignalUnpublishDataTrackResponse(unpublishDataTrackResponse *livekit.UnpublishDataTrackResponse) proto.Message
SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles *livekit.DataTrackSubscriberHandles) proto.Message
SignalStoreDataBlobResponse(storeDataBlobResponse *livekit.StoreDataBlobResponse) proto.Message
SignalGetDataBlobResponse(getDataBlobResponse *livekit.GetDataBlobResponse) proto.Message
}
+6
View File
@@ -151,6 +151,12 @@ func (s *signalhandler) HandleMessage(msg proto.Message) error {
case *livekit.SignalRequest_UpdateDataSubscription:
s.params.Participant.HandleUpdateDataSubscription(msg.UpdateDataSubscription)
case *livekit.SignalRequest_StoreDataBlobRequest:
s.params.Participant.HandleStoreDataBlobRequest(msg.StoreDataBlobRequest)
case *livekit.SignalRequest_GetDataBlobRequest:
s.params.Participant.HandleGetDataBlobRequest(msg.GetDataBlobRequest)
}
return nil
+16
View File
@@ -258,3 +258,19 @@ func (s *signalling) SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles
},
}
}
func (s *signalling) SignalStoreDataBlobResponse(storeDataBlobResponse *livekit.StoreDataBlobResponse) proto.Message {
return &livekit.SignalResponse{
Message: &livekit.SignalResponse_StoreDataBlobResponse{
StoreDataBlobResponse: storeDataBlobResponse,
},
}
}
func (s *signalling) SignalGetDataBlobResponse(getDataBlobResponse *livekit.GetDataBlobResponse) proto.Message {
return &livekit.SignalResponse{
Message: &livekit.SignalResponse_GetDataBlobResponse{
GetDataBlobResponse: getDataBlobResponse,
},
}
}
@@ -127,3 +127,11 @@ func (u *signallingUnimplemented) SignalUnpublishDataTrackResponse(unpublishData
func (u *signallingUnimplemented) SignalDataTrackSubscriberHandles(dataTrackSubscriberHandles *livekit.DataTrackSubscriberHandles) proto.Message {
return nil
}
func (u *signallingUnimplemented) SignalStoreDataBlobResponse(storeDataBlobResponse *livekit.StoreDataBlobResponse) proto.Message {
return nil
}
func (u *signallingUnimplemented) SignalGetDataBlobResponse(getDataBlobResponse *livekit.GetDataBlobResponse) proto.Message {
return nil
}
+89 -14
View File
@@ -1007,12 +1007,12 @@ func (t *PCTransport) queueOrConfigureSender(
enableAudioNACK bool,
) {
params := configureSenderParams{
transceiver,
enabledCodecs,
rtcpFeedbackConfig,
!t.params.IsOfferer,
enableAudioStereo,
enableAudioNACK,
transceiver: transceiver,
enabledCodecs: enabledCodecs,
rtcpFeedbackConfig: rtcpFeedbackConfig,
filterOutH264HighProfile: !t.params.IsOfferer,
enableAudioStereo: enableAudioStereo,
enableAudioNACK: enableAudioNACK,
}
if !t.params.IsOfferer {
t.sendersPendingConfigMu.Lock()
@@ -1021,10 +1021,17 @@ func (t *PCTransport) queueOrConfigureSender(
return
}
configureSender(params)
// Offerer: no remote offer to echo payload types from.
configureSender(params, nil)
}
func (t *PCTransport) processSendersPendingConfig() {
// processSendersPendingConfig configures the senders queued while answering the
// remote's offer (single peer connection mode). offerAudioPT (mime type -> the
// payload type the offer assigned) is parsed from the offer by the caller before
// SetRemoteDescription, so the answer echoes the offered payload types for audio
// codecs (and stays consistent with the forwarded RTP). It is nil when there is
// nothing to echo.
func (t *PCTransport) processSendersPendingConfig(offerAudioPT map[mime.MimeType]webrtc.PayloadType) {
t.sendersPendingConfigMu.Lock()
pending := t.sendersPendingConfig
t.sendersPendingConfig = nil
@@ -1037,7 +1044,7 @@ func (t *PCTransport) processSendersPendingConfig() {
continue
}
configureSender(p)
configureSender(p, offerAudioPT)
}
if len(unprocessed) != 0 {
@@ -1432,6 +1439,13 @@ func (t *PCTransport) HasEverConnected() bool {
return !t.firstConnectedAt.IsZero()
}
func (t *PCTransport) FirstConnectedAt() time.Time {
t.lock.RLock()
defer t.lock.RUnlock()
return t.firstConnectedAt
}
func (t *PCTransport) GetICEConnectionInfo() *types.ICEConnectionInfo {
return t.connectionDetails.GetInfo()
}
@@ -2340,11 +2354,19 @@ func (t *PCTransport) handleICEGatheringCompleteAnswerer() error {
t.pendingRestartIceOffer = nil
t.params.Logger.Debugw("accept remote restart ice offer after ICE gathering")
// Parse the offer payload types before SetRemoteDescription so this does not
// race with pion's use of the same description.
var offerAudioPT map[mime.MimeType]webrtc.PayloadType
if parsed, err := offer.Unmarshal(); err == nil {
offerAudioPT = offerAudioPayloadTypes(parsed)
}
if err := t.setRemoteDescription(offer); err != nil {
return err
}
t.params.Handler.OnSetRemoteDescriptionOffer()
t.processSendersPendingConfig()
t.processSendersPendingConfig(offerAudioPT)
return t.createAndSendAnswer()
}
@@ -2889,7 +2911,7 @@ func (t *PCTransport) handleRemoteOfferReceived(sd *webrtc.SessionDescription, o
}
t.params.Handler.OnSetRemoteDescriptionOffer()
t.processSendersPendingConfig()
t.processSendersPendingConfig(offerAudioPayloadTypes(parsed))
rtxRepairs := nonSimulcastRTXRepairsFromSDP(parsed, t.params.Logger)
if len(rtxRepairs) > 0 {
@@ -3074,7 +3096,7 @@ type configureSenderParams struct {
enableAudioNACK bool
}
func configureSender(params configureSenderParams) {
func configureSender(params configureSenderParams, offerAudioPT map[mime.MimeType]webrtc.PayloadType) {
configureSenderCodecs(
params.transceiver,
params.enabledCodecs,
@@ -3083,14 +3105,14 @@ func configureSender(params configureSenderParams) {
)
if params.transceiver.Kind() == webrtc.RTPCodecTypeAudio {
configureSenderAudio(params.transceiver, params.enableAudioStereo, params.enableAudioNACK)
configureSenderAudio(params.transceiver, params.enableAudioStereo, params.enableAudioNACK, offerAudioPT)
}
}
// configure subscriber transceiver for audio stereo and nack
// pion doesn't support per transciver codec configuration, so the nack of this session will be disabled
// forever once it is first disabled by a transceiver.
func configureSenderAudio(tr *webrtc.RTPTransceiver, stereo bool, nack bool) {
func configureSenderAudio(tr *webrtc.RTPTransceiver, stereo bool, nack bool, offerAudioPT map[mime.MimeType]webrtc.PayloadType) {
sender := tr.Sender()
if sender == nil {
return
@@ -3114,12 +3136,65 @@ func configureSenderAudio(tr *webrtc.RTPTransceiver, stereo bool, nack bool) {
}
}
}
// When answering a subscriber's offer (single peer connection mode), echo
// the payload type the offer assigned for this codec instead of the server's
// MediaEngine payload type. Otherwise the answer can advertise e.g. Opus on a
// PT that was never offered, which Firefox rejects (received packets decode to
// 0 samples / silence). The forwarded RTP already uses the offered PT.
if len(offerAudioPT) > 0 {
if pt, ok := offerAudioPT[mime.NormalizeMimeType(c.MimeType)]; ok {
c.PayloadType = pt
}
}
configCodecs = append(configCodecs, c)
}
tr.SetCodecPreferences(configCodecs)
}
// offerAudioPayloadTypes returns mime type -> payload type for the audio codecs
// in a remote offer, so the subscriber answer can echo the offered payload types
// (RFC 3264 6.1). The caller parses the offer before SetRemoteDescription, so this
// does not race with pion's use of the same description.
func offerAudioPayloadTypes(parsed *sdp.SessionDescription) map[mime.MimeType]webrtc.PayloadType {
if parsed == nil {
return nil
}
out := map[mime.MimeType]webrtc.PayloadType{}
for _, md := range parsed.MediaDescriptions {
if !strings.EqualFold(md.MediaName.Media, "audio") {
continue
}
for _, a := range md.Attributes {
if a.Key != "rtpmap" {
continue
}
// value e.g. "109 opus/48000/2"
fields := strings.Fields(a.Value)
if len(fields) < 2 {
continue
}
pt, err := strconv.Atoi(fields[0])
if err != nil {
continue
}
codecName := fields[1]
if i := strings.Index(codecName, "/"); i >= 0 {
codecName = codecName[:i]
}
mt := mime.NormalizeMimeTypeCodec(codecName).ToMimeType()
if mt == mime.MimeTypeUnknown {
continue
}
out[mt] = webrtc.PayloadType(pt)
}
}
if len(out) == 0 {
return nil
}
return out
}
// In single peer connection mode, set up enebled codecs for sender.
// The config provides config of direction.
// For publisher peer connection those are publish enabled codecs
+27 -1
View File
@@ -617,7 +617,7 @@ func TestConfigureAudioTransceiver(t *testing.T) {
tr, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
require.NoError(t, err)
configureSenderAudio(tr, testcase.stereo, testcase.nack)
configureSenderAudio(tr, testcase.stereo, testcase.nack, nil)
codecs := tr.Sender().GetParameters().Codecs
for _, codec := range codecs {
if mime.IsMimeTypeStringOpus(codec.MimeType) {
@@ -636,6 +636,32 @@ func TestConfigureAudioTransceiver(t *testing.T) {
}
}
// When answering a subscriber offer, the sender's audio payload type must echo
// the payload type the offer assigned (RFC 3264), otherwise Firefox decodes no
// audio. See https://github.com/livekit/livekit/issues/4599.
func TestConfigureAudioTransceiverEchoesOfferPayloadType(t *testing.T) {
var me webrtc.MediaEngine
registerCodecs(&me, []*livekit.Codec{{Mime: mime.MimeTypeOpus.String()}}, RTCPFeedbackConfig{Audio: []webrtc.RTCPFeedback{{Type: webrtc.TypeRTCPFBNACK}}}, false)
pc, err := webrtc.NewAPI(webrtc.WithMediaEngine(&me)).NewPeerConnection(webrtc.Configuration{})
require.NoError(t, err)
defer pc.Close()
tr, err := pc.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
require.NoError(t, err)
// offer mapped Opus to a payload type different from the server MediaEngine's.
const offeredOpusPT = webrtc.PayloadType(109)
configureSenderAudio(tr, false, true, map[mime.MimeType]webrtc.PayloadType{mime.MimeTypeOpus: offeredOpusPT})
var found bool
for _, codec := range tr.Sender().GetParameters().Codecs {
if mime.IsMimeTypeStringOpus(codec.MimeType) {
require.Equal(t, offeredOpusPT, codec.PayloadType)
found = true
}
}
require.True(t, found, "opus codec must be present in sender preferences")
}
// In single-PC mode the publisher PC carries both publish and subscribe
// directions. If the MediaEngine were built only from the publish codec list,
// the SDP offer would not advertise some codecs in the m-section even though
+12
View File
@@ -227,6 +227,10 @@ func (t *TransportManager) HasPublisherEverConnected() bool {
return t.publisher.HasEverConnected()
}
func (t *TransportManager) PublisherFirstConnectedAt() time.Time {
return t.publisher.FirstConnectedAt()
}
func (t *TransportManager) IsPublisherEstablished() bool {
return t.publisher.IsEstablished()
}
@@ -267,6 +271,14 @@ func (t *TransportManager) HasSubscriberEverConnected() bool {
}
}
func (t *TransportManager) SubscriberFirstConnectedAt() time.Time {
if t.params.UseOneShotSignallingMode || t.params.UseSinglePeerConnection {
return t.publisher.FirstConnectedAt()
} else {
return t.subscriber.FirstConnectedAt()
}
}
func (t *TransportManager) AddTrackLocal(
trackLocal webrtc.TrackLocal,
params types.AddTrackParams,
+14
View File
@@ -355,6 +355,9 @@ type Participant interface {
HandleReceivedDataTrackMessage([]byte, *datatrack.Packet, int64)
GetParticipantListener() ParticipantListener
AddDataBlob(dataBlob *livekit.DataBlob)
GetDataBlob(key *livekit.DataBlobKey) *livekit.DataBlob
}
// -------------------------------------------------------
@@ -562,6 +565,9 @@ type LocalParticipant interface {
HandlePublishDataTrackRequest(*livekit.PublishDataTrackRequest)
HandleUnpublishDataTrackRequest(*livekit.UnpublishDataTrackRequest)
HandleUpdateDataSubscription(*livekit.UpdateDataSubscription)
HandleStoreDataBlobRequest(*livekit.StoreDataBlobRequest)
HandleGetDataBlobRequest(*livekit.GetDataBlobRequest)
ProcessGetDataBlobRequest(*livekit.GetDataBlobRequest, Participant)
HandleSignalMessage(msg proto.Message) error
@@ -572,6 +578,8 @@ type LocalParticipant interface {
ClearParticipantListener()
GetNextSubscribedDataTrackHandle() uint16
GetAllDataBlob() []*livekit.DataBlob
}
// ---------------------------------------------
@@ -621,6 +629,8 @@ type LocalParticipantListener interface {
)
OnUpdateSubscriptionPermission(LocalParticipant, *livekit.SubscriptionPermission) error
OnUpdateDataSubscriptions(LocalParticipant, *livekit.UpdateDataSubscription)
OnStoreDataBlob(LocalParticipant, *livekit.DataBlob)
OnGetDataBlob(LocalParticipant, *livekit.GetDataBlobRequest)
OnSyncState(LocalParticipant, *livekit.SyncState) error
OnSimulateScenario(LocalParticipant, *livekit.SimulateScenario) error
OnLeave(LocalParticipant, ParticipantCloseReason)
@@ -652,6 +662,10 @@ func (*NullLocalParticipantListener) OnUpdateSubscriptionPermission(LocalPartici
}
func (*NullLocalParticipantListener) OnUpdateDataSubscriptions(LocalParticipant, *livekit.UpdateDataSubscription) {
}
func (*NullLocalParticipantListener) OnStoreDataBlob(LocalParticipant, *livekit.DataBlob) {
}
func (*NullLocalParticipantListener) OnGetDataBlob(LocalParticipant, *livekit.GetDataBlobRequest) {
}
func (*NullLocalParticipantListener) OnSyncState(LocalParticipant, *livekit.SyncState) error {
return nil
}
@@ -33,6 +33,11 @@ type FakeLocalParticipant struct {
activeAtReturnsOnCall map[int]struct {
result1 time.Time
}
AddDataBlobStub func(*livekit.DataBlob)
addDataBlobMutex sync.RWMutex
addDataBlobArgsForCall []struct {
arg1 *livekit.DataBlob
}
AddOnCloseStub func(string, func(types.LocalParticipant))
addOnCloseMutex sync.RWMutex
addOnCloseArgsForCall []struct {
@@ -216,6 +221,16 @@ type FakeLocalParticipant struct {
getAdaptiveStreamReturnsOnCall map[int]struct {
result1 bool
}
GetAllDataBlobStub func() []*livekit.DataBlob
getAllDataBlobMutex sync.RWMutex
getAllDataBlobArgsForCall []struct {
}
getAllDataBlobReturns struct {
result1 []*livekit.DataBlob
}
getAllDataBlobReturnsOnCall map[int]struct {
result1 []*livekit.DataBlob
}
GetAnswerStub func() (webrtc.SessionDescription, uint32, error)
getAnswerMutex sync.RWMutex
getAnswerArgsForCall []struct {
@@ -305,6 +320,17 @@ type FakeLocalParticipant struct {
getCountryReturnsOnCall map[int]struct {
result1 string
}
GetDataBlobStub func(*livekit.DataBlobKey) *livekit.DataBlob
getDataBlobMutex sync.RWMutex
getDataBlobArgsForCall []struct {
arg1 *livekit.DataBlobKey
}
getDataBlobReturns struct {
result1 *livekit.DataBlob
}
getDataBlobReturnsOnCall map[int]struct {
result1 *livekit.DataBlob
}
GetDataTrackTransportStub func() types.DataTrackTransport
getDataTrackTransportMutex sync.RWMutex
getDataTrackTransportArgsForCall []struct {
@@ -576,6 +602,11 @@ type FakeLocalParticipant struct {
handleAnswerArgsForCall []struct {
arg1 *livekit.SessionDescription
}
HandleGetDataBlobRequestStub func(*livekit.GetDataBlobRequest)
handleGetDataBlobRequestMutex sync.RWMutex
handleGetDataBlobRequestArgsForCall []struct {
arg1 *livekit.GetDataBlobRequest
}
HandleICERestartSDPFragmentStub func(string) (string, error)
handleICERestartSDPFragmentMutex sync.RWMutex
handleICERestartSDPFragmentArgsForCall []struct {
@@ -689,6 +720,11 @@ type FakeLocalParticipant struct {
handleSimulateScenarioReturnsOnCall map[int]struct {
result1 error
}
HandleStoreDataBlobRequestStub func(*livekit.StoreDataBlobRequest)
handleStoreDataBlobRequestMutex sync.RWMutex
handleStoreDataBlobRequestArgsForCall []struct {
arg1 *livekit.StoreDataBlobRequest
}
HandleSyncStateStub func(*livekit.SyncState) error
handleSyncStateMutex sync.RWMutex
handleSyncStateArgsForCall []struct {
@@ -986,6 +1022,12 @@ type FakeLocalParticipant struct {
arg2 chan string
arg3 chan error
}
ProcessGetDataBlobRequestStub func(*livekit.GetDataBlobRequest, types.Participant)
processGetDataBlobRequestMutex sync.RWMutex
processGetDataBlobRequestArgsForCall []struct {
arg1 *livekit.GetDataBlobRequest
arg2 types.Participant
}
ProtocolVersionStub func() types.ProtocolVersion
protocolVersionMutex sync.RWMutex
protocolVersionArgsForCall []struct {
@@ -1594,6 +1636,38 @@ func (fake *FakeLocalParticipant) ActiveAtReturnsOnCall(i int, result1 time.Time
}{result1}
}
func (fake *FakeLocalParticipant) AddDataBlob(arg1 *livekit.DataBlob) {
fake.addDataBlobMutex.Lock()
fake.addDataBlobArgsForCall = append(fake.addDataBlobArgsForCall, struct {
arg1 *livekit.DataBlob
}{arg1})
stub := fake.AddDataBlobStub
fake.recordInvocation("AddDataBlob", []interface{}{arg1})
fake.addDataBlobMutex.Unlock()
if stub != nil {
fake.AddDataBlobStub(arg1)
}
}
func (fake *FakeLocalParticipant) AddDataBlobCallCount() int {
fake.addDataBlobMutex.RLock()
defer fake.addDataBlobMutex.RUnlock()
return len(fake.addDataBlobArgsForCall)
}
func (fake *FakeLocalParticipant) AddDataBlobCalls(stub func(*livekit.DataBlob)) {
fake.addDataBlobMutex.Lock()
defer fake.addDataBlobMutex.Unlock()
fake.AddDataBlobStub = stub
}
func (fake *FakeLocalParticipant) AddDataBlobArgsForCall(i int) *livekit.DataBlob {
fake.addDataBlobMutex.RLock()
defer fake.addDataBlobMutex.RUnlock()
argsForCall := fake.addDataBlobArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeLocalParticipant) AddOnClose(arg1 string, arg2 func(types.LocalParticipant)) {
fake.addOnCloseMutex.Lock()
fake.addOnCloseArgsForCall = append(fake.addOnCloseArgsForCall, struct {
@@ -2539,6 +2613,59 @@ func (fake *FakeLocalParticipant) GetAdaptiveStreamReturnsOnCall(i int, result1
}{result1}
}
func (fake *FakeLocalParticipant) GetAllDataBlob() []*livekit.DataBlob {
fake.getAllDataBlobMutex.Lock()
ret, specificReturn := fake.getAllDataBlobReturnsOnCall[len(fake.getAllDataBlobArgsForCall)]
fake.getAllDataBlobArgsForCall = append(fake.getAllDataBlobArgsForCall, struct {
}{})
stub := fake.GetAllDataBlobStub
fakeReturns := fake.getAllDataBlobReturns
fake.recordInvocation("GetAllDataBlob", []interface{}{})
fake.getAllDataBlobMutex.Unlock()
if stub != nil {
return stub()
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeLocalParticipant) GetAllDataBlobCallCount() int {
fake.getAllDataBlobMutex.RLock()
defer fake.getAllDataBlobMutex.RUnlock()
return len(fake.getAllDataBlobArgsForCall)
}
func (fake *FakeLocalParticipant) GetAllDataBlobCalls(stub func() []*livekit.DataBlob) {
fake.getAllDataBlobMutex.Lock()
defer fake.getAllDataBlobMutex.Unlock()
fake.GetAllDataBlobStub = stub
}
func (fake *FakeLocalParticipant) GetAllDataBlobReturns(result1 []*livekit.DataBlob) {
fake.getAllDataBlobMutex.Lock()
defer fake.getAllDataBlobMutex.Unlock()
fake.GetAllDataBlobStub = nil
fake.getAllDataBlobReturns = struct {
result1 []*livekit.DataBlob
}{result1}
}
func (fake *FakeLocalParticipant) GetAllDataBlobReturnsOnCall(i int, result1 []*livekit.DataBlob) {
fake.getAllDataBlobMutex.Lock()
defer fake.getAllDataBlobMutex.Unlock()
fake.GetAllDataBlobStub = nil
if fake.getAllDataBlobReturnsOnCall == nil {
fake.getAllDataBlobReturnsOnCall = make(map[int]struct {
result1 []*livekit.DataBlob
})
}
fake.getAllDataBlobReturnsOnCall[i] = struct {
result1 []*livekit.DataBlob
}{result1}
}
func (fake *FakeLocalParticipant) GetAnswer() (webrtc.SessionDescription, uint32, error) {
fake.getAnswerMutex.Lock()
ret, specificReturn := fake.getAnswerReturnsOnCall[len(fake.getAnswerArgsForCall)]
@@ -2983,6 +3110,67 @@ func (fake *FakeLocalParticipant) GetCountryReturnsOnCall(i int, result1 string)
}{result1}
}
func (fake *FakeLocalParticipant) GetDataBlob(arg1 *livekit.DataBlobKey) *livekit.DataBlob {
fake.getDataBlobMutex.Lock()
ret, specificReturn := fake.getDataBlobReturnsOnCall[len(fake.getDataBlobArgsForCall)]
fake.getDataBlobArgsForCall = append(fake.getDataBlobArgsForCall, struct {
arg1 *livekit.DataBlobKey
}{arg1})
stub := fake.GetDataBlobStub
fakeReturns := fake.getDataBlobReturns
fake.recordInvocation("GetDataBlob", []interface{}{arg1})
fake.getDataBlobMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeLocalParticipant) GetDataBlobCallCount() int {
fake.getDataBlobMutex.RLock()
defer fake.getDataBlobMutex.RUnlock()
return len(fake.getDataBlobArgsForCall)
}
func (fake *FakeLocalParticipant) GetDataBlobCalls(stub func(*livekit.DataBlobKey) *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = stub
}
func (fake *FakeLocalParticipant) GetDataBlobArgsForCall(i int) *livekit.DataBlobKey {
fake.getDataBlobMutex.RLock()
defer fake.getDataBlobMutex.RUnlock()
argsForCall := fake.getDataBlobArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeLocalParticipant) GetDataBlobReturns(result1 *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = nil
fake.getDataBlobReturns = struct {
result1 *livekit.DataBlob
}{result1}
}
func (fake *FakeLocalParticipant) GetDataBlobReturnsOnCall(i int, result1 *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = nil
if fake.getDataBlobReturnsOnCall == nil {
fake.getDataBlobReturnsOnCall = make(map[int]struct {
result1 *livekit.DataBlob
})
}
fake.getDataBlobReturnsOnCall[i] = struct {
result1 *livekit.DataBlob
}{result1}
}
func (fake *FakeLocalParticipant) GetDataTrackTransport() types.DataTrackTransport {
fake.getDataTrackTransportMutex.Lock()
ret, specificReturn := fake.getDataTrackTransportReturnsOnCall[len(fake.getDataTrackTransportArgsForCall)]
@@ -4428,6 +4616,38 @@ func (fake *FakeLocalParticipant) HandleAnswerArgsForCall(i int) *livekit.Sessio
return argsForCall.arg1
}
func (fake *FakeLocalParticipant) HandleGetDataBlobRequest(arg1 *livekit.GetDataBlobRequest) {
fake.handleGetDataBlobRequestMutex.Lock()
fake.handleGetDataBlobRequestArgsForCall = append(fake.handleGetDataBlobRequestArgsForCall, struct {
arg1 *livekit.GetDataBlobRequest
}{arg1})
stub := fake.HandleGetDataBlobRequestStub
fake.recordInvocation("HandleGetDataBlobRequest", []interface{}{arg1})
fake.handleGetDataBlobRequestMutex.Unlock()
if stub != nil {
fake.HandleGetDataBlobRequestStub(arg1)
}
}
func (fake *FakeLocalParticipant) HandleGetDataBlobRequestCallCount() int {
fake.handleGetDataBlobRequestMutex.RLock()
defer fake.handleGetDataBlobRequestMutex.RUnlock()
return len(fake.handleGetDataBlobRequestArgsForCall)
}
func (fake *FakeLocalParticipant) HandleGetDataBlobRequestCalls(stub func(*livekit.GetDataBlobRequest)) {
fake.handleGetDataBlobRequestMutex.Lock()
defer fake.handleGetDataBlobRequestMutex.Unlock()
fake.HandleGetDataBlobRequestStub = stub
}
func (fake *FakeLocalParticipant) HandleGetDataBlobRequestArgsForCall(i int) *livekit.GetDataBlobRequest {
fake.handleGetDataBlobRequestMutex.RLock()
defer fake.handleGetDataBlobRequestMutex.RUnlock()
argsForCall := fake.handleGetDataBlobRequestArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeLocalParticipant) HandleICERestartSDPFragment(arg1 string) (string, error) {
fake.handleICERestartSDPFragmentMutex.Lock()
ret, specificReturn := fake.handleICERestartSDPFragmentReturnsOnCall[len(fake.handleICERestartSDPFragmentArgsForCall)]
@@ -5052,6 +5272,38 @@ func (fake *FakeLocalParticipant) HandleSimulateScenarioReturnsOnCall(i int, res
}{result1}
}
func (fake *FakeLocalParticipant) HandleStoreDataBlobRequest(arg1 *livekit.StoreDataBlobRequest) {
fake.handleStoreDataBlobRequestMutex.Lock()
fake.handleStoreDataBlobRequestArgsForCall = append(fake.handleStoreDataBlobRequestArgsForCall, struct {
arg1 *livekit.StoreDataBlobRequest
}{arg1})
stub := fake.HandleStoreDataBlobRequestStub
fake.recordInvocation("HandleStoreDataBlobRequest", []interface{}{arg1})
fake.handleStoreDataBlobRequestMutex.Unlock()
if stub != nil {
fake.HandleStoreDataBlobRequestStub(arg1)
}
}
func (fake *FakeLocalParticipant) HandleStoreDataBlobRequestCallCount() int {
fake.handleStoreDataBlobRequestMutex.RLock()
defer fake.handleStoreDataBlobRequestMutex.RUnlock()
return len(fake.handleStoreDataBlobRequestArgsForCall)
}
func (fake *FakeLocalParticipant) HandleStoreDataBlobRequestCalls(stub func(*livekit.StoreDataBlobRequest)) {
fake.handleStoreDataBlobRequestMutex.Lock()
defer fake.handleStoreDataBlobRequestMutex.Unlock()
fake.HandleStoreDataBlobRequestStub = stub
}
func (fake *FakeLocalParticipant) HandleStoreDataBlobRequestArgsForCall(i int) *livekit.StoreDataBlobRequest {
fake.handleStoreDataBlobRequestMutex.RLock()
defer fake.handleStoreDataBlobRequestMutex.RUnlock()
argsForCall := fake.handleStoreDataBlobRequestArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeLocalParticipant) HandleSyncState(arg1 *livekit.SyncState) error {
fake.handleSyncStateMutex.Lock()
ret, specificReturn := fake.handleSyncStateReturnsOnCall[len(fake.handleSyncStateArgsForCall)]
@@ -6680,6 +6932,39 @@ func (fake *FakeLocalParticipant) PerformRpcArgsForCall(i int) (*livekit.Perform
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *FakeLocalParticipant) ProcessGetDataBlobRequest(arg1 *livekit.GetDataBlobRequest, arg2 types.Participant) {
fake.processGetDataBlobRequestMutex.Lock()
fake.processGetDataBlobRequestArgsForCall = append(fake.processGetDataBlobRequestArgsForCall, struct {
arg1 *livekit.GetDataBlobRequest
arg2 types.Participant
}{arg1, arg2})
stub := fake.ProcessGetDataBlobRequestStub
fake.recordInvocation("ProcessGetDataBlobRequest", []interface{}{arg1, arg2})
fake.processGetDataBlobRequestMutex.Unlock()
if stub != nil {
fake.ProcessGetDataBlobRequestStub(arg1, arg2)
}
}
func (fake *FakeLocalParticipant) ProcessGetDataBlobRequestCallCount() int {
fake.processGetDataBlobRequestMutex.RLock()
defer fake.processGetDataBlobRequestMutex.RUnlock()
return len(fake.processGetDataBlobRequestArgsForCall)
}
func (fake *FakeLocalParticipant) ProcessGetDataBlobRequestCalls(stub func(*livekit.GetDataBlobRequest, types.Participant)) {
fake.processGetDataBlobRequestMutex.Lock()
defer fake.processGetDataBlobRequestMutex.Unlock()
fake.ProcessGetDataBlobRequestStub = stub
}
func (fake *FakeLocalParticipant) ProcessGetDataBlobRequestArgsForCall(i int) (*livekit.GetDataBlobRequest, types.Participant) {
fake.processGetDataBlobRequestMutex.RLock()
defer fake.processGetDataBlobRequestMutex.RUnlock()
argsForCall := fake.processGetDataBlobRequestArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeLocalParticipant) ProtocolVersion() types.ProtocolVersion {
fake.protocolVersionMutex.Lock()
ret, specificReturn := fake.protocolVersionReturnsOnCall[len(fake.protocolVersionArgsForCall)]
@@ -42,6 +42,12 @@ type FakeLocalParticipantListener struct {
arg1 types.Participant
arg2 types.DataTrack
}
OnGetDataBlobStub func(types.LocalParticipant, *livekit.GetDataBlobRequest)
onGetDataBlobMutex sync.RWMutex
onGetDataBlobArgsForCall []struct {
arg1 types.LocalParticipant
arg2 *livekit.GetDataBlobRequest
}
OnLeaveStub func(types.LocalParticipant, types.ParticipantCloseReason)
onLeaveMutex sync.RWMutex
onLeaveArgsForCall []struct {
@@ -82,6 +88,12 @@ type FakeLocalParticipantListener struct {
onStateChangeArgsForCall []struct {
arg1 types.LocalParticipant
}
OnStoreDataBlobStub func(types.LocalParticipant, *livekit.DataBlob)
onStoreDataBlobMutex sync.RWMutex
onStoreDataBlobArgsForCall []struct {
arg1 types.LocalParticipant
arg2 *livekit.DataBlob
}
OnSubscribeStatusChangedStub func(types.LocalParticipant, livekit.ParticipantID, bool)
onSubscribeStatusChangedMutex sync.RWMutex
onSubscribeStatusChangedArgsForCall []struct {
@@ -331,6 +343,39 @@ func (fake *FakeLocalParticipantListener) OnDataTrackUnpublishedArgsForCall(i in
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeLocalParticipantListener) OnGetDataBlob(arg1 types.LocalParticipant, arg2 *livekit.GetDataBlobRequest) {
fake.onGetDataBlobMutex.Lock()
fake.onGetDataBlobArgsForCall = append(fake.onGetDataBlobArgsForCall, struct {
arg1 types.LocalParticipant
arg2 *livekit.GetDataBlobRequest
}{arg1, arg2})
stub := fake.OnGetDataBlobStub
fake.recordInvocation("OnGetDataBlob", []interface{}{arg1, arg2})
fake.onGetDataBlobMutex.Unlock()
if stub != nil {
fake.OnGetDataBlobStub(arg1, arg2)
}
}
func (fake *FakeLocalParticipantListener) OnGetDataBlobCallCount() int {
fake.onGetDataBlobMutex.RLock()
defer fake.onGetDataBlobMutex.RUnlock()
return len(fake.onGetDataBlobArgsForCall)
}
func (fake *FakeLocalParticipantListener) OnGetDataBlobCalls(stub func(types.LocalParticipant, *livekit.GetDataBlobRequest)) {
fake.onGetDataBlobMutex.Lock()
defer fake.onGetDataBlobMutex.Unlock()
fake.OnGetDataBlobStub = stub
}
func (fake *FakeLocalParticipantListener) OnGetDataBlobArgsForCall(i int) (types.LocalParticipant, *livekit.GetDataBlobRequest) {
fake.onGetDataBlobMutex.RLock()
defer fake.onGetDataBlobMutex.RUnlock()
argsForCall := fake.onGetDataBlobArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeLocalParticipantListener) OnLeave(arg1 types.LocalParticipant, arg2 types.ParticipantCloseReason) {
fake.onLeaveMutex.Lock()
fake.onLeaveArgsForCall = append(fake.onLeaveArgsForCall, struct {
@@ -556,6 +601,39 @@ func (fake *FakeLocalParticipantListener) OnStateChangeArgsForCall(i int) types.
return argsForCall.arg1
}
func (fake *FakeLocalParticipantListener) OnStoreDataBlob(arg1 types.LocalParticipant, arg2 *livekit.DataBlob) {
fake.onStoreDataBlobMutex.Lock()
fake.onStoreDataBlobArgsForCall = append(fake.onStoreDataBlobArgsForCall, struct {
arg1 types.LocalParticipant
arg2 *livekit.DataBlob
}{arg1, arg2})
stub := fake.OnStoreDataBlobStub
fake.recordInvocation("OnStoreDataBlob", []interface{}{arg1, arg2})
fake.onStoreDataBlobMutex.Unlock()
if stub != nil {
fake.OnStoreDataBlobStub(arg1, arg2)
}
}
func (fake *FakeLocalParticipantListener) OnStoreDataBlobCallCount() int {
fake.onStoreDataBlobMutex.RLock()
defer fake.onStoreDataBlobMutex.RUnlock()
return len(fake.onStoreDataBlobArgsForCall)
}
func (fake *FakeLocalParticipantListener) OnStoreDataBlobCalls(stub func(types.LocalParticipant, *livekit.DataBlob)) {
fake.onStoreDataBlobMutex.Lock()
defer fake.onStoreDataBlobMutex.Unlock()
fake.OnStoreDataBlobStub = stub
}
func (fake *FakeLocalParticipantListener) OnStoreDataBlobArgsForCall(i int) (types.LocalParticipant, *livekit.DataBlob) {
fake.onStoreDataBlobMutex.RLock()
defer fake.onStoreDataBlobMutex.RUnlock()
argsForCall := fake.onStoreDataBlobArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}
func (fake *FakeLocalParticipantListener) OnSubscribeStatusChanged(arg1 types.LocalParticipant, arg2 livekit.ParticipantID, arg3 bool) {
fake.onSubscribeStatusChangedMutex.Lock()
fake.onSubscribeStatusChangedArgsForCall = append(fake.onSubscribeStatusChangedArgsForCall, struct {
@@ -13,6 +13,11 @@ import (
)
type FakeParticipant struct {
AddDataBlobStub func(*livekit.DataBlob)
addDataBlobMutex sync.RWMutex
addDataBlobArgsForCall []struct {
arg1 *livekit.DataBlob
}
CanSkipBroadcastStub func() bool
canSkipBroadcastMutex sync.RWMutex
canSkipBroadcastArgsForCall []struct {
@@ -78,6 +83,17 @@ type FakeParticipant struct {
result1 float64
result2 bool
}
GetDataBlobStub func(*livekit.DataBlobKey) *livekit.DataBlob
getDataBlobMutex sync.RWMutex
getDataBlobArgsForCall []struct {
arg1 *livekit.DataBlobKey
}
getDataBlobReturns struct {
result1 *livekit.DataBlob
}
getDataBlobReturnsOnCall map[int]struct {
result1 *livekit.DataBlob
}
GetLoggerStub func() logger.Logger
getLoggerMutex sync.RWMutex
getLoggerArgsForCall []struct {
@@ -361,6 +377,38 @@ type FakeParticipant struct {
invocationsMutex sync.RWMutex
}
func (fake *FakeParticipant) AddDataBlob(arg1 *livekit.DataBlob) {
fake.addDataBlobMutex.Lock()
fake.addDataBlobArgsForCall = append(fake.addDataBlobArgsForCall, struct {
arg1 *livekit.DataBlob
}{arg1})
stub := fake.AddDataBlobStub
fake.recordInvocation("AddDataBlob", []interface{}{arg1})
fake.addDataBlobMutex.Unlock()
if stub != nil {
fake.AddDataBlobStub(arg1)
}
}
func (fake *FakeParticipant) AddDataBlobCallCount() int {
fake.addDataBlobMutex.RLock()
defer fake.addDataBlobMutex.RUnlock()
return len(fake.addDataBlobArgsForCall)
}
func (fake *FakeParticipant) AddDataBlobCalls(stub func(*livekit.DataBlob)) {
fake.addDataBlobMutex.Lock()
defer fake.addDataBlobMutex.Unlock()
fake.AddDataBlobStub = stub
}
func (fake *FakeParticipant) AddDataBlobArgsForCall(i int) *livekit.DataBlob {
fake.addDataBlobMutex.RLock()
defer fake.addDataBlobMutex.RUnlock()
argsForCall := fake.addDataBlobArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeParticipant) CanSkipBroadcast() bool {
fake.canSkipBroadcastMutex.Lock()
ret, specificReturn := fake.canSkipBroadcastReturnsOnCall[len(fake.canSkipBroadcastArgsForCall)]
@@ -692,6 +740,67 @@ func (fake *FakeParticipant) GetAudioLevelReturnsOnCall(i int, result1 float64,
}{result1, result2}
}
func (fake *FakeParticipant) GetDataBlob(arg1 *livekit.DataBlobKey) *livekit.DataBlob {
fake.getDataBlobMutex.Lock()
ret, specificReturn := fake.getDataBlobReturnsOnCall[len(fake.getDataBlobArgsForCall)]
fake.getDataBlobArgsForCall = append(fake.getDataBlobArgsForCall, struct {
arg1 *livekit.DataBlobKey
}{arg1})
stub := fake.GetDataBlobStub
fakeReturns := fake.getDataBlobReturns
fake.recordInvocation("GetDataBlob", []interface{}{arg1})
fake.getDataBlobMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *FakeParticipant) GetDataBlobCallCount() int {
fake.getDataBlobMutex.RLock()
defer fake.getDataBlobMutex.RUnlock()
return len(fake.getDataBlobArgsForCall)
}
func (fake *FakeParticipant) GetDataBlobCalls(stub func(*livekit.DataBlobKey) *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = stub
}
func (fake *FakeParticipant) GetDataBlobArgsForCall(i int) *livekit.DataBlobKey {
fake.getDataBlobMutex.RLock()
defer fake.getDataBlobMutex.RUnlock()
argsForCall := fake.getDataBlobArgsForCall[i]
return argsForCall.arg1
}
func (fake *FakeParticipant) GetDataBlobReturns(result1 *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = nil
fake.getDataBlobReturns = struct {
result1 *livekit.DataBlob
}{result1}
}
func (fake *FakeParticipant) GetDataBlobReturnsOnCall(i int, result1 *livekit.DataBlob) {
fake.getDataBlobMutex.Lock()
defer fake.getDataBlobMutex.Unlock()
fake.GetDataBlobStub = nil
if fake.getDataBlobReturnsOnCall == nil {
fake.getDataBlobReturnsOnCall = make(map[int]struct {
result1 *livekit.DataBlob
})
}
fake.getDataBlobReturnsOnCall[i] = struct {
result1 *livekit.DataBlob
}{result1}
}
func (fake *FakeParticipant) GetLogger() logger.Logger {
fake.getLoggerMutex.Lock()
ret, specificReturn := fake.getLoggerReturnsOnCall[len(fake.getLoggerArgsForCall)]
+20 -10
View File
@@ -485,19 +485,29 @@ func (h *AgentHandler) CheckEnabled(ctx context.Context, req *rpc.CheckEnabledRe
}, nil
}
func (h *AgentHandler) DrainConnections(interval time.Duration) {
// jitter drain start
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
func (h *AgentHandler) DrainConnections(interval time.Duration, force bool) {
if !force {
// jitter drain start
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
t := time.NewTicker(interval)
defer t.Stop()
t := time.NewTicker(interval)
defer t.Stop()
h.mu.Lock()
defer h.mu.Unlock()
h.mu.Lock()
defer h.mu.Unlock()
for _, w := range h.workers {
w.Close()
<-t.C
for _, w := range h.workers {
w.Close()
<-t.C
}
} else {
// drain as quickly as possible when forced
h.mu.Lock()
defer h.mu.Unlock()
for _, w := range h.workers {
w.Close()
}
}
}
+1
View File
@@ -538,6 +538,7 @@ func (r *RoomManager) StartSession(
FireOnTrackBySdp: true,
UseSinglePeerConnection: pi.UseSinglePeerConnection,
EnableDataTracks: r.config.EnableDataTracks,
EnableParticipantDataBlob: r.config.EnableParticipantDataBlob,
EnableRTPStreamRestartDetection: r.config.RTC.EnableRTPStreamRestartDetection,
})
if err != nil {
+21 -11
View File
@@ -401,9 +401,6 @@ func (s *RTCService) serve(w http.ResponseWriter, r *http.Request, needsJoinRequ
return
}
prometheus.IncrementParticipantJoin(1)
joinDuration = time.Since(startedAt)
pLogger = pLogger.WithValues("connID", cr.ConnectionID)
if !pi.Reconnect && initialResponse.GetJoin() != nil {
joinRoomID := livekit.RoomID(initialResponse.GetJoin().GetRoom().GetSid())
@@ -445,6 +442,7 @@ func (s *RTCService) serve(w http.ResponseWriter, r *http.Request, needsJoinRequ
// upgrade only once the basics are good to go
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
prometheus.IncrementParticipantJoinUpgradeFail(1)
resolveLogger(true)
HandleError(w, r, http.StatusInternalServerError, err, getLoggerFields()...)
return
@@ -465,12 +463,17 @@ func (s *RTCService) serve(w http.ResponseWriter, r *http.Request, needsJoinRequ
pLogger.Debugw("sending initial response", "response", logger.Proto(initialResponse))
count, err := sigConn.WriteResponse(initialResponse)
if err != nil {
prometheus.IncrementParticipantJoinWriteInitialResponseFail(1)
resolveLogger(true)
pLogger.Warnw("could not write initial response", err)
return
}
signalStats.AddBytes(uint64(count), true)
prometheus.IncrementParticipantJoin(1)
joinDuration = time.Since(startedAt)
prometheus.RecordSessionJoinLatency(int(pi.Client.GetProtocol()), joinDuration)
pLogger.Debugw(
"new client WS connected",
"reconnect", pi.Reconnect,
@@ -625,20 +628,27 @@ func (s *RTCService) serve(w http.ResponseWriter, r *http.Request, needsJoinRequ
}
}
func (s *RTCService) DrainConnections(interval time.Duration) {
func (s *RTCService) DrainConnections(interval time.Duration, force bool) {
s.mu.Lock()
conns := maps.Clone(s.connections)
s.mu.Unlock()
// jitter drain start
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
if !force {
// jitter drain start
time.Sleep(time.Duration(rand.Int63n(int64(interval))))
t := time.NewTicker(interval)
defer t.Stop()
t := time.NewTicker(interval)
defer t.Stop()
for c := range conns {
_ = c.Close()
<-t.C
for c := range conns {
_ = c.Close()
<-t.C
}
} else {
// drain as quickly as possible when forced
for c := range conns {
_ = c.Close()
}
}
}
@@ -2,10 +2,11 @@ package datachannel
import (
"context"
"sync/atomic"
"testing"
"time"
"go.uber.org/atomic"
"github.com/pion/datachannel"
"github.com/pion/transport/v4/deadline"
"github.com/stretchr/testify/require"
+35 -37
View File
@@ -149,43 +149,41 @@ func GetNodeStats(nodeStartedAt int64, prevStats []*livekit.NodeStats, rateInter
promSysPacketGauge.WithLabelValues("dropped").Set(float64(sysDroppedPackets - sysDroppedPacketsStart))
stats := &livekit.NodeStats{
StartedAt: nodeStartedAt,
UpdatedAt: time.Now().Unix(),
NumRooms: roomCurrent.Load(),
NumClients: participantCurrent.Load(),
NumTracksIn: trackPublishedCurrent.Load(),
NumTracksOut: trackSubscribedCurrent.Load(),
NumTrackPublishAttempts: trackPublishAttempts.Load(),
NumTrackPublishSuccess: trackPublishSuccess.Load(),
NumTrackPublishCancels: trackPublishCancels.Load(),
NumTrackSubscribeAttempts: trackSubscribeAttempts.Load(),
NumTrackSubscribeSuccess: trackSubscribeSuccess.Load(),
NumTrackSubscribeCancels: trackSubscribeCancels.Load(),
BytesIn: bytesIn.Load(),
BytesOut: bytesOut.Load(),
PacketsIn: packetsIn.Load(),
PacketsOut: packetsOut.Load(),
RetransmitBytesOut: retransmitBytes.Load(),
RetransmitPacketsOut: retransmitPackets.Load(),
NackTotal: nackTotal.Load(),
ParticipantSignalConnected: participantSignalConnected.Load(),
ParticipantSignalFailed: participantSignalFailed.Load(),
ParticipantSignalValidationFailed: participantSignalValidationFailed.Load(),
ParticipantRtcInit: participantRTCInit.Load(),
ParticipantRtcConnected: participantRTCConnected.Load(),
ParticipantRtcCanceled: participantRTCCanceled.Load(),
ParticipantRtcActive: participantRTCActive.Load(),
ForwardLatency: forwardLatency.Load(),
ForwardJitter: forwardJitter.Load(),
NumCpus: uint32(cpuStats.NumCPU()), // this will round down to the nearest integer
CpuLoad: float32(cpuStats.GetCPULoad()),
MemoryTotal: memTotal,
MemoryUsed: memUsed,
LoadAvgLast1Min: float32(loadAvg.Loadavg1),
LoadAvgLast5Min: float32(loadAvg.Loadavg5),
LoadAvgLast15Min: float32(loadAvg.Loadavg15),
SysPacketsOut: sysPackets,
SysPacketsDropped: sysDroppedPackets,
StartedAt: nodeStartedAt,
UpdatedAt: time.Now().Unix(),
NumRooms: roomCurrent.Load(),
NumClients: participantCurrent.Load(),
NumTracksIn: trackPublishedCurrent.Load(),
NumTracksOut: trackSubscribedCurrent.Load(),
NumTrackPublishAttempts: trackPublishAttempts.Load(),
NumTrackPublishSuccess: trackPublishSuccess.Load(),
NumTrackPublishCancels: trackPublishCancels.Load(),
NumTrackSubscribeAttempts: trackSubscribeAttempts.Load(),
NumTrackSubscribeSuccess: trackSubscribeSuccess.Load(),
NumTrackSubscribeCancels: trackSubscribeCancels.Load(),
BytesIn: bytesIn.Load(),
BytesOut: bytesOut.Load(),
PacketsIn: packetsIn.Load(),
PacketsOut: packetsOut.Load(),
RetransmitBytesOut: retransmitBytes.Load(),
RetransmitPacketsOut: retransmitPackets.Load(),
NackTotal: nackTotal.Load(),
ParticipantSignalConnected: participantSignalConnected.Load(),
ParticipantRtcInit: participantRTCInit.Load(),
ParticipantRtcConnected: participantRTCConnected.Load(),
ParticipantRtcCanceled: participantRTCCanceled.Load(),
ParticipantRtcActive: participantRTCActive.Load(),
ForwardLatency: forwardLatency.Load(),
ForwardJitter: forwardJitter.Load(),
NumCpus: uint32(cpuStats.NumCPU()), // this will round down to the nearest integer
CpuLoad: float32(cpuStats.GetCPULoad()),
MemoryTotal: memTotal,
MemoryUsed: memUsed,
LoadAvgLast1Min: float32(loadAvg.Loadavg1),
LoadAvgLast5Min: float32(loadAvg.Loadavg5),
LoadAvgLast15Min: float32(loadAvg.Loadavg15),
SysPacketsOut: sysPackets,
SysPacketsDropped: sysDroppedPackets,
}
for _, rateInterval := range rateIntervals {
+38 -30
View File
@@ -36,22 +36,20 @@ const (
)
var (
bytesIn atomic.Uint64
bytesOut atomic.Uint64
packetsIn atomic.Uint64
packetsOut atomic.Uint64
nackTotal atomic.Uint64
retransmitBytes atomic.Uint64
retransmitPackets atomic.Uint64
participantSignalConnected atomic.Uint64
participantSignalFailed atomic.Uint64
participantSignalValidationFailed atomic.Uint64
participantRTCConnected atomic.Uint64
participantRTCInit atomic.Uint64
participantRTCCanceled atomic.Uint64
participantRTCActive atomic.Uint64
forwardLatency atomic.Uint32
forwardJitter atomic.Uint32
bytesIn atomic.Uint64
bytesOut atomic.Uint64
packetsIn atomic.Uint64
packetsOut atomic.Uint64
nackTotal atomic.Uint64
retransmitBytes atomic.Uint64
retransmitPackets atomic.Uint64
participantSignalConnected atomic.Uint64
participantRTCConnected atomic.Uint64
participantRTCInit atomic.Uint64
participantRTCCanceled atomic.Uint64
participantRTCActive atomic.Uint64
forwardLatency atomic.Uint32
forwardJitter atomic.Uint32
promPacketLabels = []string{"direction", "transmission", "country"}
promPacketTotal *prometheus.CounterVec
@@ -305,29 +303,39 @@ func IncrementParticipantJoin(join uint32) {
func IncrementParticipantJoinFail(fail uint32) {
if fail > 0 {
participantSignalFailed.Add(uint64(fail))
promParticipantJoin.WithLabelValues("signal_failed").Add(float64(fail))
}
}
func IncrementParticipantJoinValidationFail(validationFail uint32) {
if validationFail > 0 {
participantSignalValidationFailed.Add(uint64(validationFail))
promParticipantJoin.WithLabelValues("signal_validation_failed").Add(float64(validationFail))
}
}
func IncrementParticipantRtcInit(join uint32) {
if join > 0 {
participantRTCInit.Add(uint64(join))
promParticipantJoin.WithLabelValues("rtc_init").Add(float64(join))
func IncrementParticipantJoinUpgradeFail(upgradeFail uint32) {
if upgradeFail > 0 {
promParticipantJoin.WithLabelValues("signal_upgrade_failed").Add(float64(upgradeFail))
}
}
func IncrementParticipantRtcConnected(join uint32) {
if join > 0 {
participantRTCConnected.Add(uint64(join))
promParticipantJoin.WithLabelValues("rtc_connected").Add(float64(join))
func IncrementParticipantJoinWriteInitialResponseFail(writeInitialResponseFail uint32) {
if writeInitialResponseFail > 0 {
promParticipantJoin.WithLabelValues("signal_write_initial_response_failed").Add(float64(writeInitialResponseFail))
}
}
func IncrementParticipantRtcInit(init uint32) {
if init > 0 {
participantRTCInit.Add(uint64(init))
promParticipantJoin.WithLabelValues("rtc_init").Add(float64(init))
}
}
func IncrementParticipantRtcConnected(connected uint32) {
if connected > 0 {
participantRTCConnected.Add(uint64(connected))
promParticipantJoin.WithLabelValues("rtc_connected").Add(float64(connected))
}
}
@@ -338,10 +346,10 @@ func IncrementParticipantRtcActive(active uint32) {
}
}
func IncrementParticipantRtcCanceled(numCancels uint64) {
if numCancels > 0 {
participantRTCCanceled.Add(numCancels)
promParticipantJoin.WithLabelValues("rtc_canceled").Add(float64(numCancels))
func IncrementParticipantRtcCanceled(canceled uint64) {
if canceled > 0 {
participantRTCCanceled.Add(canceled)
promParticipantJoin.WithLabelValues("rtc_canceled").Add(float64(canceled))
}
}
+13
View File
@@ -46,6 +46,7 @@ var (
promTrackSubscribedCurrent *prometheus.GaugeVec
promTrackPublishCounter *prometheus.CounterVec
promTrackSubscribeCounter *prometheus.CounterVec
promSessionJoinLatency *prometheus.HistogramVec
promSessionStartTime *prometheus.HistogramVec
promSessionDuration *prometheus.HistogramVec
promPubSubTime *prometheus.HistogramVec
@@ -99,6 +100,13 @@ func initRoomStats(nodeID string, nodeType livekit.NodeType) {
Name: "subscribe_counter",
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
}, []string{"state", "error"})
promSessionJoinLatency = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: livekitNamespace,
Subsystem: "session",
Name: "join_latency_ms",
ConstLabels: prometheus.Labels{"node_id": nodeID, "node_type": nodeType.String()},
Buckets: prometheus.ExponentialBucketsRange(10, 10000, 15),
}, []string{"protocol_version"})
promSessionStartTime = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: livekitNamespace,
Subsystem: "session",
@@ -134,6 +142,7 @@ func initRoomStats(nodeID string, nodeType livekit.NodeType) {
prometheus.MustRegister(promTrackSubscribedCurrent)
prometheus.MustRegister(promTrackPublishCounter)
prometheus.MustRegister(promTrackSubscribeCounter)
prometheus.MustRegister(promSessionJoinLatency)
prometheus.MustRegister(promSessionStartTime)
prometheus.MustRegister(promSessionDuration)
prometheus.MustRegister(promPubSubTime)
@@ -271,6 +280,10 @@ func RecordTrackSubscribeCancels(numCancels int32) {
promTrackSubscribeCounter.WithLabelValues("cancel", "").Add(float64(numCancels))
}
func RecordSessionJoinLatency(protocolVersion int, d time.Duration) {
promSessionJoinLatency.WithLabelValues(strconv.Itoa(protocolVersion)).Observe(float64(d.Milliseconds()))
}
func RecordSessionStartTime(protocolVersion int, d time.Duration) {
promSessionStartTime.WithLabelValues(strconv.Itoa(protocolVersion)).Observe(float64(d.Milliseconds()))
}