class Discordrb::Voice::VoiceWS
Represents a websocket client connection to the voice server. The websocket connection (sometimes called vWS) is used to manage general data about the connection, such as sending the speaking packet, which determines the green circle around users on Discord, and obtaining UDP connection info.
Constants
- VOICE_GATEWAY_VERSION
The version of the voice gateway that’s supposed to be used.
Attributes
@return [VoiceUDP] the UDP voice connection over which the actual audio data is sent.
Public Class Methods
Makes a new voice websocket client, but doesn’t connect it (see {#connect} for that) @param channel [Channel] The voice channel to connect to @param bot [Bot] The regular bot to which this vWS is bound @param token [String] The authentication token which is also used for REST requests @param session [String] The voice session ID Discord sends over the regular websocket @param endpoint [String] The endpoint URL to connect to
# File lib/discordrb/voice/network.rb, line 181 def initialize(channel, bot, token, session, endpoint) raise 'libsodium is unavailable - unable to create voice bot! Please read https://github.com/shardlab/discordrb/wiki/Installing-libsodium' unless LIBSODIUM_AVAILABLE @channel = channel @bot = bot @token = token @session = session @endpoint = endpoint.split(':').first @udp = VoiceUDP.new end
Public Instance Methods
Communication goes like this: me discord
| |
websocket connect -> |
| | | <- websocket opcode 2 | |
UDP discovery -> |
| | | <- UDP reply packet | |
websocket opcode 1 -> |
| |
…
# File lib/discordrb/voice/network.rb, line 307 def connect # Connect websocket @thread = Thread.new do Thread.current[:discordrb_name] = 'vws' init_ws end @bot.debug('Started websocket initialization, now waiting for UDP discovery reply') # Now wait for opcode 2 and the resulting UDP reply packet ip, port = @udp.receive_discovery_reply @bot.debug("UDP discovery reply received! #{ip} #{port}") # Send UDP init packet with received UDP data send_udp_connection(ip, port, @udp_mode) @bot.debug('Waiting for op 4 now') # Wait for op 4, then finish sleep 0.05 until @ready end
Disconnects the websocket and kills the thread
# File lib/discordrb/voice/network.rb, line 330 def destroy @heartbeat_running = false end
Send a heartbeat (op 3), has to be done every @heartbeat_interval seconds or the connection will terminate
# File lib/discordrb/voice/network.rb, line 230 def send_heartbeat millis = Time.now.strftime('%s%L').to_i @bot.debug("Sending voice heartbeat at #{millis}") @client.send({ op: 3, d: millis }.to_json) end
Send a connection init packet (op 0) @param server_id [Integer] The ID of the server to connect to @param bot_user_id [Integer] The ID of the bot that is connecting @param session_id [String] The voice session ID @param token [String] The Discord authentication token
# File lib/discordrb/voice/network.rb, line 199 def send_init(server_id, bot_user_id, session_id, token) @client.send({ op: 0, d: { server_id: server_id, user_id: bot_user_id, session_id: session_id, token: token } }.to_json) end
Send a speaking packet (op 5). This determines the green circle around the avatar in the voice channel @param value [true, false, Integer] Whether or not the bot should be speaking, can also be a bitmask denoting audio type.
# File lib/discordrb/voice/network.rb, line 242 def send_speaking(value) @bot.debug("Speaking: #{value}") @client.send({ op: 5, d: { speaking: value, delay: 0 } }.to_json) end
Sends the UDP connection packet (op 1) @param ip [String] The IP to bind UDP to @param port [Integer] The port to bind UDP to @param mode [Object] Which mode to use for the voice connection
# File lib/discordrb/voice/network.rb, line 215 def send_udp_connection(ip, port, mode) @client.send({ op: 1, d: { protocol: 'udp', data: { address: ip, port: port, mode: mode } } }.to_json) end
@!visibility private
# File lib/discordrb/voice/network.rb, line 264 def websocket_message(msg) @bot.debug("Received VWS message! #{msg}") packet = JSON.parse(msg) case packet['op'] when 2 # Opcode 2 contains data to initialize the UDP connection @ws_data = packet['d'] @ssrc = @ws_data['ssrc'] @port = @ws_data['port'] @udp_mode = (ENCRYPTION_MODES & @ws_data['modes']).first @udp.connect(@ws_data['ip'], @port, @ssrc) @udp.send_discovery when 4 # Opcode 4 sends the secret key used for encryption @ws_data = packet['d'] @ready = true @udp.secret_key = @ws_data['secret_key'].pack('C*') @udp.mode = @ws_data['mode'] when 8 # Opcode 8 contains the heartbeat interval. @heartbeat_interval = packet['d']['heartbeat_interval'] end end
Event handlers; public for websocket-simple to work correctly @!visibility private
# File lib/discordrb/voice/network.rb, line 255 def websocket_open # Give the current thread a name ('Voice Web Socket Internal') Thread.current[:discordrb_name] = 'vws-i' # Send the init packet send_init(@channel.server.id, @bot.profile.id, @session, @token) end
Private Instance Methods
# File lib/discordrb/voice/network.rb, line 336 def heartbeat_loop @heartbeat_running = true while @heartbeat_running if @heartbeat_interval sleep @heartbeat_interval / 1000.0 send_heartbeat else # If no interval has been set yet, sleep a second and check again sleep 1 end end end
# File lib/discordrb/voice/network.rb, line 349 def init_ws host = "wss://#{@endpoint}:443/?v=#{VOICE_GATEWAY_VERSION}" @bot.debug("Connecting VWS to host: #{host}") # Connect the WS @client = Discordrb::WebSocket.new( host, method(:websocket_open), method(:websocket_message), proc { |e| Discordrb::LOGGER.error "VWS error: #{e}" }, proc { |e| Discordrb::LOGGER.warn "VWS close: #{e}" } ) @bot.debug('VWS connected') # Block any further execution heartbeat_loop end