discordrb 1.5.4 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of discordrb might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.rubocop.yml +11 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +27 -0
- data/README.md +4 -0
- data/discordrb.gemspec +2 -0
- data/lib/discordrb.rb +17 -0
- data/lib/discordrb/api.rb +57 -31
- data/lib/discordrb/await.rb +2 -1
- data/lib/discordrb/bot.rb +125 -49
- data/lib/discordrb/commands/command_bot.rb +7 -2
- data/lib/discordrb/commands/parser.rb +2 -0
- data/lib/discordrb/data.rb +471 -112
- data/lib/discordrb/events/bans.rb +54 -0
- data/lib/discordrb/events/channel_create.rb +10 -10
- data/lib/discordrb/events/channel_delete.rb +10 -10
- data/lib/discordrb/events/channel_update.rb +10 -10
- data/lib/discordrb/events/guild_role_create.rb +5 -5
- data/lib/discordrb/events/guild_role_delete.rb +5 -5
- data/lib/discordrb/events/guild_role_update.rb +5 -5
- data/lib/discordrb/events/guilds.rb +7 -8
- data/lib/discordrb/events/members.rb +5 -5
- data/lib/discordrb/events/message.rb +48 -0
- data/lib/discordrb/events/presence.rb +25 -29
- data/lib/discordrb/events/typing.rb +7 -7
- data/lib/discordrb/events/voice_state_update.rb +34 -34
- data/lib/discordrb/permissions.rb +1 -1
- data/lib/discordrb/token_cache.rb +2 -3
- data/lib/discordrb/version.rb +2 -1
- data/lib/discordrb/voice/encoder.rb +32 -2
- data/lib/discordrb/voice/network.rb +87 -11
- data/lib/discordrb/voice/voice_bot.rb +77 -20
- metadata +33 -3
@@ -5,7 +5,7 @@ require 'discordrb/api'
|
|
5
5
|
|
6
6
|
# Discordrb
|
7
7
|
module Discordrb
|
8
|
-
# Amount of bytes the key should be long (32 bytes = 256 bits -> AES256)
|
8
|
+
# Amount of bytes the token encryption key should be long (32 bytes = 256 bits -> AES256)
|
9
9
|
KEYLEN = 32
|
10
10
|
|
11
11
|
# Represents a cached token with encryption data
|
@@ -65,7 +65,6 @@ module Discordrb
|
|
65
65
|
cipher.key = key
|
66
66
|
@iv = cipher.random_iv
|
67
67
|
@encrypted_token = cipher.update(token) + cipher.final
|
68
|
-
@encrypted_token
|
69
68
|
end
|
70
69
|
|
71
70
|
def test_token(token)
|
@@ -80,7 +79,7 @@ module Discordrb
|
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
83
|
-
# Path where the cache file will be stored
|
82
|
+
# Path where the token cache file will be stored
|
84
83
|
CACHE_PATH = Dir.home + '/.discordrb_token_cache.json'
|
85
84
|
|
86
85
|
# Represents a token file
|
data/lib/discordrb/version.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# This makes opus an optional dependency
|
1
2
|
begin
|
2
3
|
require 'opus-ruby'
|
3
4
|
OPUS_AVAILABLE = true
|
@@ -7,10 +8,26 @@ end
|
|
7
8
|
|
8
9
|
# Discord voice chat support
|
9
10
|
module Discordrb::Voice
|
10
|
-
#
|
11
|
+
# This class conveniently abstracts opus and ffmpeg/avconv, for easy implementation of voice sending. It's not very
|
12
|
+
# useful for most users, but I guess it can be useful sometimes.
|
11
13
|
class Encoder
|
12
|
-
|
14
|
+
# The volume that should be used with future ffmpeg conversions. If ffmpeg is used, this can be specified as:
|
15
|
+
#
|
16
|
+
# * A number, where `1` is no change in volume, `0` is completely silent, `0.5` is half the default volume and `2` is twice the default.
|
17
|
+
# * A string representation of the above number.
|
18
|
+
# * A string representing a change in gain given in decibels, in the format `-6dB` or `6dB`.
|
19
|
+
#
|
20
|
+
# If avconv is used (see #use_avconv) then it can only be given as a number from `0` to `1`, where `1` is no change
|
21
|
+
# and `0` is completely silent.
|
22
|
+
# @return [String, Number] the volume for future playbacks, `1.0` by default.
|
23
|
+
attr_accessor :volume
|
13
24
|
|
25
|
+
# Whether or not avconv should be used instead of ffmpeg. If possible, it is recommended to use ffmpeg instead,
|
26
|
+
# as it is better supported and has a wider range of possible volume settings (see #volume).
|
27
|
+
# @return [true, false] whether avconv should be used instead of ffmpeg.
|
28
|
+
attr_accessor :use_avconv
|
29
|
+
|
30
|
+
# Create a new encoder
|
14
31
|
def initialize
|
15
32
|
@sample_rate = 48_000
|
16
33
|
@frame_size = 960
|
@@ -24,19 +41,32 @@ module Discordrb::Voice
|
|
24
41
|
end
|
25
42
|
end
|
26
43
|
|
44
|
+
# Encodes the given buffer using opus.
|
45
|
+
# @param buffer [String] An unencoded PCM (s16le) buffer.
|
46
|
+
# @return [String] A buffer encoded using opus.
|
27
47
|
def encode(buffer)
|
28
48
|
@opus.encode(buffer, 1920)
|
29
49
|
end
|
30
50
|
|
51
|
+
# Destroys this encoder and the opus connection, preventing any future encodings.
|
31
52
|
def destroy
|
32
53
|
@opus.destroy
|
33
54
|
end
|
34
55
|
|
56
|
+
# Encodes a given file (or rather, decodes it) using ffmpeg. This accepts pretty much any format, even videos with
|
57
|
+
# an audio track. For a list of supported formats, see https://ffmpeg.org/general.html#Audio-Codecs. It even accepts
|
58
|
+
# URLs, though encoding them is pretty slow - I recommend to make a stream of it and then use {#encode_io} instead.
|
59
|
+
# @param file [String] The path or URL to encode.
|
60
|
+
# @return [IO] the audio, encoded as s16le PCM
|
35
61
|
def encode_file(file)
|
36
62
|
command = "#{ffmpeg_command} -loglevel 0 -i \"#{file}\" -f s16le -ar 48000 -ac 2 #{ffmpeg_volume} pipe:1"
|
37
63
|
IO.popen(command)
|
38
64
|
end
|
39
65
|
|
66
|
+
# Encodes an arbitrary IO audio stream using ffmpeg. Accepts pretty much any media format, even videos with audio
|
67
|
+
# tracks. For a list of supported audio formats, see https://ffmpeg.org/general.html#Audio-Codecs.
|
68
|
+
# @param io [IO] The stream to encode.
|
69
|
+
# @return [IO] the audio, encoded as s16le PCM
|
40
70
|
def encode_io(io)
|
41
71
|
ret_io, writer = IO.pipe
|
42
72
|
command = "#{ffmpeg_command} -loglevel 0 -i - -f s16le -ar 48000 -ac 2 #{ffmpeg_volume} pipe:1"
|
@@ -2,16 +2,35 @@ require 'websocket-client-simple'
|
|
2
2
|
require 'resolv'
|
3
3
|
require 'socket'
|
4
4
|
require 'json'
|
5
|
+
require 'rbnacl/libsodium'
|
5
6
|
|
6
7
|
module Discordrb::Voice
|
7
|
-
#
|
8
|
+
# Signifies to Discord that encryption should be used
|
9
|
+
ENCRYPTED_MODE = 'xsalsa20_poly1305'
|
10
|
+
|
11
|
+
# Signifies to Discord that no encryption should be used
|
12
|
+
PLAIN_MODE = 'plain'
|
13
|
+
|
14
|
+
# Represents a UDP connection to a voice server. This connection is used to send the actual audio data.
|
8
15
|
class VoiceUDP
|
9
|
-
#
|
16
|
+
# @return [true, false] whether or not UDP communications are encrypted.
|
17
|
+
attr_accessor :encrypted
|
18
|
+
alias_method :encrypted?, :encrypted
|
19
|
+
|
20
|
+
# Sets the secret key used for encryption
|
21
|
+
attr_writer :secret_key
|
22
|
+
|
23
|
+
# Creates a new UDP connection. Only creates a socket as the discovery reply may come before the data is
|
24
|
+
# initialized.
|
10
25
|
def initialize
|
11
26
|
@socket = UDPSocket.new
|
12
27
|
end
|
13
28
|
|
14
|
-
# Initializes the data from opcode 2
|
29
|
+
# Initializes the UDP socket with data obtained from opcode 2.
|
30
|
+
# @param endpoint [String] The voice endpoint to connect to.
|
31
|
+
# @param port [Integer] The port to connect to.
|
32
|
+
# @param ssrc [Integer] The Super Secret Relay Code (SSRC). Discord uses this to identify different voice users
|
33
|
+
# on the same endpoint.
|
15
34
|
def connect(endpoint, port, ssrc)
|
16
35
|
@endpoint = endpoint
|
17
36
|
@endpoint = @endpoint[6..-1] if @endpoint.start_with? 'wss://'
|
@@ -22,6 +41,8 @@ module Discordrb::Voice
|
|
22
41
|
@ssrc = ssrc
|
23
42
|
end
|
24
43
|
|
44
|
+
# Waits for a UDP discovery reply, and returns the sent data.
|
45
|
+
# @return [Array(String, Integer)] the IP and port received from the discovery reply.
|
25
46
|
def receive_discovery_reply
|
26
47
|
# Wait for a UDP message
|
27
48
|
message = @socket.recv(70)
|
@@ -30,11 +51,25 @@ module Discordrb::Voice
|
|
30
51
|
[ip, port]
|
31
52
|
end
|
32
53
|
|
54
|
+
# Makes an audio packet from a buffer and sends it to Discord.
|
55
|
+
# @param buf [String] The audio data to send, must be exactly one Opus frame
|
56
|
+
# @param sequence [Integer] The packet sequence number, incremented by one for subsequent packets
|
57
|
+
# @param time [Integer] When this packet should be played back, in no particular unit (essentially just the
|
58
|
+
# sequence number multiplied by 960)
|
33
59
|
def send_audio(buf, sequence, time)
|
34
|
-
|
35
|
-
|
60
|
+
# Header of the audio packet
|
61
|
+
header = [0x80, 0x78, sequence, time, @ssrc].pack('CCnNN')
|
62
|
+
|
63
|
+
# Encrypt data, if necessary
|
64
|
+
if encrypted?
|
65
|
+
buf = encrypt_audio(header, buf)
|
66
|
+
end
|
67
|
+
|
68
|
+
send_packet(header + buf)
|
36
69
|
end
|
37
70
|
|
71
|
+
# Sends the UDP discovery packet with the internally stored SSRC. Discord will send a reply afterwards which can
|
72
|
+
# be received using {#receive_discovery_reply}
|
38
73
|
def send_discovery
|
39
74
|
discovery_packet = [@ssrc].pack('N')
|
40
75
|
|
@@ -45,15 +80,38 @@ module Discordrb::Voice
|
|
45
80
|
|
46
81
|
private
|
47
82
|
|
83
|
+
# Encrypts audio data using RbNaCl
|
84
|
+
# @param header [String] The header of the packet, to be used as the nonce
|
85
|
+
# @param buf [String] The encoded audio data to be encrypted
|
86
|
+
# @return [String] the audio data, encrypted
|
87
|
+
def encrypt_audio(header, buf)
|
88
|
+
fail 'No secret key found, despite encryption being enabled!' unless @secret_key
|
89
|
+
box = RbNaCl::SecretBox.new(@secret_key)
|
90
|
+
|
91
|
+
# The nonce is the header of the voice packet with 12 null bytes appended
|
92
|
+
nonce = header + ([0] * 12).pack('C*')
|
93
|
+
|
94
|
+
box.encrypt(nonce, buf)
|
95
|
+
end
|
96
|
+
|
48
97
|
def send_packet(packet)
|
49
98
|
@socket.send(packet, 0, @endpoint, @port)
|
50
99
|
end
|
51
100
|
end
|
52
101
|
|
53
|
-
# Represents a websocket connection to the voice server
|
102
|
+
# Represents a websocket client connection to the voice server. The websocket connection (sometimes called vWS) is
|
103
|
+
# used to manage general data about the connection, such as sending the speaking packet, which determines the green
|
104
|
+
# circle around users on Discord, and obtaining UDP connection info.
|
54
105
|
class VoiceWS
|
106
|
+
# @return [VoiceUDP] the UDP voice connection over which the actual audio data is sent.
|
55
107
|
attr_reader :udp
|
56
108
|
|
109
|
+
# Makes a new voice websocket client, but doesn't connect it (see {#connect} for that)
|
110
|
+
# @param channel [Channel] The voice channel to connect to
|
111
|
+
# @param bot [Bot] The regular bot to which this vWS is bound
|
112
|
+
# @param token [String] The authentication token which is also used for REST requests
|
113
|
+
# @param session [String] The voice session ID Discord sends over the regular websocket
|
114
|
+
# @param endpoint [String] The endpoint URL to connect to
|
57
115
|
def initialize(channel, bot, token, session, endpoint)
|
58
116
|
@channel = channel
|
59
117
|
@bot = bot
|
@@ -67,6 +125,10 @@ module Discordrb::Voice
|
|
67
125
|
end
|
68
126
|
|
69
127
|
# Send a connection init packet (op 0)
|
128
|
+
# @param server_id [Integer] The ID of the server to connect to
|
129
|
+
# @param bot_user_id [Integer] The ID of the bot that is connecting
|
130
|
+
# @param session_id [String] The voice session ID
|
131
|
+
# @param token [String] The Discord authentication token
|
70
132
|
def send_init(server_id, bot_user_id, session_id, token)
|
71
133
|
@client.send({
|
72
134
|
op: 0,
|
@@ -80,6 +142,9 @@ module Discordrb::Voice
|
|
80
142
|
end
|
81
143
|
|
82
144
|
# Sends the UDP connection packet (op 1)
|
145
|
+
# @param ip [String] The IP to bind UDP to
|
146
|
+
# @param port [Integer] The port to bind UDP to
|
147
|
+
# @param mode [Object] Which mode to use for the voice connection
|
83
148
|
def send_udp_connection(ip, port, mode)
|
84
149
|
@client.send({
|
85
150
|
op: 1,
|
@@ -100,12 +165,13 @@ module Discordrb::Voice
|
|
100
165
|
@bot.debug("Sending voice heartbeat at #{millis}")
|
101
166
|
|
102
167
|
@client.send({
|
103
|
-
|
104
|
-
|
168
|
+
op: 3,
|
169
|
+
d: nil
|
105
170
|
}.to_json)
|
106
171
|
end
|
107
172
|
|
108
173
|
# Send a speaking packet (op 5). This determines the green circle around the avatar in the voice channel
|
174
|
+
# @param value [true, false] Whether or not the bot should be speaking
|
109
175
|
def send_speaking(value)
|
110
176
|
@bot.debug("Speaking: #{value}")
|
111
177
|
@client.send({
|
@@ -118,11 +184,13 @@ module Discordrb::Voice
|
|
118
184
|
end
|
119
185
|
|
120
186
|
# Event handlers; public for websocket-simple to work correctly
|
187
|
+
# @!visibility private
|
121
188
|
def websocket_open
|
122
189
|
# Send the init packet
|
123
190
|
send_init(@channel.server.id, @bot.bot_user.id, @session, @token)
|
124
191
|
end
|
125
192
|
|
193
|
+
# @!visibility private
|
126
194
|
def websocket_message(msg)
|
127
195
|
@bot.debug("Received VWS message! #{msg}")
|
128
196
|
packet = JSON.parse(msg)
|
@@ -135,15 +203,17 @@ module Discordrb::Voice
|
|
135
203
|
@heartbeat_interval = @ws_data['heartbeat_interval']
|
136
204
|
@ssrc = @ws_data['ssrc']
|
137
205
|
@port = @ws_data['port']
|
138
|
-
@udp_mode =
|
206
|
+
@udp_mode = mode
|
139
207
|
|
140
208
|
@udp.connect(@endpoint, @port, @ssrc)
|
141
209
|
@udp.send_discovery
|
142
210
|
when 4
|
143
|
-
#
|
211
|
+
# Opcode 4 sends the secret key used for encryption
|
144
212
|
@ws_data = packet['d']
|
145
213
|
@ready = true
|
146
|
-
@
|
214
|
+
@udp.secret_key = @ws_data['secret_key'].pack('C*')
|
215
|
+
else
|
216
|
+
# irrelevant opcode, ignore
|
147
217
|
end
|
148
218
|
end
|
149
219
|
|
@@ -178,12 +248,18 @@ module Discordrb::Voice
|
|
178
248
|
send_udp_connection(ip, port, @udp_mode)
|
179
249
|
end
|
180
250
|
|
251
|
+
# Disconnects the websocket and kills the thread
|
181
252
|
def destroy
|
182
253
|
@thread.kill if @thread
|
183
254
|
end
|
184
255
|
|
185
256
|
private
|
186
257
|
|
258
|
+
# @return [String] the mode string that signifies whether encryption should be used or not
|
259
|
+
def mode
|
260
|
+
@udp.encrypted? ? ENCRYPTED_MODE : PLAIN_MODE
|
261
|
+
end
|
262
|
+
|
187
263
|
def heartbeat_loop
|
188
264
|
loop do
|
189
265
|
if @heartbeat_interval
|
@@ -3,21 +3,56 @@ require 'discordrb/voice/network'
|
|
3
3
|
|
4
4
|
# Voice support
|
5
5
|
module Discordrb::Voice
|
6
|
-
# How long one packet should ideally be (20 ms as defined by Discord)
|
6
|
+
# How long one voice packet should ideally be (20 ms as defined by Discord)
|
7
7
|
IDEAL_LENGTH = 20.0
|
8
8
|
|
9
|
-
# How many bytes of data to read (1920 bytes * 2 channels)
|
9
|
+
# How many bytes of data to read (1920 bytes * 2 channels) from audio PCM data
|
10
10
|
DATA_LENGTH = 1920 * 2
|
11
11
|
|
12
|
-
#
|
12
|
+
# This class represents a connection to a Discord voice server and channel. It can be used to play audio files and
|
13
|
+
# streams and to control playback on currently playing tracks. The method {Bot#voice_connect} can be used to connect
|
14
|
+
# to a voice channel.
|
15
|
+
#
|
16
|
+
# discordrb does latency adjustments every now and then to improve playback quality. I made sure to put useful
|
17
|
+
# defaults for the adjustment parameters, but if the sound is patchy or too fast (or the speed varies a lot) you
|
18
|
+
# should check the parameters and adjust them to your connection: {VoiceBot#adjust_interval},
|
19
|
+
# {VoiceBot#adjust_offset}, and {VoiceBot#adjust_average}.
|
13
20
|
class VoiceBot
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
21
|
+
# @return [Integer, nil] the amount of time the stream has been playing, or `nil` if nothing has been played yet.
|
22
|
+
attr_reader :stream_time
|
23
|
+
|
24
|
+
# @return [Encoder] the encoder used to encode audio files into the format required by Discord.
|
25
|
+
attr_reader :encoder
|
26
|
+
|
27
|
+
# discordrb will occasionally measure the time it takes to send a packet, and adjust future delay times based
|
28
|
+
# on that data. This makes voice playback more smooth, because if packets are sent too slowly, the audio will
|
29
|
+
# sound patchy, and if they're sent too quickly, packets will "pile up" and occasionally skip some data or
|
30
|
+
# play parts back too fast. How often these measurements should be done depends a lot on the system, and if it's
|
31
|
+
# done too quickly, especially on slow connections, the playback speed will vary wildly; if it's done too slowly
|
32
|
+
# however, small errors will cause quality problems for a longer time.
|
33
|
+
# @return [Integer] how frequently audio length adjustments should be done, in ideal packets (20 ms).
|
34
|
+
attr_accessor :adjust_interval
|
35
|
+
|
36
|
+
# This particular value is also important because ffmpeg may take longer to process the first few packets. It is
|
37
|
+
# recommended to set this to 10 at maximum, otherwise it will take too long to make the first adjustment, but it
|
38
|
+
# shouldn't be any higher than {#adjust_interval}, otherwise no adjustments will take place. If {#adjust_interval}
|
39
|
+
# is at a value higher than 10, this value should not be changed at all.
|
40
|
+
# @see #adjust_interval
|
41
|
+
# @return [Integer] the packet number (1 packet = 20 ms) at which length adjustments should start.
|
42
|
+
attr_accessor :adjust_offset
|
43
|
+
|
44
|
+
# This value determines whether or not the adjustment length should be averaged with the previous value. This may
|
45
|
+
# be useful on slower connections where latencies vary a lot. In general, it will make adjustments more smooth,
|
46
|
+
# but whether that is desired behaviour should be tried on a case-by-case basis.
|
47
|
+
# @see #adjust_interval
|
48
|
+
# @return [true, false] whether adjustment lengths should be averaged with the respective previous value.
|
49
|
+
attr_accessor :adjust_average
|
50
|
+
|
51
|
+
def initialize(channel, bot, token, session, endpoint, encrypted)
|
18
52
|
@bot = bot
|
19
53
|
@ws = VoiceWS.new(channel, bot, token, session, endpoint)
|
20
54
|
@udp = @ws.udp
|
55
|
+
@udp.encrypted = encrypted
|
21
56
|
|
22
57
|
@sequence = @time = 0
|
23
58
|
|
@@ -30,29 +65,41 @@ module Discordrb::Voice
|
|
30
65
|
end
|
31
66
|
|
32
67
|
# Set the volume. Only applies to future playbacks
|
68
|
+
# @see Encoder#volume=
|
33
69
|
def volume=(value)
|
34
70
|
@encoder.volume = value
|
35
71
|
end
|
36
72
|
|
73
|
+
# @see Encoder#volume
|
74
|
+
# @return [Integer, String] the current encoder volume.
|
37
75
|
def volume
|
38
76
|
@encoder.volume
|
39
77
|
end
|
40
78
|
|
41
|
-
#
|
79
|
+
# @return [true, false] whether audio data sent will be encrypted.
|
80
|
+
def encrypted?
|
81
|
+
@udp.encrypted?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Pause playback. This is not instant; it may take up to 20 ms for this change to take effect. (This is usually
|
85
|
+
# negligible.)
|
42
86
|
def pause
|
43
87
|
@paused = true
|
44
88
|
end
|
45
89
|
|
46
|
-
# Continue playback
|
90
|
+
# Continue playback. This change may take up to 100 ms to take effect, which is usually negligible.
|
47
91
|
def continue
|
48
92
|
@paused = false
|
49
93
|
end
|
50
94
|
|
95
|
+
# Sets whether or not the bot is speaking (green circle around user).
|
96
|
+
# @param value [true, false] whether or not the bot should be speaking.
|
51
97
|
def speaking=(value)
|
52
98
|
@playing = value
|
53
99
|
@ws.send_speaking(value)
|
54
100
|
end
|
55
101
|
|
102
|
+
# Stops the current playback entirely.
|
56
103
|
def stop_playing
|
57
104
|
@was_playing_before = @playing
|
58
105
|
@speaking = false
|
@@ -61,22 +108,34 @@ module Discordrb::Voice
|
|
61
108
|
sleep IDEAL_LENGTH / 1000.0 if @was_playing_before
|
62
109
|
end
|
63
110
|
|
111
|
+
# Permanently disconnects from the voice channel; to reconnect you will have to call {Bot#voice_connect} again.
|
64
112
|
def destroy
|
65
113
|
stop_playing
|
66
114
|
@ws.destroy
|
67
115
|
@encoder.destroy
|
68
116
|
end
|
69
117
|
|
118
|
+
# Plays a stream of raw data to the channel. All playback methods are blocking, i. e. they wait for the playback to
|
119
|
+
# finish before exiting the method. This doesn't cause a problem if you just use discordrb events/commands to
|
120
|
+
# play stuff, as these are fully threaded, but if you don't want this behaviour anyway, be sure to call these
|
121
|
+
# methods in separate threads.
|
122
|
+
# @param encoded_io [IO] A stream of raw PCM data (s16le)
|
70
123
|
def play(encoded_io)
|
71
124
|
stop_playing if @playing
|
72
125
|
@io = encoded_io
|
73
126
|
play_internal
|
74
127
|
end
|
75
128
|
|
129
|
+
# Plays an encoded audio file of arbitrary format to the channel.
|
130
|
+
# @see Encoder#encode_file
|
131
|
+
# @see #play
|
76
132
|
def play_file(file)
|
77
133
|
play @encoder.encode_file(file)
|
78
134
|
end
|
79
135
|
|
136
|
+
# Plays a stream of encoded audio data of arbitrary format to the channel.
|
137
|
+
# @see Encoder#encode_io
|
138
|
+
# @see #play
|
80
139
|
def play_io(io)
|
81
140
|
play @encoder.encode_io(io)
|
82
141
|
end
|
@@ -116,12 +175,10 @@ module Discordrb::Voice
|
|
116
175
|
# Check whether the buffer has enough data
|
117
176
|
if !buf || buf.length != DATA_LENGTH
|
118
177
|
@bot.debug("No data is available! Retrying #{@retry_attempts} more times")
|
119
|
-
if @retry_attempts == 0
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
next
|
124
|
-
end
|
178
|
+
break if @retry_attempts == 0
|
179
|
+
|
180
|
+
@retry_attempts -= 1
|
181
|
+
next
|
125
182
|
end
|
126
183
|
|
127
184
|
# Track packet count, sequence and time (Discord requires this)
|
@@ -140,11 +197,11 @@ module Discordrb::Voice
|
|
140
197
|
# Difference between length_adjust and now in ms
|
141
198
|
ms_diff = (Time.now.nsec - @length_adjust) / 1_000_000.0
|
142
199
|
if ms_diff >= 0
|
143
|
-
if @adjust_average
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
200
|
+
@length = if @adjust_average
|
201
|
+
(IDEAL_LENGTH - ms_diff + @length) / 2.0
|
202
|
+
else
|
203
|
+
IDEAL_LENGTH - ms_diff
|
204
|
+
end
|
148
205
|
|
149
206
|
@bot.debug("Length adjustment: new length #{@length}")
|
150
207
|
end
|