113 lines
2.3 KiB
Go
113 lines
2.3 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"sync"
|
||
|
||
"gospeak-live-sfu-demo/gen"
|
||
)
|
||
|
||
// roomHub 维护房间 -> 各后端分发目标(StreamTarget)的内存状态,
|
||
// 并向订阅者广播拓扑变化(实现「分流」状态实时可见)。
|
||
type roomHub struct {
|
||
mu sync.RWMutex
|
||
rooms map[string]*roomEntry
|
||
}
|
||
|
||
type roomEntry struct {
|
||
targets map[string]*gen.StreamTarget // key: 后端名(cloudflare / srs)
|
||
subs map[chan []byte]struct{}
|
||
}
|
||
|
||
func newRoomHub() *roomHub {
|
||
return &roomHub{rooms: map[string]*roomEntry{}}
|
||
}
|
||
|
||
func (h *roomHub) get(name string) *roomEntry {
|
||
e, ok := h.rooms[name]
|
||
if !ok {
|
||
e = &roomEntry{targets: map[string]*gen.StreamTarget{}, subs: map[chan []byte]struct{}{}}
|
||
h.rooms[name] = e
|
||
}
|
||
return e
|
||
}
|
||
|
||
func (h *roomHub) setTarget(room, backend string, t *gen.StreamTarget) {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
e := h.get(room)
|
||
e.targets[backend] = t
|
||
h.broadcast(room, e)
|
||
}
|
||
|
||
func (h *roomHub) removeTarget(room, backend string) {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
e, ok := h.rooms[room]
|
||
if !ok {
|
||
return
|
||
}
|
||
delete(e.targets, backend)
|
||
if len(e.targets) == 0 && len(e.subs) == 0 {
|
||
delete(h.rooms, room)
|
||
return
|
||
}
|
||
h.broadcast(room, e)
|
||
}
|
||
|
||
func (h *roomHub) targets(room string) []*gen.StreamTarget {
|
||
h.mu.RLock()
|
||
defer h.mu.RUnlock()
|
||
e, ok := h.rooms[room]
|
||
if !ok {
|
||
return nil
|
||
}
|
||
out := make([]*gen.StreamTarget, 0, len(e.targets))
|
||
for _, t := range e.targets {
|
||
out = append(out, t)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (h *roomHub) list() []*gen.Room {
|
||
h.mu.RLock()
|
||
defer h.mu.RUnlock()
|
||
out := make([]*gen.Room, 0, len(h.rooms))
|
||
for name, e := range h.rooms {
|
||
room := &gen.Room{Name: name}
|
||
for _, t := range e.targets {
|
||
room.Targets = append(room.Targets, t)
|
||
}
|
||
out = append(out, room)
|
||
}
|
||
return out
|
||
}
|
||
|
||
func (h *roomHub) subscribe(room string) (chan []byte, func()) {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
e := h.get(room)
|
||
ch := make(chan []byte, 8)
|
||
e.subs[ch] = struct{}{}
|
||
return ch, func() {
|
||
h.mu.Lock()
|
||
defer h.mu.Unlock()
|
||
if e2, ok := h.rooms[room]; ok {
|
||
delete(e2.subs, ch)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (h *roomHub) broadcast(room string, e *roomEntry) {
|
||
payload, _ := json.Marshal(map[string]interface{}{
|
||
"room": room,
|
||
"targets": e.targets,
|
||
})
|
||
for ch := range e.subs {
|
||
select {
|
||
case ch <- payload:
|
||
default:
|
||
}
|
||
}
|
||
}
|