mumbletune 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +66 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +1 -0
- data/bin/mumbletune +6 -0
- data/docs/commands.md +13 -0
- data/lib/mumbletune/collection.rb +17 -0
- data/lib/mumbletune/handle_sp_error.rb +23 -0
- data/lib/mumbletune/messages.rb +163 -0
- data/lib/mumbletune/mpd_client.rb +180 -0
- data/lib/mumbletune/mumble_client.rb +55 -0
- data/lib/mumbletune/resolver.rb +148 -0
- data/lib/mumbletune/sp_uri_server.rb +42 -0
- data/lib/mumbletune/spotify_track.rb +90 -0
- data/lib/mumbletune/templates/commands.mustache +100 -0
- data/lib/mumbletune/templates/queue.mustache +41 -0
- data/lib/mumbletune/track.rb +32 -0
- data/lib/mumbletune/version.rb +3 -0
- data/lib/mumbletune.rb +79 -0
- data/mumbletune.gemspec +36 -0
- metadata +246 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mumbletune (0.1.0)
|
5
|
+
eventmachine
|
6
|
+
meta-spotify
|
7
|
+
mumble-ruby
|
8
|
+
mustache
|
9
|
+
ruby-mpd
|
10
|
+
rubypython
|
11
|
+
sinatra
|
12
|
+
text
|
13
|
+
thin
|
14
|
+
|
15
|
+
GEM
|
16
|
+
remote: https://rubygems.org/
|
17
|
+
specs:
|
18
|
+
activesupport (3.2.12)
|
19
|
+
i18n (~> 0.6)
|
20
|
+
multi_json (~> 1.0)
|
21
|
+
blankslate (3.1.2)
|
22
|
+
celt-ruby (0.0.1)
|
23
|
+
ffi
|
24
|
+
daemons (1.1.9)
|
25
|
+
eventmachine (1.0.3)
|
26
|
+
ffi (1.4.0)
|
27
|
+
httparty (0.10.2)
|
28
|
+
multi_json (~> 1.0)
|
29
|
+
multi_xml (>= 0.5.2)
|
30
|
+
i18n (0.6.4)
|
31
|
+
meta-spotify (0.3.0)
|
32
|
+
httparty (> 0.8)
|
33
|
+
multi_json (1.6.1)
|
34
|
+
multi_xml (0.5.3)
|
35
|
+
mumble-ruby (0.0.3)
|
36
|
+
activesupport
|
37
|
+
celt-ruby
|
38
|
+
ruby_protobuf
|
39
|
+
mustache (0.99.4)
|
40
|
+
rack (1.5.2)
|
41
|
+
rack-protection (1.5.0)
|
42
|
+
rack
|
43
|
+
rake (10.0.3)
|
44
|
+
ruby-mpd (0.2.1)
|
45
|
+
ruby_protobuf (0.4.11)
|
46
|
+
rubypython (0.6.3)
|
47
|
+
blankslate (>= 2.1.2.3)
|
48
|
+
ffi (>= 1.0.7)
|
49
|
+
sinatra (1.4.1)
|
50
|
+
rack (~> 1.5, >= 1.5.2)
|
51
|
+
rack-protection (~> 1.4)
|
52
|
+
tilt (~> 1.3, >= 1.3.4)
|
53
|
+
text (1.2.1)
|
54
|
+
thin (1.5.0)
|
55
|
+
daemons (>= 1.0.9)
|
56
|
+
eventmachine (>= 0.12.6)
|
57
|
+
rack (>= 1.0.0)
|
58
|
+
tilt (1.3.5)
|
59
|
+
|
60
|
+
PLATFORMS
|
61
|
+
ruby
|
62
|
+
|
63
|
+
DEPENDENCIES
|
64
|
+
bundler (~> 1.3)
|
65
|
+
mumbletune!
|
66
|
+
rake
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Elliott Williams
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Mumbletune
|
2
|
+
|
3
|
+
MUMBLETUNE is an amiable bot that connects to a mumble server and allows users to interact with and play a queue of music. It currently plays music through Spotify.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
First, install Hexxeh's (spotify-websocket-api)[https://github.com/Hexxeh/spotify-websocket-api].
|
8
|
+
|
9
|
+
git clone git://github.com/Hexxeh/spotify-websocket-api.git
|
10
|
+
cd spotify-websocket-api
|
11
|
+
python setup.py build
|
12
|
+
puthon setup.py install
|
13
|
+
|
14
|
+
Then, add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'mumbletune'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install mumbletune
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
1. Create a configuration file. See `conf.example.yaml` for help.
|
29
|
+
2. Start Mumbletune, passing your config with `-c`.
|
30
|
+
|
31
|
+
$ mumbletune -c config_file.yaml
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mumbletune
ADDED
data/docs/commands.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Command Action
|
2
|
+
------ ------
|
3
|
+
play <track> Queue this song.
|
4
|
+
play <artist> Queue 10 tracks by this artist.
|
5
|
+
play <album> Queue this entire album.
|
6
|
+
play <something> now Play this shit right now!
|
7
|
+
what Gets what is playing, and what is queued.
|
8
|
+
next Jump to the next song in the queue.
|
9
|
+
clear Clears the current queue.
|
10
|
+
volume? Get the current volume level.
|
11
|
+
volume [0-100] Set the volume.
|
12
|
+
wisdom Obtain it.
|
13
|
+
------ ------
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Mumbletune
|
2
|
+
class Collection
|
3
|
+
attr_accessor :type, :tracks, :description, :user
|
4
|
+
|
5
|
+
def initialize(type, tracks, description)
|
6
|
+
@type = type
|
7
|
+
@description = description
|
8
|
+
|
9
|
+
if tracks.class == Array
|
10
|
+
@tracks = tracks
|
11
|
+
else
|
12
|
+
@tracks = [tracks]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mumbletune
|
2
|
+
|
3
|
+
# The Spotify Metadata API seems to throw 502 (Bad Gateway) errors *a lot*.
|
4
|
+
# There's some complaint about this online, and I can reproduce it with
|
5
|
+
# plenty of REST clients, so I am sure it's Spotify's problem. But they
|
6
|
+
# don't seem too interested in fixing what appears to be a long-standing
|
7
|
+
# issue. Ho hum.
|
8
|
+
def self.handle_sp_error
|
9
|
+
begin
|
10
|
+
yield
|
11
|
+
rescue MetaSpotify::ServerError => err
|
12
|
+
puts "Caught ServerError: #{err}"
|
13
|
+
failed ||= 0
|
14
|
+
failed += 1
|
15
|
+
if failed < 4
|
16
|
+
sleep 1
|
17
|
+
retry
|
18
|
+
else
|
19
|
+
raise
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'text'
|
3
|
+
require 'mustache'
|
4
|
+
|
5
|
+
Thread.abort_on_exception=true # development only
|
6
|
+
|
7
|
+
module Mumbletune
|
8
|
+
|
9
|
+
class Message
|
10
|
+
|
11
|
+
# class methods
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :template
|
15
|
+
end
|
16
|
+
self.template = Hash.new
|
17
|
+
|
18
|
+
def self.parse(client, data)
|
19
|
+
message = Message.new(client, data)
|
20
|
+
|
21
|
+
begin
|
22
|
+
case message.text
|
23
|
+
|
24
|
+
when /^play/i
|
25
|
+
if message.argument.length > 0 # user wants to play something
|
26
|
+
if message.words.last =~ /now/i
|
27
|
+
play_now = true
|
28
|
+
message.argument = message.words[1...message.words.length-1].join(" ")
|
29
|
+
else
|
30
|
+
play_now = false
|
31
|
+
end
|
32
|
+
|
33
|
+
# reassurance that it's working
|
34
|
+
message.respond "I'm searching. Hang tight."
|
35
|
+
|
36
|
+
collection = Mumbletune.resolve(message.argument)
|
37
|
+
|
38
|
+
# handle unknown argument
|
39
|
+
return message.respond "I couldn't find what you wanted me to play. :'(" unless collection
|
40
|
+
|
41
|
+
# associate the collection with a user
|
42
|
+
collection.user = message.sender.name
|
43
|
+
|
44
|
+
# add these tracks to the queue
|
45
|
+
Mumbletune.player.add_collection collection, (play_now) ? true : false
|
46
|
+
|
47
|
+
if play_now
|
48
|
+
message.respond_all "#{message.sender.name} is playing #{collection.description} RIGHT NOW."
|
49
|
+
else
|
50
|
+
message.respond_all "#{message.sender.name} added #{collection.description} to the queue."
|
51
|
+
end
|
52
|
+
|
53
|
+
Mumbletune.player.play unless Mumbletune.player.playing?
|
54
|
+
|
55
|
+
|
56
|
+
else # user wants to unpause
|
57
|
+
if Mumbletune.player.paused?
|
58
|
+
Mumbletune.player.play
|
59
|
+
message.respond "Unpaused."
|
60
|
+
else
|
61
|
+
message.respond "Not paused."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
when /^pause$/i
|
66
|
+
paused = Mumbletune.player.pause
|
67
|
+
response = (paused) ? "Paused." : "Unpaused."
|
68
|
+
message.respond response
|
69
|
+
|
70
|
+
when /^unpause$/i
|
71
|
+
if Mumbletune.player.paused?
|
72
|
+
Mumbletune.player.play
|
73
|
+
message.respond "Unpaused."
|
74
|
+
else
|
75
|
+
message.respond "Not paused."
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
when /^next$/i
|
80
|
+
Mumbletune.player.next
|
81
|
+
current = Mumbletune.player.current_song
|
82
|
+
message.respond "OK, now playing #{current.artist} - #{current.name}"
|
83
|
+
|
84
|
+
when /^clear$/i
|
85
|
+
Mumbletune.player.clear_queue
|
86
|
+
message.respond "#{message.sender.name} cleared the queue."
|
87
|
+
|
88
|
+
when /^undo$/i
|
89
|
+
removed = Mumbletune.player.undo
|
90
|
+
message.respond_all "#{message.sender.name} removed #{removed.description} that #{removed.user} added."
|
91
|
+
|
92
|
+
|
93
|
+
when /^(what|queue)$/i
|
94
|
+
queue = Mumbletune.player.queue
|
95
|
+
|
96
|
+
# Now, a template.
|
97
|
+
rendered = Mustache.render Message.template[:queue],
|
98
|
+
:queue => queue,
|
99
|
+
:anything? => (queue.empty?) ? false : true
|
100
|
+
message.respond rendered
|
101
|
+
|
102
|
+
when /^volume\?$/i
|
103
|
+
message.respond "The volume is #{Mumbletune.player.volume?}."
|
104
|
+
|
105
|
+
when /^volume/i
|
106
|
+
if message.argument.length == 0
|
107
|
+
message.respond "The volume is #{Mumbletune.player.volume?}."
|
108
|
+
else
|
109
|
+
Mumbletune.player.volume(message.argument)
|
110
|
+
message.respond "Now the volume is #{Mumbletune.player.volume?}."
|
111
|
+
end
|
112
|
+
|
113
|
+
when /^wisdom$/i
|
114
|
+
message.respond `fortune`
|
115
|
+
|
116
|
+
when /^help$/i
|
117
|
+
rendered = Mustache.render Message.template[:commands]
|
118
|
+
message.respond rendered
|
119
|
+
|
120
|
+
else # Unknown command was given.
|
121
|
+
rendered = Mustache.render Message.template[:commands],
|
122
|
+
:unknown => { :command => message.text }
|
123
|
+
message.respond rendered
|
124
|
+
end
|
125
|
+
|
126
|
+
rescue => err # Catch any command that errored.
|
127
|
+
message.respond "Woah, an error occurred: #{err.message}"
|
128
|
+
puts "#{err.class}: #{err.message}"
|
129
|
+
puts err.backtrace
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# instance methods
|
135
|
+
|
136
|
+
attr_accessor :client, :sender, :text, :command, :argument, :words
|
137
|
+
|
138
|
+
def initialize(client, data)
|
139
|
+
@client = client
|
140
|
+
@sender = client.users[data[:actor]] # users are stored by their session ID
|
141
|
+
@me = client.me
|
142
|
+
@text = data[:message]
|
143
|
+
|
144
|
+
@words = @text.split
|
145
|
+
@command = words[0]
|
146
|
+
@argument = words[1...words.length].join(" ")
|
147
|
+
end
|
148
|
+
|
149
|
+
def respond(message)
|
150
|
+
@client.text_user(@sender.session, message)
|
151
|
+
end
|
152
|
+
|
153
|
+
def respond_all(message) # send to entire channel
|
154
|
+
@client.text_channel(@me.channel_id, message)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# load templates
|
159
|
+
Dir.glob(File.dirname(__FILE__) + "/templates/*.mustache").each do |f_path|
|
160
|
+
f = File.open(f_path)
|
161
|
+
Message.template[File.basename(f_path, ".mustache").to_sym] = f.read
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'ruby-mpd'
|
2
|
+
|
3
|
+
module Mumbletune
|
4
|
+
|
5
|
+
class Player
|
6
|
+
|
7
|
+
attr_accessor :history
|
8
|
+
|
9
|
+
def initialize(host=Mumbletune.config['mpd']['host'], port=Mumbletune.config['mpd']['port'])
|
10
|
+
@mpd = MPD.new(host, port)
|
11
|
+
@mpd.connect true # 'true' enables callbacks
|
12
|
+
@mpd.clear
|
13
|
+
|
14
|
+
@history = Array.new
|
15
|
+
@prev_id = 0
|
16
|
+
|
17
|
+
self.default_volume
|
18
|
+
self.establish_callbacks
|
19
|
+
end
|
20
|
+
|
21
|
+
# Setup
|
22
|
+
|
23
|
+
def default_volume
|
24
|
+
@mpd.volume = Mumbletune.config["mpd"]["default_volume"] || 100
|
25
|
+
end
|
26
|
+
|
27
|
+
def establish_callbacks
|
28
|
+
@mpd.on :connection do |status|
|
29
|
+
if status == false
|
30
|
+
@mpd.connect true
|
31
|
+
else
|
32
|
+
puts ">> MPD happens to be connected."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Fires when currently playing song changes.
|
37
|
+
@mpd.on :songid do |id|
|
38
|
+
|
39
|
+
# Clear old tracks from the store.
|
40
|
+
Track.store.delete_if { |t| t.mpd_id == @prev_id }
|
41
|
+
|
42
|
+
@prev_id = id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Status methods
|
47
|
+
|
48
|
+
def playing?
|
49
|
+
state = @mpd.status[:state]
|
50
|
+
if state =~ /^(play|pause)$/i
|
51
|
+
true
|
52
|
+
else
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def paused?
|
58
|
+
state = @mpd.status[:state]
|
59
|
+
if state == :pause
|
60
|
+
true
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
# MPD Settings
|
68
|
+
|
69
|
+
def volume?
|
70
|
+
@mpd.volume
|
71
|
+
end
|
72
|
+
|
73
|
+
def volume(percent)
|
74
|
+
@mpd.volume = percent
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
# Queue
|
80
|
+
|
81
|
+
def add_collection(col, now=false)
|
82
|
+
col.tracks.each do |t|
|
83
|
+
id = @mpd.addid t.url,
|
84
|
+
(now) ? col.tracks.index(t)+1 : nil
|
85
|
+
t.mpd_id = id
|
86
|
+
end
|
87
|
+
|
88
|
+
@history.push col
|
89
|
+
|
90
|
+
@mpd.next if now
|
91
|
+
end
|
92
|
+
|
93
|
+
def queue
|
94
|
+
# associate known Tracks with Queue items
|
95
|
+
mapped_queue = @mpd.queue.map do |mpd_song|
|
96
|
+
t = Track.retreive_from_mpd_id(mpd_song.id)
|
97
|
+
t.queue_pos = mpd_song.pos
|
98
|
+
t
|
99
|
+
end
|
100
|
+
# mapped_queue.delete_if { |t| t == current_song }
|
101
|
+
mapped_queue
|
102
|
+
end
|
103
|
+
|
104
|
+
def current_song
|
105
|
+
Track.retreive_from_mpd_id(@mpd.current_song.id) if @mpd.playing?
|
106
|
+
end
|
107
|
+
|
108
|
+
def undo
|
109
|
+
last_collection = @history.pop
|
110
|
+
|
111
|
+
last_collection.tracks.each do |t|
|
112
|
+
to_delete = @mpd.queue.select { |mpd_song| mpd_song.id == t.mpd_id }.first
|
113
|
+
@mpd.delete(to_delete.pos) if to_delete
|
114
|
+
end
|
115
|
+
last_collection
|
116
|
+
end
|
117
|
+
|
118
|
+
def clear_all
|
119
|
+
@mpd.clear
|
120
|
+
end
|
121
|
+
|
122
|
+
def clear_queue
|
123
|
+
current = @mpd.current_song
|
124
|
+
@mpd.queue.each do |t|
|
125
|
+
@mpd.delete :id => t.id unless t.id == current.id
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
=begin
|
131
|
+
# Deprecated add methods. Time to remove?
|
132
|
+
|
133
|
+
def add(track)
|
134
|
+
id = @mpd.addid track.url
|
135
|
+
track.mpd_id = id
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_batch(tracks)
|
139
|
+
tracks.each do |t|
|
140
|
+
id = @mpd.addid t.url
|
141
|
+
t.mpd_id = id
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_now(track)
|
146
|
+
id = @mpd.addid track.url, 1
|
147
|
+
track.mpd_id = id
|
148
|
+
@mpd.next
|
149
|
+
end
|
150
|
+
|
151
|
+
def add_now_batch(tracks)
|
152
|
+
tracks.each do |t|
|
153
|
+
id = @mpd.addid t.url, tracks.index(t)+1
|
154
|
+
t.mpd_id = id
|
155
|
+
end
|
156
|
+
@mpd.next
|
157
|
+
end
|
158
|
+
=end
|
159
|
+
|
160
|
+
# Playback commands
|
161
|
+
|
162
|
+
def play
|
163
|
+
@mpd.play
|
164
|
+
end
|
165
|
+
|
166
|
+
def pause
|
167
|
+
@mpd.pause = (@mpd.playing?) ? true : false
|
168
|
+
end
|
169
|
+
|
170
|
+
def next
|
171
|
+
@mpd.next
|
172
|
+
end
|
173
|
+
|
174
|
+
def stop
|
175
|
+
@mpd.stop
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'mumble-ruby'
|
2
|
+
|
3
|
+
module Mumbletune
|
4
|
+
class MumbleClient
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
opts = Mumbletune.config['mumble']
|
8
|
+
@cli = Mumble::Client.new(opts['host'], opts['port'], opts['username'], opts['password'])
|
9
|
+
|
10
|
+
@cli.on_server_sync do |message| # Once connected.
|
11
|
+
@cli.session = message.session # housekeeping for mumble-ruby
|
12
|
+
connect_to = @cli.channels.select { |key, hash| hash["name"] == opts['channel'] }.first[1][:name]
|
13
|
+
@cli.join_channel(connect_to)
|
14
|
+
|
15
|
+
@ready = true
|
16
|
+
puts ">> Connected to Mumble server at #{opts['host']}."
|
17
|
+
end
|
18
|
+
|
19
|
+
@cli.on_text_message do |data|
|
20
|
+
if data[:session].include?(@cli.me[:actor]) # if message was sent to us
|
21
|
+
# interpret the message in a separate thread
|
22
|
+
Thread.new { Message.parse(@cli, data) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect
|
28
|
+
@ready = false
|
29
|
+
@cli.connect
|
30
|
+
|
31
|
+
@ready_wait = Thread.new do
|
32
|
+
sleep 0.1 until @ready
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stream
|
37
|
+
@ready_wait.join
|
38
|
+
input = Mumbletune.config['mpd']['fifo_out']
|
39
|
+
Thread.current.priority = 5
|
40
|
+
puts ">> Streaming to Mumble from #{input}"
|
41
|
+
@cli.stream_raw_audio(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
# on_server_sync is used internally, and our callback overloads its own.
|
50
|
+
# We need access to `session` to handle the function internally
|
51
|
+
module Mumble
|
52
|
+
class Client
|
53
|
+
attr_accessor :session
|
54
|
+
end
|
55
|
+
end
|