8000 fix(voice): keep audio alive across channel moves by jovolopez · Pull Request #603 · disgoorg/disgo · GitHub
[go: up one dir, main page]

Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 84 additions & 13 deletions voice/conn.go
B3C8
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,31 @@ func NewConn(guildID snowflake.ID, userID snowflake.ID, voiceStateUpdateFunc Sta
ssrcs: map[uint32]snowflake.ID{},
}

daveSession := cfg.DaveSessionCreate(cfg.DaveSessionLogger, godave.UserID(userID.String()), conn)
conn.dave = daveSession
conn.gateway = cfg.GatewayCreateFunc(daveSession, conn.handleMessage, conn.handleGatewayClose, append([]GatewayConfigOpt{WithGatewayLogger(cfg.Logger)}, cfg.GatewayConfigOpts...)...)
conn.udp = cfg.UDPConnCreateFunc(daveSession, conn.UserIDBySSRC, append([]UDPConnConfigOpt{WithUDPConnLogger(cfg.Logger)}, cfg.UDPConnConfigOpts...)...)
conn.initTransports()

return conn
}

func (c *connImpl) initTransports() {
daveSession := c.config.DaveSessionCreate(c.config.DaveSessionLogger, godave.UserID(c.state.UserID.String()), c)
c.dave = daveSession
c.gateway = c.config.GatewayCreateFunc(daveSession, c.handleMessage, c.handleGatewayClose, append([]GatewayConfigOpt{WithGatewayLogger(c.config.Logger)}, c.config.GatewayConfigOpts...)...)
c.udp = c.config.UDPConnCreateFunc(daveSession, c.UserIDBySSRC, append([]UDPConnConfigOpt{WithUDPConnLogger(c.config.Logger)}, c.config.UDPConnConfigOpts...)...)
}

// resetTransportsLocked replaces the voice transport generation. stateMu must be held.
func (c *connImpl) resetTransportsLocked() {
oldGateway, oldUDP, oldDAVE := c.gateway, c.udp, c.dave
c.initTransports()
oldGateway.Close()
_ = oldUDP.Close()
_ = oldDAVE.Close()

c.ssrcsMu.Lock()
clear(c.ssrcs)
c.ssrcsMu.Unlock()
}

type connImpl struct {
config connConfig
voiceStateUpdateFunc StateUpdateFunc
Expand All @@ -108,6 +125,9 @@ type connImpl struct {
voiceStateReceived bool
voiceServerReceived bool

speakingFlags SpeakingFlags
speakingSet bool

ssrcs map[uint32]snowflake.ID
ssrcsMu sync.Mutex
}
Expand Down Expand Up @@ -149,21 +169,32 @@ func (c *connImpl) UserIDBySSRC(ssrc uint32) snowflake.ID {
}

func (c *connImpl) Gateway() Gateway {
c.stateMu.Lock()
defer c.stateMu.Unlock()
return c.gateway
}

func (c *connImpl) SetSpeaking(ctx context.Context, flags SpeakingFlags) error {
return c.gateway.Send(ctx, OpcodeSpeaking, GatewayMessageDataSpeaking{
SSRC: c.Gateway().SSRC(),
c.stateMu.Lock()
c.speakingFlags = flags
c.speakingSet = true
gateway := c.gateway
c.stateMu.Unlock()
return gateway.Send(ctx, OpcodeSpeaking, GatewayMessageDataSpeaking{
SSRC: gateway.SSRC(),
Speaking: flags,
})
}

func (c *connImpl) UDP() UDPConn {
c.stateMu.Lock()
defer c.stateMu.Unlock()
return c.udp
}

func (c *connImpl) DAVE() godave.Session {
c.stateMu.Lock()
defer c.stateMu.Unlock()
return c.dave
}

Expand Down Expand Up @@ -202,10 +233,16 @@ func (c *connImpl) HandleVoiceStateUpdate(update botgateway.EventVoiceStateUpdat
c.audioReceiver.Close()
c.audioReceiver = nil
}
_ = c.udp.Close()
c.gateway.Close()
c.speakingFlags = 0
c.speakingSet = false
c.resetTransportsLocked()
} else {
moved := c.state.ChannelID != 0 && c.state.ChannelID != *update.ChannelID
c.state.ChannelID = *update.ChannelID
if moved {
// A move needs a new voice-server handshake
c.resetTransportsLocked()
}
c.voiceStateReceived = true
}
c.state.SessionID = update.SessionID
Expand Down Expand Up @@ -248,11 +285,19 @@ func (c *connImpl) tryOpenGateway() {
}

func (c *connImpl) handleMessage(gateway Gateway, op Opcode, sequenceNumber int, data GatewayMessageData) {
c.stateMu.Lock()
if gateway != c.gateway {
c.stateMu.Unlock()
return
}
udp := c.udp
c.stateMu.Unlock()

switch d := data.(type) {
case GatewayMessageDataReady:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ourAddress, ourPort, err := c.udp.Open(ctx, d.IP, d.Port, d.SSRC)
ourAddress, ourPort, err := udp.Open(ctx, d.IP, d.Port, d.SSRC)
if err != nil {
c.config.Logger.Error("voice: failed to open voiceudp conn", slog.Any("err", err))
break
Expand All @@ -264,7 +309,7 @@ func (c *connImpl) handleMessage(gateway Gateway, op Opcode, sequenceNumber int,
break
}

if err = c.Gateway().Send(ctx, OpcodeSelectProtocol, GatewayMessageDataSelectProtocol{
if err = gateway.Send(ctx, OpcodeSelectProtocol, GatewayMessageDataSelectProtocol{
Protocol: ProtocolUDP,
Data: GatewayMessageDataSelectProtocolData{
Address: ourAddress,
Expand All @@ -276,9 +321,23 @@ func (c *connImpl) handleMessage(gateway Gateway, op Opcode, sequenceNumber int,
}

case GatewayMessageDataSessionDescription:
if err := c.udp.SetSecretKey(d.Mode, d.SecretKey); err != nil {
if err := udp.SetSecretKey(d.Mode, d.SecretKey); err != nil {
c.config.Logger.Error("voice: failed to set secret key", slog.Any("err", err))
}
c.stateMu.Lock()
speakingFlags, speakingSet := c.speakingFlags, c.speakingSet
current := gateway == c.gateway
c.stateMu.Unlock()
if current && speakingSet {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
if err := gateway.Send(ctx, OpcodeSpeaking, GatewayMessageDataSpeaking{
SSRC: gateway.SSRC(),
Speaking: speakingFlags,
}); err != nil {
c.config.Logger.Error("voice: failed to restore speaking state", slog.Any("err", err))
}
cancel()
}
if openedFunc := c.openedFunc; openedFunc != nil {
openedFunc()
}
Expand Down Expand Up @@ -306,11 +365,22 @@ func (c *connImpl) handleMessage(gateway Gateway, op Opcode, sequenceNumber int,
}
}

func (c *connImpl) handleGatewayClose(_ Gateway, err error) {
func (c *connImpl) handleGatewayClose(gateway Gateway, err error) {
c.stateMu.Lock()
if gateway != c.gateway {
c.stateMu.Unlock()
return
}
c.stateMu.Unlock()

newConnection := true
var closeError *websocket.CloseError
if errors.As(err, &closeError) {
closeCode := GatewayCloseEventCodeByCode(closeError.Code)
// Wait for `ServerUpdate` and `StateUpdate` to close the gateway (a channel move could trigger this handler)
if closeCode == GatewayCloseEventCodeDisconnected || closeCode == GatewayCloseEventCodeCallTerminated {
return
}
newConnection = closeCode.NewConnection
}

Expand All @@ -324,7 +394,8 @@ func (c *connImpl) handleGatewayClose(_ Gateway, err error) {
c.state.Endpoint = ""
c.stateMu.Unlock()

_ = c.udp.Close()
udp := c.UDP()
_ = udp.Close()
if channelID == 0 {
return
}
Expand Down
Loading
0