mumble-ruby 1.0.2 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|