Checkpoint audio-only Skald fork work

This commit is contained in:
Storm Dragon
2026-05-18 13:06:57 -04:00
parent a8ada950d5
commit 965347cad4
48 changed files with 1080 additions and 3651 deletions
+22 -22
View File
@@ -151,7 +151,7 @@ func (u UserDescription) MarshalJSON() ([]byte, error) {
// about the JSON file it was deserialised from.
type Description struct {
// The file this was deserialised from. This is not necessarily
// the name of the hall, for example in case of a subgroup.
// the name of the hall, for example in case of a subhall.
FileName string `json:"-"`
// The modtime and size of the file. These are used to detect
@@ -159,8 +159,8 @@ type Description struct {
modTime time.Time `json:"-"`
fileSize int64 `json:"-"`
// Whether this is an automatically generated subgroup
isSubgroup bool `json:"-"`
// Whether this is an automatically generated subhall
isSubhall bool `json:"-"`
// The user-friendly hall name
DisplayName string `json:"displayName,omitempty"`
@@ -199,8 +199,8 @@ type Description struct {
// Whether creating tokens is allowed
UnrestrictedTokens bool `json:"unrestricted-tokens,omitempty"`
// Whether subgroups are created on the fly.
AutoSubgroups bool `json:"auto-subgroups,omitempty"`
// Whether subhalls are created on the fly.
AutoSubhalls bool `json:"auto-subhalls,omitempty"`
// Whether to lock the hall when the last op logs out.
Autolock bool `json:"autolock,omitempty"`
@@ -231,7 +231,7 @@ type Description struct {
Op []ClientPattern `json:"op,omitempty"`
Presenter []ClientPattern `json:"presenter,omitempty"`
Other []ClientPattern `json:"other,omitempty"`
AllowSubgroups bool `json:"allow-subgroups,omitempty"`
AllowSubhalls bool `json:"allow-subhalls,omitempty"`
AllowAnonymous bool `json:"allow-anonymous,omitempty"`
}
@@ -244,20 +244,20 @@ func maxHistoryAge(desc *Description) time.Duration {
return DefaultMaxHistoryAge
}
func getDescriptionFile[T any](name string, allowSubgroups bool, get func(string) (T, error)) (T, string, bool, error) {
isSubgroup := false
func getDescriptionFile[T any](name string, allowSubhalls bool, get func(string) (T, error)) (T, string, bool, error) {
isSubhall := false
for name != "" {
fileName := filepath.Join(
Directory, path.Clean("/"+name)+".json",
)
r, err := get(fileName)
if !errors.Is(err, os.ErrNotExist) {
return r, fileName, isSubgroup, err
return r, fileName, isSubhall, err
}
if !allowSubgroups {
if !allowSubhalls {
break
}
isSubgroup = true
isSubhall = true
name, _ = path.Split(name)
name = strings.TrimRight(name, "/")
}
@@ -311,7 +311,7 @@ func GetSanitisedDescription(name string) (*Description, string, error) {
if err != nil {
return nil, "", err
}
if d.isSubgroup {
if d.isSubhall {
return nil, "", os.ErrNotExist
}
@@ -395,7 +395,7 @@ func rewriteDescriptionFile(filename string, desc *Description) error {
if err != nil {
return err
}
if !conf.WritableGroups {
if !conf.WritableHalls {
return ErrDescriptionsNotWritable
}
@@ -439,9 +439,9 @@ func rewriteDescriptionFile(filename string, desc *Description) error {
}
// readDescription reads a hall's description from disk
func readDescription(name string, allowSubgroups bool) (*Description, error) {
r, fileName, isSubgroup, err :=
getDescriptionFile(name, allowSubgroups, os.Open)
func readDescription(name string, allowSubhalls bool) (*Description, error) {
r, fileName, isSubhall, err :=
getDescriptionFile(name, allowSubhalls, os.Open)
if err != nil {
return nil, err
}
@@ -469,11 +469,11 @@ func readDescription(name string, allowSubgroups bool) (*Description, error) {
return nil, err
}
if isSubgroup {
if !desc.AutoSubgroups {
if isSubhall {
if !desc.AutoSubhalls {
return nil, os.ErrNotExist
}
desc.isSubgroup = true
desc.isSubhall = true
desc.Public = false
desc.Description = ""
}
@@ -490,9 +490,9 @@ func upgradeDescription(desc *Description) error {
desc.AllowAnonymous = false
}
if desc.AllowSubgroups {
desc.AutoSubgroups = true
desc.AllowSubgroups = false
if desc.AllowSubhalls {
desc.AutoSubhalls = true
desc.AllowSubhalls = false
}
upgradePassword := func(pw *Password) Password {
+19 -19
View File
@@ -59,12 +59,12 @@ func TestEmptyJSON(t *testing.T) {
var descJSON = `
{
"max-history-age": 10,
"auto-subgroups": true,
"auto-subhalls": true,
"users": {
"jch": {"password": "topsecret", "permissions": "op"},
"john": {"password": "secret", "permissions": "present"},
"james": {"password": "secret2", "permissions": "message"},
"peter": {"password": "secret4"}
"user1": {"password": "topsecret", "permissions": "op"},
"user2": {"password": "secret", "permissions": "present"},
"user3": {"password": "secret2", "permissions": "message"},
"user4": {"password": "secret4"}
},
"wildcard-user":
{"permissions": "message", "password": {"type":"wildcard"}}
@@ -95,15 +95,15 @@ func TestDescriptionJSON(t *testing.T) {
var obsoleteJSON = `
{
"op": [{"username": "jch","password": "topsecret"}],
"op": [{"username": "user1","password": "topsecret"}],
"max-history-age": 10,
"allow-subgroups": true,
"allow-subhalls": true,
"presenter": [
{"username": "john", "password": "secret"}
{"username": "user2", "password": "secret"}
],
"other": [
{"username": "james", "password": "secret2"},
{"username": "peter", "password": "secret4"},
{"username": "user3", "password": "secret2"},
{"username": "user4", "password": "secret4"},
{}
]
}`
@@ -124,9 +124,9 @@ func TestUpgradeDescription(t *testing.T) {
t.Fatalf("upgradeDescription: %v", err)
}
if d1.AutoSubgroups != d2.AutoSubgroups ||
d1.AllowSubgroups != d2.AllowSubgroups {
t.Errorf("AllowSubgroups not upgraded correctly")
if d1.AutoSubhalls != d2.AutoSubhalls ||
d1.AllowSubhalls != d2.AllowSubhalls {
t.Errorf("AllowSubhalls not upgraded correctly")
}
if d2.Op != nil || d2.Presenter != nil || d2.Other != nil {
@@ -139,7 +139,7 @@ func TestUpgradeDescription(t *testing.T) {
}
for k, v1 := range d1.Users {
if k == "peter" {
if k == "user4" {
// not representable in the old format
continue
}
@@ -176,13 +176,13 @@ func setupTest(dir, datadir string, writable bool) error {
defer f.Close()
conf := `{}`
if writable {
conf = `{"writableGroups": true}`
conf = `{"writableHalls": true}`
}
_, err = f.WriteString(conf)
return err
}
func TestNonWritableGroups(t *testing.T) {
func TestNonWritableHalls(t *testing.T) {
err := setupTest(t.TempDir(), t.TempDir(), false)
if err != nil {
t.Fatalf("setupTest: %v", err)
@@ -200,7 +200,7 @@ func TestNonWritableGroups(t *testing.T) {
}
}
func TestWritableGroups(t *testing.T) {
func TestWritableHalls(t *testing.T) {
err := setupTest(t.TempDir(), t.TempDir(), true)
if err != nil {
t.Fatalf("setupTest: %v", err)
@@ -259,7 +259,7 @@ func TestWritableGroups(t *testing.T) {
nil, "Test", err, desc.AllowAnonymous,
)
}
testUser(t, "jch", false)
testUser(t, "user1", false)
testUser(t, "", true)
}
@@ -308,7 +308,7 @@ func testUser(t *testing.T, username string, wildcard bool) {
}
}
func TestSubGroup(t *testing.T) {
func TestSubHall(t *testing.T) {
err := setupTest(t.TempDir(), t.TempDir(), true)
if err != nil {
t.Fatalf("setupTest: %v", err)
+28 -111
View File
@@ -216,39 +216,6 @@ func fmtpValue(fmtp, key string) string {
func CodecPayloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, error) {
switch strings.ToLower(codec.MimeType) {
case "video/vp8":
return 96, nil
case "video/vp9":
profile := fmtpValue(codec.SDPFmtpLine, "profile-id")
switch profile {
case "", "0":
return 98, nil
case "2":
return 100, nil
default:
return 0, fmt.Errorf("unknown VP9 profile %v", profile)
}
case "video/av1":
return 35, nil
case "video/h264":
profile := fmtpValue(codec.SDPFmtpLine, "profile-level-id")
if profile == "" {
return 102, nil
}
if len(profile) < 4 {
return 0, errors.New("malforned H.264 profile")
}
switch strings.ToLower(profile[:4]) {
case "4200":
return 102, nil
case "42e0":
return 108, nil
default:
return 0, fmt.Errorf(
"unknown H.264 profile %v", profile,
)
}
case "audio/opus":
return 111, nil
case "audio/g722":
@@ -262,89 +229,48 @@ func CodecPayloadType(codec webrtc.RTPCodecCapability) (webrtc.PayloadType, erro
}
}
// VideoRTCPFeedback are the RTCP feedback types that we expect for video
// tracks.
var VideoRTCPFeedback = []webrtc.RTCPFeedback{
{"goog-remb", ""},
{"nack", ""},
{"nack", "pli"},
{"ccm", "fir"},
}
// AudioRTCPFeedback is like VideoRTCPFeedback but for audio tracks.
// AudioRTCPFeedback is the RTCP feedback for audio tracks.
var AudioRTCPFeedback = []webrtc.RTCPFeedback(nil)
func codecsFromName(name string) ([]webrtc.RTPCodecParameters, error) {
var codecs []webrtc.RTPCodecCapability
switch name {
case "vp8":
codecs = []webrtc.RTPCodecCapability{
{
"video/VP8", 90000, 0,
"",
VideoRTCPFeedback,
},
}
case "vp9":
codecs = []webrtc.RTPCodecCapability{
{
"video/VP9", 90000, 0,
"profile-id=0",
VideoRTCPFeedback,
},
{
"video/VP9", 90000, 0,
"profile-id=2",
VideoRTCPFeedback,
},
}
case "av1":
codecs = []webrtc.RTPCodecCapability{
{
"video/AV1", 90000, 0,
"",
VideoRTCPFeedback,
},
}
case "h264":
codecs = []webrtc.RTPCodecCapability{
{
"video/H264", 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
VideoRTCPFeedback,
},
}
case "opus":
codecs = []webrtc.RTPCodecCapability{
{
"audio/opus", 48000, 2,
"minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
AudioRTCPFeedback,
MimeType: "audio/opus",
ClockRate: 48000,
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
RTCPFeedback: AudioRTCPFeedback,
},
}
case "g722":
codecs = []webrtc.RTPCodecCapability{
{
"audio/G722", 8000, 1,
"",
AudioRTCPFeedback,
MimeType: "audio/G722",
ClockRate: 8000,
Channels: 1,
RTCPFeedback: AudioRTCPFeedback,
},
}
case "pcmu":
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMU", 8000, 1,
"",
AudioRTCPFeedback,
MimeType: "audio/PCMU",
ClockRate: 8000,
Channels: 1,
RTCPFeedback: AudioRTCPFeedback,
},
}
case "pcma":
codecs = []webrtc.RTPCodecCapability{
{
"audio/PCMA", 8000, 1,
"",
AudioRTCPFeedback,
MimeType: "audio/PCMA",
ClockRate: 8000,
Channels: 1,
RTCPFeedback: AudioRTCPFeedback,
},
}
default:
@@ -384,11 +310,7 @@ func APIFromCodecs(codecs []webrtc.RTPCodecParameters) (*webrtc.API, error) {
m := webrtc.MediaEngine{}
for _, codec := range codecs {
tpe := webrtc.RTPCodecTypeVideo
if strings.HasPrefix(strings.ToLower(codec.MimeType), "audio/") {
tpe = webrtc.RTPCodecTypeAudio
}
err := m.RegisterCodec(codec, tpe)
err := m.RegisterCodec(codec, webrtc.RTPCodecTypeAudio)
if err != nil {
log.Printf("%v", err)
continue
@@ -401,11 +323,6 @@ func APIFromCodecs(codecs []webrtc.RTPCodecParameters) (*webrtc.API, error) {
s.SetEphemeralUDPPortRange(UDPMin, UDPMax)
}
err := webrtc.ConfigureSimulcastExtensionHeaders(&m)
if err != nil {
return nil, err
}
ir := interceptor.Registry{}
return webrtc.NewAPI(
@@ -417,7 +334,7 @@ func APIFromCodecs(codecs []webrtc.RTPCodecParameters) (*webrtc.API, error) {
func APIFromNames(names []string) (*webrtc.API, error) {
if len(names) == 0 {
names = []string{"vp8", "opus"}
names = []string{"opus"}
}
var codecs []webrtc.RTPCodecParameters
for _, n := range names {
@@ -543,14 +460,14 @@ func GetNames() []string {
return names
}
type SubGroup struct {
type SubHall struct {
Name string
Clients int
}
func GetSubGroups(parent string) []SubGroup {
func GetSubHalls(parent string) []SubHall {
prefix := parent + "/"
subgroups := make([]SubGroup, 0)
subhalls := make([]SubHall, 0)
Range(func(g *Hall) bool {
if strings.HasPrefix(g.name, prefix) {
@@ -558,13 +475,13 @@ func GetSubGroups(parent string) []SubGroup {
count := len(g.clients)
g.mu.Unlock()
if count > 0 {
subgroups = append(subgroups,
SubGroup{g.name, count})
subhalls = append(subhalls,
SubHall{g.name, count})
}
}
return true
})
return subgroups
return subhalls
}
func Get(name string) *Hall {
@@ -929,7 +846,7 @@ type Configuration struct {
AllowOrigin []string `json:"allowOrigin,omitempty"`
AllowAdminOrigin []string `json:"allowAdminOrigin,omitempty"`
ProxyURL string `json:"proxyURL,omitempty"`
WritableGroups bool `json:"writableGroups,omitempty"`
WritableHalls bool `json:"writableHalls,omitempty"`
Users map[string]UserDescription `json:"users,omitempty"`
// obsolete fields
@@ -1173,7 +1090,7 @@ func (g *Hall) Status(authentified bool, base *url.URL) Status {
if authentified {
conf, err := GetConfiguration()
if err == nil {
d.CanChangePassword = conf.WritableGroups
d.CanChangePassword = conf.WritableHalls
}
}
return d
+9 -10
View File
@@ -34,23 +34,23 @@ func TestConstantTimeCompare(t *testing.T) {
}
}
func TestGroup(t *testing.T) {
func TestHall(t *testing.T) {
halls.halls = nil
Add("hall", &Description{})
Add("hall/subgroup", &Description{Public: true})
Add("hall/subhall", &Description{Public: true})
if len(halls.halls) != 2 {
t.Errorf("Expected 2, got %v", len(halls.halls))
}
g := Get("hall")
g2 := Get("hall/subgroup")
g2 := Get("hall/subhall")
if g == nil {
t.Fatalf("Couldn't get hall")
}
if g2 == nil {
t.Fatalf("Couldn't get hall/subgroup")
t.Fatalf("Couldn't get hall/subhall")
}
if name := g.Name(); name != "hall" {
t.Errorf("Name: expected group1, got %v", name)
t.Errorf("Name: expected hall, got %v", name)
}
if locked, _ := g.Locked(); locked {
t.Errorf("Locked: expected false, got %v", locked)
@@ -64,12 +64,12 @@ func TestGroup(t *testing.T) {
t.Errorf("Expected 2, got %v", names)
}
if subs := GetSubGroups("hall"); len(subs) != 0 {
if subs := GetSubHalls("hall"); len(subs) != 0 {
t.Errorf("Expected [], got %v", subs)
}
if public := GetPublic(nil); len(public) != 1 || public[0].Name != "hall/subgroup" {
t.Errorf("Expected hall/subgroup, got %v", public)
if public := GetPublic(nil); len(public) != 1 || public[0].Name != "hall/subhall" {
t.Errorf("Expected hall/subhall, got %v", public)
}
}
@@ -167,7 +167,7 @@ type credPerm struct {
var desc2JSON = `
{
"max-history-age": 10,
"auto-subgroups": true,
"auto-subhalls": true,
"users": {
"jch": {"password": "topsecret", "permissions": "op"},
"john": {"password": "secret", "permissions": "present"},
@@ -377,7 +377,6 @@ func TestValidHallName(t *testing.T) {
func TestPayloadTypeDistinct(t *testing.T) {
names := []string{
"vp8", "vp9", "av1", "h264",
"opus", "g722", "pcmu", "pcma",
}