Class: Discordrb::Voice::VoiceBot
- Inherits:
-
Object
- Object
- Discordrb::Voice::VoiceBot
- Defined in:
- lib/discordrb/voice/voice_bot.rb
Overview
This class represents a connection to a Discord voice server and channel. It can be used to play audio files and streams and to control playback on currently playing tracks. The method Bot#voice_connect can be used to connect to a voice channel.
discordrb does latency adjustments every now and then to improve playback quality. I made sure to put useful defaults for the adjustment parameters, but if the sound is patchy or too fast (or the speed varies a lot) you should check the parameters and adjust them to your connection: #adjust_interval, #adjust_offset, and #adjust_average.
Instance Attribute Summary collapse
-
#adjust_average ⇒ true, false
This value determines whether or not the adjustment length should be averaged with the previous value.
-
#adjust_debug ⇒ true, false
Disable the debug message for length adjustment specifically, as it can get quite spammy with very low intervals.
-
#adjust_interval ⇒ Integer
discordrb will occasionally measure the time it takes to send a packet, and adjust future delay times based on that data.
-
#adjust_offset ⇒ Integer
This particular value is also important because ffmpeg may take longer to process the first few packets.
-
#channel ⇒ Channel
readonly
The current voice channel.
-
#encoder ⇒ Encoder
readonly
The encoder used to encode audio files into the format required by Discord.
-
#length_override ⇒ Float
If this value is set, no length adjustments will ever be done and this value will always be used as the length (i.e. packets will be sent every N seconds).
-
#stream_time ⇒ Integer?
readonly
The amount of time the stream has been playing, or
nil
if nothing has been played yet. -
#volume ⇒ Float
The factor the audio's volume should be multiplied with.
Instance Method Summary collapse
-
#continue ⇒ Object
Continue playback.
-
#destroy ⇒ Object
Permanently disconnects from the voice channel; to reconnect you will have to call Bot#voice_connect again.
-
#encrypted? ⇒ true, false
deprecated
Deprecated.
Discord no longer supports unencrypted voice communication.
-
#filter_volume ⇒ Integer
The volume used as a filter for ffmpeg/avconv.
-
#filter_volume=(value) ⇒ Object
Set the filter volume.
-
#pause ⇒ Object
Pause playback.
-
#play(encoded_io) ⇒ Object
Plays a stream of raw data to the channel.
-
#play_dca(file) ⇒ Object
Plays a stream of audio data in the DCA format.
-
#play_file(file, options = '') ⇒ Object
Plays an encoded audio file of arbitrary format to the channel.
-
#play_io(io, options = '') ⇒ Object
(also: #play_stream)
Plays a stream of encoded audio data of arbitrary format to the channel.
-
#playing? ⇒ true, false
(also: #isplaying?)
Whether it is playing sound or not.
-
#skip(secs) ⇒ Object
Skips to a later time in the song.
-
#speaking=(value) ⇒ Object
Sets whether or not the bot is speaking (green circle around user).
-
#stop_playing(wait_for_confirmation = false) ⇒ Object
Stops the current playback entirely.
Instance Attribute Details
#adjust_average ⇒ true, false
This value determines whether or not the adjustment length should be averaged with the previous value. This may be useful on slower connections where latencies vary a lot. In general, it will make adjustments more smooth, but whether that is desired behaviour should be tried on a case-by-case basis.
58 59 60 |
# File 'lib/discordrb/voice/voice_bot.rb', line 58 def adjust_average @adjust_average end |
#adjust_debug ⇒ true, false
Disable the debug message for length adjustment specifically, as it can get quite spammy with very low intervals
63 64 65 |
# File 'lib/discordrb/voice/voice_bot.rb', line 63 def adjust_debug @adjust_debug end |
#adjust_interval ⇒ Integer
discordrb will occasionally measure the time it takes to send a packet, and adjust future delay times based on that data. This makes voice playback more smooth, because if packets are sent too slowly, the audio will sound patchy, and if they're sent too quickly, packets will "pile up" and occasionally skip some data or play parts back too fast. How often these measurements should be done depends a lot on the system, and if it's done too quickly, especially on slow connections, the playback speed will vary wildly; if it's done too slowly however, small errors will cause quality problems for a longer time.
43 44 45 |
# File 'lib/discordrb/voice/voice_bot.rb', line 43 def adjust_interval @adjust_interval end |
#adjust_offset ⇒ Integer
This particular value is also important because ffmpeg may take longer to process the first few packets. It is recommended to set this to 10 at maximum, otherwise it will take too long to make the first adjustment, but it shouldn't be any higher than #adjust_interval, otherwise no adjustments will take place. If #adjust_interval is at a value higher than 10, this value should not be changed at all.
51 52 53 |
# File 'lib/discordrb/voice/voice_bot.rb', line 51 def adjust_offset @adjust_offset end |
#channel ⇒ Channel
Returns the current voice channel.
25 26 27 |
# File 'lib/discordrb/voice/voice_bot.rb', line 25 def channel @channel end |
#encoder ⇒ Encoder (readonly)
Returns the encoder used to encode audio files into the format required by Discord.
34 35 36 |
# File 'lib/discordrb/voice/voice_bot.rb', line 34 def encoder @encoder end |
#length_override ⇒ Float
If this value is set, no length adjustments will ever be done and this value will always be used as the length (i.e. packets will be sent every N seconds). Be careful not to set it too low as to not spam Discord's servers. The ideal length is 20ms (accessible by the IDEAL_LENGTH constant), this value should be slightly lower than that because encoding + sending takes time. Note that sending DCA files is significantly faster than sending regular audio files (usually about four times as fast), so you might want to set this value to something else if you're sending a DCA file.
72 73 74 |
# File 'lib/discordrb/voice/voice_bot.rb', line 72 def length_override @length_override end |
#stream_time ⇒ Integer? (readonly)
Returns the amount of time the stream has been playing, or nil
if nothing has been played yet.
31 32 33 |
# File 'lib/discordrb/voice/voice_bot.rb', line 31 def stream_time @stream_time end |
#volume ⇒ Float
The factor the audio's volume should be multiplied with. 1
is no change in volume, 0
is completely silent,
0.5
is half the default volume and 2
is twice the default.
77 78 79 |
# File 'lib/discordrb/voice/voice_bot.rb', line 77 def volume @volume end |
Instance Method Details
#continue ⇒ Object
Continue playback. This change may take up to 100ms to take effect, which is usually negligible.
139 140 141 |
# File 'lib/discordrb/voice/voice_bot.rb', line 139 def continue @paused = false end |
#destroy ⇒ Object
Permanently disconnects from the voice channel; to reconnect you will have to call Bot#voice_connect again.
175 176 177 178 179 |
# File 'lib/discordrb/voice/voice_bot.rb', line 175 def destroy @bot.voice_destroy(@channel.server.id, false) @ws.destroy end |
#encrypted? ⇒ true, false
Discord no longer supports unencrypted voice communication.
Returns whether audio data sent will be encrypted.
107 108 109 |
# File 'lib/discordrb/voice/voice_bot.rb', line 107 def encrypted? true end |
#filter_volume ⇒ Integer
Returns the volume used as a filter for ffmpeg/avconv.
120 121 122 |
# File 'lib/discordrb/voice/voice_bot.rb', line 120 def filter_volume @encoder.filter_volume end |
#filter_volume=(value) ⇒ Object
Set the filter volume. This volume is applied as a filter for decoded audio data. It has the advantage that using it is much faster than regular volume, but it can only be changed before starting to play something.
114 115 116 |
# File 'lib/discordrb/voice/voice_bot.rb', line 114 def filter_volume=(value) @encoder.filter_volume = value end |
#pause ⇒ Object
Pause playback. This is not instant; it may take up to 20 ms for this change to take effect. (This is usually negligible.)
126 127 128 |
# File 'lib/discordrb/voice/voice_bot.rb', line 126 def pause @paused = true end |
#play(encoded_io) ⇒ Object
Plays a stream of raw data to the channel. All playback methods are blocking, i.e. they wait for the playback to finish before exiting the method. This doesn't cause a problem if you just use discordrb events/commands to play stuff, as these are fully threaded, but if you don't want this behaviour anyway, be sure to call these methods in separate threads.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/discordrb/voice/voice_bot.rb', line 186 def play(encoded_io) (true) if @playing @retry_attempts = 3 @first_packet = true play_internal do buf = nil # Read some data from the buffer begin buf = encoded_io.readpartial(DATA_LENGTH) if encoded_io rescue EOFError raise IOError, 'File or stream not found!' if @first_packet @bot.debug('EOF while reading, breaking immediately') next :stop end # Check whether the buffer has enough data if !buf || buf.length != DATA_LENGTH @bot.debug("No data is available! Retrying #{@retry_attempts} more times") next :stop if @retry_attempts.zero? @retry_attempts -= 1 next end # Adjust volume buf = @encoder.adjust_volume(buf, @volume) if @volume != 1.0 # rubocop:disable Lint/FloatComparison @first_packet = false # Encode data @encoder.encode(buf) end # If the stream is a process, kill it if encoded_io&.pid Discordrb::LOGGER.debug("Killing ffmpeg process with pid #{encoded_io.pid.inspect}") begin pid = encoded_io.pid # Windows does not support TERM as a kill signal, so we use KILL. `Process.waitpid` verifies that our # child process has not already completed. Process.kill(Gem.win_platform? ? 'KILL' : 'TERM', pid) if Process.waitpid(pid, Process::WNOHANG).nil? rescue StandardError => e Discordrb::LOGGER.warn('Failed to kill ffmpeg process! You *might* have a process leak now.') Discordrb::LOGGER.warn("Reason: #{e}") end end # Close the stream encoded_io.close end |
#play_dca(file) ⇒ Object
DCA playback will not be affected by the volume modifier (#volume) because the modifier operates on raw PCM, not opus data. Modifying the volume of DCA data would involve decoding it, multiplying the samples and re-encoding it, which defeats its entire purpose (no recoding).
Plays a stream of audio data in the DCA format. This format has the advantage that no recoding has to be done - the file contains the data exactly as Discord needs it.
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/discordrb/voice/voice_bot.rb', line 262 def play_dca(file) (true) if @playing @bot.debug "Reading DCA file #{file}" input_stream = File.open(file) magic = input_stream.read(4) raise ArgumentError, 'Not a DCA1 file! The file might have been corrupted, please recreate it.' unless magic == 'DCA1' # Read the metadata header, then read the metadata and discard it as we don't care about it = input_stream.read(4).unpack1('l<') input_stream.read() # Play the data, without re-encoding it to opus play_internal do begin # Read header header_str = input_stream.read(2) unless header_str @bot.debug 'Finished DCA parsing (header is nil)' next :stop end header = header_str.unpack1('s<') raise 'Negative header in DCA file! Your file is likely corrupted.' if header.negative? rescue EOFError @bot.debug 'Finished DCA parsing (EOFError)' next :stop end # Read bytes input_stream.read(header) end end |
#play_file(file, options = '') ⇒ Object
Plays an encoded audio file of arbitrary format to the channel.
244 245 246 |
# File 'lib/discordrb/voice/voice_bot.rb', line 244 def play_file(file, = '') play @encoder.encode_file(file, ) end |
#play_io(io, options = '') ⇒ Object Also known as: play_stream
Plays a stream of encoded audio data of arbitrary format to the channel.
251 252 253 |
# File 'lib/discordrb/voice/voice_bot.rb', line 251 def play_io(io, = '') play @encoder.encode_io(io, ) end |
#playing? ⇒ true, false Also known as: isplaying?
Returns Whether it is playing sound or not.
132 133 134 |
# File 'lib/discordrb/voice/voice_bot.rb', line 132 def @playing end |
#skip(secs) ⇒ Object
Skips to a later time in the song. It's impossible to go back without replaying the song.
146 147 148 |
# File 'lib/discordrb/voice/voice_bot.rb', line 146 def skip(secs) @skips += (secs * (1000 / IDEAL_LENGTH)).ceil end |
#speaking=(value) ⇒ Object
https://discord.com/developers/docs/topics/voice-connections#speaking for information on the speaking bitmask
Sets whether or not the bot is speaking (green circle around user).
153 154 155 156 |
# File 'lib/discordrb/voice/voice_bot.rb', line 153 def speaking=(value) @playing = value @ws.send_speaking(value) end |
#stop_playing(wait_for_confirmation = false) ⇒ Object
Stops the current playback entirely.
161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/discordrb/voice/voice_bot.rb', line 161 def (wait_for_confirmation = false) @was_playing_before = @playing @speaking = false @playing = false sleep IDEAL_LENGTH / 1000.0 if @was_playing_before return unless wait_for_confirmation @has_stopped_playing = false sleep IDEAL_LENGTH / 1000.0 until @has_stopped_playing @has_stopped_playing = false end |