mirror of
https://github.com/livekit/livekit.git
synced 2026-09-17 05:54:49 +00:00
Client configuration (#452)
* client configuration * fix init roommanager
This commit is contained in:
@@ -50,6 +50,7 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/d5/tengo/v2 v2.10.1
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/eapache/channels v1.1.0 // indirect
|
||||
|
||||
@@ -31,6 +31,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/d5/tengo/v2 v2.10.1 h1:Z7vmTAQfdoExNEB9kxgqxvoBBW9bf+8uYMiDyriX5HM=
|
||||
github.com/d5/tengo/v2 v2.10.1/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package clientconfiguration
|
||||
|
||||
// configurations for livekit-client, add more configuration to StaticConfigurations as need
|
||||
var StaticConfigurations = []ConfigurationItem{
|
||||
// {
|
||||
// Match: &ScriptMatch{Expr: `c.protocol <= 5 || c.browser == "firefox"`},
|
||||
// Configuration: &livekit.ClientConfiguration{ResumeConnection: livekit.ClientConfigSetting_DISABLED},
|
||||
// Merge: false,
|
||||
// },
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package clientconfiguration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScriptMatchConfiguration(t *testing.T) {
|
||||
t.Run("no merge", func(t *testing.T) {
|
||||
confs := []ConfigurationItem{
|
||||
{
|
||||
Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`},
|
||||
Configuration: &livekit.ClientConfiguration{
|
||||
ResumeConnection: livekit.ClientConfigSetting_ENABLED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cm := NewStaticClientConfigurationManager(confs)
|
||||
|
||||
conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4})
|
||||
require.Nil(t, conf)
|
||||
|
||||
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"})
|
||||
require.Nil(t, conf)
|
||||
|
||||
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome"})
|
||||
require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED)
|
||||
})
|
||||
|
||||
t.Run("merge", func(t *testing.T) {
|
||||
confs := []ConfigurationItem{
|
||||
{
|
||||
Match: &ScriptMatch{Expr: `c.protocol > 5 && c.browser != "firefox"`},
|
||||
Configuration: &livekit.ClientConfiguration{
|
||||
ResumeConnection: livekit.ClientConfigSetting_ENABLED,
|
||||
},
|
||||
Merge: true,
|
||||
},
|
||||
{
|
||||
Match: &ScriptMatch{Expr: `c.sdk == "ANDROID"`},
|
||||
Configuration: &livekit.ClientConfiguration{
|
||||
Video: &livekit.VideoConfiguration{
|
||||
HardwareEncoder: livekit.ClientConfigSetting_DISABLED,
|
||||
},
|
||||
},
|
||||
Merge: true,
|
||||
},
|
||||
}
|
||||
|
||||
cm := NewStaticClientConfigurationManager(confs)
|
||||
|
||||
conf := cm.GetConfiguration(&livekit.ClientInfo{Protocol: 4})
|
||||
require.Nil(t, conf)
|
||||
|
||||
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "firefox"})
|
||||
require.Nil(t, conf)
|
||||
|
||||
conf = cm.GetConfiguration(&livekit.ClientInfo{Protocol: 6, Browser: "chrome", Sdk: 3})
|
||||
require.Equal(t, conf.ResumeConnection, livekit.ClientConfigSetting_ENABLED)
|
||||
require.Equal(t, conf.Video.HardwareEncoder, livekit.ClientConfigSetting_DISABLED)
|
||||
})
|
||||
}
|
||||
|
||||
func TestScriptMatch(t *testing.T) {
|
||||
client := &livekit.ClientInfo{
|
||||
Protocol: 6,
|
||||
Browser: "chrome",
|
||||
Sdk: 3, // android
|
||||
DeviceModel: "12345",
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
name string
|
||||
expr string
|
||||
result bool
|
||||
err bool
|
||||
}
|
||||
|
||||
cases := []testcase{
|
||||
{name: "simple match", expr: `c.protocol > 5`, result: true},
|
||||
{name: "invalid expr", expr: `cc.protocol > 5`, err: true},
|
||||
{name: "unexist field", expr: `c.protocols > 5`, err: true},
|
||||
{name: "combined condition", expr: `c.protocol > 5 && (c.sdk=="ANDROID" || c.sdk=="IOS")`, result: true},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
match := &ScriptMatch{Expr: c.expr}
|
||||
m, err := match.Match(client)
|
||||
if c.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Equal(t, c.result, m)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package clientconfiguration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/d5/tengo/v2"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type Match interface {
|
||||
Match(clientInfo *livekit.ClientInfo) (bool, error)
|
||||
}
|
||||
|
||||
type ScriptMatch struct {
|
||||
Expr string
|
||||
}
|
||||
|
||||
// use result of eval script expression for match.
|
||||
// expression examples:
|
||||
// protocol bigger than 5 : c.protocol > 5
|
||||
// browser if firefox: c.browser == "firefox"
|
||||
// combined rule : c.protocol > 5 && c.browser == "firefox"
|
||||
func (m *ScriptMatch) Match(clientInfo *livekit.ClientInfo) (bool, error) {
|
||||
res, err := tengo.Eval(context.TODO(), m.Expr, map[string]interface{}{"c": &clientObject{info: clientInfo}})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if val, ok := res.(bool); ok {
|
||||
return val, nil
|
||||
}
|
||||
return false, errors.New("invalid match expression result")
|
||||
}
|
||||
|
||||
type clientObject struct {
|
||||
tengo.ObjectImpl
|
||||
info *livekit.ClientInfo
|
||||
}
|
||||
|
||||
func (c *clientObject) TypeName() string {
|
||||
return "clientObject"
|
||||
}
|
||||
|
||||
func (c *clientObject) String() string {
|
||||
return c.info.String()
|
||||
}
|
||||
|
||||
func (c *clientObject) IndexGet(index tengo.Object) (res tengo.Object, err error) {
|
||||
field, ok := index.(*tengo.String)
|
||||
if !ok {
|
||||
return nil, tengo.ErrInvalidIndexType
|
||||
}
|
||||
|
||||
switch field.Value {
|
||||
case "sdk":
|
||||
return &tengo.String{Value: c.info.Sdk.String()}, nil
|
||||
case "version":
|
||||
return &tengo.String{Value: c.info.Version}, nil
|
||||
case "protocol":
|
||||
return &tengo.Int{Value: int64(c.info.Protocol)}, nil
|
||||
case "os":
|
||||
return &tengo.String{Value: c.info.Os}, nil
|
||||
case "os_version":
|
||||
return &tengo.String{Value: c.info.OsVersion}, nil
|
||||
case "device_model":
|
||||
return &tengo.String{Value: c.info.DeviceModel}, nil
|
||||
case "browser":
|
||||
return &tengo.String{Value: c.info.Browser}, nil
|
||||
case "browser_version":
|
||||
return &tengo.String{Value: c.info.BrowserVersion}, nil
|
||||
case "address":
|
||||
return &tengo.String{Value: c.info.Address}, nil
|
||||
}
|
||||
return &tengo.Undefined{}, nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package clientconfiguration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type ConfigurationItem struct {
|
||||
Match
|
||||
Configuration *livekit.ClientConfiguration
|
||||
Merge bool
|
||||
}
|
||||
|
||||
type StaticClientConfigurationManager struct {
|
||||
confs []ConfigurationItem
|
||||
}
|
||||
|
||||
func NewStaticClientConfigurationManager(confs []ConfigurationItem) *StaticClientConfigurationManager {
|
||||
return &StaticClientConfigurationManager{confs: confs}
|
||||
}
|
||||
|
||||
func (s *StaticClientConfigurationManager) GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration {
|
||||
var matchedConf []*livekit.ClientConfiguration
|
||||
for _, c := range s.confs {
|
||||
matched, err := c.Match.Match(clientInfo)
|
||||
if err != nil {
|
||||
logger.Errorw(fmt.Sprintf("matchrule failed, clientInfo: %s", clientInfo.String()), err)
|
||||
continue
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
if !c.Merge {
|
||||
return c.Configuration
|
||||
}
|
||||
matchedConf = append(matchedConf, c.Configuration)
|
||||
}
|
||||
|
||||
var conf *livekit.ClientConfiguration
|
||||
for k, v := range matchedConf {
|
||||
if k == 0 {
|
||||
conf = proto.Clone(matchedConf[0]).(*livekit.ClientConfiguration)
|
||||
} else {
|
||||
// TODO : there is a problem use protobuf merge, we don't have flag to indicate 'no value',
|
||||
// don't override default behavior or other configuration's field. So a bool value = false or
|
||||
// a int value = 0 will override same field in other configuration
|
||||
proto.Merge(conf, v)
|
||||
}
|
||||
}
|
||||
return conf
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package clientconfiguration
|
||||
|
||||
import (
|
||||
"github.com/livekit/protocol/livekit"
|
||||
)
|
||||
|
||||
type ClientConfigurationManager interface {
|
||||
GetConfiguration(clientInfo *livekit.ClientInfo) *livekit.ClientConfiguration
|
||||
}
|
||||
@@ -60,6 +60,7 @@ type ParticipantParams struct {
|
||||
SimTracks map[uint32]SimulcastTrackInfo
|
||||
Grants *auth.ClaimGrants
|
||||
InitialVersion uint32
|
||||
ClientConf *livekit.ClientConfiguration
|
||||
}
|
||||
|
||||
type ParticipantImpl struct {
|
||||
@@ -612,7 +613,8 @@ func (p *ParticipantImpl) SendJoinResponse(
|
||||
ServerRegion: region,
|
||||
IceServers: iceServers,
|
||||
// indicates both server and client support subscriber as primary
|
||||
SubscriberPrimary: p.SubscriberAsPrimary(),
|
||||
SubscriberPrimary: p.SubscriberAsPrimary(),
|
||||
ClientConfiguration: p.params.ClientConf,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
+19
-12
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/livekit/protocol/logger"
|
||||
"github.com/livekit/protocol/utils"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc"
|
||||
@@ -29,12 +30,13 @@ const (
|
||||
type RoomManager struct {
|
||||
lock sync.RWMutex
|
||||
|
||||
config *config.Config
|
||||
rtcConfig *rtc.WebRTCConfig
|
||||
currentNode routing.LocalNode
|
||||
router routing.Router
|
||||
roomStore ObjectStore
|
||||
telemetry telemetry.TelemetryService
|
||||
config *config.Config
|
||||
rtcConfig *rtc.WebRTCConfig
|
||||
currentNode routing.LocalNode
|
||||
router routing.Router
|
||||
roomStore ObjectStore
|
||||
telemetry telemetry.TelemetryService
|
||||
clientConfManager clientconfiguration.ClientConfigurationManager
|
||||
|
||||
rooms map[livekit.RoomName]*rtc.Room
|
||||
}
|
||||
@@ -45,6 +47,7 @@ func NewLocalRoomManager(
|
||||
currentNode routing.LocalNode,
|
||||
router routing.Router,
|
||||
telemetry telemetry.TelemetryService,
|
||||
clientConfManager clientconfiguration.ClientConfigurationManager,
|
||||
) (*RoomManager, error) {
|
||||
|
||||
rtcConf, err := rtc.NewWebRTCConfig(conf, currentNode.Ip)
|
||||
@@ -53,12 +56,13 @@ func NewLocalRoomManager(
|
||||
}
|
||||
|
||||
r := &RoomManager{
|
||||
config: conf,
|
||||
rtcConfig: rtcConf,
|
||||
currentNode: currentNode,
|
||||
router: router,
|
||||
roomStore: roomStore,
|
||||
telemetry: telemetry,
|
||||
config: conf,
|
||||
rtcConfig: rtcConf,
|
||||
currentNode: currentNode,
|
||||
router: router,
|
||||
roomStore: roomStore,
|
||||
telemetry: telemetry,
|
||||
clientConfManager: clientConfManager,
|
||||
|
||||
rooms: make(map[livekit.RoomName]*rtc.Room),
|
||||
}
|
||||
@@ -228,6 +232,8 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam
|
||||
"protocol", pi.Client.Protocol,
|
||||
)
|
||||
|
||||
clientConf := r.clientConfManager.GetConfiguration(pi.Client)
|
||||
|
||||
pv := types.ProtocolVersion(pi.Client.Protocol)
|
||||
rtcConf := *r.rtcConfig
|
||||
rtcConf.SetBufferFactory(room.GetBufferFactory())
|
||||
@@ -248,6 +254,7 @@ func (r *RoomManager) StartSession(ctx context.Context, roomName livekit.RoomNam
|
||||
Grants: pi.Grants,
|
||||
Hidden: pi.Hidden,
|
||||
Logger: pLogger,
|
||||
ClientConf: clientConf,
|
||||
}, pi.Permission)
|
||||
if err != nil {
|
||||
logger.Errorw("could not create participant", err)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/google/wire"
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/logger"
|
||||
@@ -33,6 +34,7 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
|
||||
wire.Bind(new(EgressStore), new(ObjectStore)),
|
||||
createKeyProvider,
|
||||
createWebhookNotifier,
|
||||
createClientConfiguration,
|
||||
routing.CreateRouter,
|
||||
wire.Bind(new(routing.MessageRouter), new(routing.Router)),
|
||||
wire.Bind(new(livekit.RoomService), new(*RoomService)),
|
||||
@@ -147,3 +149,7 @@ func createStore(rc *redis.Client) ObjectStore {
|
||||
}
|
||||
return NewLocalStore()
|
||||
}
|
||||
|
||||
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
|
||||
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/livekit/livekit-server/pkg/clientconfiguration"
|
||||
"github.com/livekit/livekit-server/pkg/config"
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/telemetry"
|
||||
@@ -54,7 +55,8 @@ func InitializeServer(conf *config.Config, currentNode routing.LocalNode) (*Live
|
||||
egressService := NewEgressService(messageBus, objectStore, roomService, telemetryService)
|
||||
recordingService := NewRecordingService(messageBus, telemetryService)
|
||||
rtcService := NewRTCService(conf, roomAllocator, objectStore, router, currentNode)
|
||||
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService)
|
||||
clientConfigurationManager := createClientConfiguration()
|
||||
roomManager, err := NewLocalRoomManager(conf, objectStore, currentNode, router, telemetryService, clientConfigurationManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -167,3 +169,7 @@ func createStore(rc *redis.Client) ObjectStore {
|
||||
}
|
||||
return NewLocalStore()
|
||||
}
|
||||
|
||||
func createClientConfiguration() clientconfiguration.ClientConfigurationManager {
|
||||
return clientconfiguration.NewStaticClientConfigurationManager(clientconfiguration.StaticConfigurations)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user