Class Player
In: srv/player.rb
Parent: Object

Generic class to drive a third-party player application.

Methods
_updatestats    exit    getfinfo    interface    new    pause    paused?    play    playnext    poll?    repeat    restart    start    status?    stop    unpause    updatestats   
Included modules
RemoteSignaller
Public Class methods
interface(prefix)

Return an interface using the given prefix.

# File srv/player.rb, line 51
  def self.interface(prefix)
    XMLRPC::interface(prefix) {
      meth "boolean start(string, int, int)",
	"Play mp3 file after current file. Returns true if file queued."
      meth "int pause()",
	"Pause if playing. Returns PID of player or zero."
      meth "int unpause()",
	"Unpause if paused. Returns PID of player or zero."
      meth "boolean paused?()",
	"Returns true if player is paused."
      meth "int stop()",
	"Stop playing. Returns 0."
      meth "boolean poll?()",
	"Returns true if playing or paused."
      meth "array status?()",
	"array[0] is servername. array[1] is true if playing. " +
	"Rest of array is status dependent."
      meth "int exit()",
	"Causes server to exit. May not return."
    }
  end
new(myname, cmdline)
myname
name by which this server instance is known
cmdline
command line for player; filename replaces "%s"
# File srv/player.rb, line 77
  def initialize(myname, cmdline)
    extend(MonitorMixin)
    @status = [ false ]
    @myname = myname
    @cmdline = cmdline
    @pid = 0
    @queue = nil
    @paused = false
    @file = nil
    @proxy = 0
    @tpaused =
      @tstarted =
      @secsdone =
      @secstotal = 0
    @playcond = new_cond
    Thread.start {
      while true
	playnext; Thread.pass
      end
    }
    Thread.start {
      while sleep(1)
	updatestats; Thread.pass
      end
    }
  end
Public Instance methods
getfinfo(file, secs)

Get playing time of file if not supplied.

file
pathname
secs
supplied playing time of file
# File srv/player.rb, line 109
  def getfinfo(file, secs)
    if file != nil
      if File.file?(file)
	begin
	  secstotal = secs
	  secstotal = Mp3Info.new(file).length if secstotal < 1
	rescue
	  secstotal = 0
	end
      else
	nil
      end
    end
    [ file, secstotal ]
  end
start(file, secs, proxy)

Enter a file into the depth-one play queue.

file
pathname
secs
supplied playing time of file
proxy
PID of proxy playing file; proxy will be killed approximately 5 seconds before file is done playing.
# File srv/player.rb, line 133
  def start(file, secs, proxy)
    arr = getfinfo(file, secs)
    synchronize {
      if arr.nil?
	false
      elsif @queue.nil?
	@queue = arr
	@queue.push(proxy)
	@playcond.signal
	true
      else
	false
      end
    }
  end
playnext()

Main player function. Runs continuously in an infinite loop. Waits for a song to be enqueued, removes it from the queue, and plays it.

# File srv/player.rb, line 152
  def playnext()
    synchronize {
      @playcond.wait_while {
	@queue.nil?
      }
      @file = @queue.shift
      @secstotal = @queue.shift
      @proxy = @queue.shift
      @queue = nil
      @tpaused =
	@secspaused =
	@secsdone = 0
      @tstarted = Time.now.to_i
      add_signalled("HUP", @proxy) if @proxy > 0
      @pid = fork {
	exec("ulimit -c 0; exec " + 
	     @cmdline.sub("%s", @file) + " >/dev/null 2>&1")
      }
      _updatestats
    }
    Process.waitpid(@pid)
    synchronize {
      @pid = 0
      _updatestats
    }
  end
stop()

Kill player app.

# File srv/player.rb, line 180
  def stop()
    unpause
    synchronize {
      Process.kill("TERM", @pid) if @pid > 0
    }
    Thread.pass
    return 0
  end
pause()

Pause player app.

# File srv/player.rb, line 190
  def pause()
    synchronize {
      if @pid > 0 && !@paused
	begin
	  @paused = true
	  Process.kill("STOP", @pid)
	  @tpaused = Time.now.to_i
	rescue
	end
      end
      @pid
    }
  end
unpause()

Resume player app if paused.

# File srv/player.rb, line 205
  def unpause()
    synchronize {
      if @pid > 0 && @paused
	begin
	  @paused = false
	  Process.kill("CONT", @pid)
	  @secspaused += (Time.now.to_i - @tpaused)
	  @tpaused = 0
	rescue
	end
      end
      @pid
    }
  end
status?()

Return array describing current state of player.

# File srv/player.rb, line 221
  def status?()
    synchronize {
      [@myname] + @status
    }
  end
poll?()

Checks if player is playing or paused, or if it is idle.

# File srv/player.rb, line 228
  def poll?()
    synchronize {
      @status.first
    }
  end
paused?()

Checks if player is paused.

# File srv/player.rb, line 235
  def paused?()
    synchronize {
      @paused
    }
  end
play(name, secs, proxy)

Plays song immediately.

file
pathname
secs
supplied playing time of file
proxy
PID of proxy playing file; proxy will be killed approximately 5 seconds before file is done playing.
# File srv/player.rb, line 249
  def play(name, secs, proxy)
    synchronize {
      @playcond.wait_while {
	!@queue.nil?
      }
      @queue = []
      @queue.push(name)
      @queue.push(secs)
      @queue.push(proxy)
      @playcond.signal()
    }
    stop
  end
repeat()

Enqueue current song again. *Warning:* race condition for queue exists.

# File srv/player.rb, line 265
  def repeat()
    synchronize {
      return false if @file.nil?
      @playcond.wait_while {
	!@queue.nil?
      }
      @queue = []
      @queue.push(@name)
      @queue.push(@secs)
      @queue.push(@proxy)
      @playcond.signal()
    }
  end
restart()

Restart play from beginning of song.

# File srv/player.rb, line 280
  def restart()
    synchronize {
      return false if @file.nil?
      file = @file
      secs = @secstotal
      proxy = @proxy
    }
    play(file, secs, proxy)
  end
exit()

Kill XMLRPC server in order to exit application.

# File srv/player.rb, line 291
  def exit()
    Process.kill("HUP",$$)
  end
_updatestats()

Create appropriate status array based on play state.

# File srv/player.rb, line 296
  def _updatestats()
    if @pid > 0
      if @paused
	secsdone = @tpaused - (@tstarted + @secspaused)
      else
	secsdone =  Time.now.to_i - (@tstarted + @secspaused)
      end	  
      pcdone = @secstotal > 0 ? (secsdone * 100) / @secstotal : 0
      @status.clear
      @status.push(true)
      @status.push(@file)
      @status.push(pcdone)
      @status.push(secsdone)
      @status.push(@secstotal)
      if @secstotal - secsdone <= 5
	signal_waiting()
	del_all_signalled()
      end
    else
      @status.clear
      @status.push(false)
    end
  end
updatestats()

Create status array inside monitor. Wrapper for _updatestats.

# File srv/player.rb, line 321
  def updatestats()
    synchronize { _updatestats }
  end