Make upConnections generic.
This commit is contained in:
1 parent
0f96f94417
commit
208f023d9e
5 files changed
+1300
-1187
No files matched your search
@@ -7,338 +7,38 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"sfu/estimator"
|
|
||||||
"sfu/jitter"
|
|
||||||
"sfu/packetcache"
|
|
||||||
"sfu/rtptime"
|
|
||||||
|
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/webrtc/v2"
|
"github.com/pion/webrtc/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type localTrackAction struct {
|
|
||||||
add bool
|
|
||||||
track downTrack
|
|
||||||
}
|
|
||||||
|
|
||||||
type upTrack struct {
|
|
||||||
track *webrtc.Track
|
|
||||||
label string
|
|
||||||
rate *estimator.Estimator
|
|
||||||
cache *packetcache.Cache
|
|
||||||
jitter *jitter.Estimator
|
|
||||||
maxBitrate uint64
|
|
||||||
lastPLI uint64
|
|
||||||
lastFIR uint64
|
|
||||||
firSeqno uint32
|
|
||||||
|
|
||||||
localCh chan localTrackAction // signals that local has changed
|
|
||||||
writerDone chan struct{} // closed when the loop dies
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
local []downTrack
|
|
||||||
srTime uint64
|
|
||||||
srNTPTime uint64
|
|
||||||
srRTPTime uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upTrack) notifyLocal(add bool, track downTrack) {
|
|
||||||
select {
|
|
||||||
case up.localCh <- localTrackAction{add, track}:
|
|
||||||
case <-up.writerDone:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upTrack) addLocal(local downTrack) error {
|
|
||||||
up.mu.Lock()
|
|
||||||
for _, t := range up.local {
|
|
||||||
if t == local {
|
|
||||||
up.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
up.local = append(up.local, local)
|
|
||||||
up.mu.Unlock()
|
|
||||||
|
|
||||||
up.notifyLocal(true, local)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upTrack) delLocal(local downTrack) bool {
|
|
||||||
up.mu.Lock()
|
|
||||||
for i, l := range up.local {
|
|
||||||
if l == local {
|
|
||||||
up.local = append(up.local[:i], up.local[i+1:]...)
|
|
||||||
up.mu.Unlock()
|
|
||||||
up.notifyLocal(false, l)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
up.mu.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upTrack) getLocal() []downTrack {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
local := make([]downTrack, len(up.local))
|
|
||||||
copy(local, up.local)
|
|
||||||
return local
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upTrack) hasRtcpFb(tpe, parameter string) bool {
|
|
||||||
for _, fb := range up.track.Codec().RTCPFeedback {
|
|
||||||
if fb.Type == tpe && fb.Parameter == parameter {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type iceConnection interface {
|
|
||||||
addICECandidate(candidate *webrtc.ICECandidateInit) error
|
|
||||||
flushICECandidates() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type upConnection struct {
|
|
||||||
id string
|
|
||||||
label string
|
|
||||||
pc *webrtc.PeerConnection
|
|
||||||
labels map[string]string
|
|
||||||
iceCandidates []*webrtc.ICECandidateInit
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
closed bool
|
|
||||||
tracks []*upTrack
|
|
||||||
local []downConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrConnectionClosed = errors.New("connection is closed")
|
var ErrConnectionClosed = errors.New("connection is closed")
|
||||||
|
|
||||||
func (up *upConnection) getTracks() []*upTrack {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
tracks := make([]*upTrack, len(up.tracks))
|
|
||||||
copy(tracks, up.tracks)
|
|
||||||
return tracks
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) addLocal(local downConnection) error {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
if up.closed {
|
|
||||||
return ErrConnectionClosed
|
|
||||||
}
|
|
||||||
for _, t := range up.local {
|
|
||||||
if t == local {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
up.local = append(up.local, local)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) delLocal(local downConnection) bool {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
for i, l := range up.local {
|
|
||||||
if l == local {
|
|
||||||
up.local = append(up.local[:i], up.local[i+1:]...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) getLocal() []downConnection {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
local := make([]downConnection, len(up.local))
|
|
||||||
copy(local, up.local)
|
|
||||||
return local
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) Close() error {
|
|
||||||
up.mu.Lock()
|
|
||||||
defer up.mu.Unlock()
|
|
||||||
|
|
||||||
go func(local []downConnection) {
|
|
||||||
for _, l := range local {
|
|
||||||
l.Close()
|
|
||||||
}
|
|
||||||
}(up.local)
|
|
||||||
|
|
||||||
up.local = nil
|
|
||||||
up.closed = true
|
|
||||||
return up.pc.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) addICECandidate(candidate *webrtc.ICECandidateInit) error {
|
|
||||||
if up.pc.RemoteDescription() != nil {
|
|
||||||
return up.pc.AddICECandidate(*candidate)
|
|
||||||
}
|
|
||||||
up.iceCandidates = append(up.iceCandidates, candidate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func flushICECandidates(pc *webrtc.PeerConnection, candidates []*webrtc.ICECandidateInit) error {
|
|
||||||
if pc.RemoteDescription() == nil {
|
|
||||||
return errors.New("flushICECandidates called in bad state")
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, candidate := range candidates {
|
|
||||||
err2 := pc.AddICECandidate(*candidate)
|
|
||||||
if err == nil {
|
|
||||||
err = err2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (up *upConnection) flushICECandidates() error {
|
|
||||||
err := flushICECandidates(up.pc, up.iceCandidates)
|
|
||||||
up.iceCandidates = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUpMid(pc *webrtc.PeerConnection, track *webrtc.Track) string {
|
|
||||||
for _, t := range pc.GetTransceivers() {
|
|
||||||
if t.Receiver() != nil && t.Receiver().Track() == track {
|
|
||||||
return t.Mid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// called locked
|
|
||||||
func (up *upConnection) complete() bool {
|
|
||||||
for mid, _ := range up.labels {
|
|
||||||
found := false
|
|
||||||
for _, t := range up.tracks {
|
|
||||||
m := getUpMid(up.pc, t.track)
|
|
||||||
if m == mid {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type bitrate struct {
|
|
||||||
bitrate uint64
|
|
||||||
jiffies uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
const receiverReportTimeout = 8 * rtptime.JiffiesPerSec
|
|
||||||
|
|
||||||
func (br *bitrate) Set(bitrate uint64, now uint64) {
|
|
||||||
// this is racy -- a reader might read the
|
|
||||||
// data between the two writes. This shouldn't
|
|
||||||
// matter, we'll recover at the next sample.
|
|
||||||
atomic.StoreUint64(&br.bitrate, bitrate)
|
|
||||||
atomic.StoreUint64(&br.jiffies, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (br *bitrate) Get(now uint64) uint64 {
|
|
||||||
ts := atomic.LoadUint64(&br.jiffies)
|
|
||||||
if now < ts || now-ts > receiverReportTimeout {
|
|
||||||
return ^uint64(0)
|
|
||||||
}
|
|
||||||
return atomic.LoadUint64(&br.bitrate)
|
|
||||||
}
|
|
||||||
|
|
||||||
type receiverStats struct {
|
|
||||||
loss uint32
|
|
||||||
jitter uint32
|
|
||||||
jiffies uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *receiverStats) Set(loss uint8, jitter uint32, now uint64) {
|
|
||||||
atomic.StoreUint32(&s.loss, uint32(loss))
|
|
||||||
atomic.StoreUint32(&s.jitter, jitter)
|
|
||||||
atomic.StoreUint64(&s.jiffies, now)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *receiverStats) Get(now uint64) (uint8, uint32) {
|
|
||||||
ts := atomic.LoadUint64(&s.jiffies)
|
|
||||||
if now < ts || now > ts+receiverReportTimeout {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
return uint8(atomic.LoadUint32(&s.loss)), atomic.LoadUint32(&s.jitter)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrKeyframeNeeded = errors.New("keyframe needed")
|
var ErrKeyframeNeeded = errors.New("keyframe needed")
|
||||||
|
|
||||||
type downTrack interface {
|
type upConnection interface {
|
||||||
WriteRTP(packat *rtp.Packet) error
|
addLocal(downConnection) error
|
||||||
Accumulate(bytes uint32)
|
delLocal(downConnection) bool
|
||||||
GetMaxBitrate(now uint64) uint64
|
Id() string
|
||||||
|
Label() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type rtpDownTrack struct {
|
type upTrack interface {
|
||||||
track *webrtc.Track
|
addLocal(downTrack) error
|
||||||
remote *upTrack
|
delLocal(downTrack) bool
|
||||||
maxLossBitrate *bitrate
|
Label() string
|
||||||
maxREMBBitrate *bitrate
|
Codec() *webrtc.RTPCodec
|
||||||
rate *estimator.Estimator
|
// get a recent packet. Returns 0 if the packet is not in cache.
|
||||||
stats *receiverStats
|
getRTP(seqno uint16, result []byte) uint16
|
||||||
srTime uint64
|
// returns the last timestamp, if possible
|
||||||
srNTPTime uint64
|
getTimestamp() (uint32, bool)
|
||||||
rtt uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownTrack) WriteRTP(packet *rtp.Packet) error {
|
|
||||||
return down.track.WriteRTP(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownTrack) Accumulate(bytes uint32) {
|
|
||||||
down.rate.Accumulate(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownTrack) GetMaxBitrate(now uint64) uint64 {
|
|
||||||
br1 := down.maxLossBitrate.Get(now)
|
|
||||||
br2 := down.maxREMBBitrate.Get(now)
|
|
||||||
if br1 < br2 {
|
|
||||||
return br1
|
|
||||||
}
|
|
||||||
return br2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type downConnection interface {
|
type downConnection interface {
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type rtpDownConnection struct {
|
type downTrack interface {
|
||||||
id string
|
WriteRTP(packat *rtp.Packet) error
|
||||||
client *webClient
|
Accumulate(bytes uint32)
|
||||||
pc *webrtc.PeerConnection
|
GetMaxBitrate(now uint64) uint64
|
||||||
remote *upConnection
|
|
||||||
tracks []*rtpDownTrack
|
|
||||||
iceCandidates []*webrtc.ICECandidateInit
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownConnection) Close() error {
|
|
||||||
return down.client.action(delConnAction{down.id})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownConnection) addICECandidate(candidate *webrtc.ICECandidateInit) error {
|
|
||||||
if down.pc.RemoteDescription() != nil {
|
|
||||||
return down.pc.AddICECandidate(*candidate)
|
|
||||||
}
|
|
||||||
down.iceCandidates = append(down.iceCandidates, candidate)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (down *rtpDownConnection) flushICECandidates() error {
|
|
||||||
err := flushICECandidates(down.pc, down.iceCandidates)
|
|
||||||
down.iceCandidates = nil
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
@@ -24,15 +24,15 @@ type diskClient struct {
|
|||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *diskClient) getGroup() *group {
|
func (client *diskClient) Group() *group {
|
||||||
return client.group
|
return client.group
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *diskClient) getId() string {
|
func (client *diskClient) Id() string {
|
||||||
return client.id
|
return client.id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *diskClient) getUsername() string {
|
func (client *diskClient) Username() string {
|
||||||
return "RECORDING"
|
return "RECORDING"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ func (client *diskClient) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *diskClient) pushConn(conn *upConnection, tracks []*upTrack, label string) error {
|
func (client *diskClient) pushConn(conn upConnection, tracks []upTrack, label string) error {
|
||||||
client.mu.Lock()
|
client.mu.Lock()
|
||||||
defer client.mu.Unlock()
|
defer client.mu.Unlock()
|
||||||
|
|
||||||
@@ -75,15 +75,13 @@ func (client *diskClient) pushConn(conn *upConnection, tracks []*upTrack, label
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ client = &diskClient{}
|
|
||||||
|
|
||||||
type diskConn struct {
|
type diskConn struct {
|
||||||
directory string
|
directory string
|
||||||
label string
|
label string
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
file *os.File
|
file *os.File
|
||||||
remote *upConnection
|
remote upConnection
|
||||||
tracks []*diskTrack
|
tracks []*diskTrack
|
||||||
width, height uint32
|
width, height uint32
|
||||||
}
|
}
|
||||||
@@ -154,7 +152,7 @@ func openDiskFile(directory, label string) (*os.File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type diskTrack struct {
|
type diskTrack struct {
|
||||||
remote *upTrack
|
remote upTrack
|
||||||
conn *diskConn
|
conn *diskConn
|
||||||
|
|
||||||
writer webm.BlockWriteCloser
|
writer webm.BlockWriteCloser
|
||||||
@@ -162,7 +160,7 @@ type diskTrack struct {
|
|||||||
timestamp uint32
|
timestamp uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDiskConn(directory, label string, up *upConnection, remoteTracks []*upTrack) (*diskConn, error) {
|
func newDiskConn(directory, label string, up upConnection, remoteTracks []upTrack) (*diskConn, error) {
|
||||||
conn := diskConn{
|
conn := diskConn{
|
||||||
directory: directory,
|
directory: directory,
|
||||||
label: label,
|
label: label,
|
||||||
@@ -172,7 +170,7 @@ func newDiskConn(directory, label string, up *upConnection, remoteTracks []*upTr
|
|||||||
video := false
|
video := false
|
||||||
for _, remote := range remoteTracks {
|
for _, remote := range remoteTracks {
|
||||||
var builder *samplebuilder.SampleBuilder
|
var builder *samplebuilder.SampleBuilder
|
||||||
switch remote.track.Codec().Name {
|
switch remote.Codec().Name {
|
||||||
case webrtc.Opus:
|
case webrtc.Opus:
|
||||||
builder = samplebuilder.New(16, &codecs.OpusPacket{})
|
builder = samplebuilder.New(16, &codecs.OpusPacket{})
|
||||||
case webrtc.VP8:
|
case webrtc.VP8:
|
||||||
@@ -245,7 +243,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
|||||||
|
|
||||||
keyframe := true
|
keyframe := true
|
||||||
|
|
||||||
switch t.remote.track.Codec().Name {
|
switch t.remote.Codec().Name {
|
||||||
case webrtc.VP8:
|
case webrtc.VP8:
|
||||||
if len(sample.Data) < 1 {
|
if len(sample.Data) < 1 {
|
||||||
return nil
|
return nil
|
||||||
@@ -265,7 +263,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tm := t.timestamp / (t.remote.track.Codec().ClockRate / 1000)
|
tm := t.timestamp / (t.remote.Codec().ClockRate / 1000)
|
||||||
_, err := t.writer.Write(keyframe, int64(tm), sample.Data)
|
_, err := t.writer.Write(keyframe, int64(tm), sample.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -275,7 +273,7 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
|||||||
|
|
||||||
// called locked
|
// called locked
|
||||||
func (t *diskTrack) initWriter(data []byte) error {
|
func (t *diskTrack) initWriter(data []byte) error {
|
||||||
switch t.remote.track.Codec().Name {
|
switch t.remote.Codec().Name {
|
||||||
case webrtc.VP8:
|
case webrtc.VP8:
|
||||||
if len(data) < 10 {
|
if len(data) < 10 {
|
||||||
return nil
|
return nil
|
||||||
@@ -300,9 +298,9 @@ func (conn *diskConn) initWriter(width, height uint32) error {
|
|||||||
}
|
}
|
||||||
var entries []webm.TrackEntry
|
var entries []webm.TrackEntry
|
||||||
for i, t := range conn.tracks {
|
for i, t := range conn.tracks {
|
||||||
codec := t.remote.track.Codec()
|
codec := t.remote.Codec()
|
||||||
var entry webm.TrackEntry
|
var entry webm.TrackEntry
|
||||||
switch t.remote.track.Codec().Name {
|
switch t.remote.Codec().Name {
|
||||||
case webrtc.Opus:
|
case webrtc.Opus:
|
||||||
entry = webm.TrackEntry{
|
entry = webm.TrackEntry{
|
||||||
Name: "Audio",
|
Name: "Audio",
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type client interface {
|
type client interface {
|
||||||
getGroup() *group
|
Group() *group
|
||||||
getId() string
|
Id() string
|
||||||
getUsername() string
|
Username() string
|
||||||
pushConn(conn *upConnection, tracks []*upTrack, label string) error
|
pushConn(conn upConnection, tracks []upTrack, label string) error
|
||||||
pushClient(id, username string, add bool) error
|
pushClient(id, username string, add bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ type delConnAction struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type addConnAction struct {
|
type addConnAction struct {
|
||||||
conn *upConnection
|
conn upConnection
|
||||||
tracks []*upTrack
|
tracks []upTrack
|
||||||
}
|
}
|
||||||
|
|
||||||
type addLabelAction struct {
|
type addLabelAction struct {
|
||||||
@@ -230,20 +230,20 @@ func addClient(name string, c client, user, pass string) (*group, error) {
|
|||||||
return nil, userError("too many users")
|
return nil, userError("too many users")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if g.clients[c.getId()] != nil {
|
if g.clients[c.Id()] != nil {
|
||||||
return nil, protocolError("duplicate client id")
|
return nil, protocolError("duplicate client id")
|
||||||
}
|
}
|
||||||
|
|
||||||
g.clients[c.getId()] = c
|
g.clients[c.Id()] = c
|
||||||
|
|
||||||
go func(clients []client) {
|
go func(clients []client) {
|
||||||
c.pushClient(c.getId(), c.getUsername(), true)
|
c.pushClient(c.Id(), c.Username(), true)
|
||||||
for _, cc := range clients {
|
for _, cc := range clients {
|
||||||
err := c.pushClient(cc.getId(), cc.getUsername(), true)
|
err := c.pushClient(cc.Id(), cc.Username(), true)
|
||||||
if err == ErrClientDead {
|
if err == ErrClientDead {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cc.pushClient(c.getId(), c.getUsername(), true)
|
cc.pushClient(c.Id(), c.Username(), true)
|
||||||
}
|
}
|
||||||
}(g.getClientsUnlocked(c))
|
}(g.getClientsUnlocked(c))
|
||||||
|
|
||||||
@@ -251,19 +251,19 @@ func addClient(name string, c client, user, pass string) (*group, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func delClient(c client) {
|
func delClient(c client) {
|
||||||
g := c.getGroup()
|
g := c.Group()
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
defer g.mu.Unlock()
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
if g.clients[c.getId()] != c {
|
if g.clients[c.Id()] != c {
|
||||||
log.Printf("Deleting unknown client")
|
log.Printf("Deleting unknown client")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(g.clients, c.getId())
|
delete(g.clients, c.Id())
|
||||||
|
|
||||||
go func(clients []client) {
|
go func(clients []client) {
|
||||||
for _, cc := range clients {
|
for _, cc := range clients {
|
||||||
cc.pushClient(c.getId(), c.getUsername(), false)
|
cc.pushClient(c.Id(), c.Username(), false)
|
||||||
}
|
}
|
||||||
}(g.getClientsUnlocked(nil))
|
}(g.getClientsUnlocked(nil))
|
||||||
}
|
}
|
||||||
|
|||||||
+1127
File diff suppressed because it is too large.
Load diff
+127
-839
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user