ruby-mpd
ruby-mpd copied to clipboard
Support `idle`
The information.rb
plugin has an idle
command, but it is currently commented out.
I'd like to be able to watch for changes. As it is I'll have to use background threads shelling out to mpc idle
to implement the same functionality.
https://github.com/archSeer/ruby-mpd#idle
https://github.com/archSeer/ruby-mpd#idle
To implement idle, what is needed is a lock that prevents sending commands to the daemon while waiting for the response (except
noidle
). An intermediate solution would be to queue the commands to send them later, when idle has returned the response.
That's not how I'd do it. Idle is ~useless to me if I can't do anything else once I invoke it. I'd implement idle by having each call to idle
spawn another connection in another thread, ideally with an idleloop
style option that allows the thread and connection to be re-used indefinitely if desired.
Some background on how I'm using idle, in case it helps shape the reason for my argument above:
My (web-based) music player shows the currently-playing song, progress, volume, and a list of all available playlists, and the "up next" live playlist. I want these to be exactly up-to-date.
I could have the web browser poll the web server, and have the web server poll MPD at high frequency. Turns out to be an ugly amount of network traffic and high server CPU usage. (There are currently over 17k songs in the music database. When all of them are added to the up-next queue, it takes several seconds to ask MPD for the current list.)
I could use ruby-mpd callbacks. However, polling the MPD server at 5Hz for changes seems ugly. I worry that it will also be CPU intensive (though I've not yet tried it.)
Instead, I want to write:
# Starts idling on another (identical) connection under another thread.
# Invokes the block once the idle command returns.
# The continuous:true option causes the idle command to be immediately re-issued
# in the same thread/connection (for convenience and minimal thread thrash).
@mpd.idle_until( 'stored_playlist', continuous:true ){ update_playlists }
@mpd.idle_until( 'playlist database', continuous:true ){ update_upnext }
@mpd.idle_until( 'player mixer options', continuous:true ){ update_status }
Right now I'm using EventMachine.defer
to spawn a thread for each of these, and executing mpc idle
. I use Faye to send updates to all clients when a change occurs. It's working well at first glance, but I'd rather not shell to mpc when I have ruby-mpd
.
def initialize
@mpd = MPD.new( ENV['MPD_HOST'], ENV['MPD_PORT'] )
@mpd.connect
@faye = Faye::Client.new("http://#{ENV['RB3JAY_HOST']}:#{ENV['RB3JAY_PORT']}/faye")
watch_for_changes
end
def watch_for_changes
watch_status
watch_playlists
watch_upnext
end
def watch_status
# TODO: replace fast status updates with client-side "dead-reckoning"
# of progress while playing; notify only for play/pause/song changes.
# Run at higher than 1Hz so that the progress bar updates ~smoothly.
EM.add_periodic_timer(0.25) do
if (info=mpd_status) != @last_status
send_status( @last_status=info )
end
end
end
def watch_playlists
EM.defer(
->( ){ idle_until 'stored_playlist' },
->(_){ send_playlists; watch_playlists }
)
end
def watch_upnext
EM.defer(
->( ){ idle_until 'playlist', 'database' },
->(_){ send_next; watch_upnext }
)
end
def idle_until(*events)
`mpc -h #{ENV['MPD_HOST']} -p #{ENV['MPD_PORT']} idle #{events.join(' ')}`
end
def mpd_status
@mpd.status
end
def send_status( info=mpd_status )
@faye.publish '/status', info
end
def up_next
@mpd.queue.slice(0,ENV['RB3JAY_LISTLIMIT'].to_i).map(&:details)
end
def send_next( songs=up_next )
@faye.publish '/next', songs
end
def playlists
@mpd.playlists.map(&:name).grep(/^(?!user-)/).sort
end
def send_playlists( lists=playlists )
@faye.publish '/playlists', playlists
end
I haven't actively been using this library in the past 2 years so I'm unlikely to work on such features, however I will be more than happy to accept pull requests.
I haven't actively been using this library in the past 2 years so I'm unlikely to work on such features, however I will be more than happy to accept pull requests.
Both are good to know. I'll see what I can do. Any suggestion on how to spec or test what I described above (another thread waiting on idle from MPD)?