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