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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e46d3df87918f5012997b654c5f47a78ed4e19e3
4
- data.tar.gz: 345f9359d29321c10a4a69cceec83c9468d1d8af
3
+ metadata.gz: e8dc2810d57d5afb6c1f6c7ad838e296c0f5b074
4
+ data.tar.gz: 9239fee6276e50cbbad6fdc773556d5abbb4ead4
5
5
  SHA512:
6
- metadata.gz: c3f904facc7c604f718701924fa1b5809e1268eb442b55906800efcfb78c13dea30c324bc3ff432435ac2343c4e7d7a54d573cd70e419538c3e72ec35e1abf5e
7
- data.tar.gz: 0628cfddb4b8e04c2bb34f18e9c17de8a29d151c7371af35918b022346f793a9e23fe5d38decd448160a18f6abe59ce2a9fe930dacb7e37bf9312ff3b4fb703c
6
+ metadata.gz: 0c2ef16226e6063220e1ddfa34f94f673240ae7bbecc09873bd0784771601e5ee7822bfa998d75e0d48f1ba6b85f7673d6f1348dd816a3aa8bc63b3fe9f07e69
7
+ data.tar.gz: 37a828c91ea9df41015fb3be82bedc85d9e168bcf86ef2f2e8b7444788cee2a29582b7c710ae66a775ec2dbfaf610c2534db5fe506b353048badebf20a00b7c1
@@ -1 +1 @@
1
- ruby-2.1.0
1
+ ruby-2.1.1
@@ -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.mute
67
- cli.deafen
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 returns the number of bytes written to the socket)
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: ChannelState Messages
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 ChannelState Message
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.stream_raw_audio('/tmp/mpd.fifo')
96
- # => #<Mumble::AudioStream ...>
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
@@ -1,13 +1,19 @@
1
1
  require 'opus-ruby'
2
- require 'active_support/inflector.rb'
2
+ require 'active_support/inflector'
3
3
  require 'mumble-ruby/version'
4
- require 'mumble-ruby/messages.rb'
5
- require 'mumble-ruby/connection.rb'
6
- require 'mumble-ruby/client.rb'
7
- require 'mumble-ruby/audio_stream.rb'
8
- require 'mumble-ruby/packet_data_stream.rb'
9
- require 'mumble-ruby/img_reader.rb'
10
- require 'mumble-ruby/cert_manager.rb'
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
@@ -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
- attr_reader :users, :channels, :connected
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
- create_encoder
31
+ init_callbacks
34
32
  version_exchange
35
33
  authenticate
36
- init_callbacks
37
34
 
38
- @read_thread = spawn_thread :read
39
- @ping_thread = spawn_thread :ping
35
+ spawn_threads :read, :ping
36
+ connected? # just to get a nice return value
40
37
  end
41
38
 
42
39
  def disconnect
43
- @encoder.destroy
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 me
51
- users[@session]
45
+ def connected?
46
+ @connected ||= false
52
47
  end
53
48
 
54
- def current_channel
55
- channels[me.channel_id]
49
+ def cert_manager
50
+ @cert_manager ||= CertManager.new @config.username, @config.ssl_cert_opts
56
51
  end
57
52
 
58
- def stream_raw_audio(file)
53
+ def recorder
59
54
  raise NoSupportedCodec unless @codec
60
- AudioStream.new(@codec, 0, @encoder, file, @conn)
55
+ @recorder ||= AudioRecorder.new self, @config.sample_rate
61
56
  end
62
57
 
63
- Messages.all_types.each do |msg_type|
64
- define_method "on_#{msg_type}" do |&block|
65
- @callbacks[msg_type] << block
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 deafen(bool=true)
78
- send_user_state self_deaf: bool
63
+ def me
64
+ users[@session]
79
65
  end
80
66
 
81
67
  def join_channel(channel)
82
- send_user_state({
83
- session: me.session,
84
- channel_id: channel_id(channel)
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
- send_text_message({
90
- session: [user_session(user)],
91
- message: string
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
- img = ImgReader.new file
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
- send_text_message({
102
- channel_id: [channel_id(channel)],
103
- message: string
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
- img = ImgReader.new file
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
- private
129
- def spawn_thread(sym)
130
- Thread.new { loop { send sym } }
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.merge! message.to_hash
143
+ channel.update message.to_hash
157
144
  else
158
- channels[message.channel_id] = Hashie::Mash.new(message.to_hash)
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.merge! message.to_hash
153
+ user.update(message.to_hash)
167
154
  else
168
- users[message.session] = Hashie::Mash.new(message.to_hash)
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, 5),
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
- attr_reader :file
18
- FORMATS = %w(png jpg jpeg svg)
17
+ class << self
18
+ FORMATS = %w(png jpg jpeg svg)
19
19
 
20
- def initialize(file)
21
- @file = file
22
- raise LoadError.new("#{file} not found") unless File.exists? file
23
- raise UnsupportedImgFormat unless FORMATS.include? ext
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
- def data
32
- @data ||= File.read @file
33
- end
25
+ data = File.read @@file
26
+ "<img src='data:image/#{@@ext};base64,#{Base64.encode64(data)}'/>"
27
+ end
34
28
 
35
- def to_msg
36
- "<img src='data:image/#{ext};base64,#{Base64.encode64(data)}'/>"
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
@@ -1,3 +1,3 @@
1
1
  module Mumble
2
- VERSION = "1.0.2"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -19,4 +19,5 @@ Gem::Specification.new do |gem|
19
19
  gem.add_dependency "ruby_protobuf"
20
20
  gem.add_dependency "hashie"
21
21
  gem.add_dependency "opus-ruby"
22
+ gem.add_dependency "wavefile"
22
23
  end
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.2
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-02-26 00:00:00.000000000 Z
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/audio_stream.rb
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