diff --git a/structs.go b/structs.go index 9ffa96e..cee6b16 100644 --- a/structs.go +++ b/structs.go @@ -54,7 +54,7 @@ type Session struct { // Whether the UDP Connection is ready UDPReady bool - // Stores a mapping of channel id's to VoiceConnections + // Stores a mapping of guild id's to VoiceConnections VoiceConnections map[string]*VoiceConnection // Managed state object, updated internally with events when diff --git a/voice.go b/voice.go index 9d67ed5..49484a4 100644 --- a/voice.go +++ b/voice.go @@ -12,6 +12,7 @@ package discordgo import ( "encoding/binary" "encoding/json" + "errors" "fmt" "net" "runtime" @@ -97,7 +98,6 @@ type voiceHandshakeOp struct { // after VoiceConnectionChannelJoin is used and the data VOICE websocket events // are captured. func (v *VoiceConnection) Open() (err error) { - v.Lock() defer v.Unlock() @@ -131,8 +131,19 @@ func (v *VoiceConnection) Open() (err error) { return } -func (v *VoiceConnection) WaitUntilConnected() { - <-v.connected +func (v *VoiceConnection) WaitUntilConnected() error { + if v.Ready { + return nil + } + + value, ok := <-v.connected + + if (!value && !v.Ready) || !ok { + delete(v.session.VoiceConnections, v.GuildID) + return errors.New("Timed out connecting to voice") + } + + return nil } // wsListen listens on the voice websocket for messages and passes them @@ -603,9 +614,11 @@ func (v *VoiceConnection) Close() { v.Lock() defer v.Unlock() - if v.Ready { + // Send a OP4 with a nil channel to disconnect + if v.sessionID != "" { data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} v.session.wsConn.WriteJSON(data) + v.sessionID = "" } v.Ready = false @@ -632,15 +645,10 @@ func (v *VoiceConnection) Close() { } } -// Change channels +// Request to change channels func (v *VoiceConnection) ChangeChannel(channelID string) (err error) { data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, true, true}} - err = v.session.wsConn.WriteJSON(data) - if err == nil { - v.ChannelID = channelID - } - return err } diff --git a/wsapi.go b/wsapi.go index d177a96..7bfbc39 100644 --- a/wsapi.go +++ b/wsapi.go @@ -332,11 +332,17 @@ type voiceChannelJoinOp struct { // this func please monitor the Session.Voice.Ready bool to determine when // it is ready and able to send/receive audio, that should happen quickly. // -// gID : Guild ID of the channel to join. -// cID : Channel ID of the channel to join. -// mute : If true, you will be set to muted upon joining. -// deaf : If true, you will be set to deafened upon joining. -func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) { +// gID : Guild ID of the channel to join. +// cID : Channel ID of the channel to join. +// mute : If true, you will be set to muted upon joining. +// deaf : If true, you will be set to deafened upon joining. +// timeout : If greater than zero, the timeout in milliseconds after which connecting will fail +func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool, timeout int) (voice *VoiceConnection, err error) { + // If a voice connection for the guild exists, return that + if _, exists := s.VoiceConnections[gID]; exists { + return s.VoiceConnections[gID], err + } + // Send the request to Discord that we want to join the voice channel data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}} err = s.wsConn.WriteJSON(data) @@ -359,6 +365,16 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi voice.GuildID = gID voice.ChannelID = cID + // Queue the timeout in case we fail to connect + if timeout > 0 { + go func() { + time.Sleep(time.Millisecond * time.Duration(timeout)) + if !voice.Ready { + voice.connected <- false + } + }() + } + return voice, err }