mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-08 19:45:18 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
738
transport/call/wbstream/session.go
Normal file
738
transport/call/wbstream/session.go
Normal file
@@ -0,0 +1,738 @@
|
||||
package wbstream
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/rtp/codecs"
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/transport/call/common"
|
||||
"github.com/sagernet/sing-box/transport/call/livekit"
|
||||
"github.com/sagernet/sing-box/transport/call/tunnel"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type peerEntry struct {
|
||||
sid string
|
||||
identity string
|
||||
firstSeen time.Time
|
||||
state int32
|
||||
promoted bool
|
||||
}
|
||||
|
||||
const (
|
||||
TunnelModeVideo = "video"
|
||||
TunnelModeDC = "dc"
|
||||
)
|
||||
|
||||
type SessionConfig struct {
|
||||
RoomToken string
|
||||
ServerURL string
|
||||
DisplayName string
|
||||
TunnelMode string
|
||||
Obfuscator *tunnel.TunnelObfuscator
|
||||
Logger logger.ContextLogger
|
||||
SettingEngine *webrtc.SettingEngine
|
||||
Dialer N.Dialer
|
||||
DNSRouter adapter.DNSRouter
|
||||
VP8FPS int
|
||||
VP8Batch int
|
||||
RoomID string
|
||||
AccessToken string
|
||||
ReadBuf int
|
||||
ScreenShare bool
|
||||
IsJoiner bool
|
||||
Reliable bool
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
cfg SessionConfig
|
||||
|
||||
lk *livekit.Client
|
||||
sampleTracks []*webrtc.TrackLocalStaticSample
|
||||
sampleTransceivers []*webrtc.RTPTransceiver
|
||||
|
||||
pubReliableDC *webrtc.DataChannel
|
||||
pubReliableDCReady bool
|
||||
subReliableDC *webrtc.DataChannel
|
||||
|
||||
vp8tun *tunnel.MultiTrackTunnel
|
||||
kcptun *tunnel.MultiTrackKCPTunnel
|
||||
dctun *tunnel.DCTunnel
|
||||
mu sync.Mutex
|
||||
tunFired bool
|
||||
done chan struct{}
|
||||
|
||||
peersBySID map[string]peerEntry
|
||||
kickedSIDs map[string]bool
|
||||
|
||||
configAcked chan struct{}
|
||||
configAckedOnce sync.Once
|
||||
|
||||
OnConnected func(tunnel.DataTunnel)
|
||||
OnPeerRestart func()
|
||||
OnRemoteCandidate func(target int, candidateOrSDP string)
|
||||
}
|
||||
|
||||
func NewSession(cfg SessionConfig) *Session {
|
||||
return &Session{
|
||||
cfg: cfg,
|
||||
done: make(chan struct{}),
|
||||
configAcked: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) MarkConfigAcked() {
|
||||
s.configAckedOnce.Do(func() {
|
||||
s.cfg.Logger.Debug("[lk] peer acked vp8 config")
|
||||
close(s.configAcked)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) Done() <-chan struct{} { return s.done }
|
||||
|
||||
func (s *Session) Start() error {
|
||||
s.lk = livekit.NewClient(livekit.Config{
|
||||
ServerURL: s.cfg.ServerURL,
|
||||
Token: s.cfg.RoomToken,
|
||||
Origin: Origin,
|
||||
UserAgent: common.UserAgent,
|
||||
Logger: s.cfg.Logger,
|
||||
SettingEngine: s.cfg.SettingEngine,
|
||||
NetDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return s.cfg.Dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
DNSRouter: s.cfg.DNSRouter,
|
||||
})
|
||||
s.lk.OnReady = s.onLKReady
|
||||
s.lk.OnTrack = s.onRemoteTrack
|
||||
s.lk.OnDataChannel = s.onRemoteDataChannel
|
||||
s.lk.OnPubConnected = s.startTunnel
|
||||
if s.cfg.AccessToken != "" && s.cfg.RoomID != "" {
|
||||
s.lk.OnParticipantUpdate = s.onParticipantUpdate
|
||||
}
|
||||
s.lk.OnRemoteCandidate = func(target int, ic webrtc.ICECandidateInit) {
|
||||
if s.OnRemoteCandidate != nil {
|
||||
s.OnRemoteCandidate(target, ic.Candidate)
|
||||
}
|
||||
}
|
||||
s.lk.OnRemoteSDP = func(target int, _, sdp string) {
|
||||
if s.OnRemoteCandidate != nil {
|
||||
s.OnRemoteCandidate(-1, sdp)
|
||||
}
|
||||
}
|
||||
if err := s.lk.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
go s.lk.PingLoop()
|
||||
go func() {
|
||||
if err := s.lk.ReadLoop(); err != nil {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] read loop ended: %v", err))
|
||||
}
|
||||
s.stopTunnels()
|
||||
close(s.done)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) AdaptTrackCount(peerCount int) {
|
||||
if peerCount < 1 {
|
||||
return
|
||||
}
|
||||
pubPC := s.lk.PubPC()
|
||||
if pubPC == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
current := len(s.sampleTracks)
|
||||
s.mu.Unlock()
|
||||
if peerCount == current {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] adapt-track-count: peer=%d current=%d, no change", peerCount, current))
|
||||
return
|
||||
}
|
||||
if peerCount > current {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] adapt-track-count: scaling publisher tracks %d -> %d", current, peerCount))
|
||||
for i := current; i < peerCount; i++ {
|
||||
if !s.addPublisherTrack(pubPC, i) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] adapt-track-count: shrinking publisher tracks %d -> %d", current, peerCount))
|
||||
for i := current; i > peerCount; i-- {
|
||||
if !s.removePublisherTrack() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
offer, err := pubPC.CreateOffer(nil)
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: create offer: %v", err))
|
||||
return
|
||||
}
|
||||
if err := pubPC.SetLocalDescription(offer); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: set local offer: %v", err))
|
||||
return
|
||||
}
|
||||
if err := s.lk.SendOffer(offer.SDP); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: send offer: %v", err))
|
||||
return
|
||||
}
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] adapt-track-count: renegotiation offer sent (%d bytes)", len(offer.SDP)))
|
||||
}
|
||||
|
||||
func (s *Session) Close() {
|
||||
if s.lk != nil {
|
||||
s.lk.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) stopTunnels() {
|
||||
s.mu.Lock()
|
||||
vp8 := s.vp8tun
|
||||
kcptun := s.kcptun
|
||||
s.mu.Unlock()
|
||||
if kcptun != nil {
|
||||
kcptun.Stop()
|
||||
}
|
||||
if vp8 != nil {
|
||||
vp8.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) onLKReady() {
|
||||
pubPC := s.lk.PubPC()
|
||||
if pubPC == nil {
|
||||
return
|
||||
}
|
||||
camID := "videochannel-" + uuid.New().String()
|
||||
trackCam, err := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000},
|
||||
camID, "tunnel-video-"+uuid.New().String(),
|
||||
)
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] create local cam track: %v", err))
|
||||
return
|
||||
}
|
||||
tracks := []*webrtc.TrackLocalStaticSample{trackCam}
|
||||
if s.cfg.ScreenShare {
|
||||
screenID := "screenchannel-" + uuid.New().String()
|
||||
trackScreen, err := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000},
|
||||
screenID, "tunnel-screen-"+uuid.New().String(),
|
||||
)
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] create local screen track: %v", err))
|
||||
return
|
||||
}
|
||||
tracks = append(tracks, trackScreen)
|
||||
}
|
||||
transceivers := make([]*webrtc.RTPTransceiver, 0, len(tracks))
|
||||
for _, t := range tracks {
|
||||
trx, err := pubPC.AddTransceiverFromTrack(t,
|
||||
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] add transceiver: %v", err))
|
||||
return
|
||||
}
|
||||
transceivers = append(transceivers, trx)
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.sampleTracks = tracks
|
||||
s.sampleTransceivers = transceivers
|
||||
s.mu.Unlock()
|
||||
ordered := true
|
||||
dc, err := pubPC.CreateDataChannel("_reliable", &webrtc.DataChannelInit{
|
||||
Ordered: &ordered,
|
||||
})
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] create reliable DC: %v", err))
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.pubReliableDC = dc
|
||||
s.mu.Unlock()
|
||||
dc.OnOpen(func() {
|
||||
s.cfg.Logger.Debug("[lk] reliable DC open")
|
||||
s.mu.Lock()
|
||||
s.pubReliableDCReady = true
|
||||
s.mu.Unlock()
|
||||
s.maybeStartDCTunnel()
|
||||
})
|
||||
for i, t := range tracks {
|
||||
source := livekit.TrackSourceCamera
|
||||
if i > 0 {
|
||||
source = livekit.TrackSourceScreenShare
|
||||
}
|
||||
if err := s.lk.SendAddTrack(t.ID(), "videochannel",
|
||||
livekit.TrackTypeVideo, source, 1280, 720); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] send add-track: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
offer, err := pubPC.CreateOffer(nil)
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] create offer: %v", err))
|
||||
return
|
||||
}
|
||||
if err := pubPC.SetLocalDescription(offer); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] set local offer: %v", err))
|
||||
return
|
||||
}
|
||||
if err := s.lk.SendOffer(offer.SDP); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] send offer: %v", err))
|
||||
return
|
||||
}
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] sent publisher offer (%d bytes)", len(offer.SDP)))
|
||||
}
|
||||
|
||||
func (s *Session) startTunnel() {
|
||||
s.mu.Lock()
|
||||
if s.vp8tun != nil || len(s.sampleTracks) == 0 {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
subs := make([]*tunnel.VP8DataTunnel, 0, len(s.sampleTracks))
|
||||
for _, t := range s.sampleTracks {
|
||||
subs = append(subs, tunnel.NewVP8DataTunnelWithQueue(t, s.cfg.Obfuscator, s.cfg.Logger, tunnel.KCPCarrierQueueDepth))
|
||||
}
|
||||
s.vp8tun = tunnel.NewMultiTrackTunnel(subs)
|
||||
s.vp8tun.SetOnPeerRestart(func() {
|
||||
s.cfg.Logger.Debug("[wb] peer epoch changed, signalling peer-restart")
|
||||
s.rearmAutoDetect()
|
||||
if s.OnPeerRestart != nil {
|
||||
s.OnPeerRestart()
|
||||
}
|
||||
})
|
||||
s.vp8tun.Start(s.cfg.VP8FPS, s.cfg.VP8Batch)
|
||||
tun := s.vp8tun
|
||||
s.mu.Unlock()
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] vp8 tunnel writer started tracks=%d", len(subs)))
|
||||
var active tunnel.DataTunnel = tun
|
||||
if s.cfg.TunnelMode == TunnelModeVideo && s.cfg.Reliable {
|
||||
active = s.maybeWrapReliable(tun)
|
||||
}
|
||||
if s.cfg.IsJoiner && s.cfg.TunnelMode != TunnelModeDC {
|
||||
go s.configPingPong(active, len(subs))
|
||||
}
|
||||
if s.cfg.TunnelMode == TunnelModeVideo {
|
||||
s.fireOnConnected(active)
|
||||
return
|
||||
}
|
||||
if s.cfg.TunnelMode == "" {
|
||||
tun.SetOnData(func(payload []byte) { s.activate(tun, payload) })
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) configPingPong(tun tunnel.DataTunnel, trackCount int) {
|
||||
frame := tunnel.EncodeVP8Config(s.cfg.VP8FPS, s.cfg.VP8Batch, trackCount)
|
||||
tun.SendData(frame)
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.configAcked:
|
||||
return
|
||||
case <-s.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
s.cfg.Logger.Debug("[lk] resending vp8 config (no ack yet)")
|
||||
tun.SendData(tunnel.EncodeVP8Config(s.cfg.VP8FPS, s.cfg.VP8Batch, trackCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) maybeStartDCTunnel() {
|
||||
s.mu.Lock()
|
||||
if s.dctun != nil {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
pubDC := s.pubReliableDC
|
||||
subDC := s.subReliableDC
|
||||
pubReady := s.pubReliableDCReady
|
||||
s.mu.Unlock()
|
||||
if pubDC == nil || subDC == nil || !pubReady {
|
||||
return
|
||||
}
|
||||
if subDC.ReadyState() != webrtc.DataChannelStateOpen {
|
||||
return
|
||||
}
|
||||
subRaw, err := subDC.Detach()
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] detach sub DC: %v", err))
|
||||
return
|
||||
}
|
||||
pubRaw, err := pubDC.Detach()
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] detach pub DC: %v", err))
|
||||
return
|
||||
}
|
||||
readWrapped := newDataPacketWrapper(subRaw, livekit.DataPacketKindReliable)
|
||||
writeWrapped := newDataPacketWrapper(pubRaw, livekit.DataPacketKindReliable)
|
||||
readBuf := s.cfg.ReadBuf
|
||||
if readBuf == 0 {
|
||||
readBuf = common.DCBufSize
|
||||
}
|
||||
dctun := tunnel.NewChunkedDCTunnelFromRaw(readWrapped, writeWrapped, s.cfg.Obfuscator, readBuf, s.cfg.Logger)
|
||||
if dctun == nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.dctun = dctun
|
||||
s.mu.Unlock()
|
||||
s.cfg.Logger.Debug("[lk] dc tunnel ready (pub+sub _reliable)")
|
||||
if s.cfg.TunnelMode == TunnelModeDC {
|
||||
s.fireOnConnected(dctun)
|
||||
return
|
||||
}
|
||||
if s.cfg.TunnelMode == "" {
|
||||
dctun.SetOnData(func(payload []byte) { s.activate(dctun, payload) })
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) fireOnConnected(tun tunnel.DataTunnel) {
|
||||
s.mu.Lock()
|
||||
if s.tunFired {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.tunFired = true
|
||||
s.mu.Unlock()
|
||||
if s.OnConnected != nil {
|
||||
s.OnConnected(tun)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) activate(tun tunnel.DataTunnel, payload []byte) {
|
||||
s.mu.Lock()
|
||||
if s.tunFired {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.tunFired = true
|
||||
s.mu.Unlock()
|
||||
var delivered tunnel.DataTunnel = tun
|
||||
useKCP := false
|
||||
if _, ok := tun.(*tunnel.MultiTrackTunnel); ok && !tunnel.LooksLikeRelayFrame(payload) {
|
||||
delivered = s.maybeWrapReliable(tun)
|
||||
useKCP = true
|
||||
}
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] auto-detected active tunnel: %T", delivered))
|
||||
if s.OnConnected != nil {
|
||||
s.OnConnected(delivered)
|
||||
}
|
||||
switch v := tun.(type) {
|
||||
case *tunnel.DCTunnel:
|
||||
if fwd := v.OnData(); fwd != nil {
|
||||
fwd(payload)
|
||||
}
|
||||
case *tunnel.MultiTrackTunnel:
|
||||
if useKCP {
|
||||
if kcptun, ok := delivered.(*tunnel.MultiTrackKCPTunnel); ok {
|
||||
kcptun.InjectSegment(payload)
|
||||
}
|
||||
} else {
|
||||
v.DeliverData(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) maybeWrapReliable(tun tunnel.DataTunnel) tunnel.DataTunnel {
|
||||
vp8, ok := tun.(*tunnel.MultiTrackTunnel)
|
||||
if !ok {
|
||||
return tun
|
||||
}
|
||||
wrapped := tunnel.NewMultiTrackKCPTunnel(vp8, s.cfg.Logger)
|
||||
s.mu.Lock()
|
||||
if s.kcptun != nil {
|
||||
s.kcptun.StopLayer()
|
||||
}
|
||||
s.kcptun = wrapped
|
||||
s.mu.Unlock()
|
||||
s.cfg.Logger.Debug("[lk] per-track kcp reliability active over video tunnel")
|
||||
return wrapped
|
||||
}
|
||||
|
||||
func (s *Session) currentVP8Tun() *tunnel.MultiTrackTunnel {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.vp8tun
|
||||
}
|
||||
|
||||
func (s *Session) removePublisherTrack() bool {
|
||||
s.mu.Lock()
|
||||
if len(s.sampleTransceivers) <= 1 || len(s.sampleTracks) <= 1 {
|
||||
s.mu.Unlock()
|
||||
s.cfg.Logger.Debug("[lk] adapt-track-count: refusing to remove cam slot")
|
||||
return false
|
||||
}
|
||||
last := len(s.sampleTransceivers) - 1
|
||||
trx := s.sampleTransceivers[last]
|
||||
s.sampleTransceivers = s.sampleTransceivers[:last]
|
||||
s.sampleTracks = s.sampleTracks[:last]
|
||||
vp8 := s.vp8tun
|
||||
kcptun := s.kcptun
|
||||
s.mu.Unlock()
|
||||
if kcptun != nil {
|
||||
kcptun.RemoveLastSession()
|
||||
}
|
||||
if vp8 != nil {
|
||||
vp8.RemoveLastSubTunnel()
|
||||
}
|
||||
if err := trx.Stop(); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: stop transceiver: %v", err))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Session) addPublisherTrack(pubPC *webrtc.PeerConnection, slot int) bool {
|
||||
labelPrefix := "screenchannel-"
|
||||
streamPrefix := "tunnel-screen-"
|
||||
source := livekit.TrackSourceScreenShare
|
||||
if slot == 0 {
|
||||
labelPrefix = "videochannel-"
|
||||
streamPrefix = "tunnel-video-"
|
||||
source = livekit.TrackSourceCamera
|
||||
}
|
||||
track, err := webrtc.NewTrackLocalStaticSample(
|
||||
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000},
|
||||
labelPrefix+uuid.New().String(), streamPrefix+uuid.New().String(),
|
||||
)
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: new track slot=%d: %v", slot, err))
|
||||
return false
|
||||
}
|
||||
trx, err := pubPC.AddTransceiverFromTrack(track,
|
||||
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly})
|
||||
if err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: add transceiver slot=%d: %v", slot, err))
|
||||
return false
|
||||
}
|
||||
if err := s.lk.SendAddTrack(track.ID(), "videochannel",
|
||||
livekit.TrackTypeVideo, source, 1280, 720); err != nil {
|
||||
s.cfg.Logger.Error(fmt.Sprintf("[lk] adapt-track-count: send add-track slot=%d: %v", slot, err))
|
||||
return false
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.sampleTracks = append(s.sampleTracks, track)
|
||||
s.sampleTransceivers = append(s.sampleTransceivers, trx)
|
||||
vp8 := s.vp8tun
|
||||
kcptun := s.kcptun
|
||||
s.mu.Unlock()
|
||||
if vp8 != nil {
|
||||
newSub := tunnel.NewVP8DataTunnelWithQueue(track, s.cfg.Obfuscator, s.cfg.Logger, tunnel.KCPCarrierQueueDepth)
|
||||
vp8.AddSubTunnel(newSub)
|
||||
if kcptun != nil {
|
||||
kcptun.AddSession(newSub)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Session) rearmAutoDetect() {
|
||||
if s.cfg.TunnelMode != "" {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.tunFired = false
|
||||
orphanKCP := s.kcptun
|
||||
s.kcptun = nil
|
||||
vp8 := s.vp8tun
|
||||
dc := s.dctun
|
||||
s.mu.Unlock()
|
||||
if orphanKCP != nil {
|
||||
orphanKCP.StopLayer()
|
||||
}
|
||||
if vp8 != nil {
|
||||
vp8.SetOnData(func(payload []byte) { s.activate(vp8, payload) })
|
||||
}
|
||||
if dc != nil {
|
||||
dc.SetOnData(func(payload []byte) { s.activate(dc, payload) })
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) onRemoteTrack(track *webrtc.TrackRemote, _ *webrtc.RTPReceiver) {
|
||||
if track.Codec().MimeType != webrtc.MimeTypeVP8 {
|
||||
go func() {
|
||||
buf := make([]byte, common.UDPBufSize)
|
||||
for {
|
||||
if _, _, err := track.Read(buf); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
go s.readVP8Track(track)
|
||||
}
|
||||
|
||||
func (s *Session) readVP8Track(track *webrtc.TrackRemote) {
|
||||
var vp8Pkt codecs.VP8Packet
|
||||
var pkt rtp.Packet
|
||||
var frameBuf []byte
|
||||
var lastSeq uint16
|
||||
var haveLastSeq bool
|
||||
frameValid := false
|
||||
var recvCount int
|
||||
buf := make([]byte, common.RTPBufSize)
|
||||
for {
|
||||
n, _, err := track.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if pkt.Unmarshal(buf[:n]) != nil {
|
||||
continue
|
||||
}
|
||||
if haveLastSeq && pkt.SequenceNumber != lastSeq+1 {
|
||||
frameValid = false
|
||||
frameBuf = frameBuf[:0]
|
||||
}
|
||||
lastSeq = pkt.SequenceNumber
|
||||
haveLastSeq = true
|
||||
vp8Payload, err := vp8Pkt.Unmarshal(pkt.Payload)
|
||||
if err != nil {
|
||||
frameValid = false
|
||||
frameBuf = frameBuf[:0]
|
||||
continue
|
||||
}
|
||||
if vp8Pkt.S == 1 {
|
||||
frameBuf = frameBuf[:0]
|
||||
frameValid = true
|
||||
}
|
||||
if !frameValid {
|
||||
continue
|
||||
}
|
||||
frameBuf = append(frameBuf, vp8Payload...)
|
||||
if !pkt.Marker {
|
||||
continue
|
||||
}
|
||||
recvCount++
|
||||
if recvCount <= 3 || recvCount%200 == 0 {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk-video] recv vp8 frame #%d %d bytes", recvCount, len(frameBuf)))
|
||||
}
|
||||
tun := s.currentVP8Tun()
|
||||
if tun != nil {
|
||||
tun.HandleFrame(frameBuf)
|
||||
}
|
||||
frameBuf = frameBuf[:0]
|
||||
frameValid = false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) onRemoteDataChannel(dc *webrtc.DataChannel) {
|
||||
s.cfg.Logger.Debug(fmt.Sprintf("[lk] remote DC label=%s id=%v", dc.Label(), dc.ID()))
|
||||
if dc.Label() != "_reliable" {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.subReliableDC = dc
|
||||
s.mu.Unlock()
|
||||
dc.OnOpen(func() {
|
||||
s.cfg.Logger.Debug("[lk] remote _reliable DC open")
|
||||
s.maybeStartDCTunnel()
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Session) onParticipantUpdate(updates []livekit.ParticipantInfo) {
|
||||
selfSID := s.lk.Join().ParticipantSID
|
||||
s.mu.Lock()
|
||||
if s.peersBySID == nil {
|
||||
s.peersBySID = make(map[string]peerEntry)
|
||||
}
|
||||
newcomerSIDs := make(map[string]bool)
|
||||
canPromote := s.cfg.AccessToken != "" && s.cfg.RoomID != ""
|
||||
for _, p := range updates {
|
||||
if p.SID == "" || p.SID == selfSID {
|
||||
continue
|
||||
}
|
||||
if p.State == livekit.ParticipantStateDisconnected {
|
||||
delete(s.peersBySID, p.SID)
|
||||
delete(s.kickedSIDs, p.SID)
|
||||
continue
|
||||
}
|
||||
if s.kickedSIDs[p.SID] {
|
||||
continue
|
||||
}
|
||||
entry, ok := s.peersBySID[p.SID]
|
||||
if !ok {
|
||||
entry = peerEntry{sid: p.SID, identity: p.Identity, firstSeen: time.Now()}
|
||||
newcomerSIDs[p.SID] = true
|
||||
}
|
||||
if p.Identity != "" {
|
||||
entry.identity = p.Identity
|
||||
}
|
||||
entry.state = p.State
|
||||
s.peersBySID[p.SID] = entry
|
||||
}
|
||||
var stale []peerEntry
|
||||
staleSIDs := make(map[string]bool)
|
||||
if len(newcomerSIDs) > 0 {
|
||||
for _, e := range s.peersBySID {
|
||||
if e.state == livekit.ParticipantStateActive && !newcomerSIDs[e.sid] {
|
||||
stale = append(stale, e)
|
||||
staleSIDs[e.sid] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
var toPromote []peerEntry
|
||||
if canPromote {
|
||||
for sid, entry := range s.peersBySID {
|
||||
if staleSIDs[sid] {
|
||||
continue
|
||||
}
|
||||
if !entry.promoted && entry.state == livekit.ParticipantStateActive && entry.identity != "" {
|
||||
entry.promoted = true
|
||||
s.peersBySID[sid] = entry
|
||||
toPromote = append(toPromote, entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
for _, e := range toPromote {
|
||||
go s.promotePeer(e.sid, e.identity)
|
||||
}
|
||||
if len(stale) == 0 {
|
||||
return
|
||||
}
|
||||
for _, e := range stale {
|
||||
if e.identity == "" {
|
||||
continue
|
||||
}
|
||||
if err := KickParticipant(common.HttpClient(s.cfg.Dialer), s.cfg.AccessToken, s.cfg.RoomID, e.identity); err != nil {
|
||||
s.cfg.Logger.Warn(fmt.Sprintf("[wb] kick failed identity=%s: %v", e.identity, err))
|
||||
continue
|
||||
}
|
||||
s.cfg.Logger.Info(fmt.Sprintf("[wb] kicked stale peer identity=%s sid=%s", e.identity, e.sid))
|
||||
s.mu.Lock()
|
||||
delete(s.peersBySID, e.sid)
|
||||
if s.kickedSIDs == nil {
|
||||
s.kickedSIDs = make(map[string]bool)
|
||||
}
|
||||
s.kickedSIDs[e.sid] = true
|
||||
s.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) promotePeer(sid, identity string) {
|
||||
if err := SetParticipantPermissions(common.HttpClient(s.cfg.Dialer), s.cfg.AccessToken, s.cfg.RoomID, identity, ModeratorPermissions); err != nil {
|
||||
s.cfg.Logger.Warn(fmt.Sprintf("[wb] promote failed identity=%s: %v", identity, err))
|
||||
s.mu.Lock()
|
||||
if entry, ok := s.peersBySID[sid]; ok {
|
||||
entry.promoted = false
|
||||
s.peersBySID[sid] = entry
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.cfg.Logger.Info(fmt.Sprintf("[wb] promoted to moderator identity=%s sid=%s", identity, sid))
|
||||
}
|
||||
Reference in New Issue
Block a user