ruby-mpd 0.1.5 → 0.1.7
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.
- data/README.rdoc +100 -45
- data/lib/ruby-mpd.rb +42 -83
- data/lib/ruby-mpd/exceptions.rb +33 -0
- data/lib/ruby-mpd/parser.rb +24 -7
- data/lib/ruby-mpd/playlist.rb +5 -6
- data/lib/ruby-mpd/plugins/channels.rb +2 -5
- data/lib/ruby-mpd/plugins/controls.rb +2 -5
- data/lib/ruby-mpd/plugins/database.rb +44 -6
- data/lib/ruby-mpd/plugins/information.rb +7 -4
- data/lib/ruby-mpd/plugins/playback_options.rb +3 -3
- data/lib/ruby-mpd/plugins/playlists.rb +1 -3
- data/lib/ruby-mpd/plugins/queue.rb +3 -5
- data/lib/ruby-mpd/plugins/reflection.rb +3 -3
- data/lib/ruby-mpd/song.rb +9 -3
- data/ruby-mpd.gemspec +1 -1
- data/{tests → test}/libtests.rb +78 -89
- metadata +6 -11
- data/AUTHORS +0 -1
- data/DOC.rdoc +0 -78
- data/data/database.yaml +0 -347
- data/examples/rmpc.rb +0 -67
- data/examples/tailmpc.rb +0 -115
- data/lib/mpdserver.rb +0 -1206
- data/tests/servertests.rb +0 -3405
data/README.rdoc
CHANGED
@@ -26,7 +26,7 @@ Then, make a new MPD instance:
|
|
26
26
|
|
27
27
|
mpd = MPD.new 'localhost', 6600
|
28
28
|
|
29
|
-
You can also omit the host and
|
29
|
+
You can also omit the host and port, and it will use the defaults.
|
30
30
|
|
31
31
|
mpd = MPD.new 'localhost'
|
32
32
|
mpd = MPD.new
|
@@ -43,7 +43,7 @@ When you are done, disconnect by calling disconnect.
|
|
43
43
|
be fixed by enabling callbacks (see the Callbacks section) or by issuing a
|
44
44
|
`ping` command at certain intervals
|
45
45
|
|
46
|
-
Once connected, you can issue commands to talk to the server
|
46
|
+
Once connected, you can issue commands to talk to the server.
|
47
47
|
|
48
48
|
mpd.connect
|
49
49
|
|
@@ -52,34 +52,45 @@ Once connected, you can issue commands to talk to the server
|
|
52
52
|
song = mpd.current_song
|
53
53
|
puts "Current Song: #{song.artist} - #{song.title}"
|
54
54
|
|
55
|
-
You can find documentation
|
55
|
+
You can find command documentation can be found {here}[http://www.rubydoc.info/github/archSeer/ruby-mpd/master/MPD].
|
56
56
|
|
57
57
|
== Commands
|
58
58
|
|
59
|
+
Some commands require URI paths. ruby-mpd allows you to use MPD::Song objects directly
|
60
|
+
and it extracts the file paths behind the scenes.
|
61
|
+
|
62
|
+
song = mpd.songs_by_artist('Elvis Presley').first # => MPD::Song
|
63
|
+
mpd.add song
|
64
|
+
|
65
|
+
=== Options
|
66
|
+
|
59
67
|
Some commands accept "option hashes" besides their default values. For example, +#move+
|
60
68
|
accepts an ID key instead of the position:
|
61
69
|
|
62
70
|
mpd.move(1, 10) # => move first song to position 10.
|
63
|
-
|
64
|
-
mpd.move(:id => 1, 10) # => move the song with the ID of 1 to position 10.
|
71
|
+
mpd.move({:id => 1}, 10) # => move the song with the ID of 1 to position 10.
|
65
72
|
|
66
73
|
Commands that accept ID's: +#move+, +#delete+, +#play+, +#song_priority+. +#seek+
|
67
|
-
accepts both +:pos+ and +:id+. *Note*:
|
74
|
+
accepts both +:pos+ and +:id+. *Note*: +#swap+ and +#swapid+ are still separate!
|
68
75
|
|
69
76
|
=== Ranges
|
70
77
|
|
71
78
|
Some commands also allow ranges instead of numbers, specifying a range of songs.
|
72
|
-
ruby-mpd correctly handles inclusive and exclusive ranges (1..10 vs 1...10).
|
73
|
-
|
79
|
+
ruby-mpd correctly handles inclusive and exclusive ranges (1..10 vs 1...10). Negative
|
80
|
+
range end means that we want the range to span until the end of the list.
|
81
|
+
|
82
|
+
For example, +#queue+ allows us to return only a subset of the queue:
|
74
83
|
|
75
84
|
mpd.queue.count # => 20
|
76
85
|
|
77
86
|
mpd.queue(1..10).count # => 10
|
78
87
|
|
79
|
-
|
88
|
+
mpd.queue(5..-1).count # => 15 (from 5 to the end of the range)
|
89
|
+
mpd.queue(5...-1).count # => 15 (does the same)
|
80
90
|
|
81
|
-
|
91
|
+
Move also allows specifying ranges to move a range of songs instead of just one.
|
82
92
|
|
93
|
+
mpd.move 1, 10 # => move song 1 to position 10.
|
83
94
|
mpd.move 1..3, 10 # => move songs 1, 2 and 3 to position 10 (and 11 and 12).
|
84
95
|
|
85
96
|
Commands that support ranges: +#delete+, +#move+, +#queue+, +#song_priority+, +#shuffle+,
|
@@ -88,7 +99,7 @@ Commands that support ranges: +#delete+, +#move+, +#queue+, +#song_priority+, +#
|
|
88
99
|
== Searching
|
89
100
|
|
90
101
|
Searching is case insensitive by default. To enable case sensitivity, pass a third
|
91
|
-
argument
|
102
|
+
argument with the key +:case_sensitive+. This does not work for Playlist#searchadd.
|
92
103
|
|
93
104
|
mpd.search(:artist, 'MyArtiSt', :case_sensitive => true)
|
94
105
|
|
@@ -104,7 +115,7 @@ Playlists are one of the objects that map the MPD commands onto a simple to use
|
|
104
115
|
object. Instead of going trough all those function calls, passing data along to
|
105
116
|
get your results, you simply use the object in an object-oriented way:
|
106
117
|
|
107
|
-
mpd.playlists #
|
118
|
+
mpd.playlists # => [MPD::Playlist, MPD::Playlist...]
|
108
119
|
|
109
120
|
playlist = mpd.playlists.first
|
110
121
|
|
@@ -124,8 +135,8 @@ action on them (add, delete, rename).
|
|
124
135
|
|
125
136
|
MPD::Playlist.new(mpd, 'name')
|
126
137
|
|
127
|
-
Currently, one also has to pass in the MPD
|
128
|
-
|
138
|
+
Currently, one also has to pass in the MPD instance, as playlists are tied to a
|
139
|
+
certain connection.
|
129
140
|
|
130
141
|
== Callbacks
|
131
142
|
|
@@ -139,24 +150,24 @@ the server without having to worry about timeouts.
|
|
139
150
|
To make use of callbacks, we need to:
|
140
151
|
|
141
152
|
1. Setup a callback to be called when something happens.
|
142
|
-
2. Connect to the server with callbacks
|
153
|
+
2. Connect to the server with callbacks enabled.
|
143
154
|
|
144
155
|
Firstly, we need to create a callback block and subscribe it, so that will get
|
145
156
|
triggered whenever a specific event happens. When the callback is triggered,
|
146
157
|
it will also recieve the new values of the event that happened.
|
147
158
|
|
148
|
-
So how do we do this? We use the MPD#on method, which sets it all up for us. The
|
159
|
+
So how do we do this? We use the +MPD#on+ method, which sets it all up for us. The
|
149
160
|
argument takes a symbol with the name of the event. The function also requires a block,
|
150
161
|
which is our actual callback that will get called.
|
151
162
|
|
152
163
|
mpd.on :volume do |volume|
|
153
|
-
puts "Volume was set to #{volume}"
|
164
|
+
puts "Volume was set to #{volume}!"
|
154
165
|
end
|
155
166
|
|
156
167
|
One can also use separate methods or Procs and whatnot, just pass them in as a parameter.
|
157
168
|
|
158
169
|
# Proc
|
159
|
-
proc = Proc.new {|volume| puts "Volume was set to #{volume}"
|
170
|
+
proc = Proc.new {|volume| puts "Volume was set to #{volume}!" }
|
160
171
|
mpd.on :volume, &proc
|
161
172
|
|
162
173
|
# Method
|
@@ -167,32 +178,33 @@ One can also use separate methods or Procs and whatnot, just pass them in as a p
|
|
167
178
|
method = self.method(:volume_change)
|
168
179
|
mpd.on :volume, &method
|
169
180
|
|
170
|
-
ruby-mpd supports callbacks for any of the keys returned by MPD#status
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
181
|
+
ruby-mpd supports callbacks for any of the keys returned by +MPD#status+, as well as +:connection+.
|
182
|
+
Here's the full list of events, along with the variables it will return:
|
183
|
+
|
184
|
+
* *volume*: The volume level as an Integer between 0-100.
|
185
|
+
* *repeat*: true or false
|
186
|
+
* *random*: true or false
|
187
|
+
* *single*: true or false
|
188
|
+
* *consume*: true or false
|
189
|
+
* *playlist*: 31-bit unsigned Integer, the playlist version number.
|
190
|
+
* *playlistlength*: Integer, the length of the playlist
|
191
|
+
* *state*: :play, :stop, or :pause, state of the playback.
|
192
|
+
* *song*: An MPD::Song object, representing the current song.
|
193
|
+
* *songid*: playlist songid of the current song stopped on or playing.
|
194
|
+
* *nextsong*: playlist song number of the next song to be played.
|
195
|
+
* *nextsongid*: playlist songid of the next song to be played.
|
196
|
+
* *time*: Returns two variables, *+total+* and *+elapsed+*, Integers representing seconds.
|
197
|
+
* *elapsed*: Float, representing total time elapsed within the current song, but with higher accuracy.
|
198
|
+
* *bitrate*: instantaneous bitrate in kbps.
|
199
|
+
* *xfade*: crossfade in seconds
|
200
|
+
* *mixrampdb*: mixramp threshold in dB (Float)
|
201
|
+
* *mixrampdelay*: mixrampdelay in seconds
|
202
|
+
* *audio*: Returns three variables: sampleRate, bits and channels.
|
203
|
+
* *updating_db*: job id
|
204
|
+
* *error*: if there is an error, returns message here
|
205
|
+
|
206
|
+
* *connection*: Are we connected to the daemon? true or false
|
207
|
+
|
196
208
|
Note that if the callback returns more than one value, the callback needs more arguments
|
197
209
|
in order to recieve those values:
|
198
210
|
|
@@ -208,10 +220,53 @@ in order to recieve those values:
|
|
208
220
|
Finally, the easiest step. In order for callbacks to work, connect to the server
|
209
221
|
with callbacks enabled:
|
210
222
|
|
211
|
-
|
223
|
+
mpd.connect true
|
212
224
|
|
213
225
|
Easy as pie. The above will connect to the server like normal, but this time it will
|
214
226
|
create a new thread that loops until you issue a `disconnect`. This loop checks the
|
215
227
|
server, then sleeps for two tenths of a second, then loops. Because it's continuously
|
216
228
|
polling the server, there's the added benefit of your client not being disconnected
|
217
229
|
due to inactivity.
|
230
|
+
|
231
|
+
== Not yet implemented
|
232
|
+
|
233
|
+
This section documents the features that are missing in this library at the moment.
|
234
|
+
|
235
|
+
=== Command lists
|
236
|
+
|
237
|
+
Command lists are not implemented yet. The proposed API would look like:
|
238
|
+
|
239
|
+
mpd.command_list do
|
240
|
+
volume 80
|
241
|
+
repeat true
|
242
|
+
status
|
243
|
+
end
|
244
|
+
|
245
|
+
What makes me not so eager to implement this is that MPD returns all values one after
|
246
|
+
another. This gets fixed with +command_list_ok_begin+, which returns +list_OK+ for every
|
247
|
+
command used, however then we still get more than one response, and I can't think of a
|
248
|
+
reasonable way to retun all of them back to the user. Maybe just ignore the return values?
|
249
|
+
|
250
|
+
=== Idle
|
251
|
+
|
252
|
+
To implement idle, what is needed is a lock that prevents sending commands to the daemon
|
253
|
+
while waiting for the response (except +noidle+). An intermediate solution would be to
|
254
|
+
queue the commands to send them later, when idle has returned the response.
|
255
|
+
|
256
|
+
Idle seems like a possible way to reimplement callbacks; make a separate connection
|
257
|
+
and just use idle and when it returns, simply use idle again and again.
|
258
|
+
|
259
|
+
=== Tests
|
260
|
+
|
261
|
+
Tests fail at the moment, as they are 6 years old. The entire MPD server mock class
|
262
|
+
either needs to be rewritten, or a mpd.conf along with a sample database and instructions
|
263
|
+
for a controlled environment needs to be written.
|
264
|
+
|
265
|
+
== TODO list
|
266
|
+
|
267
|
+
* MPD::Song, MPD::Directory.
|
268
|
+
* Make stickers a mixin for Playlist, Song, Directory...
|
269
|
+
* Namespace queue
|
270
|
+
* Make parsing per-command (because playlist is an integer in the :status command and
|
271
|
+
a string in :listplaylists, and :time is an array in status and integer in songs)
|
272
|
+
* Per command parsing should also skip :directory and :playlist keys inside #songs command.
|
data/lib/ruby-mpd.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'socket'
|
2
2
|
require 'thread'
|
3
3
|
|
4
|
+
require 'ruby-mpd/exceptions'
|
4
5
|
require 'ruby-mpd/song'
|
5
6
|
require 'ruby-mpd/parser'
|
6
7
|
require 'ruby-mpd/playlist'
|
@@ -16,27 +17,6 @@ require 'ruby-mpd/plugins/outputs'
|
|
16
17
|
require 'ruby-mpd/plugins/reflection'
|
17
18
|
require 'ruby-mpd/plugins/channels'
|
18
19
|
|
19
|
-
# TODO: object oriented: song commands and dir commands
|
20
|
-
# in MPD::Song, MPD::Directory.
|
21
|
-
# Make stickers a mixin for Playlist, Song, Directory...
|
22
|
-
# TODO: Namespace queue?
|
23
|
-
# TODO: fix parser to use build_group also
|
24
|
-
|
25
|
-
# command list as a do block
|
26
|
-
# mpd.command_list do
|
27
|
-
# volume 10
|
28
|
-
# play xyz
|
29
|
-
# end
|
30
|
-
|
31
|
-
# Playlist#rename -> Playlist#name= ?
|
32
|
-
# MPD::Playlist.new(mpd, 'name') no mpd?
|
33
|
-
|
34
|
-
# make it possible to use MPD::Song objects instead of filepath strings
|
35
|
-
|
36
|
-
# commands missing: #plchangeposid, #lsinfo, #listall.
|
37
|
-
|
38
|
-
# error codes stored in ack.h
|
39
|
-
|
40
20
|
# @!macro [new] error_raise
|
41
21
|
# @raise (see #send_command)
|
42
22
|
# @!macro [new] returnraise
|
@@ -45,10 +25,6 @@ require 'ruby-mpd/plugins/channels'
|
|
45
25
|
|
46
26
|
# The main class/namespace of the MPD client.
|
47
27
|
class MPD
|
48
|
-
|
49
|
-
# Standard MPD error.
|
50
|
-
class MPDError < StandardError; end
|
51
|
-
|
52
28
|
include Parser
|
53
29
|
|
54
30
|
include Plugins::Information
|
@@ -64,8 +40,6 @@ class MPD
|
|
64
40
|
|
65
41
|
# The version of the MPD protocol the server is using.
|
66
42
|
attr_reader :version
|
67
|
-
# A list of tags MPD accepts.
|
68
|
-
attr_reader :tags
|
69
43
|
|
70
44
|
# Initialize an MPD object with the specified hostname and port.
|
71
45
|
# When called without arguments, 'localhost' and 6600 are used.
|
@@ -74,6 +48,7 @@ class MPD
|
|
74
48
|
@port = port
|
75
49
|
@socket = nil
|
76
50
|
@version = nil
|
51
|
+
@tags = nil
|
77
52
|
|
78
53
|
@stop_cb_thread = false
|
79
54
|
@mutex = Mutex.new
|
@@ -85,15 +60,18 @@ class MPD
|
|
85
60
|
# that specific event happens.
|
86
61
|
#
|
87
62
|
# mpd.on :volume do |volume|
|
88
|
-
# puts "Volume was set to #{volume}"
|
63
|
+
# puts "Volume was set to #{volume}!"
|
89
64
|
# end
|
90
65
|
#
|
91
66
|
# One can also define separate methods or Procs and whatnot,
|
92
67
|
# just pass them in as a parameter.
|
93
68
|
#
|
94
|
-
# method = Proc.new {|volume| puts "Volume was set to #{volume}"
|
69
|
+
# method = Proc.new {|volume| puts "Volume was set to #{volume}!" }
|
95
70
|
# mpd.on :volume, &method
|
96
71
|
#
|
72
|
+
# @param [Symbol] event The event we wish to listen for.
|
73
|
+
# @param [Proc, Method] block The actual callback.
|
74
|
+
# @return [void]
|
97
75
|
def on(event, &block)
|
98
76
|
@callbacks[event] ||= []
|
99
77
|
@callbacks[event].push block
|
@@ -101,6 +79,7 @@ class MPD
|
|
101
79
|
|
102
80
|
# Triggers an event, running it's callbacks.
|
103
81
|
# @param [Symbol] event The event that happened.
|
82
|
+
# @return [void]
|
104
83
|
def emit(event, *args)
|
105
84
|
@callbacks[event] ||= []
|
106
85
|
@callbacks[event].each do |handle|
|
@@ -120,7 +99,7 @@ class MPD
|
|
120
99
|
# @return [true] Successfully connected.
|
121
100
|
# @raise [MPDError] If connect is called on an already connected instance.
|
122
101
|
def connect(callbacks = false)
|
123
|
-
raise
|
102
|
+
raise ConnectionError, 'Already connected!' if self.connected?
|
124
103
|
|
125
104
|
@socket = File.exists?(@hostname) ? UNIXSocket.new(@hostname) : TCPSocket.new(@hostname, @port)
|
126
105
|
@version = @socket.gets.chomp.gsub('OK MPD ', '') # Read the version
|
@@ -182,14 +161,16 @@ class MPD
|
|
182
161
|
# Disconnect from the MPD daemon. This has no effect if the client is not
|
183
162
|
# connected. Reconnect using the {#connect} method. This will also stop
|
184
163
|
# the callback thread, thus disabling callbacks.
|
164
|
+
# @return [Boolean] True if successfully disconnected, false otherwise.
|
185
165
|
def disconnect
|
186
166
|
@stop_cb_thread = true
|
187
167
|
|
188
|
-
return if
|
168
|
+
return false if !@socket
|
189
169
|
|
190
170
|
@socket.puts 'close'
|
191
171
|
@socket.close
|
192
172
|
@socket = nil
|
173
|
+
return true
|
193
174
|
end
|
194
175
|
|
195
176
|
# Kills the MPD process.
|
@@ -198,8 +179,9 @@ class MPD
|
|
198
179
|
send_command :kill
|
199
180
|
end
|
200
181
|
|
201
|
-
# Used for authentication with the server
|
182
|
+
# Used for authentication with the server.
|
202
183
|
# @param [String] pass Plaintext password
|
184
|
+
# @macro returnraise
|
203
185
|
def password(pass)
|
204
186
|
send_command :password, pass
|
205
187
|
end
|
@@ -210,64 +192,26 @@ class MPD
|
|
210
192
|
send_command :ping
|
211
193
|
end
|
212
194
|
|
213
|
-
|
214
|
-
|
215
|
-
# Lists all of the albums in the database.
|
216
|
-
# The optional argument is for specifying an artist to list
|
217
|
-
# the albums for
|
218
|
-
#
|
219
|
-
# @return [Array<String>] An array of album names.
|
220
|
-
def albums(artist = nil)
|
221
|
-
list :album, artist
|
222
|
-
end
|
223
|
-
|
224
|
-
# Lists all of the artists in the database.
|
225
|
-
#
|
226
|
-
# @return [Array<String>] An array of artist names.
|
227
|
-
def artists
|
228
|
-
list :artist
|
229
|
-
end
|
230
|
-
|
231
|
-
# List all of the directories in the database, starting at path.
|
232
|
-
# If path isn't specified, the root of the database is used
|
233
|
-
#
|
234
|
-
# @return [Array<String>] Array of directory names
|
235
|
-
def directories(path = nil)
|
236
|
-
response = send_command :listall, path
|
237
|
-
return response[:directory]
|
238
|
-
end
|
239
|
-
|
240
|
-
# List all of the files in the database, starting at path.
|
241
|
-
# If path isn't specified, the root of the database is used
|
195
|
+
# Used to send a command to the server, and to recieve the reply.
|
196
|
+
# Reply gets parsed. Synchronized on a mutex to be thread safe.
|
242
197
|
#
|
243
|
-
#
|
244
|
-
|
245
|
-
|
246
|
-
return response[:file]
|
247
|
-
end
|
248
|
-
|
249
|
-
# List all of the songs by an artist.
|
250
|
-
#
|
251
|
-
# @return [Array<MPD::Song>]
|
252
|
-
def songs_by_artist(artist)
|
253
|
-
search :artist, artist
|
254
|
-
end
|
255
|
-
|
256
|
-
# Used to send a command to the server. This synchronizes
|
257
|
-
# on a mutex to be thread safe
|
198
|
+
# Can be used to get low level direct access to MPD daemon. Not
|
199
|
+
# recommended, should be just left for internal use by other
|
200
|
+
# methods.
|
258
201
|
#
|
259
202
|
# @return (see #handle_server_response)
|
260
203
|
# @raise [MPDError] if the command failed.
|
261
204
|
def send_command(command, *args)
|
262
|
-
raise
|
205
|
+
raise ConnectionError, "Not connected to the server!" if @socket.nil?
|
263
206
|
|
264
207
|
@mutex.synchronize do
|
265
208
|
begin
|
266
209
|
@socket.puts convert_command(command, *args)
|
267
|
-
|
210
|
+
response = handle_server_response
|
211
|
+
return parse_response(command, response)
|
268
212
|
rescue Errno::EPIPE
|
269
213
|
@socket = nil
|
270
|
-
raise
|
214
|
+
raise ConnectionError, 'Broken pipe (got disconnected)'
|
271
215
|
end
|
272
216
|
end
|
273
217
|
end
|
@@ -275,8 +219,7 @@ class MPD
|
|
275
219
|
private
|
276
220
|
|
277
221
|
# Handles the server's response (called inside {#send_command}).
|
278
|
-
# Repeatedly reads the server's response from the socket
|
279
|
-
# processes the output.
|
222
|
+
# Repeatedly reads the server's response from the socket.
|
280
223
|
#
|
281
224
|
# @return (see Parser#build_response)
|
282
225
|
# @return [true] If "OK" is returned.
|
@@ -302,11 +245,27 @@ class MPD
|
|
302
245
|
|
303
246
|
if !error
|
304
247
|
return true if msg.empty?
|
305
|
-
return
|
248
|
+
return msg
|
306
249
|
else
|
307
250
|
err = error.match(/^ACK \[(?<code>\d+)\@(?<pos>\d+)\] \{(?<command>.*)\} (?<message>.+)$/)
|
308
|
-
raise
|
251
|
+
raise SERVER_ERRORS[err[:code].to_i], "[#{err[:command]}] #{err[:message]}"
|
309
252
|
end
|
310
253
|
end
|
311
254
|
|
255
|
+
SERVER_ERRORS = {
|
256
|
+
1 => NotListError,
|
257
|
+
2 => ServerArgumentError,
|
258
|
+
3 => IncorrectPassword,
|
259
|
+
4 => PermissionError,
|
260
|
+
5 => ServerError,
|
261
|
+
|
262
|
+
50 => NotFound,
|
263
|
+
51 => PlaylistMaxError,
|
264
|
+
52 => SystemError,
|
265
|
+
53 => PlaylistLoadError,
|
266
|
+
54 => AlreadyUpdating,
|
267
|
+
55 => NotPlaying,
|
268
|
+
56 => AlreadyExists
|
269
|
+
}
|
270
|
+
|
312
271
|
end
|