diff --git a/go.mod b/go.mod index c0e4ae49f..51cc6a595 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/jxskiss/base62 v1.1.0 github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 github.com/livekit/mediatransportutil v0.0.0-20230523035537-27577c4e1646 - github.com/livekit/protocol v1.5.7-0.20230522074955-efad03ffe4d0 + github.com/livekit/protocol v1.5.7 github.com/livekit/psrpc v0.3.1-0.20230518234341-6f6847e10b09 github.com/mackerelio/go-osstat v0.2.4 github.com/magefile/mage v1.14.0 diff --git a/go.sum b/go.sum index 23fd47715..38470cbe1 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1 h1:jm09419p0lqTkD github.com/livekit/mageutil v0.0.0-20230125210925-54e8a70427c1/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= github.com/livekit/mediatransportutil v0.0.0-20230523035537-27577c4e1646 h1:acGSGkWJdut7TUWozCDheHu4dwWFDqqRzv+SBbIY9Xo= github.com/livekit/mediatransportutil v0.0.0-20230523035537-27577c4e1646/go.mod h1:MRc0zSOSzXuFt0X218SgabzlaKevkvCckPgBEoHYc34= -github.com/livekit/protocol v1.5.7-0.20230522074955-efad03ffe4d0 h1:s2essRiWF//fVVBhjmxgjLHetpKYQ2QJfzi5w1a8rOA= -github.com/livekit/protocol v1.5.7-0.20230522074955-efad03ffe4d0/go.mod h1:ZaOnsvP+JS4s7vI1UO+JVdBagvvLp/lBXDAl2hkDS0I= +github.com/livekit/protocol v1.5.7 h1:jZeFQEmLuIhFblXDGPRCBbfjVJHb+YU7AsO+SMoXF70= +github.com/livekit/protocol v1.5.7/go.mod h1:ZaOnsvP+JS4s7vI1UO+JVdBagvvLp/lBXDAl2hkDS0I= github.com/livekit/psrpc v0.3.1-0.20230518234341-6f6847e10b09 h1:mb6jRcg57U0HQ4tKRsueHHKcvTqBinL6+0Aa84vTtWk= github.com/livekit/psrpc v0.3.1-0.20230518234341-6f6847e10b09/go.mod h1:n6JntEg+zT6Ji8InoyTpV7wusPNwGqqtxmHlkNhDN0U= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= diff --git a/pkg/clientconfiguration/conf.go b/pkg/clientconfiguration/conf.go index e7f84896e..916cd153f 100644 --- a/pkg/clientconfiguration/conf.go +++ b/pkg/clientconfiguration/conf.go @@ -1,6 +1,10 @@ package clientconfiguration -// configurations for livekit-client, add more configuration to StaticConfigurations as need +import ( + "github.com/livekit/protocol/livekit" +) + +// StaticConfigurations list specific device-side limitations that should be disabled at a global level var StaticConfigurations = []ConfigurationItem{ // { // Match: &ScriptMatch{Expr: `c.protocol <= 5 || c.browser == "firefox"`}, @@ -14,4 +18,13 @@ var StaticConfigurations = []ConfigurationItem{ // }}}, // Merge: false, // }, + { + Match: &ScriptMatch{Expr: `c.device_model == "Xiaomi 2201117TI" && c.os == "android"`}, + Configuration: &livekit.ClientConfiguration{ + DisabledCodecs: &livekit.DisabledCodecs{ + Publish: []*livekit.Codec{{Mime: "video/h264"}}, + }, + }, + Merge: false, + }, } diff --git a/pkg/rtc/transportmanager.go b/pkg/rtc/transportmanager.go index 397482dab..e34cb3e71 100644 --- a/pkg/rtc/transportmanager.go +++ b/pkg/rtc/transportmanager.go @@ -95,18 +95,32 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro } t.mediaLossProxy.OnMediaLossUpdate(t.onMediaLossUpdate) - enabledCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs)) - for _, c := range params.EnabledCodecs { - var disabled bool - for _, disableCodec := range params.ClientConf.GetDisabledCodecs().GetCodecs() { + subscribeCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs)) + publishCodecs := make([]*livekit.Codec, 0, len(params.EnabledCodecs)) + shouldDisable := func(c *livekit.Codec, disabledCodecs []*livekit.Codec) bool { + for _, disableCodec := range disabledCodecs { // disable codec's fmtp is empty means disable this codec entirely if strings.EqualFold(c.Mime, disableCodec.Mime) && (disableCodec.FmtpLine == "" || disableCodec.FmtpLine == c.FmtpLine) { - disabled = true - break + return true } } - if !disabled { - enabledCodecs = append(enabledCodecs, c) + return false + } + for _, c := range params.EnabledCodecs { + var publishDisabled bool + var subscribeDisabled bool + if shouldDisable(c, params.ClientConf.GetDisabledCodecs().GetCodecs()) { + publishDisabled = true + subscribeDisabled = true + } + if shouldDisable(c, params.ClientConf.GetDisabledCodecs().GetPublish()) { + publishDisabled = true + } + if !publishDisabled { + publishCodecs = append(publishCodecs, c) + } + if !subscribeDisabled { + subscribeCodecs = append(subscribeCodecs, c) } } @@ -118,7 +132,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro DirectionConfig: params.Config.Publisher, CongestionControlConfig: params.CongestionControlConfig, Telemetry: params.Telemetry, - EnabledCodecs: enabledCodecs, + EnabledCodecs: publishCodecs, Logger: LoggerWithPCTarget(params.Logger, livekit.SignalTarget_PUBLISHER), SimTracks: params.SimTracks, ClientInfo: params.ClientInfo, @@ -150,7 +164,7 @@ func NewTransportManager(params TransportManagerParams) (*TransportManager, erro DirectionConfig: params.Config.Subscriber, CongestionControlConfig: params.CongestionControlConfig, Telemetry: params.Telemetry, - EnabledCodecs: enabledCodecs, + EnabledCodecs: subscribeCodecs, Logger: LoggerWithPCTarget(params.Logger, livekit.SignalTarget_SUBSCRIBER), ClientInfo: params.ClientInfo, IsOfferer: true, diff --git a/test/client/client.go b/test/client/client.go index 384cdff79..17b085b3f 100644 --- a/test/client/client.go +++ b/test/client/client.go @@ -48,6 +48,7 @@ type RTCClient struct { publisherFullyEstablished atomic.Bool subscriberFullyEstablished atomic.Bool pongReceivedAt atomic.Int64 + lastAnswer atomic.Pointer[webrtc.SessionDescription] // tracks waiting to be acked, cid => trackInfo pendingPublishedTracks map[string]*livekit.TrackInfo @@ -81,6 +82,7 @@ var ( type Options struct { AutoSubscribe bool Publish string + ClientInfo *livekit.ClientInfo } func NewWebSocketConn(host, token string, opts *Options) (*websocket.Conn, error) { @@ -93,8 +95,18 @@ func NewWebSocketConn(host, token string, opts *Options) (*websocket.Conn, error connectUrl := u.String() if opts != nil { - connectUrl = fmt.Sprintf("%s&auto_subscribe=%t&publish=%s", - connectUrl, opts.AutoSubscribe, opts.Publish) + connectUrl = fmt.Sprintf("%s&auto_subscribe=%t", connectUrl, opts.AutoSubscribe) + if opts.Publish != "" { + connectUrl += encodeQueryParam("publish", opts.Publish) + } + if opts.ClientInfo != nil { + if opts.ClientInfo.DeviceModel != "" { + connectUrl += encodeQueryParam("device_model", opts.ClientInfo.DeviceModel) + } + if opts.ClientInfo.Os != "" { + connectUrl += encodeQueryParam("os", opts.ClientInfo.Os) + } + } } conn, _, err := websocket.DefaultDialer.Dial(connectUrl, requestHeader) return conn, err @@ -610,6 +622,11 @@ func (c *RTCClient) GetPublishedTrackIDs() []string { return trackIDs } +// LastAnswer return SDP of the last answer for the publisher connection +func (c *RTCClient) LastAnswer() *webrtc.SessionDescription { + return c.lastAnswer.Load() +} + func (c *RTCClient) ensurePublisherConnected() error { if c.publisher.HasEverConnected() { return nil @@ -654,6 +671,8 @@ func (c *RTCClient) handleOffer(desc webrtc.SessionDescription) { // the client handles answer on the publisher PC func (c *RTCClient) handleAnswer(desc webrtc.SessionDescription) { logger.Infow("handling server answer", "participant", c.localParticipant.Identity) + + c.lastAnswer.Store(&desc) // remote answered the offer, establish connection c.publisher.HandleRemoteDescription(desc) } @@ -744,3 +763,7 @@ func (c *RTCClient) SendNacks(count int) { _ = c.subscriber.WriteRTCP(packets) } + +func encodeQueryParam(key, value string) string { + return fmt.Sprintf("&%s=%s", url.QueryEscape(key), url.QueryEscape(value)) +} diff --git a/test/singlenode_test.go b/test/singlenode_test.go index 5af6602e0..a658a4bce 100644 --- a/test/singlenode_test.go +++ b/test/singlenode_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/pion/sdp/v3" "github.com/pion/webrtc/v3" "github.com/stretchr/testify/require" "github.com/thoas/go-funk" @@ -22,6 +23,11 @@ import ( testclient "github.com/livekit/livekit-server/test/client" ) +const ( + waitTick = 10 * time.Millisecond + waitTimeout = 5 * time.Second +) + func TestClientCouldConnect(t *testing.T) { if testing.Short() { t.SkipNow() @@ -477,3 +483,63 @@ func TestSingleNodeUpdateSubscriptionPermissions(t *testing.T) { } }) } + +// TestDeviceCodecOverride checks that codecs that are incompatible with a device is not +// negotiated by the server +func TestDeviceCodecOverride(t *testing.T) { + if testing.Short() { + t.SkipNow() + return + } + + _, finish := setupSingleNodeTest("TestDeviceCodecOverride") + defer finish() + + // simulate device that isn't compatible with H.264 + c1 := createRTCClient("c1", defaultServerPort, &testclient.Options{ + ClientInfo: &livekit.ClientInfo{ + Os: "android", + DeviceModel: "Xiaomi 2201117TI", + }, + }) + defer c1.Stop() + waitUntilConnected(t, c1) + + // it doesn't really matter what the codec set here is, uses default Pion MediaEngine codecs + tw, err := c1.AddStaticTrack("video/h264", "video", "webcam") + require.NoError(t, err) + defer stopWriters(tw) + + // wait for server to receive track + require.Eventually(t, func() bool { + return c1.LastAnswer() != nil + }, waitTimeout, waitTick, "did not receive answer") + + sd := webrtc.SessionDescription{ + Type: webrtc.SDPTypeAnswer, + SDP: c1.LastAnswer().SDP, + } + answer, err := sd.Unmarshal() + require.NoError(t, err) + + // video and data channel + require.Len(t, answer.MediaDescriptions, 2) + var desc *sdp.MediaDescription + for _, md := range answer.MediaDescriptions { + if md.MediaName.Media == "video" { + desc = md + break + } + } + require.NotNil(t, desc) + hasSeenVP8 := false + for _, a := range desc.Attributes { + if a.Key == "rtpmap" { + require.NotContains(t, a.Value, "H264", "should not contain H264 codec") + if strings.Contains(a.Value, "VP8") { + hasSeenVP8 = true + } + } + } + require.True(t, hasSeenVP8, "should have seen VP8 codec in SDP") +}