Skip to content
This repository has been archived by the owner on Oct 18, 2019. It is now read-only.

Commit

Permalink
Let's have two servers, player and devplayer, rather than multiplexin…
Browse files Browse the repository at this point in the history
…g on the clients. #16
  • Loading branch information
smashwilson committed Mar 4, 2012
1 parent 127e3f6 commit 2ac522c
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 120 deletions.
1 change: 1 addition & 0 deletions lib/mpg123player/common.rb
Expand Up @@ -60,6 +60,7 @@ def clear

# Determine whether or not this Status has changed sufficiently from +other+ to justify rewriting it to disk.
def is_close_to? other
return false if other.nil?
return false unless @playback_state == other.playback_state
return false unless @artist == other.artist
return false unless @album == other.album
Expand Down
85 changes: 85 additions & 0 deletions lib/mpg123player/development_server.rb
@@ -0,0 +1,85 @@
# Requires an ActiveRecord context.

require 'mpg123player/common'
require 'mpg123player/server'
require 'logger'

module Mpg123Player

# The fake back-end used to support the DevelopmentClient. Launches a single background thread that emulates
# progressing through the queue like the real Server, including consuming and responding to PlayerCommands.
class DevelopmentServer < Server
include ActiveSupport::BufferedLogger::Severity

def create_logger
Logger.new(STDOUT)
end

def main_loop
@logger.info "Entering main loop."

# Record the player pid (of this process).
File.open(@pid_path, 'w') { |f| f.puts Process.pid }

advance
until @shutting_down
play_track
process_command_queue
end
@logger.info "Shutting down."
end

# Advance the playing of the current track by a single second.
def play_track
return if @status.playback_state == :paused
@logger.debug "Playing track: #{@status.seconds} out of #{@status.length}"
sleep 1
@status.seconds += 1
update_status
if @status.seconds >= @status.length
@logger.debug "Track complete. Advancing to next."
@status.clear
@status.playback_state = :stopped
update_status
advance
end
end

# Fake commands that just manipulate the status directly.

def play_action
@status.playback_state = :playing
update_status
end

def pause_action
@status.playback_state = :paused
update_status
end

def jump_action seconds
@status.seconds = seconds.to_f
update_status
end

def volume_action percent
@status.volume = percent.to_f
update_status
end

def restart_action
@status.seconds = 0
update_status
end

def skip_action
advance
end

def shutdown_action
@shutting_down = true
end

end

end
166 changes: 166 additions & 0 deletions lib/mpg123player/production_server.rb
@@ -0,0 +1,166 @@
# Requires an ActiveRecord environment.

require 'io/wait'
require 'logger'

require 'mpg123player/common'
require 'mpg123player/server'
require 'active_support/json'

module Mpg123Player

# Server process that plays enqueued tracks by sending commands through a pipe to an mpg123 child process.
class ProductionServer < Server

def create_logger log_level
Logger.new(@log_path, 1, 1024 * 1024)
end

# Lifecycle events.

# Open a pipe to the player executable and start the parsing loop. Only return once a shutdown command is processed.
def main_loop
@logger.info 'Starting player.'
@pipe = IO.popen("#{@player_path} -R -", 'w+')

# Record the player pid. Also, be sure that the player will be SIGTERM'd if we are.
File.open(@pid_path, 'w') { |f| f.puts @pipe.pid }
Signal.trap('TERM') { Process.kill 'TERM', @pipe.pid }

# Load the first track, or wait for the first track to be enqueued.
advance

# Cycle until a shutdown command is received.
until @shutting_down
process_line(@pipe.gets) if @pipe.ready?
process_command_queue
end

# Kill the mpg123 process.
Process.kill 'TERM', @pipe.pid

@logger.info 'Stopping player.'
@logger.close
end

def load_track t, state = :playing
command = state == :playing ? 'L' : 'LP'
execute "#{command} #{t.path}"
super(t, state)
end

# Actions.

def play_action
execute 'P' if @status.playback_state != :playing
end

def pause_action
execute 'P' if @status.playback_state != :paused
end

# Jump to an absolute track position, specified in seconds.
def jump_action seconds
unless seconds =~ /[0-9]+/
@logger.error "Invalid jump offset: #{seconds}"
return
end
if [:playing, :paused].include?(@status.playback_state)
execute "J #{seconds}s"
@status.seconds = seconds.to_f
update_status
end
end

# Set player volume as a percent, 0 to 100.
def volume_action percent
unless percent =~ /100|[0-9]?[0-9]/
@logger.error "E Invalid volume: #{percent}"
return
end
execute "V #{percent}"
end

# Restart the current track.
def restart_action
execute "J 0"
update_status
end

# Skip to the next enqueued track, if one is present. Stop playback if the queue is empty.
def skip_action
e = EnqueuedTrack.top
if e.nil?
execute 'S'
else
load_track(e.track, @status.playback_state)
end
end

# Unload the track and stop the current player.
def shutdown_action
execute 'Q'
@shutting_down = true
end

protected

# Parse MPG123 remote interface output.
# For documentation, see http://mpg123.org/cgi-bin/viewvc.cgi/tags/1.2.1/doc/README.remote
def process_line line
@logger.debug line

# EOF from pipe.
return if line.nil?

# Startup version message.
return if line =~ /^@R MPG123/

# Stream information that we don't care about.
return if line =~ /^@S /

# Jump feedback.
return if line =~ /^@J/

# ID3v2 metadata tags. We pull track data from ActiveRecord instead.
return if line =~ /^@I/

# Frame info (during playback).
if md = /^@F [0-9-]+ [0-9-]+ ([0-9.-]+) ([0-9.-]+)/.match(line)
@status.seconds = md[1].to_f
update_status
return
end

# Playing status changed.
if md = /^@P (\d+)/.match(line)
transition_to_state([:stopped, :paused, :playing][md[1].to_i])
return
end

# Error.
if md = /^@E (.+)/.match(line)
@logger.error md[1]
return
end

# Volume change.
if md = /^@V (\d+)/.match(line)
@status.volume = md[1].to_i
update_status
return
end

# Unparsed!
@logger.warn "UNPARSED #{line}"
end

# Send a command to the mpg123 pipe.
def execute string
@logger.debug "> #{string}"
@pipe.print "#{string}\n"
end

end

end

0 comments on commit 2ac522c

Please sign in to comment.