class Plllayer::SinglePlayers::MPlayer

This is the SinglePlayer implementation for mplayer. It requires mplayer to be in your $PATH. It uses open4 to start up and communicate with the mplayer process, and mplayer’s slave protocol to issue commands to mplayer.

Attributes

track_path[R]

Public Class Methods

new() click to toggle source

Create a new MPlayer. The mplayer process is only started when the play method is called to start playing a song. The process quits when the song ends. Even though a new process is started for each song, the MPlayer object keeps track of the volume, speed, and mute properties and sets these properties when a new song is played.

# File lib/plllayer/single_players/mplayer.rb, line 14
def initialize
  @paused = false
  @muted = false
  @volume = 50
  @speed = 1.0
  @track_path = nil
end

Public Instance Methods

log_start!() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 161
def log_start!
  @log = true
end
log_stop!() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 165
def log_stop!
  @log = false
end
mute() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 128
def mute
  @muted = true
  answer = _command "mute 1", expect_answer: true
  !!answer
end
muted?() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 124
def muted?
  @muted
end
pause() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 81
def pause
  if not @paused
    @paused = true
    _command "pause"
  else
    false
  end
end
paused?() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 77
def paused?
  @paused
end
play(track_path, &on_end) click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 26
def play(track_path, &on_end)
  # Make sure we're only playing one song at any one time.
  _quit

  # Make sure the audio file exists.
  raise FileNotFoundError, "file '#{track_path}' doesn't exist" unless File.exists? track_path

  # Run the mplayer process in slave mode, passing it the location of
  # the track's audio file.
  cmd = ["mplayer", "-slave", "-quiet", track_path]
  @pid, @stdin, @stdout, @stderr = Open4.popen4(*cmd)

  # This should skip past mplayer's initial lines of output so we can
  # start reading its replies to our commands.
  begin
    line = @stdout.gets
    raise InvalidAudioFileError, "file '#{track_path}' isn't a valid audio file" if line.nil?
  end until line.chomp == "Starting playback..."

  @paused = false
  @track_path = track_path

  # Persist the previous speed, volume, and mute properties into this
  # process.
  self.speed = @speed
  muted = @muted
  self.volume = @volume
  @muted = muted
  mute if @muted

  # Start a thread that waits for the mplayer process to end, then calls
  # the end of song callback. If the #quit method is called, this thread
  # will be killed if it's still waiting for the process to end.
  @quit_hook_active = false
  @quit_hook = Thread.new do
    Process.wait(@pid)
    @quit_hook_active = true
    @paused = false
    @track_path = nil
    on_end.call
  end

  at_exit { stop }

  true
end
playing?() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 22
def playing?
  not @track_path.nil?
end
position() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 151
def position
  answer = _command "get_time_pos", expect_answer: /^ANS_TIME_POSITION=([0-9.]+)$/
  answer ? (answer.to_f * 1000).to_i : false
end
resume() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 90
def resume
  if @paused
    @paused = false
    _command "pause"
  else
    false
  end
end
seek(where, type = :absolute) click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 99
def seek(where, type = :absolute)
  # mplayer talks seconds, not milliseconds.
  seconds = where.to_f / 1_000
  case type
  when :absolute
    _command "seek #{seconds} 2", expect_answer: true
  when :relative
    _command "seek #{seconds} 0", expect_answer: true
  when :percent
    _command "seek #{where} 1", expect_answer: true
  else
    raise NotImplementedError
  end
end
speed() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 114
def speed
  @speed
end
speed=(new_speed) click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 118
def speed=(new_speed)
  @speed = new_speed.to_f
  answer = _command "speed_set #{@speed}", expect_answer: true
  !!answer
end
stop() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 73
def stop
  _quit
end
track_length() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 156
def track_length
  answer = _command "get_time_length", expect_answer: /^ANS_LENGTH=([0-9.]+)$/
  answer ? (answer.to_f * 1000).to_i : false
end
unmute() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 134
def unmute
  @muted = false
  answer = _command "mute 0", expect_answer: true
  !!answer
end
volume() click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 140
def volume
  @volume
end
volume=(new_volume) click to toggle source
# File lib/plllayer/single_players/mplayer.rb, line 144
def volume=(new_volume)
  @muted = false
  @volume = new_volume.to_f
  answer = _command "volume #{@volume} 1", expect_answer: true
  !!answer
end

Private Instance Methods

_alive?() click to toggle source

Check if the mplayer process is still around.

# File lib/plllayer/single_players/mplayer.rb, line 231
def _alive?
  return false if @pid.nil?
  Process.getpgid(@pid)
  true
rescue Errno::ESRCH
  false
end
_command(cmd, options = {}) click to toggle source

Issue a command to mplayer through the slave protocol. False is returned if the process is dead (not playing anything).

If the :expect_answer option is set to true, this will wait for a legible answer back from mplayer, and send it as a return value. If :expect_answer is set to a Regexp, the answer mplayer gives back will be matched to that Regexp and the first match will be returned. If there are no matches, nil will be returned.

# File lib/plllayer/single_players/mplayer.rb, line 179
def _command(cmd, options = {})
  if _alive? and playing?
    # If the player is paused, prefix the command with "pausing ".
    # Otherwise it unpauses when it runs a command. The only exception to
    # this is when the "pause" command itself is issued.
    if @paused and cmd != "pause"
      cmd = "pausing #{cmd}"
    end

    # Send the command to mplayer.
    @stdin.puts cmd
    File.open("log", "a") { |f| f << cmd << "\n" } if @log

    if options[:expect_answer]
      # Read lines of output from mplayer until we get an actual message.
      answer = "\n"
      while answer == "\n"
        answer = @stdout.gets
        File.open("log", "a") { |f| f << answer } if @log
        answer.sub! "\e[A\r\e[K", ""
        answer = "\n" if options[:expect_answer].is_a?(Regexp) && answer !~ options[:expect_answer]
      end

      if options[:expect_answer].is_a? Regexp
        matches = answer.match(options[:expect_answer])
        answer = matches && matches[1]
      end

      answer
    else
      true
    end
  else
    false
  end
end
_quit() click to toggle source

Quit the mplayer process, stopping playback. The end of song callback will not be called if this method is called.

# File lib/plllayer/single_players/mplayer.rb, line 218
def _quit
  if _alive?
    @quit_hook.kill unless @quit_hook_active
    _command "quit"
    @paused = false
    @track_path = nil
    true
  else
    false
  end
end