mirror of
https://github.com/jech/galene.git
synced 2024-11-22 08:35:57 +01:00
Merge branch 'modular' into master
This commit is contained in:
commit
2347417f83
15 changed files with 586 additions and 555 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ data/*.pem
|
|||
sfu
|
||||
passwd
|
||||
groups/*.json
|
||||
static/*.d.ts
|
||||
|
|
25
client.go
25
client.go
|
@ -1,25 +0,0 @@
|
|||
package main
|
||||
|
||||
type clientCredentials struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type clientPermissions struct {
|
||||
Op bool `json:"op,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
Record bool `json:"record,omitempty"`
|
||||
}
|
||||
|
||||
type client interface {
|
||||
Group() *group
|
||||
Id() string
|
||||
Credentials() clientCredentials
|
||||
SetPermissions(clientPermissions)
|
||||
pushConn(id string, conn upConnection, tracks []upTrack, label string) error
|
||||
pushClient(id, username string, add bool) error
|
||||
}
|
||||
|
||||
type kickable interface {
|
||||
kick(message string) error
|
||||
}
|
43
conn.go
43
conn.go
|
@ -1,43 +0,0 @@
|
|||
// Copyright (c) 2020 by Juliusz Chroboczek.
|
||||
|
||||
// This is not open source software. Copy it, and I'll break into your
|
||||
// house and tell your three year-old that Santa doesn't exist.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
var ErrConnectionClosed = errors.New("connection is closed")
|
||||
var ErrKeyframeNeeded = errors.New("keyframe needed")
|
||||
|
||||
type upConnection interface {
|
||||
addLocal(downConnection) error
|
||||
delLocal(downConnection) bool
|
||||
Id() string
|
||||
Label() string
|
||||
}
|
||||
|
||||
type upTrack interface {
|
||||
addLocal(downTrack) error
|
||||
delLocal(downTrack) bool
|
||||
Label() string
|
||||
Codec() *webrtc.RTPCodec
|
||||
// get a recent packet. Returns 0 if the packet is not in cache.
|
||||
getRTP(seqno uint16, result []byte) uint16
|
||||
}
|
||||
|
||||
type downConnection interface {
|
||||
GetMaxBitrate(now uint64) uint64
|
||||
}
|
||||
|
||||
type downTrack interface {
|
||||
WriteRTP(packat *rtp.Packet) error
|
||||
Accumulate(bytes uint32)
|
||||
setTimeOffset(ntp uint64, rtp uint32)
|
||||
setCname(string)
|
||||
}
|
48
conn/conn.go
Normal file
48
conn/conn.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2020 by Juliusz Chroboczek.
|
||||
|
||||
// This is not open source software. Copy it, and I'll break into your
|
||||
// house and tell your three year-old that Santa doesn't exist.
|
||||
|
||||
// Package conn defines interfaces for connections and tracks.
|
||||
package conn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
var ErrConnectionClosed = errors.New("connection is closed")
|
||||
var ErrKeyframeNeeded = errors.New("keyframe needed")
|
||||
|
||||
// Type Up represents a connection in the client to server direction.
|
||||
type Up interface {
|
||||
AddLocal(Down) error
|
||||
DelLocal(Down) bool
|
||||
Id() string
|
||||
Label() string
|
||||
}
|
||||
|
||||
// Type UpTrack represents a track in the client to server direction.
|
||||
type UpTrack interface {
|
||||
AddLocal(DownTrack) error
|
||||
DelLocal(DownTrack) bool
|
||||
Label() string
|
||||
Codec() *webrtc.RTPCodec
|
||||
// get a recent packet. Returns 0 if the packet is not in cache.
|
||||
GetRTP(seqno uint16, result []byte) uint16
|
||||
}
|
||||
|
||||
// Type Down represents a connection in the server to client direction.
|
||||
type Down interface {
|
||||
GetMaxBitrate(now uint64) uint64
|
||||
}
|
||||
|
||||
// Type DownTrack represents a track in the server to client direction.
|
||||
type DownTrack interface {
|
||||
WriteRTP(packat *rtp.Packet) error
|
||||
Accumulate(bytes uint32)
|
||||
SetTimeOffset(ntp uint64, rtp uint32)
|
||||
SetCname(string)
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
package main
|
||||
package disk
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -14,10 +15,15 @@ import (
|
|||
"github.com/pion/rtp/codecs"
|
||||
"github.com/pion/webrtc/v3"
|
||||
"github.com/pion/webrtc/v3/pkg/media/samplebuilder"
|
||||
|
||||
"sfu/conn"
|
||||
"sfu/group"
|
||||
)
|
||||
|
||||
type diskClient struct {
|
||||
group *group
|
||||
var Directory string
|
||||
|
||||
type Client struct {
|
||||
group *group.Group
|
||||
id string
|
||||
|
||||
mu sync.Mutex
|
||||
|
@ -25,45 +31,37 @@ type diskClient struct {
|
|||
closed bool
|
||||
}
|
||||
|
||||
var idCounter struct {
|
||||
mu sync.Mutex
|
||||
counter int
|
||||
}
|
||||
|
||||
func newId() string {
|
||||
idCounter.mu.Lock()
|
||||
defer idCounter.mu.Unlock()
|
||||
|
||||
s := strconv.FormatInt(int64(idCounter.counter), 16)
|
||||
idCounter.counter++
|
||||
return s
|
||||
b := make([]byte, 16)
|
||||
crand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func NewDiskClient(g *group) *diskClient {
|
||||
return &diskClient{group: g, id: newId()}
|
||||
func New(g *group.Group) *Client {
|
||||
return &Client{group: g, id: newId()}
|
||||
}
|
||||
|
||||
func (client *diskClient) Group() *group {
|
||||
func (client *Client) Group() *group.Group {
|
||||
return client.group
|
||||
}
|
||||
|
||||
func (client *diskClient) Id() string {
|
||||
func (client *Client) Id() string {
|
||||
return client.id
|
||||
}
|
||||
|
||||
func (client *diskClient) Credentials() clientCredentials {
|
||||
return clientCredentials{"RECORDING", ""}
|
||||
func (client *Client) Credentials() group.ClientCredentials {
|
||||
return group.ClientCredentials{"RECORDING", ""}
|
||||
}
|
||||
|
||||
func (client *diskClient) SetPermissions(perms clientPermissions) {
|
||||
func (client *Client) SetPermissions(perms group.ClientPermissions) {
|
||||
return
|
||||
}
|
||||
|
||||
func (client *diskClient) pushClient(id, username string, add bool) error {
|
||||
func (client *Client) PushClient(id, username string, add bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *diskClient) Close() error {
|
||||
func (client *Client) Close() error {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
|
@ -75,13 +73,13 @@ func (client *diskClient) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (client *diskClient) kick(message string) error {
|
||||
func (client *Client) kick(message string) error {
|
||||
err := client.Close()
|
||||
delClient(client)
|
||||
group.DelClient(client)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *diskClient) pushConn(id string, conn upConnection, tracks []upTrack, label string) error {
|
||||
func (client *Client) PushConn(id string, up conn.Up, tracks []conn.UpTrack, label string) error {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
|
@ -95,11 +93,11 @@ func (client *diskClient) pushConn(id string, conn upConnection, tracks []upTrac
|
|||
delete(client.down, id)
|
||||
}
|
||||
|
||||
if conn == nil {
|
||||
if up == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
directory := filepath.Join(recordingsDir, client.group.name)
|
||||
directory := filepath.Join(Directory, client.group.Name())
|
||||
err := os.MkdirAll(directory, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -109,12 +107,12 @@ func (client *diskClient) pushConn(id string, conn upConnection, tracks []upTrac
|
|||
client.down = make(map[string]*diskConn)
|
||||
}
|
||||
|
||||
down, err := newDiskConn(directory, label, conn, tracks)
|
||||
down, err := newDiskConn(directory, label, up, tracks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.down[conn.Id()] = down
|
||||
client.down[up.Id()] = down
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -125,7 +123,7 @@ type diskConn struct {
|
|||
|
||||
mu sync.Mutex
|
||||
file *os.File
|
||||
remote upConnection
|
||||
remote conn.Up
|
||||
tracks []*diskTrack
|
||||
width, height uint32
|
||||
}
|
||||
|
@ -150,7 +148,7 @@ func (conn *diskConn) reopen() error {
|
|||
}
|
||||
|
||||
func (conn *diskConn) Close() error {
|
||||
conn.remote.delLocal(conn)
|
||||
conn.remote.DelLocal(conn)
|
||||
|
||||
conn.mu.Lock()
|
||||
tracks := make([]*diskTrack, 0, len(conn.tracks))
|
||||
|
@ -164,7 +162,7 @@ func (conn *diskConn) Close() error {
|
|||
conn.mu.Unlock()
|
||||
|
||||
for _, t := range tracks {
|
||||
t.remote.delLocal(t)
|
||||
t.remote.DelLocal(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -196,7 +194,7 @@ func openDiskFile(directory, label string) (*os.File, error) {
|
|||
}
|
||||
|
||||
type diskTrack struct {
|
||||
remote upTrack
|
||||
remote conn.UpTrack
|
||||
conn *diskConn
|
||||
|
||||
writer webm.BlockWriteCloser
|
||||
|
@ -206,7 +204,7 @@ type diskTrack struct {
|
|||
origin uint64
|
||||
}
|
||||
|
||||
func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrack) (*diskConn, error) {
|
||||
func newDiskConn(directory, label string, up conn.Up, remoteTracks []conn.UpTrack) (*diskConn, error) {
|
||||
conn := diskConn{
|
||||
directory: directory,
|
||||
label: label,
|
||||
|
@ -231,10 +229,10 @@ func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrac
|
|||
conn: &conn,
|
||||
}
|
||||
conn.tracks = append(conn.tracks, track)
|
||||
remote.addLocal(track)
|
||||
remote.AddLocal(track)
|
||||
}
|
||||
|
||||
err := up.addLocal(&conn)
|
||||
err := up.AddLocal(&conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -242,10 +240,10 @@ func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrac
|
|||
return &conn, nil
|
||||
}
|
||||
|
||||
func (t *diskTrack) setTimeOffset(ntp uint64, rtp uint32) {
|
||||
func (t *diskTrack) SetTimeOffset(ntp uint64, rtp uint32) {
|
||||
}
|
||||
|
||||
func (t *diskTrack) setCname(string) {
|
||||
func (t *diskTrack) SetCname(string) {
|
||||
}
|
||||
|
||||
func clonePacket(packet *rtp.Packet) *rtp.Packet {
|
||||
|
@ -310,7 +308,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
|||
|
||||
if t.writer == nil {
|
||||
if !keyframe {
|
||||
return ErrKeyframeNeeded
|
||||
return conn.ErrKeyframeNeeded
|
||||
}
|
||||
return nil
|
||||
}
|
29
group/client.go
Normal file
29
group/client.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package group
|
||||
|
||||
import (
|
||||
"sfu/conn"
|
||||
)
|
||||
|
||||
type ClientCredentials struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type ClientPermissions struct {
|
||||
Op bool `json:"op,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
Record bool `json:"record,omitempty"`
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
Group() *Group
|
||||
Id() string
|
||||
Credentials() ClientCredentials
|
||||
SetPermissions(ClientPermissions)
|
||||
PushConn(id string, conn conn.Up, tracks []conn.UpTrack, label string) error
|
||||
PushClient(id, username string, add bool) error
|
||||
}
|
||||
|
||||
type Kickable interface {
|
||||
Kick(message string) error
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
// This is not open source software. Copy it, and I'll break into your
|
||||
// house and tell your three year-old that Santa doesn't exist.
|
||||
|
||||
package main
|
||||
package group
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -18,18 +18,60 @@ import (
|
|||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
type chatHistoryEntry struct {
|
||||
id string
|
||||
user string
|
||||
kind string
|
||||
value string
|
||||
var Directory string
|
||||
|
||||
type UserError string
|
||||
|
||||
func (err UserError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
type ProtocolError string
|
||||
|
||||
func (err ProtocolError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
var IceFilename string
|
||||
|
||||
var iceConf webrtc.Configuration
|
||||
var iceOnce sync.Once
|
||||
|
||||
func IceConfiguration() webrtc.Configuration {
|
||||
iceOnce.Do(func() {
|
||||
var iceServers []webrtc.ICEServer
|
||||
file, err := os.Open(IceFilename)
|
||||
if err != nil {
|
||||
log.Printf("Open %v: %v", IceFilename, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
d := json.NewDecoder(file)
|
||||
err = d.Decode(&iceServers)
|
||||
if err != nil {
|
||||
log.Printf("Get ICE configuration: %v", err)
|
||||
return
|
||||
}
|
||||
iceConf = webrtc.Configuration{
|
||||
ICEServers: iceServers,
|
||||
}
|
||||
})
|
||||
|
||||
return iceConf
|
||||
}
|
||||
|
||||
type ChatHistoryEntry struct {
|
||||
Id string
|
||||
User string
|
||||
Kind string
|
||||
Value string
|
||||
}
|
||||
|
||||
const (
|
||||
minBitrate = 200000
|
||||
MinBitrate = 200000
|
||||
)
|
||||
|
||||
type group struct {
|
||||
type Group struct {
|
||||
name string
|
||||
|
||||
mu sync.Mutex
|
||||
|
@ -37,35 +79,39 @@ type group struct {
|
|||
// indicates that the group no longer exists, but it still has clients
|
||||
dead bool
|
||||
locked bool
|
||||
clients map[string]client
|
||||
history []chatHistoryEntry
|
||||
clients map[string]Client
|
||||
history []ChatHistoryEntry
|
||||
}
|
||||
|
||||
func (g *group) Locked() bool {
|
||||
func (g *Group) Name() string {
|
||||
return g.name
|
||||
}
|
||||
|
||||
func (g *Group) Locked() bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.locked
|
||||
}
|
||||
|
||||
func (g *group) SetLocked(locked bool) {
|
||||
func (g *Group) SetLocked(locked bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.locked = locked
|
||||
}
|
||||
|
||||
func (g *group) Public() bool {
|
||||
func (g *Group) Public() bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.description.Public
|
||||
}
|
||||
|
||||
func (g *group) Redirect() string {
|
||||
func (g *Group) Redirect() string {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.description.Redirect
|
||||
}
|
||||
|
||||
func (g *group) AllowRecording() bool {
|
||||
func (g *Group) AllowRecording() bool {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.description.AllowRecording
|
||||
|
@ -73,20 +119,20 @@ func (g *group) AllowRecording() bool {
|
|||
|
||||
var groups struct {
|
||||
mu sync.Mutex
|
||||
groups map[string]*group
|
||||
groups map[string]*Group
|
||||
api *webrtc.API
|
||||
}
|
||||
|
||||
func (g *group) API() *webrtc.API {
|
||||
func (g *Group) API() *webrtc.API {
|
||||
return groups.api
|
||||
}
|
||||
|
||||
func addGroup(name string, desc *groupDescription) (*group, error) {
|
||||
func Add(name string, desc *groupDescription) (*Group, error) {
|
||||
groups.mu.Lock()
|
||||
defer groups.mu.Unlock()
|
||||
|
||||
if groups.groups == nil {
|
||||
groups.groups = make(map[string]*group)
|
||||
groups.groups = make(map[string]*Group)
|
||||
s := webrtc.SettingEngine{}
|
||||
m := webrtc.MediaEngine{}
|
||||
m.RegisterCodec(webrtc.NewRTPVP8CodecExt(
|
||||
|
@ -113,15 +159,15 @@ func addGroup(name string, desc *groupDescription) (*group, error) {
|
|||
g := groups.groups[name]
|
||||
if g == nil {
|
||||
if desc == nil {
|
||||
desc, err = getDescription(name)
|
||||
desc, err = GetDescription(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
g = &group{
|
||||
g = &Group{
|
||||
name: name,
|
||||
description: desc,
|
||||
clients: make(map[string]client),
|
||||
clients: make(map[string]Client),
|
||||
}
|
||||
groups.groups[name] = g
|
||||
return g, nil
|
||||
|
@ -147,7 +193,7 @@ func addGroup(name string, desc *groupDescription) (*group, error) {
|
|||
return nil, err
|
||||
}
|
||||
if changed {
|
||||
desc, err := getDescription(name)
|
||||
desc, err := GetDescription(name)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Printf("Reading group %v: %v",
|
||||
|
@ -167,7 +213,7 @@ func addGroup(name string, desc *groupDescription) (*group, error) {
|
|||
return g, nil
|
||||
}
|
||||
|
||||
func rangeGroups(f func(g *group) bool) {
|
||||
func Range(f func(g *Group) bool) {
|
||||
groups.mu.Lock()
|
||||
defer groups.mu.Unlock()
|
||||
|
||||
|
@ -179,17 +225,17 @@ func rangeGroups(f func(g *group) bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func getGroupNames() []string {
|
||||
func GetNames() []string {
|
||||
names := make([]string, 0)
|
||||
|
||||
rangeGroups(func(g *group) bool {
|
||||
Range(func(g *Group) bool {
|
||||
names = append(names, g.name)
|
||||
return true
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
func getGroup(name string) *group {
|
||||
func Get(name string) *Group {
|
||||
groups.mu.Lock()
|
||||
defer groups.mu.Unlock()
|
||||
|
||||
|
@ -210,8 +256,8 @@ func delGroupUnlocked(name string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func addClient(name string, c client) (*group, error) {
|
||||
g, err := addGroup(name, nil)
|
||||
func AddClient(name string, c Client) (*Group, error) {
|
||||
g, err := Add(name, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -219,7 +265,7 @@ func addClient(name string, c client) (*group, error) {
|
|||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
perms, err := getPermission(g.description, c.Credentials())
|
||||
perms, err := g.description.GetPermission(c.Credentials())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -227,37 +273,34 @@ func addClient(name string, c client) (*group, error) {
|
|||
c.SetPermissions(perms)
|
||||
|
||||
if !perms.Op && g.locked {
|
||||
return nil, userError("group is locked")
|
||||
return nil, UserError("group is locked")
|
||||
}
|
||||
|
||||
if !perms.Op && g.description.MaxClients > 0 {
|
||||
if len(g.clients) >= g.description.MaxClients {
|
||||
return nil, userError("too many users")
|
||||
return nil, UserError("too many users")
|
||||
}
|
||||
}
|
||||
if g.clients[c.Id()] != nil {
|
||||
return nil, protocolError("duplicate client id")
|
||||
return nil, ProtocolError("duplicate client id")
|
||||
}
|
||||
|
||||
g.clients[c.Id()] = c
|
||||
|
||||
go func(clients []client) {
|
||||
go func(clients []Client) {
|
||||
u := c.Credentials().Username
|
||||
c.pushClient(c.Id(), u, true)
|
||||
c.PushClient(c.Id(), u, true)
|
||||
for _, cc := range clients {
|
||||
uu := cc.Credentials().Username
|
||||
err := c.pushClient(cc.Id(), uu, true)
|
||||
if err == ErrClientDead {
|
||||
return
|
||||
}
|
||||
cc.pushClient(c.Id(), u, true)
|
||||
c.PushClient(cc.Id(), uu, true)
|
||||
cc.PushClient(c.Id(), u, true)
|
||||
}
|
||||
}(g.getClientsUnlocked(c))
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func delClient(c client) {
|
||||
func DelClient(c Client) {
|
||||
g := c.Group()
|
||||
if g == nil {
|
||||
return
|
||||
|
@ -271,21 +314,21 @@ func delClient(c client) {
|
|||
}
|
||||
delete(g.clients, c.Id())
|
||||
|
||||
go func(clients []client) {
|
||||
go func(clients []Client) {
|
||||
for _, cc := range clients {
|
||||
cc.pushClient(c.Id(), c.Credentials().Username, false)
|
||||
cc.PushClient(c.Id(), c.Credentials().Username, false)
|
||||
}
|
||||
}(g.getClientsUnlocked(nil))
|
||||
}
|
||||
|
||||
func (g *group) getClients(except client) []client {
|
||||
func (g *Group) GetClients(except Client) []Client {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.getClientsUnlocked(except)
|
||||
}
|
||||
|
||||
func (g *group) getClientsUnlocked(except client) []client {
|
||||
clients := make([]client, 0, len(g.clients))
|
||||
func (g *Group) getClientsUnlocked(except Client) []Client {
|
||||
clients := make([]Client, 0, len(g.clients))
|
||||
for _, c := range g.clients {
|
||||
if c != except {
|
||||
clients = append(clients, c)
|
||||
|
@ -294,13 +337,13 @@ func (g *group) getClientsUnlocked(except client) []client {
|
|||
return clients
|
||||
}
|
||||
|
||||
func (g *group) getClient(id string) client {
|
||||
func (g *Group) GetClient(id string) Client {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.getClientUnlocked(id)
|
||||
}
|
||||
|
||||
func (g *group) getClientUnlocked(id string) client {
|
||||
func (g *Group) getClientUnlocked(id string) Client {
|
||||
for idd, c := range g.clients {
|
||||
if idd == id {
|
||||
return c
|
||||
|
@ -309,7 +352,7 @@ func (g *group) getClientUnlocked(id string) client {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (g *group) Range(f func(c client) bool) {
|
||||
func (g *Group) Range(f func(c Client) bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
for _, c := range g.clients {
|
||||
|
@ -320,11 +363,11 @@ func (g *group) Range(f func(c client) bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func (g *group) shutdown(message string) {
|
||||
g.Range(func(c client) bool {
|
||||
cc, ok := c.(kickable)
|
||||
func (g *Group) Shutdown(message string) {
|
||||
g.Range(func(c Client) bool {
|
||||
cc, ok := c.(Kickable)
|
||||
if ok {
|
||||
cc.kick(message)
|
||||
cc.Kick(message)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
@ -332,13 +375,13 @@ func (g *group) shutdown(message string) {
|
|||
|
||||
const maxChatHistory = 20
|
||||
|
||||
func (g *group) clearChatHistory() {
|
||||
func (g *Group) ClearChatHistory() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.history = nil
|
||||
}
|
||||
|
||||
func (g *group) addToChatHistory(id, user, kind, value string) {
|
||||
func (g *Group) AddToChatHistory(id, user, kind, value string) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
|
@ -347,20 +390,20 @@ func (g *group) addToChatHistory(id, user, kind, value string) {
|
|||
g.history = g.history[:len(g.history)-1]
|
||||
}
|
||||
g.history = append(g.history,
|
||||
chatHistoryEntry{id: id, user: user, kind: kind, value: value},
|
||||
ChatHistoryEntry{Id: id, User: user, Kind: kind, Value: value},
|
||||
)
|
||||
}
|
||||
|
||||
func (g *group) getChatHistory() []chatHistoryEntry {
|
||||
func (g *Group) GetChatHistory() []ChatHistoryEntry {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
h := make([]chatHistoryEntry, len(g.history))
|
||||
h := make([]ChatHistoryEntry, len(g.history))
|
||||
copy(h, g.history)
|
||||
return h
|
||||
}
|
||||
|
||||
func matchUser(user clientCredentials, users []clientCredentials) (bool, bool) {
|
||||
func matchUser(user ClientCredentials, users []ClientCredentials) (bool, bool) {
|
||||
for _, u := range users {
|
||||
if u.Username == "" {
|
||||
if u.Password == "" || u.Password == user.Password {
|
||||
|
@ -383,13 +426,13 @@ type groupDescription struct {
|
|||
MaxClients int `json:"max-clients,omitempty"`
|
||||
AllowAnonymous bool `json:"allow-anonymous,omitempty"`
|
||||
AllowRecording bool `json:"allow-recording,omitempty"`
|
||||
Op []clientCredentials `json:"op,omitempty"`
|
||||
Presenter []clientCredentials `json:"presenter,omitempty"`
|
||||
Other []clientCredentials `json:"other,omitempty"`
|
||||
Op []ClientCredentials `json:"op,omitempty"`
|
||||
Presenter []ClientCredentials `json:"presenter,omitempty"`
|
||||
Other []ClientCredentials `json:"other,omitempty"`
|
||||
}
|
||||
|
||||
func descriptionChanged(name string, old *groupDescription) (bool, error) {
|
||||
fi, err := os.Stat(filepath.Join(groupsDir, name+".json"))
|
||||
fi, err := os.Stat(filepath.Join(Directory, name+".json"))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -399,8 +442,8 @@ func descriptionChanged(name string, old *groupDescription) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
func getDescription(name string) (*groupDescription, error) {
|
||||
r, err := os.Open(filepath.Join(groupsDir, name+".json"))
|
||||
func GetDescription(name string) (*groupDescription, error) {
|
||||
r, err := os.Open(filepath.Join(Directory, name+".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -424,10 +467,10 @@ func getDescription(name string) (*groupDescription, error) {
|
|||
return &desc, nil
|
||||
}
|
||||
|
||||
func getPermission(desc *groupDescription, creds clientCredentials) (clientPermissions, error) {
|
||||
var p clientPermissions
|
||||
func (desc *groupDescription) GetPermission (creds ClientCredentials) (ClientPermissions, error) {
|
||||
var p ClientPermissions
|
||||
if !desc.AllowAnonymous && creds.Username == "" {
|
||||
return p, userError("anonymous users not allowed in this group, please choose a username")
|
||||
return p, UserError("anonymous users not allowed in this group, please choose a username")
|
||||
}
|
||||
if found, good := matchUser(creds, desc.Op); found {
|
||||
if good {
|
||||
|
@ -438,34 +481,34 @@ func getPermission(desc *groupDescription, creds clientCredentials) (clientPermi
|
|||
}
|
||||
return p, nil
|
||||
}
|
||||
return p, userError("not authorised")
|
||||
return p, UserError("not authorised")
|
||||
}
|
||||
if found, good := matchUser(creds, desc.Presenter); found {
|
||||
if good {
|
||||
p.Present = true
|
||||
return p, nil
|
||||
}
|
||||
return p, userError("not authorised")
|
||||
return p, UserError("not authorised")
|
||||
}
|
||||
if found, good := matchUser(creds, desc.Other); found {
|
||||
if good {
|
||||
return p, nil
|
||||
}
|
||||
return p, userError("not authorised")
|
||||
return p, UserError("not authorised")
|
||||
}
|
||||
return p, userError("not authorised")
|
||||
return p, UserError("not authorised")
|
||||
}
|
||||
|
||||
type publicGroup struct {
|
||||
type Public struct {
|
||||
Name string `json:"name"`
|
||||
ClientCount int `json:"clientCount"`
|
||||
}
|
||||
|
||||
func getPublicGroups() []publicGroup {
|
||||
gs := make([]publicGroup, 0)
|
||||
rangeGroups(func(g *group) bool {
|
||||
func GetPublic() []Public {
|
||||
gs := make([]Public, 0)
|
||||
Range(func(g *Group) bool {
|
||||
if g.Public() {
|
||||
gs = append(gs, publicGroup{
|
||||
gs = append(gs, Public{
|
||||
Name: g.name,
|
||||
ClientCount: len(g.clients),
|
||||
})
|
||||
|
@ -478,8 +521,8 @@ func getPublicGroups() []publicGroup {
|
|||
return gs
|
||||
}
|
||||
|
||||
func readPublicGroups() {
|
||||
dir, err := os.Open(groupsDir)
|
||||
func ReadPublicGroups() {
|
||||
dir, err := os.Open(Directory)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -496,7 +539,7 @@ func readPublicGroups() {
|
|||
continue
|
||||
}
|
||||
name := fi.Name()[:len(fi.Name())-5]
|
||||
desc, err := getDescription(name)
|
||||
desc, err := GetDescription(name)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Printf("Reading group %v: %v", name, err)
|
||||
|
@ -504,7 +547,7 @@ func readPublicGroups() {
|
|||
continue
|
||||
}
|
||||
if desc.Public {
|
||||
addGroup(name, desc)
|
||||
Add(name, desc)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,4 @@
|
|||
// Copyright (c) 2020 by Juliusz Chroboczek.
|
||||
|
||||
// This is not open source software. Copy it, and I'll break into your
|
||||
// house and tell your three year-old that Santa doesn't exist.
|
||||
|
||||
package main
|
||||
package rtpconn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -14,14 +9,16 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"sfu/estimator"
|
||||
"sfu/jitter"
|
||||
"sfu/packetcache"
|
||||
"sfu/rtptime"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"sfu/conn"
|
||||
"sfu/estimator"
|
||||
"sfu/group"
|
||||
"sfu/jitter"
|
||||
"sfu/packetcache"
|
||||
"sfu/rtptime"
|
||||
)
|
||||
|
||||
type bitrate struct {
|
||||
|
@ -71,7 +68,7 @@ type iceConnection interface {
|
|||
|
||||
type rtpDownTrack struct {
|
||||
track *webrtc.Track
|
||||
remote upTrack
|
||||
remote conn.UpTrack
|
||||
maxBitrate *bitrate
|
||||
rate *estimator.Estimator
|
||||
stats *receiverStats
|
||||
|
@ -91,26 +88,26 @@ func (down *rtpDownTrack) Accumulate(bytes uint32) {
|
|||
down.rate.Accumulate(bytes)
|
||||
}
|
||||
|
||||
func (down *rtpDownTrack) setTimeOffset(ntp uint64, rtp uint32) {
|
||||
func (down *rtpDownTrack) SetTimeOffset(ntp uint64, rtp uint32) {
|
||||
atomic.StoreUint64(&down.remoteNTPTime, ntp)
|
||||
atomic.StoreUint32(&down.remoteRTPTime, rtp)
|
||||
}
|
||||
|
||||
func (down *rtpDownTrack) setCname(cname string) {
|
||||
func (down *rtpDownTrack) SetCname(cname string) {
|
||||
down.cname.Store(cname)
|
||||
}
|
||||
|
||||
type rtpDownConnection struct {
|
||||
id string
|
||||
pc *webrtc.PeerConnection
|
||||
remote upConnection
|
||||
remote conn.Up
|
||||
tracks []*rtpDownTrack
|
||||
maxREMBBitrate *bitrate
|
||||
iceCandidates []*webrtc.ICECandidateInit
|
||||
}
|
||||
|
||||
func newDownConn(c client, id string, remote upConnection) (*rtpDownConnection, error) {
|
||||
pc, err := c.Group().API().NewPeerConnection(iceConfiguration())
|
||||
func newDownConn(c group.Client, id string, remote conn.Up) (*rtpDownConnection, error) {
|
||||
pc, err := c.Group().API().NewPeerConnection(group.IceConfiguration())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -193,7 +190,7 @@ type rtpUpTrack struct {
|
|||
|
||||
mu sync.Mutex
|
||||
cname string
|
||||
local []downTrack
|
||||
local []conn.DownTrack
|
||||
srTime uint64
|
||||
srNTPTime uint64
|
||||
srRTPTime uint32
|
||||
|
@ -201,17 +198,17 @@ type rtpUpTrack struct {
|
|||
|
||||
type localTrackAction struct {
|
||||
add bool
|
||||
track downTrack
|
||||
track conn.DownTrack
|
||||
}
|
||||
|
||||
func (up *rtpUpTrack) notifyLocal(add bool, track downTrack) {
|
||||
func (up *rtpUpTrack) notifyLocal(add bool, track conn.DownTrack) {
|
||||
select {
|
||||
case up.localCh <- localTrackAction{add, track}:
|
||||
case <-up.readerDone:
|
||||
}
|
||||
}
|
||||
|
||||
func (up *rtpUpTrack) addLocal(local downTrack) error {
|
||||
func (up *rtpUpTrack) AddLocal(local conn.DownTrack) error {
|
||||
up.mu.Lock()
|
||||
for _, t := range up.local {
|
||||
if t == local {
|
||||
|
@ -226,7 +223,7 @@ func (up *rtpUpTrack) addLocal(local downTrack) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (up *rtpUpTrack) delLocal(local downTrack) bool {
|
||||
func (up *rtpUpTrack) DelLocal(local conn.DownTrack) bool {
|
||||
up.mu.Lock()
|
||||
for i, l := range up.local {
|
||||
if l == local {
|
||||
|
@ -240,15 +237,15 @@ func (up *rtpUpTrack) delLocal(local downTrack) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (up *rtpUpTrack) getLocal() []downTrack {
|
||||
func (up *rtpUpTrack) getLocal() []conn.DownTrack {
|
||||
up.mu.Lock()
|
||||
defer up.mu.Unlock()
|
||||
local := make([]downTrack, len(up.local))
|
||||
local := make([]conn.DownTrack, len(up.local))
|
||||
copy(local, up.local)
|
||||
return local
|
||||
}
|
||||
|
||||
func (up *rtpUpTrack) getRTP(seqno uint16, result []byte) uint16 {
|
||||
func (up *rtpUpTrack) GetRTP(seqno uint16, result []byte) uint16 {
|
||||
return up.cache.Get(seqno, result)
|
||||
}
|
||||
|
||||
|
@ -278,7 +275,7 @@ type rtpUpConnection struct {
|
|||
|
||||
mu sync.Mutex
|
||||
tracks []*rtpUpTrack
|
||||
local []downConnection
|
||||
local []conn.Down
|
||||
}
|
||||
|
||||
func (up *rtpUpConnection) getTracks() []*rtpUpTrack {
|
||||
|
@ -297,7 +294,7 @@ func (up *rtpUpConnection) Label() string {
|
|||
return up.label
|
||||
}
|
||||
|
||||
func (up *rtpUpConnection) addLocal(local downConnection) error {
|
||||
func (up *rtpUpConnection) AddLocal(local conn.Down) error {
|
||||
up.mu.Lock()
|
||||
defer up.mu.Unlock()
|
||||
for _, t := range up.local {
|
||||
|
@ -309,7 +306,7 @@ func (up *rtpUpConnection) addLocal(local downConnection) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (up *rtpUpConnection) delLocal(local downConnection) bool {
|
||||
func (up *rtpUpConnection) DelLocal(local conn.Down) bool {
|
||||
up.mu.Lock()
|
||||
defer up.mu.Unlock()
|
||||
for i, l := range up.local {
|
||||
|
@ -321,10 +318,10 @@ func (up *rtpUpConnection) delLocal(local downConnection) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (up *rtpUpConnection) getLocal() []downConnection {
|
||||
func (up *rtpUpConnection) getLocal() []conn.Down {
|
||||
up.mu.Lock()
|
||||
defer up.mu.Unlock()
|
||||
local := make([]downConnection, len(up.local))
|
||||
local := make([]conn.Down, len(up.local))
|
||||
copy(local, up.local)
|
||||
return local
|
||||
}
|
||||
|
@ -370,8 +367,8 @@ func (up *rtpUpConnection) complete() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func newUpConn(c client, id string) (*rtpUpConnection, error) {
|
||||
pc, err := c.Group().API().NewPeerConnection(iceConfiguration())
|
||||
func newUpConn(c group.Client, id string) (*rtpUpConnection, error) {
|
||||
pc, err := c.Group().API().NewPeerConnection(group.IceConfiguration())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -396,10 +393,10 @@ func newUpConn(c client, id string) (*rtpUpConnection, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
conn := &rtpUpConnection{id: id, pc: pc}
|
||||
up := &rtpUpConnection{id: id, pc: pc}
|
||||
|
||||
pc.OnTrack(func(remote *webrtc.Track, receiver *webrtc.RTPReceiver) {
|
||||
conn.mu.Lock()
|
||||
up.mu.Lock()
|
||||
|
||||
mid := getTrackMid(pc, remote)
|
||||
if mid == "" {
|
||||
|
@ -407,7 +404,7 @@ func newUpConn(c client, id string) (*rtpUpConnection, error) {
|
|||
return
|
||||
}
|
||||
|
||||
label, ok := conn.labels[mid]
|
||||
label, ok := up.labels[mid]
|
||||
if !ok {
|
||||
log.Printf("Couldn't get track's label")
|
||||
isvideo := remote.Kind() == webrtc.RTPCodecTypeVideo
|
||||
|
@ -428,34 +425,34 @@ func newUpConn(c client, id string) (*rtpUpConnection, error) {
|
|||
readerDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
conn.tracks = append(conn.tracks, track)
|
||||
up.tracks = append(up.tracks, track)
|
||||
|
||||
go readLoop(conn, track)
|
||||
go readLoop(up, track)
|
||||
|
||||
go rtcpUpListener(conn, track, receiver)
|
||||
go rtcpUpListener(up, track, receiver)
|
||||
|
||||
complete := conn.complete()
|
||||
var tracks []upTrack
|
||||
if(complete) {
|
||||
tracks = make([]upTrack, len(conn.tracks))
|
||||
for i, t := range conn.tracks {
|
||||
complete := up.complete()
|
||||
var tracks []conn.UpTrack
|
||||
if complete {
|
||||
tracks = make([]conn.UpTrack, len(up.tracks))
|
||||
for i, t := range up.tracks {
|
||||
tracks[i] = t
|
||||
}
|
||||
}
|
||||
|
||||
// pushConn might need to take the lock
|
||||
conn.mu.Unlock()
|
||||
up.mu.Unlock()
|
||||
|
||||
if complete {
|
||||
clients := c.Group().getClients(c)
|
||||
clients := c.Group().GetClients(c)
|
||||
for _, cc := range clients {
|
||||
cc.pushConn(conn.id, conn, tracks, conn.label)
|
||||
cc.PushConn(up.id, up, tracks, up.label)
|
||||
}
|
||||
go rtcpUpSender(conn)
|
||||
go rtcpUpSender(up)
|
||||
}
|
||||
})
|
||||
|
||||
return conn, nil
|
||||
return up, nil
|
||||
}
|
||||
|
||||
func readLoop(conn *rtpUpConnection, track *rtpUpTrack) {
|
||||
|
@ -606,7 +603,7 @@ func sendRecovery(p *rtcp.TransportLayerNack, track *rtpDownTrack) {
|
|||
buf := make([]byte, packetcache.BufSize)
|
||||
for _, nack := range p.Nacks {
|
||||
for _, seqno := range nack.PacketList() {
|
||||
l := track.remote.getRTP(seqno, buf)
|
||||
l := track.remote.GetRTP(seqno, buf)
|
||||
if l == 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -650,7 +647,7 @@ func rtcpUpListener(conn *rtpUpConnection, track *rtpUpTrack, r *webrtc.RTPRecei
|
|||
track.srRTPTime = p.RTPTime
|
||||
track.mu.Unlock()
|
||||
for _, l := range local {
|
||||
l.setTimeOffset(p.NTPTime, p.RTPTime)
|
||||
l.SetTimeOffset(p.NTPTime, p.RTPTime)
|
||||
}
|
||||
case *rtcp.SourceDescription:
|
||||
for _, c := range p.Chunks {
|
||||
|
@ -665,7 +662,7 @@ func rtcpUpListener(conn *rtpUpConnection, track *rtpUpTrack, r *webrtc.RTPRecei
|
|||
track.cname = i.Text
|
||||
track.mu.Unlock()
|
||||
for _, l := range local {
|
||||
l.setCname(i.Text)
|
||||
l.SetCname(i.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -749,8 +746,8 @@ func sendUpRTCP(conn *rtpUpConnection) error {
|
|||
rate = r
|
||||
}
|
||||
}
|
||||
if rate < minBitrate {
|
||||
rate = minBitrate
|
||||
if rate < group.MinBitrate {
|
||||
rate = group.MinBitrate
|
||||
}
|
||||
|
||||
var ssrcs []uint32
|
74
rtpconn/rtpstats.go
Normal file
74
rtpconn/rtpstats.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package rtpconn
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"sfu/rtptime"
|
||||
"sfu/stats"
|
||||
)
|
||||
|
||||
func (c *webClient) GetStats() *stats.Client {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
cs := stats.Client{
|
||||
Id: c.id,
|
||||
}
|
||||
|
||||
for _, up := range c.up {
|
||||
conns := stats.Conn{
|
||||
Id: up.id,
|
||||
}
|
||||
tracks := up.getTracks()
|
||||
for _, t := range tracks {
|
||||
expected, lost, _, _ := t.cache.GetStats(false)
|
||||
if expected == 0 {
|
||||
expected = 1
|
||||
}
|
||||
loss := uint8(lost * 100 / expected)
|
||||
jitter := time.Duration(t.jitter.Jitter()) *
|
||||
(time.Second / time.Duration(t.jitter.HZ()))
|
||||
rate, _ := t.rate.Estimate()
|
||||
conns.Tracks = append(conns.Tracks, stats.Track{
|
||||
Bitrate: uint64(rate) * 8,
|
||||
Loss: loss,
|
||||
Jitter: jitter,
|
||||
})
|
||||
}
|
||||
cs.Up = append(cs.Up, conns)
|
||||
}
|
||||
sort.Slice(cs.Up, func(i, j int) bool {
|
||||
return cs.Up[i].Id < cs.Up[j].Id
|
||||
})
|
||||
|
||||
jiffies := rtptime.Jiffies()
|
||||
for _, down := range c.down {
|
||||
conns := stats.Conn{
|
||||
Id: down.id,
|
||||
MaxBitrate: down.GetMaxBitrate(jiffies),
|
||||
}
|
||||
for _, t := range down.tracks {
|
||||
rate, _ := t.rate.Estimate()
|
||||
rtt := rtptime.ToDuration(atomic.LoadUint64(&t.rtt),
|
||||
rtptime.JiffiesPerSec)
|
||||
loss, jitter := t.stats.Get(jiffies)
|
||||
j := time.Duration(jitter) * time.Second /
|
||||
time.Duration(t.track.Codec().ClockRate)
|
||||
conns.Tracks = append(conns.Tracks, stats.Track{
|
||||
Bitrate: uint64(rate) * 8,
|
||||
MaxBitrate: t.maxBitrate.Get(jiffies),
|
||||
Loss: uint8(uint32(loss) * 100 / 256),
|
||||
Rtt: rtt,
|
||||
Jitter: j,
|
||||
})
|
||||
}
|
||||
cs.Down = append(cs.Down, conns)
|
||||
}
|
||||
sort.Slice(cs.Down, func(i, j int) bool {
|
||||
return cs.Down[i].Id < cs.Down[j].Id
|
||||
})
|
||||
|
||||
return &cs
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package rtpconn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"sfu/conn"
|
||||
"sfu/packetcache"
|
||||
"sfu/rtptime"
|
||||
)
|
||||
|
@ -43,7 +44,7 @@ func sqrt(n int) int {
|
|||
}
|
||||
|
||||
// add adds or removes a track from a writer pool
|
||||
func (wp *rtpWriterPool) add(track downTrack, add bool) error {
|
||||
func (wp *rtpWriterPool) add(track conn.DownTrack, add bool) error {
|
||||
n := 4
|
||||
if wp.count > 16 {
|
||||
n = sqrt(wp.count)
|
||||
|
@ -166,7 +167,7 @@ var ErrUnknownTrack = errors.New("unknown track")
|
|||
|
||||
type writerAction struct {
|
||||
add bool
|
||||
track downTrack
|
||||
track conn.DownTrack
|
||||
maxTracks int
|
||||
ch chan error
|
||||
}
|
||||
|
@ -192,7 +193,7 @@ func newRtpWriter(conn *rtpUpConnection, track *rtpUpTrack) *rtpWriter {
|
|||
}
|
||||
|
||||
// add adds or removes a track from a writer.
|
||||
func (writer *rtpWriter) add(track downTrack, add bool, max int) error {
|
||||
func (writer *rtpWriter) add(track conn.DownTrack, add bool, max int) error {
|
||||
ch := make(chan error, 1)
|
||||
select {
|
||||
case writer.action <- writerAction{add, track, max, ch}:
|
||||
|
@ -208,13 +209,13 @@ func (writer *rtpWriter) add(track downTrack, add bool, max int) error {
|
|||
}
|
||||
|
||||
// rtpWriterLoop is the main loop of an rtpWriter.
|
||||
func rtpWriterLoop(writer *rtpWriter, conn *rtpUpConnection, track *rtpUpTrack) {
|
||||
func rtpWriterLoop(writer *rtpWriter, up *rtpUpConnection, track *rtpUpTrack) {
|
||||
defer close(writer.done)
|
||||
|
||||
buf := make([]byte, packetcache.BufSize)
|
||||
var packet rtp.Packet
|
||||
|
||||
local := make([]downTrack, 0)
|
||||
local := make([]conn.DownTrack, 0)
|
||||
|
||||
// reset whenever a new track is inserted
|
||||
firSent := false
|
||||
|
@ -239,10 +240,10 @@ func rtpWriterLoop(writer *rtpWriter, conn *rtpUpConnection, track *rtpUpTrack)
|
|||
cname := track.cname
|
||||
track.mu.Unlock()
|
||||
if ntp != 0 {
|
||||
action.track.setTimeOffset(ntp, rtp)
|
||||
action.track.SetTimeOffset(ntp, rtp)
|
||||
}
|
||||
if cname != "" {
|
||||
action.track.setCname(cname)
|
||||
action.track.SetCname(cname)
|
||||
}
|
||||
} else {
|
||||
found := false
|
||||
|
@ -283,7 +284,7 @@ func rtpWriterLoop(writer *rtpWriter, conn *rtpUpConnection, track *rtpUpTrack)
|
|||
for _, l := range local {
|
||||
err := l.WriteRTP(&packet)
|
||||
if err != nil {
|
||||
if err == ErrKeyframeNeeded {
|
||||
if err == conn.ErrKeyframeNeeded {
|
||||
kfNeeded = true
|
||||
}
|
||||
continue
|
||||
|
@ -292,9 +293,9 @@ func rtpWriterLoop(writer *rtpWriter, conn *rtpUpConnection, track *rtpUpTrack)
|
|||
}
|
||||
|
||||
if kfNeeded {
|
||||
err := conn.sendFIR(track, !firSent)
|
||||
err := up.sendFIR(track, !firSent)
|
||||
if err == ErrUnsupportedFeedback {
|
||||
conn.sendPLI(track)
|
||||
up.sendPLI(track)
|
||||
}
|
||||
firSent = true
|
||||
}
|
|
@ -1,9 +1,4 @@
|
|||
// Copyright (c) 2020 by Juliusz Chroboczek.
|
||||
|
||||
// This is not open source software. Copy it, and I'll break into your
|
||||
// house and tell your three year-old that Santa doesn't exist.
|
||||
|
||||
package main
|
||||
package rtpconn
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -13,60 +8,25 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"sfu/estimator"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pion/webrtc/v3"
|
||||
|
||||
"sfu/conn"
|
||||
"sfu/disk"
|
||||
"sfu/estimator"
|
||||
"sfu/group"
|
||||
)
|
||||
|
||||
var iceConf webrtc.Configuration
|
||||
var iceOnce sync.Once
|
||||
|
||||
func iceConfiguration() webrtc.Configuration {
|
||||
iceOnce.Do(func() {
|
||||
var iceServers []webrtc.ICEServer
|
||||
file, err := os.Open(iceFilename)
|
||||
if err != nil {
|
||||
log.Printf("Open %v: %v", iceFilename, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
d := json.NewDecoder(file)
|
||||
err = d.Decode(&iceServers)
|
||||
if err != nil {
|
||||
log.Printf("Get ICE configuration: %v", err)
|
||||
return
|
||||
}
|
||||
iceConf = webrtc.Configuration{
|
||||
ICEServers: iceServers,
|
||||
}
|
||||
})
|
||||
|
||||
return iceConf
|
||||
}
|
||||
|
||||
type protocolError string
|
||||
|
||||
func (err protocolError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
type userError string
|
||||
|
||||
func (err userError) Error() string {
|
||||
return string(err)
|
||||
}
|
||||
|
||||
func errorToWSCloseMessage(err error) (string, []byte) {
|
||||
var code int
|
||||
var text string
|
||||
switch e := err.(type) {
|
||||
case *websocket.CloseError:
|
||||
code = websocket.CloseNormalClosure
|
||||
case protocolError:
|
||||
case group.ProtocolError:
|
||||
code = websocket.CloseProtocolError
|
||||
text = string(e)
|
||||
case userError:
|
||||
case group.UserError:
|
||||
code = websocket.CloseNormalClosure
|
||||
text = string(e)
|
||||
default:
|
||||
|
@ -82,10 +42,10 @@ func isWSNormalError(err error) bool {
|
|||
}
|
||||
|
||||
type webClient struct {
|
||||
group *group
|
||||
group *group.Group
|
||||
id string
|
||||
credentials clientCredentials
|
||||
permissions clientPermissions
|
||||
credentials group.ClientCredentials
|
||||
permissions group.ClientPermissions
|
||||
requested map[string]uint32
|
||||
done chan struct{}
|
||||
writeCh chan interface{}
|
||||
|
@ -97,7 +57,7 @@ type webClient struct {
|
|||
up map[string]*rtpUpConnection
|
||||
}
|
||||
|
||||
func (c *webClient) Group() *group {
|
||||
func (c *webClient) Group() *group.Group {
|
||||
return c.group
|
||||
}
|
||||
|
||||
|
@ -105,15 +65,15 @@ func (c *webClient) Id() string {
|
|||
return c.id
|
||||
}
|
||||
|
||||
func (c *webClient) Credentials() clientCredentials {
|
||||
func (c *webClient) Credentials() group.ClientCredentials {
|
||||
return c.credentials
|
||||
}
|
||||
|
||||
func (c *webClient) SetPermissions(perms clientPermissions) {
|
||||
func (c *webClient) SetPermissions(perms group.ClientPermissions) {
|
||||
c.permissions = perms
|
||||
}
|
||||
|
||||
func (c *webClient) pushClient(id, username string, add bool) error {
|
||||
func (c *webClient) PushClient(id, username string, add bool) error {
|
||||
kind := "add"
|
||||
if !add {
|
||||
kind = "delete"
|
||||
|
@ -179,7 +139,7 @@ type clientMessage struct {
|
|||
Id string `json:"id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Permissions clientPermissions `json:"permissions,omitempty"`
|
||||
Permissions group.ClientPermissions `json:"permissions,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Offer *webrtc.SessionDescription `json:"offer,omitempty"`
|
||||
|
@ -263,11 +223,11 @@ func delUpConn(c *webClient, id string) bool {
|
|||
delete(c.up, id)
|
||||
c.mu.Unlock()
|
||||
|
||||
go func(clients []client) {
|
||||
go func(clients []group.Client) {
|
||||
for _, c := range clients {
|
||||
c.pushConn(conn.id, nil, nil, "")
|
||||
c.PushConn(conn.id, nil, nil, "")
|
||||
}
|
||||
}(c.Group().getClients(c))
|
||||
}(c.Group().GetClients(c))
|
||||
|
||||
conn.pc.Close()
|
||||
return true
|
||||
|
@ -299,7 +259,7 @@ func getConn(c *webClient, id string) iceConnection {
|
|||
return nil
|
||||
}
|
||||
|
||||
func addDownConn(c *webClient, id string, remote upConnection) (*rtpDownConnection, error) {
|
||||
func addDownConn(c *webClient, id string, remote conn.Up) (*rtpDownConnection, error) {
|
||||
conn, err := newDownConn(c, id, remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -332,7 +292,7 @@ func addDownConn(c *webClient, id string, remote upConnection) (*rtpDownConnecti
|
|||
}
|
||||
})
|
||||
|
||||
err = remote.addLocal(conn)
|
||||
err = remote.AddLocal(conn)
|
||||
if err != nil {
|
||||
conn.pc.Close()
|
||||
return nil, err
|
||||
|
@ -354,18 +314,18 @@ func delDownConn(c *webClient, id string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
conn.remote.delLocal(conn)
|
||||
conn.remote.DelLocal(conn)
|
||||
for _, track := range conn.tracks {
|
||||
// we only insert the track after we get an answer, so
|
||||
// ignore errors here.
|
||||
track.remote.delLocal(track)
|
||||
track.remote.DelLocal(track)
|
||||
}
|
||||
conn.pc.Close()
|
||||
delete(c.down, id)
|
||||
return true
|
||||
}
|
||||
|
||||
func addDownTrack(c *webClient, conn *rtpDownConnection, remoteTrack upTrack, remoteConn upConnection) (*webrtc.RTPSender, error) {
|
||||
func addDownTrack(c *webClient, conn *rtpDownConnection, remoteTrack conn.UpTrack, remoteConn conn.Up) (*webrtc.RTPSender, error) {
|
||||
var pt uint8
|
||||
var ssrc uint32
|
||||
var id, label string
|
||||
|
@ -510,7 +470,7 @@ func gotOffer(c *webClient, id string, offer webrtc.SessionDescription, renegoti
|
|||
func gotAnswer(c *webClient, id string, answer webrtc.SessionDescription) error {
|
||||
down := getDownConn(c, id)
|
||||
if down == nil {
|
||||
return protocolError("unknown id in answer")
|
||||
return group.ProtocolError("unknown id in answer")
|
||||
}
|
||||
err := down.pc.SetRemoteDescription(answer)
|
||||
if err != nil {
|
||||
|
@ -523,7 +483,7 @@ func gotAnswer(c *webClient, id string, answer webrtc.SessionDescription) error
|
|||
}
|
||||
|
||||
for _, t := range down.tracks {
|
||||
t.remote.addLocal(t)
|
||||
t.remote.AddLocal(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -553,8 +513,8 @@ func (c *webClient) setRequested(requested map[string]uint32) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func pushConns(c client) {
|
||||
clients := c.Group().getClients(c)
|
||||
func pushConns(c group.Client) {
|
||||
clients := c.Group().GetClients(c)
|
||||
for _, cc := range clients {
|
||||
ccc, ok := cc.(*webClient)
|
||||
if ok {
|
||||
|
@ -567,7 +527,7 @@ func (c *webClient) isRequested(label string) bool {
|
|||
return c.requested[label] != 0
|
||||
}
|
||||
|
||||
func addDownConnTracks(c *webClient, remote upConnection, tracks []upTrack) (*rtpDownConnection, error) {
|
||||
func addDownConnTracks(c *webClient, remote conn.Up, tracks []conn.UpTrack) (*rtpDownConnection, error) {
|
||||
requested := false
|
||||
for _, t := range tracks {
|
||||
if c.isRequested(t.Label()) {
|
||||
|
@ -600,13 +560,13 @@ func addDownConnTracks(c *webClient, remote upConnection, tracks []upTrack) (*rt
|
|||
return down, nil
|
||||
}
|
||||
|
||||
func (c *webClient) pushConn(id string, conn upConnection, tracks []upTrack, label string) error {
|
||||
err := c.action(pushConnAction{id, conn, tracks})
|
||||
func (c *webClient) PushConn(id string, up conn.Up, tracks []conn.UpTrack, label string) error {
|
||||
err := c.action(pushConnAction{id, up, tracks})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn != nil && label != "" {
|
||||
err := c.action(addLabelAction{conn.Id(), conn.Label()})
|
||||
if up != nil && label != "" {
|
||||
err := c.action(addLabelAction{up.Id(), up.Label()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -614,7 +574,7 @@ func (c *webClient) pushConn(id string, conn upConnection, tracks []upTrack, lab
|
|||
return nil
|
||||
}
|
||||
|
||||
func startClient(conn *websocket.Conn) (err error) {
|
||||
func StartClient(conn *websocket.Conn) (err error) {
|
||||
var m clientMessage
|
||||
|
||||
err = conn.SetReadDeadline(time.Now().Add(15 * time.Second))
|
||||
|
@ -646,7 +606,7 @@ func startClient(conn *websocket.Conn) (err error) {
|
|||
|
||||
c := &webClient{
|
||||
id: m.Id,
|
||||
credentials: clientCredentials{
|
||||
credentials: group.ClientCredentials{
|
||||
m.Username,
|
||||
m.Password,
|
||||
},
|
||||
|
@ -683,32 +643,32 @@ func startClient(conn *websocket.Conn) (err error) {
|
|||
}
|
||||
|
||||
if m.Type != "join" {
|
||||
return protocolError("you must join a group first")
|
||||
return group.ProtocolError("you must join a group first")
|
||||
}
|
||||
|
||||
g, err := addClient(m.Group, c)
|
||||
g, err := group.AddClient(m.Group, c)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = userError("group does not exist")
|
||||
err = group.UserError("group does not exist")
|
||||
}
|
||||
return
|
||||
}
|
||||
if redirect := g.Redirect(); redirect != "" {
|
||||
// We normally redirect at the HTTP level, but the group
|
||||
// description could have been edited in the meantime.
|
||||
err = userError("group is now at " + redirect)
|
||||
err = group.UserError("group is now at " + redirect)
|
||||
return
|
||||
}
|
||||
c.group = g
|
||||
defer delClient(c)
|
||||
defer group.DelClient(c)
|
||||
|
||||
return clientLoop(c, conn)
|
||||
}
|
||||
|
||||
type pushConnAction struct {
|
||||
id string
|
||||
conn upConnection
|
||||
tracks []upTrack
|
||||
conn conn.Up
|
||||
tracks []conn.UpTrack
|
||||
}
|
||||
|
||||
type addLabelAction struct {
|
||||
|
@ -717,7 +677,7 @@ type addLabelAction struct {
|
|||
}
|
||||
|
||||
type pushConnsAction struct {
|
||||
c client
|
||||
c group.Client
|
||||
}
|
||||
|
||||
type connectionFailedAction struct {
|
||||
|
@ -730,9 +690,9 @@ type kickAction struct {
|
|||
message string
|
||||
}
|
||||
|
||||
func clientLoop(c *webClient, conn *websocket.Conn) error {
|
||||
func clientLoop(c *webClient, ws *websocket.Conn) error {
|
||||
read := make(chan interface{}, 1)
|
||||
go clientReader(conn, read, c.done)
|
||||
go clientReader(ws, read, c.done)
|
||||
|
||||
defer func() {
|
||||
c.setRequested(map[string]uint32{})
|
||||
|
@ -748,14 +708,14 @@ func clientLoop(c *webClient, conn *websocket.Conn) error {
|
|||
Permissions: c.permissions,
|
||||
})
|
||||
|
||||
h := c.group.getChatHistory()
|
||||
h := c.group.GetChatHistory()
|
||||
for _, m := range h {
|
||||
err := c.write(clientMessage{
|
||||
Type: "chat",
|
||||
Id: m.id,
|
||||
Username: m.user,
|
||||
Value: m.value,
|
||||
Kind: m.kind,
|
||||
Id: m.Id,
|
||||
Username: m.User,
|
||||
Value: m.Value,
|
||||
Kind: m.Kind,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -829,11 +789,11 @@ func clientLoop(c *webClient, conn *websocket.Conn) error {
|
|||
case pushConnsAction:
|
||||
for _, u := range c.up {
|
||||
tracks := u.getTracks()
|
||||
ts := make([]upTrack, len(tracks))
|
||||
ts := make([]conn.UpTrack, len(tracks))
|
||||
for i, t := range tracks {
|
||||
ts[i] = t
|
||||
}
|
||||
go a.c.pushConn(u.id, u, ts, u.label)
|
||||
go a.c.PushConn(u.id, u, ts, u.label)
|
||||
}
|
||||
case connectionFailedAction:
|
||||
if down := getDownConn(c, a.id); down != nil {
|
||||
|
@ -842,12 +802,12 @@ func clientLoop(c *webClient, conn *websocket.Conn) error {
|
|||
return err
|
||||
}
|
||||
tracks := make(
|
||||
[]upTrack, len(down.tracks),
|
||||
[]conn.UpTrack, len(down.tracks),
|
||||
)
|
||||
for i, t := range down.tracks {
|
||||
tracks[i] = t.remote
|
||||
}
|
||||
go c.pushConn(
|
||||
go c.PushConn(
|
||||
down.remote.Id(), down.remote,
|
||||
tracks, down.remote.Label(),
|
||||
)
|
||||
|
@ -879,7 +839,7 @@ func clientLoop(c *webClient, conn *websocket.Conn) error {
|
|||
}
|
||||
}
|
||||
case kickAction:
|
||||
return userError(a.message)
|
||||
return group.UserError(a.message)
|
||||
default:
|
||||
log.Printf("unexpected action %T", a)
|
||||
return errors.New("unexpected action")
|
||||
|
@ -911,7 +871,7 @@ func failConnection(c *webClient, id string, message string) error {
|
|||
}
|
||||
}
|
||||
if message != "" {
|
||||
err := c.error(userError(message))
|
||||
err := c.error(group.UserError(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -919,15 +879,15 @@ func failConnection(c *webClient, id string, message string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setPermissions(g *group, id string, perm string) error {
|
||||
client := g.getClient(id)
|
||||
func setPermissions(g *group.Group, id string, perm string) error {
|
||||
client := g.GetClient(id)
|
||||
if client == nil {
|
||||
return userError("no such user")
|
||||
return group.UserError("no such user")
|
||||
}
|
||||
|
||||
c, ok := client.(*webClient)
|
||||
if !ok {
|
||||
return userError("this is not a real user")
|
||||
return group.UserError("this is not a real user")
|
||||
}
|
||||
|
||||
switch perm {
|
||||
|
@ -944,7 +904,7 @@ func setPermissions(g *group, id string, perm string) error {
|
|||
case "unpresent":
|
||||
c.permissions.Present = false
|
||||
default:
|
||||
return userError("unknown permission")
|
||||
return group.UserError("unknown permission")
|
||||
}
|
||||
return c.action(permissionsChangedAction{})
|
||||
}
|
||||
|
@ -953,18 +913,18 @@ func (c *webClient) kick(message string) error {
|
|||
return c.action(kickAction{message})
|
||||
}
|
||||
|
||||
func kickClient(g *group, id string, message string) error {
|
||||
client := g.getClient(id)
|
||||
func kickClient(g *group.Group, id string, message string) error {
|
||||
client := g.GetClient(id)
|
||||
if client == nil {
|
||||
return userError("no such user")
|
||||
return group.UserError("no such user")
|
||||
}
|
||||
|
||||
c, ok := client.(kickable)
|
||||
c, ok := client.(group.Kickable)
|
||||
if !ok {
|
||||
return userError("this client is not kickable")
|
||||
return group.UserError("this client is not kickable")
|
||||
}
|
||||
|
||||
return c.kick(message)
|
||||
return c.Kick(message)
|
||||
}
|
||||
|
||||
func handleClientMessage(c *webClient, m clientMessage) error {
|
||||
|
@ -980,10 +940,10 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
Type: "abort",
|
||||
Id: m.Id,
|
||||
})
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
if m.Offer == nil {
|
||||
return protocolError("null offer")
|
||||
return group.ProtocolError("null offer")
|
||||
}
|
||||
err := gotOffer(
|
||||
c, m.Id, *m.Offer, m.Kind == "renegotiate", m.Labels,
|
||||
|
@ -994,7 +954,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
}
|
||||
case "answer":
|
||||
if m.Answer == nil {
|
||||
return protocolError("null answer")
|
||||
return group.ProtocolError("null answer")
|
||||
}
|
||||
err := gotAnswer(c, m.Id, *m.Answer)
|
||||
if err != nil {
|
||||
|
@ -1017,15 +977,15 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
}
|
||||
case "ice":
|
||||
if m.Candidate == nil {
|
||||
return protocolError("null candidate")
|
||||
return group.ProtocolError("null candidate")
|
||||
}
|
||||
err := gotICE(c, m.Candidate, m.Id)
|
||||
if err != nil {
|
||||
log.Printf("ICE: %v", err)
|
||||
}
|
||||
case "chat":
|
||||
c.group.addToChatHistory(m.Id, m.Username, m.Kind, m.Value)
|
||||
clients := c.group.getClients(nil)
|
||||
c.group.AddToChatHistory(m.Id, m.Username, m.Kind, m.Value)
|
||||
clients := c.group.GetClients(nil)
|
||||
for _, cc := range clients {
|
||||
cc, ok := cc.(*webClient)
|
||||
if ok {
|
||||
|
@ -1035,9 +995,9 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
case "groupaction":
|
||||
switch m.Kind {
|
||||
case "clearchat":
|
||||
c.group.clearChatHistory()
|
||||
c.group.ClearChatHistory()
|
||||
m := clientMessage{Type: "clearchat"}
|
||||
clients := c.group.getClients(nil)
|
||||
clients := c.group.GetClients(nil)
|
||||
for _, cc := range clients {
|
||||
cc, ok := cc.(*webClient)
|
||||
if ok {
|
||||
|
@ -1046,21 +1006,21 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
}
|
||||
case "lock", "unlock":
|
||||
if !c.permissions.Op {
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
c.group.SetLocked(m.Kind == "lock")
|
||||
case "record":
|
||||
if !c.permissions.Record {
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
for _, cc := range c.group.getClients(c) {
|
||||
_, ok := cc.(*diskClient)
|
||||
for _, cc := range c.group.GetClients(c) {
|
||||
_, ok := cc.(*disk.Client)
|
||||
if ok {
|
||||
return c.error(userError("already recording"))
|
||||
return c.error(group.UserError("already recording"))
|
||||
}
|
||||
}
|
||||
disk := NewDiskClient(c.group)
|
||||
_, err := addClient(c.group.name, disk)
|
||||
disk := disk.New(c.group)
|
||||
_, err := group.AddClient(c.group.Name(), disk)
|
||||
if err != nil {
|
||||
disk.Close()
|
||||
return c.error(err)
|
||||
|
@ -1068,23 +1028,23 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
go pushConns(disk)
|
||||
case "unrecord":
|
||||
if !c.permissions.Record {
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
for _, cc := range c.group.getClients(c) {
|
||||
disk, ok := cc.(*diskClient)
|
||||
for _, cc := range c.group.GetClients(c) {
|
||||
disk, ok := cc.(*disk.Client)
|
||||
if ok {
|
||||
disk.Close()
|
||||
delClient(disk)
|
||||
group.DelClient(disk)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return protocolError("unknown group action")
|
||||
return group.ProtocolError("unknown group action")
|
||||
}
|
||||
case "useraction":
|
||||
switch m.Kind {
|
||||
case "op", "unop", "present", "unpresent":
|
||||
if !c.permissions.Op {
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
err := setPermissions(c.group, m.Id, m.Kind)
|
||||
if err != nil {
|
||||
|
@ -1092,7 +1052,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
}
|
||||
case "kick":
|
||||
if !c.permissions.Op {
|
||||
return c.error(userError("not authorised"))
|
||||
return c.error(group.UserError("not authorised"))
|
||||
}
|
||||
message := m.Value
|
||||
if message == "" {
|
||||
|
@ -1103,7 +1063,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
return c.error(err)
|
||||
}
|
||||
default:
|
||||
return protocolError("unknown user action")
|
||||
return group.ProtocolError("unknown user action")
|
||||
}
|
||||
case "pong":
|
||||
// nothing
|
||||
|
@ -1113,7 +1073,7 @@ func handleClientMessage(c *webClient, m clientMessage) error {
|
|||
})
|
||||
default:
|
||||
log.Printf("unexpected message: %v", m.Type)
|
||||
return protocolError("unexpected message")
|
||||
return group.ProtocolError("unexpected message")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1207,7 +1167,7 @@ func (c *webClient) close(data []byte) error {
|
|||
|
||||
func (c *webClient) error(err error) error {
|
||||
switch e := err.(type) {
|
||||
case userError:
|
||||
case group.UserError:
|
||||
return c.write(clientMessage{
|
||||
Type: "usermessage",
|
||||
Kind: "error",
|
14
sfu.go
14
sfu.go
|
@ -14,14 +14,14 @@ import (
|
|||
"runtime"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
|
||||
"sfu/disk"
|
||||
"sfu/group"
|
||||
)
|
||||
|
||||
var httpAddr string
|
||||
var staticRoot string
|
||||
var dataDir string
|
||||
var groupsDir string
|
||||
var recordingsDir string
|
||||
var iceFilename string
|
||||
|
||||
func main() {
|
||||
var cpuprofile, memprofile, mutexprofile string
|
||||
|
@ -31,9 +31,9 @@ func main() {
|
|||
"web server root `directory`")
|
||||
flag.StringVar(&dataDir, "data", "./data/",
|
||||
"data `directory`")
|
||||
flag.StringVar(&groupsDir, "groups", "./groups/",
|
||||
flag.StringVar(&group.Directory, "groups", "./groups/",
|
||||
"group description `directory`")
|
||||
flag.StringVar(&recordingsDir, "recordings", "./recordings/",
|
||||
flag.StringVar(&disk.Directory, "recordings", "./recordings/",
|
||||
"recordings `directory`")
|
||||
flag.StringVar(&cpuprofile, "cpuprofile", "",
|
||||
"store CPU profile in `file`")
|
||||
|
@ -81,9 +81,9 @@ func main() {
|
|||
}()
|
||||
}
|
||||
|
||||
iceFilename = filepath.Join(dataDir, "ice-servers.json")
|
||||
group.IceFilename = filepath.Join(dataDir, "ice-servers.json")
|
||||
|
||||
go readPublicGroups()
|
||||
go group.ReadPublicGroups()
|
||||
webserver()
|
||||
|
||||
terminate := make(chan os.Signal, 1)
|
||||
|
|
130
stats.go
130
stats.go
|
@ -1,130 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"sfu/rtptime"
|
||||
)
|
||||
|
||||
type groupStats struct {
|
||||
name string
|
||||
clients []clientStats
|
||||
}
|
||||
|
||||
type clientStats struct {
|
||||
id string
|
||||
up, down []connStats
|
||||
}
|
||||
|
||||
type connStats struct {
|
||||
id string
|
||||
maxBitrate uint64
|
||||
tracks []trackStats
|
||||
}
|
||||
|
||||
type trackStats struct {
|
||||
bitrate uint64
|
||||
maxBitrate uint64
|
||||
loss uint8
|
||||
rtt time.Duration
|
||||
jitter time.Duration
|
||||
}
|
||||
|
||||
func getGroupStats() []groupStats {
|
||||
names := getGroupNames()
|
||||
|
||||
gs := make([]groupStats, 0, len(names))
|
||||
for _, name := range names {
|
||||
g := getGroup(name)
|
||||
if g == nil {
|
||||
continue
|
||||
}
|
||||
clients := g.getClients(nil)
|
||||
stats := groupStats{
|
||||
name: name,
|
||||
clients: make([]clientStats, 0, len(clients)),
|
||||
}
|
||||
for _, c := range clients {
|
||||
c, ok := c.(*webClient)
|
||||
if ok {
|
||||
cs := getClientStats(c)
|
||||
stats.clients = append(stats.clients, cs)
|
||||
}
|
||||
}
|
||||
sort.Slice(stats.clients, func(i, j int) bool {
|
||||
return stats.clients[i].id < stats.clients[j].id
|
||||
})
|
||||
gs = append(gs, stats)
|
||||
}
|
||||
sort.Slice(gs, func(i, j int) bool {
|
||||
return gs[i].name < gs[j].name
|
||||
})
|
||||
|
||||
return gs
|
||||
}
|
||||
|
||||
func getClientStats(c *webClient) clientStats {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
cs := clientStats{
|
||||
id: c.id,
|
||||
}
|
||||
|
||||
for _, up := range c.up {
|
||||
conns := connStats{
|
||||
id: up.id,
|
||||
}
|
||||
tracks := up.getTracks()
|
||||
for _, t := range tracks {
|
||||
expected, lost, _, _ := t.cache.GetStats(false)
|
||||
if expected == 0 {
|
||||
expected = 1
|
||||
}
|
||||
loss := uint8(lost * 100 / expected)
|
||||
jitter := time.Duration(t.jitter.Jitter()) *
|
||||
(time.Second / time.Duration(t.jitter.HZ()))
|
||||
rate, _ := t.rate.Estimate()
|
||||
conns.tracks = append(conns.tracks, trackStats{
|
||||
bitrate: uint64(rate) * 8,
|
||||
loss: loss,
|
||||
jitter: jitter,
|
||||
})
|
||||
}
|
||||
cs.up = append(cs.up, conns)
|
||||
}
|
||||
sort.Slice(cs.up, func(i, j int) bool {
|
||||
return cs.up[i].id < cs.up[j].id
|
||||
})
|
||||
|
||||
jiffies := rtptime.Jiffies()
|
||||
for _, down := range c.down {
|
||||
conns := connStats{
|
||||
id: down.id,
|
||||
maxBitrate: down.GetMaxBitrate(jiffies),
|
||||
}
|
||||
for _, t := range down.tracks {
|
||||
rate, _ := t.rate.Estimate()
|
||||
rtt := rtptime.ToDuration(atomic.LoadUint64(&t.rtt),
|
||||
rtptime.JiffiesPerSec)
|
||||
loss, jitter := t.stats.Get(jiffies)
|
||||
j := time.Duration(jitter) * time.Second /
|
||||
time.Duration(t.track.Codec().ClockRate)
|
||||
conns.tracks = append(conns.tracks, trackStats{
|
||||
bitrate: uint64(rate) * 8,
|
||||
maxBitrate: t.maxBitrate.Get(jiffies),
|
||||
loss: uint8(uint32(loss) * 100 / 256),
|
||||
rtt: rtt,
|
||||
jitter: j,
|
||||
})
|
||||
}
|
||||
cs.down = append(cs.down, conns)
|
||||
}
|
||||
sort.Slice(cs.down, func(i, j int) bool {
|
||||
return cs.down[i].id < cs.down[j].id
|
||||
})
|
||||
|
||||
return cs
|
||||
}
|
73
stats/stats.go
Normal file
73
stats/stats.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package stats
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"sfu/group"
|
||||
)
|
||||
|
||||
type GroupStats struct {
|
||||
Name string
|
||||
Clients []*Client
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Id string
|
||||
Up, Down []Conn
|
||||
}
|
||||
|
||||
type Statable interface {
|
||||
GetStats() *Client
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
Id string
|
||||
MaxBitrate uint64
|
||||
Tracks []Track
|
||||
}
|
||||
|
||||
type Track struct {
|
||||
Bitrate uint64
|
||||
MaxBitrate uint64
|
||||
Loss uint8
|
||||
Rtt time.Duration
|
||||
Jitter time.Duration
|
||||
}
|
||||
|
||||
func GetGroups() []GroupStats {
|
||||
names := group.GetNames()
|
||||
|
||||
gs := make([]GroupStats, 0, len(names))
|
||||
for _, name := range names {
|
||||
g := group.Get(name)
|
||||
if g == nil {
|
||||
continue
|
||||
}
|
||||
clients := g.GetClients(nil)
|
||||
stats := GroupStats{
|
||||
Name: name,
|
||||
Clients: make([]*Client, 0, len(clients)),
|
||||
}
|
||||
for _, c := range clients {
|
||||
s, ok := c.(Statable)
|
||||
if ok {
|
||||
cs := s.GetStats()
|
||||
stats.Clients = append(stats.Clients, cs)
|
||||
} else {
|
||||
stats.Clients = append(stats.Clients,
|
||||
&Client{Id: c.Id()},
|
||||
)
|
||||
}
|
||||
}
|
||||
sort.Slice(stats.Clients, func(i, j int) bool {
|
||||
return stats.Clients[i].Id < stats.Clients[j].Id
|
||||
})
|
||||
gs = append(gs, stats)
|
||||
}
|
||||
sort.Slice(gs, func(i, j int) bool {
|
||||
return gs[i].Name < gs[j].Name
|
||||
})
|
||||
|
||||
return gs
|
||||
}
|
69
webserver.go
69
webserver.go
|
@ -18,6 +18,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"sfu/disk"
|
||||
"sfu/group"
|
||||
"sfu/rtpconn"
|
||||
"sfu/stats"
|
||||
)
|
||||
|
||||
var server *http.Server
|
||||
|
@ -47,8 +52,8 @@ func webserver() {
|
|||
IdleTimeout: 120 * time.Second,
|
||||
}
|
||||
server.RegisterOnShutdown(func() {
|
||||
rangeGroups(func (g *group) bool {
|
||||
go g.shutdown("server is shutting down")
|
||||
group.Range(func(g *group.Group) bool {
|
||||
go g.Shutdown("server is shutting down")
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
@ -139,7 +144,7 @@ func groupHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
g, err := addGroup(name, nil)
|
||||
g, err := group.Add(name, nil)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
notFound(w)
|
||||
|
@ -168,7 +173,7 @@ func publicHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
g := getPublicGroups()
|
||||
g := group.GetPublic()
|
||||
e := json.NewEncoder(w)
|
||||
e.Encode(g)
|
||||
return
|
||||
|
@ -222,7 +227,7 @@ func statsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
stats := getGroupStats()
|
||||
ss := stats.GetGroups()
|
||||
|
||||
fmt.Fprintf(w, "<!DOCTYPE html>\n<html><head>\n")
|
||||
fmt.Fprintf(w, "<title>Stats</title>\n")
|
||||
|
@ -239,51 +244,51 @@ func statsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return err
|
||||
}
|
||||
|
||||
printTrack := func(w io.Writer, t trackStats) {
|
||||
printTrack := func(w io.Writer, t stats.Track) {
|
||||
fmt.Fprintf(w, "<tr><td></td><td></td><td></td>")
|
||||
fmt.Fprintf(w, "<td>")
|
||||
printBitrate(w, t.bitrate, t.maxBitrate)
|
||||
printBitrate(w, t.Bitrate, t.MaxBitrate)
|
||||
fmt.Fprintf(w, "</td>")
|
||||
fmt.Fprintf(w, "<td>%d%%</td>",
|
||||
t.loss,
|
||||
t.Loss,
|
||||
)
|
||||
fmt.Fprintf(w, "<td>")
|
||||
if t.rtt > 0 {
|
||||
fmt.Fprintf(w, "%v", t.rtt)
|
||||
if t.Rtt > 0 {
|
||||
fmt.Fprintf(w, "%v", t.Rtt)
|
||||
}
|
||||
if t.jitter > 0 {
|
||||
fmt.Fprintf(w, "±%v", t.jitter)
|
||||
if t.Jitter > 0 {
|
||||
fmt.Fprintf(w, "±%v", t.Jitter)
|
||||
}
|
||||
fmt.Fprintf(w, "</td>")
|
||||
fmt.Fprintf(w, "</tr>")
|
||||
}
|
||||
|
||||
for _, gs := range stats {
|
||||
fmt.Fprintf(w, "<p>%v</p>\n", html.EscapeString(gs.name))
|
||||
for _, gs := range ss {
|
||||
fmt.Fprintf(w, "<p>%v</p>\n", html.EscapeString(gs.Name))
|
||||
fmt.Fprintf(w, "<table>")
|
||||
for _, cs := range gs.clients {
|
||||
fmt.Fprintf(w, "<tr><td>%v</td></tr>\n", cs.id)
|
||||
for _, up := range cs.up {
|
||||
for _, cs := range gs.Clients {
|
||||
fmt.Fprintf(w, "<tr><td>%v</td></tr>\n", cs.Id)
|
||||
for _, up := range cs.Up {
|
||||
fmt.Fprintf(w, "<tr><td></td><td>Up</td><td>%v</td>",
|
||||
up.id)
|
||||
if up.maxBitrate > 0 {
|
||||
up.Id)
|
||||
if up.MaxBitrate > 0 {
|
||||
fmt.Fprintf(w, "<td>%v</td>",
|
||||
up.maxBitrate)
|
||||
up.MaxBitrate)
|
||||
}
|
||||
fmt.Fprintf(w, "</tr>\n")
|
||||
for _, t := range up.tracks {
|
||||
for _, t := range up.Tracks {
|
||||
printTrack(w, t)
|
||||
}
|
||||
}
|
||||
for _, down := range cs.down {
|
||||
for _, down := range cs.Down {
|
||||
fmt.Fprintf(w, "<tr><td></td><td>Down</td><td> %v</td>",
|
||||
down.id)
|
||||
if down.maxBitrate > 0 {
|
||||
down.Id)
|
||||
if down.MaxBitrate > 0 {
|
||||
fmt.Fprintf(w, "<td>%v</td>",
|
||||
down.maxBitrate)
|
||||
down.MaxBitrate)
|
||||
}
|
||||
fmt.Fprintf(w, "</tr>\n")
|
||||
for _, t := range down.tracks {
|
||||
for _, t := range down.Tracks {
|
||||
printTrack(w, t)
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +307,7 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
go func() {
|
||||
err := startClient(conn)
|
||||
err := rtpconn.StartClient(conn)
|
||||
if err != nil {
|
||||
log.Printf("client: %v", err)
|
||||
}
|
||||
|
@ -322,7 +327,7 @@ func recordingsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Join(recordingsDir, pth))
|
||||
f, err := os.Open(filepath.Join(disk.Directory, pth))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
notFound(w)
|
||||
|
@ -389,7 +394,7 @@ func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
|
|||
return
|
||||
}
|
||||
err := os.Remove(
|
||||
filepath.Join(recordingsDir, group+"/"+filename),
|
||||
filepath.Join(disk.Directory, group+"/"+filename),
|
||||
)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
|
@ -409,8 +414,8 @@ func handleGroupAction(w http.ResponseWriter, r *http.Request, group string) {
|
|||
}
|
||||
}
|
||||
|
||||
func checkGroupPermissions(w http.ResponseWriter, r *http.Request, group string) bool {
|
||||
desc, err := getDescription(group)
|
||||
func checkGroupPermissions(w http.ResponseWriter, r *http.Request, groupname string) bool {
|
||||
desc, err := group.GetDescription(groupname)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
@ -420,7 +425,7 @@ func checkGroupPermissions(w http.ResponseWriter, r *http.Request, group string)
|
|||
return false
|
||||
}
|
||||
|
||||
p, err := getPermission(desc, clientCredentials{user, pass})
|
||||
p, err := desc.GetPermission(group.ClientCredentials{user, pass})
|
||||
if err != nil || !p.Record {
|
||||
return false
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue