mumble-ruby 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.rdoc +24 -11
- data/lib/mumble-ruby.rb +14 -8
- data/lib/mumble-ruby/audio_player.rb +102 -0
- data/lib/mumble-ruby/audio_recorder.rb +79 -0
- data/lib/mumble-ruby/channel.rb +48 -0
- data/lib/mumble-ruby/client.rb +46 -65
- data/lib/mumble-ruby/img_reader.rb +15 -17
- data/lib/mumble-ruby/model.rb +48 -0
- data/lib/mumble-ruby/thread_tools.rb +24 -0
- data/lib/mumble-ruby/user.rb +46 -0
- data/lib/mumble-ruby/version.rb +1 -1
- data/mumble-ruby.gemspec +1 -0
- metadata +22 -3
- data/lib/mumble-ruby/audio_stream.rb +0 -67
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8dc2810d57d5afb6c1f6c7ad838e296c0f5b074
|
4
|
+
data.tar.gz: 9239fee6276e50cbbad6fdc773556d5abbb4ead4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c2ef16226e6063220e1ddfa34f94f673240ae7bbecc09873bd0784771601e5ee7822bfa998d75e0d48f1ba6b85f7673d6f1348dd816a3aa8bc63b3fe9f07e69
|
7
|
+
data.tar.gz: 37a828c91ea9df41015fb3be82bedc85d9e168bcf86ef2f2e8b7444788cee2a29582b7c710ae66a775ec2dbfaf610c2534db5fe506b353048badebf20a00b7c1
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.1.
|
1
|
+
ruby-2.1.1
|
data/README.rdoc
CHANGED
@@ -16,10 +16,15 @@ There is huge room for improvement in this library and I am willing to accept al
|
|
16
16
|
|
17
17
|
* Ruby >= 2.1.0
|
18
18
|
* OPUS Audio Codec
|
19
|
-
* Murmur server > 1.2.4
|
19
|
+
* Murmur server > 1.2.4 -- NOTE: mumble-ruby will not be able to stream audio to servers that don't support OPUS anymore. I haven't looked into backwards-compatability with CELT.
|
20
20
|
|
21
21
|
== RECENT CHANGES:
|
22
22
|
|
23
|
+
* Merged changes for proper user/channel objects
|
24
|
+
* Merged changes for recording feature
|
25
|
+
* Added half-broken support for playing files (Something is wrong, wouldn't recommend using it)
|
26
|
+
* Bit of refactoring and renaming
|
27
|
+
|
23
28
|
* Added OPUS support
|
24
29
|
* Added more configuration options
|
25
30
|
* Added image text messages
|
@@ -62,23 +67,25 @@ There is huge room for improvement in this library and I am willing to accept al
|
|
62
67
|
cli.connect
|
63
68
|
# => #<Thread:0x000000033d7388 run>
|
64
69
|
|
65
|
-
# Mute and Deafen yourself
|
66
|
-
cli.
|
67
|
-
|
70
|
+
# Mute and Deafen yourself after connecting
|
71
|
+
cli.on_connect do
|
72
|
+
cli.me.mute
|
73
|
+
cli.me.deafen
|
74
|
+
end
|
68
75
|
|
69
|
-
# Join the channel titled "Chillen" (this
|
76
|
+
# Join the channel titled "Chillen" (this will return a channel object for that channel)
|
70
77
|
cli.join_channel('Chillen')
|
71
|
-
# => 11
|
72
78
|
|
73
79
|
# Get a list of channels
|
74
80
|
cli.channels
|
75
|
-
# Returns a hash of channel_id:
|
81
|
+
# Returns a hash of channel_id: Channel objects
|
76
82
|
|
77
83
|
# Join Channel using ID
|
78
84
|
cli.join_channel(0)
|
79
85
|
|
80
|
-
# Join Channel using
|
86
|
+
# Join Channel using Channel object
|
81
87
|
cli.join_channel(cli.channels[0])
|
88
|
+
cli.channels[0].join
|
82
89
|
|
83
90
|
# Get a list of users
|
84
91
|
cli.users
|
@@ -86,14 +93,20 @@ There is huge room for improvement in this library and I am willing to accept al
|
|
86
93
|
|
87
94
|
# Text user
|
88
95
|
cli.text_user('perrym5', "Hello there, I'm a robot!")
|
89
|
-
# => 35
|
90
96
|
|
91
97
|
# Text an image to a channel
|
92
98
|
cli.text_channel_img('Chillen', '/path/to/image.jpg')
|
93
99
|
|
94
100
|
# Start streaming from a FIFO queue of raw PCM data
|
95
|
-
cli.
|
96
|
-
|
101
|
+
cli.player.stream_named_pipe('/tmp/mpd.fifo')
|
102
|
+
|
103
|
+
# EXPERIMENTAL: Recording feature
|
104
|
+
cli.recorder.start('/home/matt/record.wav')
|
105
|
+
sleep(2)
|
106
|
+
cli.recorder.stop
|
107
|
+
|
108
|
+
# EXPERIMENTAL: Play wav files
|
109
|
+
cli.player.play_file('/home/matt/record.wav')
|
97
110
|
|
98
111
|
# Safely disconnect
|
99
112
|
cli.disconnect
|
data/lib/mumble-ruby.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require 'opus-ruby'
|
2
|
-
require 'active_support/inflector
|
2
|
+
require 'active_support/inflector'
|
3
3
|
require 'mumble-ruby/version'
|
4
|
-
require 'mumble-ruby/
|
5
|
-
require 'mumble-ruby/
|
6
|
-
require 'mumble-ruby/
|
7
|
-
require 'mumble-ruby/
|
8
|
-
require 'mumble-ruby/
|
9
|
-
require 'mumble-ruby/
|
10
|
-
require 'mumble-ruby/
|
4
|
+
require 'mumble-ruby/thread_tools'
|
5
|
+
require 'mumble-ruby/messages'
|
6
|
+
require 'mumble-ruby/connection'
|
7
|
+
require 'mumble-ruby/model'
|
8
|
+
require 'mumble-ruby/user'
|
9
|
+
require 'mumble-ruby/channel'
|
10
|
+
require 'mumble-ruby/client'
|
11
|
+
require 'mumble-ruby/audio_player'
|
12
|
+
require 'mumble-ruby/packet_data_stream'
|
13
|
+
require 'mumble-ruby/img_reader'
|
14
|
+
require 'mumble-ruby/cert_manager'
|
15
|
+
require 'mumble-ruby/audio_recorder'
|
16
|
+
require 'hashie'
|
11
17
|
|
12
18
|
module Mumble
|
13
19
|
DEFAULTS = {
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'wavefile'
|
2
|
+
|
3
|
+
module Mumble
|
4
|
+
class AudioPlayer
|
5
|
+
include ThreadTools
|
6
|
+
COMPRESSED_SIZE = 960
|
7
|
+
|
8
|
+
def initialize(type, connection, sample_rate, bitrate)
|
9
|
+
@packet_header = (type << 5).chr
|
10
|
+
@conn = connection
|
11
|
+
@pds = PacketDataStream.new
|
12
|
+
@queue = Queue.new
|
13
|
+
@wav_format = WaveFile::Format.new :mono, :pcm_16, sample_rate
|
14
|
+
|
15
|
+
create_encoder sample_rate, bitrate
|
16
|
+
end
|
17
|
+
|
18
|
+
def volume
|
19
|
+
@volume ||= 100
|
20
|
+
end
|
21
|
+
|
22
|
+
def volume=(volume)
|
23
|
+
@volume = volume
|
24
|
+
end
|
25
|
+
|
26
|
+
def playing?
|
27
|
+
@playing ||= false
|
28
|
+
end
|
29
|
+
|
30
|
+
def play_file(file)
|
31
|
+
unless playing?
|
32
|
+
@file = WaveFile::Reader.new(file, @wav_format)
|
33
|
+
Thread.new { bounded_produce }
|
34
|
+
@playing = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stream_named_pipe(pipe)
|
39
|
+
unless playing?
|
40
|
+
@file = File.open(pipe, 'rb')
|
41
|
+
spawn_thread :produce, :consume
|
42
|
+
@playing = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop
|
47
|
+
if playing?
|
48
|
+
kill_threads
|
49
|
+
@encoder.reset
|
50
|
+
@file.close unless @file.closed?
|
51
|
+
@playing = false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def create_encoder(sample_rate, bitrate)
|
57
|
+
@encoder = Opus::Encoder.new sample_rate, sample_rate / 100, 1
|
58
|
+
@encoder.vbr_rate = 0 # CBR
|
59
|
+
@encoder.bitrate = bitrate
|
60
|
+
end
|
61
|
+
|
62
|
+
def change_volume(pcm_data)
|
63
|
+
pcm_data.unpack('s*').map { |s| s * (volume / 100.0) }.pack('s*')
|
64
|
+
end
|
65
|
+
|
66
|
+
def bounded_produce
|
67
|
+
@file.each_buffer(@encoder.frame_size) do |buffer|
|
68
|
+
encode_sample buffer.samples.pack('s*')
|
69
|
+
consume
|
70
|
+
end
|
71
|
+
|
72
|
+
stop
|
73
|
+
end
|
74
|
+
|
75
|
+
def produce
|
76
|
+
encode_sample @file.read(@encoder.frame_size * 2)
|
77
|
+
end
|
78
|
+
|
79
|
+
def encode_sample(sample)
|
80
|
+
pcm_data = change_volume sample
|
81
|
+
@queue << @encoder.encode(pcm_data, COMPRESSED_SIZE)
|
82
|
+
end
|
83
|
+
|
84
|
+
def consume
|
85
|
+
@seq ||= 0
|
86
|
+
@seq %= 1000000 # Keep sequence number reasonable for long runs
|
87
|
+
|
88
|
+
@pds.rewind
|
89
|
+
@seq += 1
|
90
|
+
@pds.put_int @seq
|
91
|
+
|
92
|
+
frame = @queue.pop
|
93
|
+
@pds.put_int frame.size
|
94
|
+
@pds.append_block frame
|
95
|
+
|
96
|
+
size = @pds.size
|
97
|
+
@pds.rewind
|
98
|
+
data = [@packet_header, @pds.get_block(size)].flatten.join
|
99
|
+
@conn.send_udp_packet data
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'wavefile'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module Mumble
|
5
|
+
class AudioRecorder
|
6
|
+
include ThreadTools
|
7
|
+
|
8
|
+
def initialize(client, sample_rate)
|
9
|
+
@client = client
|
10
|
+
@wav_format = WaveFile::Format.new(:mono, :pcm_16, sample_rate)
|
11
|
+
@pds = PacketDataStream.new
|
12
|
+
@pds_lock = Mutex.new
|
13
|
+
|
14
|
+
@decoders = Hash.new do |h, k|
|
15
|
+
h[k] = Opus::Decoder.new sample_rate, sample_rate / 100, 1
|
16
|
+
end
|
17
|
+
|
18
|
+
@queues = Hash.new do |h, k|
|
19
|
+
h[k] = Queue.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def recording?
|
24
|
+
@recording ||= false
|
25
|
+
end
|
26
|
+
|
27
|
+
def start(file)
|
28
|
+
unless recording?
|
29
|
+
@file = WaveFile::Writer.new(file, @wav_format)
|
30
|
+
@callback = @client.on_udp_tunnel { |msg| process_udp_tunnel msg }
|
31
|
+
spawn_thread :write_audio
|
32
|
+
@recording = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop
|
37
|
+
if recording?
|
38
|
+
@client.remove_callback :udp_tunnel, @callback
|
39
|
+
kill_threads
|
40
|
+
@decoders.values.each &:destroy
|
41
|
+
@decoders.clear
|
42
|
+
@queues.clear
|
43
|
+
@file.close
|
44
|
+
@recording = false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def process_udp_tunnel(message)
|
50
|
+
@pds_lock.synchronize do
|
51
|
+
@pds.rewind
|
52
|
+
@pds.append_block message.packet[1..-1]
|
53
|
+
|
54
|
+
@pds.rewind
|
55
|
+
source = @pds.get_int
|
56
|
+
seq = @pds.get_int
|
57
|
+
len = @pds.get_int
|
58
|
+
opus = @pds.get_block len
|
59
|
+
|
60
|
+
@queues[source] << @decoders[source].decode(opus.join)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: Better audio stream merge with normalization
|
65
|
+
def write_audio
|
66
|
+
pcms = @queues.values
|
67
|
+
.reject { |q| q.empty? } # Remove empty queues
|
68
|
+
.map { |q| q.pop.unpack 's*' } # Grab the top element of each queue and expand
|
69
|
+
|
70
|
+
head, *tail = pcms
|
71
|
+
if head
|
72
|
+
samples = head.zip(*tail)
|
73
|
+
.map { |pcms| pcms.reduce(:+) / pcms.size } # Average together all the columns of the matrix (merge audio streams)
|
74
|
+
.flatten # Flatten the resulting 1d matrix
|
75
|
+
@file.write WaveFile::Buffer.new(samples, @wav_format)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mumble
|
2
|
+
class Channel < Model
|
3
|
+
attribute :channel_id do
|
4
|
+
self.data.fetch('channel_id', 0)
|
5
|
+
end
|
6
|
+
attribute :name
|
7
|
+
attribute :parent_id do
|
8
|
+
self.data['parent']
|
9
|
+
end
|
10
|
+
attribute :links do
|
11
|
+
self.data.fetch('links', [])
|
12
|
+
end
|
13
|
+
|
14
|
+
def parent
|
15
|
+
client.channels[parent_id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def children
|
19
|
+
client.channels.values.select do |channel|
|
20
|
+
channel.parent_id == channel_id
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def linked_channels
|
25
|
+
links.map do |channel_id|
|
26
|
+
client.channels[channel_id]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def users
|
31
|
+
client.users.values.select do |user|
|
32
|
+
user.channel_id == channel_id
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def join
|
37
|
+
client.join_channel(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_text(string)
|
41
|
+
client.text_channel(self, string)
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_image(file)
|
45
|
+
client.text_channel_img(self, file)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/mumble-ruby/client.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'thread'
|
2
1
|
require 'hashie'
|
3
2
|
|
4
3
|
module Mumble
|
@@ -7,14 +6,14 @@ module Mumble
|
|
7
6
|
class NoSupportedCodec < StandardError; end
|
8
7
|
|
9
8
|
class Client
|
10
|
-
|
9
|
+
include ThreadTools
|
10
|
+
attr_reader :users, :channels
|
11
11
|
|
12
12
|
CODEC_OPUS = 4
|
13
13
|
|
14
14
|
def initialize(host, port=64738, username="RubyClient", password="")
|
15
15
|
@users, @channels = {}, {}
|
16
16
|
@callbacks = Hash.new { |h, k| h[k] = [] }
|
17
|
-
@connected = false
|
18
17
|
|
19
18
|
@config = Mumble.configuration.dup.tap do |c|
|
20
19
|
c.host = host
|
@@ -26,91 +25,69 @@ module Mumble
|
|
26
25
|
end
|
27
26
|
|
28
27
|
def connect
|
29
|
-
cert_manager = CertManager.new(@config.username, @config.ssl_cert_opts)
|
30
28
|
@conn = Connection.new @config.host, @config.port, cert_manager
|
31
29
|
@conn.connect
|
32
30
|
|
33
|
-
|
31
|
+
init_callbacks
|
34
32
|
version_exchange
|
35
33
|
authenticate
|
36
|
-
init_callbacks
|
37
34
|
|
38
|
-
|
39
|
-
|
35
|
+
spawn_threads :read, :ping
|
36
|
+
connected? # just to get a nice return value
|
40
37
|
end
|
41
38
|
|
42
39
|
def disconnect
|
43
|
-
|
44
|
-
@read_thread.kill
|
45
|
-
@ping_thread.kill
|
40
|
+
kill_threads
|
46
41
|
@conn.disconnect
|
47
42
|
@connected = false
|
48
43
|
end
|
49
44
|
|
50
|
-
def
|
51
|
-
|
45
|
+
def connected?
|
46
|
+
@connected ||= false
|
52
47
|
end
|
53
48
|
|
54
|
-
def
|
55
|
-
|
49
|
+
def cert_manager
|
50
|
+
@cert_manager ||= CertManager.new @config.username, @config.ssl_cert_opts
|
56
51
|
end
|
57
52
|
|
58
|
-
def
|
53
|
+
def recorder
|
59
54
|
raise NoSupportedCodec unless @codec
|
60
|
-
|
55
|
+
@recorder ||= AudioRecorder.new self, @config.sample_rate
|
61
56
|
end
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
define_method "send_#{msg_type}" do |opts|
|
69
|
-
@conn.send_message(msg_type, opts)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def mute(bool=true)
|
74
|
-
send_user_state self_mute: bool
|
58
|
+
def player
|
59
|
+
raise NoSupportedCodec unless @codec
|
60
|
+
@audio_streamer ||= AudioPlayer.new @codec, @conn, @config.sample_rate, @config.bitrate
|
75
61
|
end
|
76
62
|
|
77
|
-
def
|
78
|
-
|
63
|
+
def me
|
64
|
+
users[@session]
|
79
65
|
end
|
80
66
|
|
81
67
|
def join_channel(channel)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
})
|
68
|
+
id = channel_id channel
|
69
|
+
send_user_state(session: @session, channel_id: id)
|
70
|
+
channels[id]
|
86
71
|
end
|
87
72
|
|
88
73
|
def text_user(user, string)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
})
|
74
|
+
session = user_session user
|
75
|
+
send_text_message(session: [user_session(user)], message: string)
|
76
|
+
users[session]
|
93
77
|
end
|
94
78
|
|
95
79
|
def text_user_img(user, file)
|
96
|
-
|
97
|
-
text_user(user, img.to_msg)
|
80
|
+
text_user(user, ImgReader.msg_from_file(file))
|
98
81
|
end
|
99
82
|
|
100
83
|
def text_channel(channel, string)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
})
|
84
|
+
id = channel_id channel
|
85
|
+
send_text_message(channel_id: [id], message: string)
|
86
|
+
channels[id]
|
105
87
|
end
|
106
88
|
|
107
89
|
def text_channel_img(channel, file)
|
108
|
-
|
109
|
-
text_channel(channel, img.to_msg)
|
110
|
-
end
|
111
|
-
|
112
|
-
def user_stats(user)
|
113
|
-
send_user_stats session: user_session(user)
|
90
|
+
text_channel(channel, ImgReader.msg_from_file(file))
|
114
91
|
end
|
115
92
|
|
116
93
|
def find_user(name)
|
@@ -125,11 +102,21 @@ module Mumble
|
|
125
102
|
@callbacks[:connected] << block
|
126
103
|
end
|
127
104
|
|
128
|
-
|
129
|
-
|
130
|
-
|
105
|
+
def remove_callback(symbol, callback)
|
106
|
+
@callbacks[symbol].delete callback
|
107
|
+
end
|
108
|
+
|
109
|
+
Messages.all_types.each do |msg_type|
|
110
|
+
define_method "on_#{msg_type}" do |&block|
|
111
|
+
@callbacks[msg_type] << block
|
112
|
+
end
|
113
|
+
|
114
|
+
define_method "send_#{msg_type}" do |opts|
|
115
|
+
@conn.send_message(msg_type, opts)
|
116
|
+
end
|
131
117
|
end
|
132
118
|
|
119
|
+
private
|
133
120
|
def read
|
134
121
|
message = @conn.read_message
|
135
122
|
sym = message.class.to_s.demodulize.underscore.to_sym
|
@@ -153,9 +140,9 @@ module Mumble
|
|
153
140
|
end
|
154
141
|
on_channel_state do |message|
|
155
142
|
if channel = channels[message.channel_id]
|
156
|
-
channel.
|
143
|
+
channel.update message.to_hash
|
157
144
|
else
|
158
|
-
channels[message.channel_id] =
|
145
|
+
channels[message.channel_id] = Channel.new(self, message.to_hash)
|
159
146
|
end
|
160
147
|
end
|
161
148
|
on_channel_remove do |message|
|
@@ -163,9 +150,9 @@ module Mumble
|
|
163
150
|
end
|
164
151
|
on_user_state do |message|
|
165
152
|
if user = users[message.session]
|
166
|
-
user.
|
153
|
+
user.update(message.to_hash)
|
167
154
|
else
|
168
|
-
users[message.session] =
|
155
|
+
users[message.session] = User.new(self, message.to_hash)
|
169
156
|
end
|
170
157
|
end
|
171
158
|
on_user_remove do |message|
|
@@ -176,15 +163,9 @@ module Mumble
|
|
176
163
|
end
|
177
164
|
end
|
178
165
|
|
179
|
-
def create_encoder
|
180
|
-
@encoder = Opus::Encoder.new @config.sample_rate, @config.sample_rate / 100, 1
|
181
|
-
@encoder.vbr_rate = 0 # CBR
|
182
|
-
@encoder.bitrate = @config.bitrate
|
183
|
-
end
|
184
|
-
|
185
166
|
def version_exchange
|
186
167
|
send_version({
|
187
|
-
version: encode_version(1, 2,
|
168
|
+
version: encode_version(1, 2, 7),
|
188
169
|
release: "mumble-ruby #{Mumble::VERSION}",
|
189
170
|
os: %x{uname -s}.strip,
|
190
171
|
os_version: %x{uname -v}.strip
|
@@ -14,26 +14,24 @@ module Mumble
|
|
14
14
|
end
|
15
15
|
|
16
16
|
class ImgReader
|
17
|
-
|
18
|
-
|
17
|
+
class << self
|
18
|
+
FORMATS = %w(png jpg jpeg svg)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
raise ImgTooLarge unless File.size(file) <= 128 * 1024
|
25
|
-
end
|
26
|
-
|
27
|
-
def ext
|
28
|
-
@ext ||= File.extname(@file)[1..-1]
|
29
|
-
end
|
20
|
+
def msg_from_file(file)
|
21
|
+
@@file = file
|
22
|
+
@@ext = File.extname(@@file)[1..-1]
|
23
|
+
validate_file
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
25
|
+
data = File.read @@file
|
26
|
+
"<img src='data:image/#{@@ext};base64,#{Base64.encode64(data)}'/>"
|
27
|
+
end
|
34
28
|
|
35
|
-
|
36
|
-
|
29
|
+
private
|
30
|
+
def validate_file
|
31
|
+
raise LoadError.new("#{@@file} not found") unless File.exists? @@file
|
32
|
+
raise UnsupportedImgFormat unless FORMATS.include? @@ext
|
33
|
+
raise ImgTooLarge unless File.size(@@file) <= 128 * 1024
|
34
|
+
end
|
37
35
|
end
|
38
36
|
end
|
39
37
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Mumble
|
4
|
+
class Model
|
5
|
+
extend ::Forwardable
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def attribute(name, &block)
|
9
|
+
attributes << name
|
10
|
+
define_method(name) do
|
11
|
+
if block_given?
|
12
|
+
self.instance_eval(&block)
|
13
|
+
else
|
14
|
+
@data[name.to_s]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def attributes
|
20
|
+
@attributes ||= []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(client, data)
|
25
|
+
@client = client
|
26
|
+
@data = data
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(data)
|
30
|
+
@data.merge!(data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
attrs = self.class.attributes.map do |attr|
|
35
|
+
[attr, send(attr)].join("=")
|
36
|
+
end.join(" ")
|
37
|
+
%Q{#<#{self.class.name} #{attrs}>}
|
38
|
+
end
|
39
|
+
|
40
|
+
protected def data
|
41
|
+
@data
|
42
|
+
end
|
43
|
+
|
44
|
+
protected def client
|
45
|
+
@client
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mumble
|
2
|
+
module ThreadTools
|
3
|
+
class DuplicateThread < StandardError; end
|
4
|
+
|
5
|
+
protected
|
6
|
+
def spawn_thread(sym)
|
7
|
+
raise DuplicateThread if threads.has_key? sym
|
8
|
+
threads[sym] = Thread.new { loop { send sym } }
|
9
|
+
end
|
10
|
+
|
11
|
+
def spawn_threads(*symbols)
|
12
|
+
symbols.map { |sym| spawn_thread sym }
|
13
|
+
end
|
14
|
+
|
15
|
+
def kill_threads
|
16
|
+
threads.values.map(&:kill)
|
17
|
+
threads.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def threads
|
21
|
+
@threads ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Mumble
|
2
|
+
class User < Model
|
3
|
+
attribute :session
|
4
|
+
attribute :actor
|
5
|
+
attribute :name
|
6
|
+
attribute :channel_id
|
7
|
+
attribute :hash
|
8
|
+
attribute :comment
|
9
|
+
attribute :mute
|
10
|
+
attribute :deaf
|
11
|
+
attribute :self_mute
|
12
|
+
attribute :self_deaf
|
13
|
+
|
14
|
+
def current_channel
|
15
|
+
client.channels[channel_id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def send_text(string)
|
19
|
+
client.text_user(self, string)
|
20
|
+
end
|
21
|
+
|
22
|
+
def send_image(file)
|
23
|
+
client.text_user_img(self, file)
|
24
|
+
end
|
25
|
+
|
26
|
+
def mute(bool=true)
|
27
|
+
client.send_user_state self_mute: bool
|
28
|
+
end
|
29
|
+
|
30
|
+
def deafen(bool=true)
|
31
|
+
client.send_user_state self_deaf: bool
|
32
|
+
end
|
33
|
+
|
34
|
+
def muted?
|
35
|
+
!!mute || !!self_mute
|
36
|
+
end
|
37
|
+
|
38
|
+
def deafened?
|
39
|
+
!!deaf || !!self_deaf
|
40
|
+
end
|
41
|
+
|
42
|
+
def stats
|
43
|
+
client.send_user_stats session: session
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/mumble-ruby/version.rb
CHANGED
data/mumble-ruby.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mumble-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Perry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: wavefile
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: Ruby API for interacting with a mumble server
|
70
84
|
email:
|
71
85
|
- perrym5@rpi.edu
|
@@ -81,14 +95,19 @@ files:
|
|
81
95
|
- README.rdoc
|
82
96
|
- Rakefile
|
83
97
|
- lib/mumble-ruby.rb
|
84
|
-
- lib/mumble-ruby/
|
98
|
+
- lib/mumble-ruby/audio_player.rb
|
99
|
+
- lib/mumble-ruby/audio_recorder.rb
|
85
100
|
- lib/mumble-ruby/cert_manager.rb
|
101
|
+
- lib/mumble-ruby/channel.rb
|
86
102
|
- lib/mumble-ruby/client.rb
|
87
103
|
- lib/mumble-ruby/connection.rb
|
88
104
|
- lib/mumble-ruby/img_reader.rb
|
89
105
|
- lib/mumble-ruby/messages.rb
|
106
|
+
- lib/mumble-ruby/model.rb
|
90
107
|
- lib/mumble-ruby/mumble.proto
|
91
108
|
- lib/mumble-ruby/packet_data_stream.rb
|
109
|
+
- lib/mumble-ruby/thread_tools.rb
|
110
|
+
- lib/mumble-ruby/user.rb
|
92
111
|
- lib/mumble-ruby/version.rb
|
93
112
|
- mumble-ruby.gemspec
|
94
113
|
homepage: http://www.github.com/perrym5/mumble-ruby
|
@@ -1,67 +0,0 @@
|
|
1
|
-
module Mumble
|
2
|
-
class AudioStream
|
3
|
-
attr_reader :volume
|
4
|
-
|
5
|
-
def initialize(type, target, encoder, file, connection)
|
6
|
-
@type = type
|
7
|
-
@target = target
|
8
|
-
@encoder = encoder
|
9
|
-
@file = File.open(file, 'rb')
|
10
|
-
@conn = connection
|
11
|
-
@seq = 0
|
12
|
-
@compressed_size = 960
|
13
|
-
@pds = PacketDataStream.new
|
14
|
-
@volume = 1.0
|
15
|
-
|
16
|
-
@queue = Queue.new
|
17
|
-
@producer = spawn_thread :produce
|
18
|
-
@consumer = spawn_thread :consume
|
19
|
-
end
|
20
|
-
|
21
|
-
def volume=(volume)
|
22
|
-
@volume = volume / 100.0
|
23
|
-
end
|
24
|
-
|
25
|
-
def stop
|
26
|
-
@producer.kill
|
27
|
-
@consumer.kill
|
28
|
-
@file.close
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
def change_volume(pcm_data)
|
33
|
-
pcm_data.unpack('s*').map { |s| s * @volume }.pack('s*')
|
34
|
-
end
|
35
|
-
|
36
|
-
def packet_header
|
37
|
-
((@type << 5) | @target).chr
|
38
|
-
end
|
39
|
-
|
40
|
-
def produce
|
41
|
-
pcm_data = change_volume @file.read(@encoder.frame_size * 2)
|
42
|
-
@queue << @encoder.encode(pcm_data, @compressed_size)
|
43
|
-
end
|
44
|
-
|
45
|
-
def consume
|
46
|
-
@seq %= 1000000 # Keep sequence number reasonable for long runs
|
47
|
-
|
48
|
-
@pds.rewind
|
49
|
-
@seq += 1
|
50
|
-
@pds.put_int @seq
|
51
|
-
|
52
|
-
frame = @queue.pop
|
53
|
-
len = frame.size
|
54
|
-
@pds.put_int len
|
55
|
-
@pds.append_block frame
|
56
|
-
|
57
|
-
size = @pds.size
|
58
|
-
@pds.rewind
|
59
|
-
data = [packet_header, @pds.get_block(size)].flatten.join
|
60
|
-
@conn.send_udp_packet data
|
61
|
-
end
|
62
|
-
|
63
|
-
def spawn_thread(sym)
|
64
|
-
Thread.new { loop { send sym } }
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|