mirror of
https://github.com/livekit/livekit.git
synced 2026-10-15 19:50:07 +00:00
Firefox 155 enabled AV1 for WebRTC by default, but Firefox 155/156 decode only the base spatial layer of an AV1 SVC stream. SVC is not visible in SDP, so such a subscriber that negotiates AV1 is forwarded the publisher's AV1 SVC stream instead of its backup codec, rendering black whenever a higher layer is selected. Firefox 157 decodes the higher layers correctly (client-sdk-js#2116), so it keeps AV1. Removing AV1 from the subscriber's enabled codecs keeps it out of the SFU's offers and answers, so the down track binds to the backup codec. Firefox never publishes AV1 either, so it is disabled in both directions, the same as Safari. The Firefox for Android H.264 rule is switched to merge so it no longer drops the AV1 rule for that browser. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
192 lines
6.6 KiB
Go
192 lines
6.6 KiB
Go
// Copyright 2023 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 clientconfiguration
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/livekit/protocol/codecs/mime"
|
|
"github.com/livekit/protocol/livekit"
|
|
"github.com/livekit/protocol/utils/must"
|
|
)
|
|
|
|
func TestStaticConfigurationH264(t *testing.T) {
|
|
cm := NewStaticClientConfigurationManager(StaticConfigurations)
|
|
|
|
h264Disabled := func(info *livekit.ClientInfo) bool {
|
|
conf := cm.GetConfiguration(info)
|
|
if conf == nil || conf.DisabledCodecs == nil {
|
|
return false
|
|
}
|
|
for _, c := range conf.DisabledCodecs.Publish {
|
|
if mime.IsMimeTypeStringH264(c.Mime) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// desktop Linux Firefox publishes H.264 through OpenH264, so it must not be disabled
|
|
require.False(t, h264Disabled(&livekit.ClientInfo{Browser: "firefox", Os: "linux"}))
|
|
// Firefox for Android is still worked around
|
|
require.True(t, h264Disabled(&livekit.ClientInfo{Browser: "firefox mobile", Os: "android"}))
|
|
}
|
|
|
|
func TestStaticConfigurationAV1(t *testing.T) {
|
|
cm := NewStaticClientConfigurationManager(StaticConfigurations)
|
|
|
|
av1Disabled := func(info *livekit.ClientInfo) bool {
|
|
conf := cm.GetConfiguration(info)
|
|
if conf == nil || conf.DisabledCodecs == nil {
|
|
return false
|
|
}
|
|
for _, c := range conf.DisabledCodecs.Codecs {
|
|
if mime.IsMimeTypeStringEqual(c.Mime, mime.MimeTypeAV1.String()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Firefox 155/156 decode only the base spatial layer of AV1 SVC
|
|
require.True(t, av1Disabled(&livekit.ClientInfo{Browser: "Firefox", BrowserVersion: "155.0", Os: "macOS"}))
|
|
require.True(t, av1Disabled(&livekit.ClientInfo{Browser: "firefox", BrowserVersion: "156.0.1", Os: "windows"}))
|
|
// an unknown version is treated as affected
|
|
require.True(t, av1Disabled(&livekit.ClientInfo{Browser: "firefox", Os: "linux"}))
|
|
// Firefox 157 decodes every layer, so it keeps AV1
|
|
require.False(t, av1Disabled(&livekit.ClientInfo{Browser: "firefox", BrowserVersion: "157.0", Os: "windows"}))
|
|
require.False(t, av1Disabled(&livekit.ClientInfo{Browser: "firefox", BrowserVersion: "158.0.2", Os: "windows"}))
|
|
require.False(t, av1Disabled(&livekit.ClientInfo{Browser: "chrome", Os: "linux"}))
|
|
|
|
// Firefox for Android keeps the AV1 rule alongside its H.264 publish rule
|
|
android := &livekit.ClientInfo{Browser: "firefox mobile", BrowserVersion: "156.0", Os: "android"}
|
|
require.True(t, av1Disabled(android))
|
|
conf := cm.GetConfiguration(android)
|
|
require.Len(t, conf.DisabledCodecs.Publish, 1)
|
|
require.True(t, mime.IsMimeTypeStringH264(conf.DisabledCodecs.Publish[0].Mime))
|
|
}
|
|
|
|
func TestScriptMatchConfiguration(t *testing.T) {
|
|
t.Run("no merge", func(t *testing.T) {
|
|
confs := []ConfigurationItem{
|
|
{
|
|
Match: must.Get(NewScriptMatch(`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: must.Get(NewScriptMatch(`c.protocol > 5 && c.browser != "firefox"`)),
|
|
Configuration: &livekit.ClientConfiguration{
|
|
ResumeConnection: livekit.ClientConfigSetting_ENABLED,
|
|
},
|
|
Merge: true,
|
|
},
|
|
{
|
|
Match: must.Get(NewScriptMatch(`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",
|
|
BrowserVersion: "13.2",
|
|
Version: "2.17.1",
|
|
}
|
|
|
|
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},
|
|
{name: "combined condition2", expr: `(c.device_model == "xiaomi 2201117ti" && c.os == "android") || ((c.browser == "firefox" || c.browser == "firefox mobile") && (c.os == "linux" || c.os == "android"))`, result: false},
|
|
{name: "string lesser", expr: `c.browser_version < "11.3"`, result: false},
|
|
{name: "string lesser eq", expr: `c.browser_version <= "13.2"`, result: true},
|
|
{name: "string greater", expr: `c.browser_version > "11.3"`, result: true},
|
|
{name: "string greater eq", expr: `c.browser_version >= "13.2"`, result: true},
|
|
{name: "semantic lesser", expr: `c.version < "2.16.10"`, result: false},
|
|
{name: "semantic lesser eq", expr: `c.version <= "2.17.1"`, result: true},
|
|
{name: "semantic greater", expr: `c.version > "2.16.10"`, result: true},
|
|
{name: "semantic greater eq", expr: `c.version >= "2.17.1"`, result: true},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
match, err := NewScriptMatch(c.expr)
|
|
if err != nil {
|
|
if !c.err {
|
|
require.NoError(t, err)
|
|
}
|
|
return
|
|
}
|
|
m, err := match.Match(client)
|
|
if c.err {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.Equal(t, c.result, m)
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|