require "mpd"
class MpdMonitor
# Number of tracks to maintain in the upcoming queue. If set to -1,
# MpdMonitor will always return true from the playlist_too_short? method,
# which can be useful for testing.
QUEUE_SIZE = 3 # -1 for testing
# Number of tracks to allow to remain in the queue's history. It is useful
# to leave this a bit long, since that way, if we crash, MPD will continue
# to play, looping through the tracks in its playlist. If that list is
# long, it should be less annoying to unsuspecting listeners.
HISTORY_SIZE = 60
cattr_accessor :logger
QBeat.setup_logging
def initialize(logger=nil)
# MpdMonitor is used by both QBeat, and Rails. We allow Rails to override
# the log file, so that MpdMonitor log info for the Rails site goes into
# the Rails logfile
@@logger = logger ? logger : RAILS_DEFAULT_LOGGER
end
# Given a track, append it to the MPD playlist. Mark the track queued
# whether we're successful or not.
def append_track_to_playlist(track)
results = conn.search("filename", track.filename)
if results.size > 0 && results.first.file.size > 0 && conn.add(results.first.file.gsub("\\", "\\\\\\")).ok?
logger.info("queued file: #{track.id} #{track.filename}")
else
logger.error(conn.error)
Notifier.deliver_failed_to_queue_track(track.id, track.album.id, track.filename)
end
track.queued
end
def loop_pre
@current_track = conn.current_song unless @current_track
cs = conn.current_song
if playing? && @current_track && cs && @current_track.id != cs.id
@current_track = cs
report_now_playing
if track = Track.find_by_md5sum(@current_track.md5sum)
track.played
end
end
end
def loop_post
return unless playing?
status = conn.status
if status.song > HISTORY_SIZE
(1..(status.song - HISTORY_SIZE)).each {conn.delete(1)}
end
end
def playlist_too_short?
return true if QUEUE_SIZE < 0 # used for testing
status = conn.status
if playing?
queue_size = status.playlistlength - status.song
return queue_size <= QUEUE_SIZE
else
return status.playlistlength <= QUEUE_SIZE
end
end
def get_playlist_info
info = {}
info = {:currentsong => conn.current_song,
:playlistinfo => conn.playlist_info,
:status => conn.status,}
begin
next_up = conn.playlist_info(conn.current_song.pos + 1).first
if next_up.empty?
info[:next_up] = conn.playlist_info.first
else
info[:next_up] = next_up
end
rescue
info[:next_up] = conn.playlist_info.first
end
info
end
private
def playing?
if conn.status.state == "play"
return true
elsif ListenersMonitor.listeners(true) > 0
logger.warn("MPD not playing (#{conn.status.state}), " +
"attempting to begin playing.")
start_playing
return false
end
end
def start_playing
conn.play
conn.repeat(1)
conn.random(0)
end
def conn
return @conn unless @conn.nil?
@conn = Mpd::Connection.new(File.join(RAILS_ROOT, "config", "mpd.yml"))
end
def generate_track_filename(track)
pos = sprintf("%02d", track.position)
# XXX to fix the media merge differences
if track.album.media.size > 1
medium_name = " (#{track.medium.name})"
else
medium_name = ""
end
[pos, track.title, track.album.title + medium_name,
track.artist.name].join("-")
end
# used to search by filename
def prep(filename)
filename.gsub("\"", "\\\\\"").gsub("/", "\\\\\\\\")
end
# used to add by filename
def prep2(filename)
filename.gsub("\\", "\\\\\\\\").gsub("\"", "\\\"")
end
def report_now_playing
logger.info("Now playing \"#{conn.current_song.title}\" by " +
"#{conn.current_song.artist}")
end
end