class Plllayer

Plllayer provides an interface to an external media player, such as mplayer. It contains a playlist of tracks, which may be as simple as an Array of paths to some audio files. You can then control the playback of this playlist by calling various command-like methods, like play, pause, seek, skip, shuffle, and so on.

Constants

FileNotFoundError

Raise this exception when the file at the given track path doesn’t exist.

InvalidAudioFileError

Raise this one when the file can’t be played for whatever reason

SINGLE_PLAYERS

Attributes

repeat_mode[R]

Public Class Methods

new(*args) click to toggle source

Create a new Plllayer. Optionally, you can pass in an initial playlist to be loaded (won’t start playing until you call play). You can also pass the :external_player option to specify the preferred external player to use. Otherwise, it will try to figure out what external players are available, and attempt to use the best one available.

However, only mplayer is supported at the moment, so this option isn’t useful right now.

TODO: check if external player is available before trying to use it.

# File lib/plllayer.rb, line 36
def initialize(*args)
  options = {}
  options = args.pop if args.last.is_a? Hash
  options[:external_player] ||= :mplayer

  # Look up the single player class, raise error if it doesn't exist.
  single_player_class = SINGLE_PLAYERS[options[:external_player].to_sym]
  if single_player_class.nil?
    raise NotImplementedError, "external player #{options[:external_player]} not supported"
  end

  @single_player = single_player_class.new
  @playlist = []
  append(args.first) unless args.empty?
  @index = nil
  @paused = false
  @playing = false
  @repeat_mode = nil

  @index_mutex = Mutex.new
end

Public Instance Methods

<<(*tracks)
Alias for: append
append(*tracks) click to toggle source

Append tracks to the playlist. Can be done while the playlist is playing. A track is either a String containing the path to the audio file, or an object with a location method that returns the path to the audio file. An ArgumentError is raised when you try to pass a non-track.

This method is aliased as the << operator.

# File lib/plllayer.rb, line 64
def append(*tracks)
  tracks = tracks.flatten
  tracks.each do |track|
    if !track.is_a?(String) && !track.respond_to?(:location)
      raise ArgumentError, "a #{track.class} is not a track (try adding a #location method)"
    end
  end
  @playlist += tracks
  @playlist.dup
end
Also aliased as: <<
back(n = 1) click to toggle source

Play the previous track. Pass a number to go back that many tracks. Treats the playlist like a circular array if the repeat mode is :all.

# File lib/plllayer.rb, line 270
def back(n = 1)
  change_track(-n)
end
clear() click to toggle source

Stop playback and empty the playlist.

# File lib/plllayer.rb, line 170
def clear
  stop
  @playlist.clear
  true
end
formatted_position(options = {}) click to toggle source

Returns the current position of the currently-playing track, as a String like “1:23”.

# File lib/plllayer.rb, line 392
def formatted_position(options = {})
  Plllayer.format_time(position, options)
end
formatted_track_length(options = {}) click to toggle source

Returns the length of the currently-playing track, as a String like “1:23”.

# File lib/plllayer.rb, line 406
def formatted_track_length(options = {})
  if length = track_length
    Plllayer.format_time(length, options)
  end
end
insert(*tracks) click to toggle source

Insert one or more tracks in the playlist, right after the currently-playing track. If no track is playing, insert to the head of the playlist.

# File lib/plllayer.rb, line 79
def insert(*tracks)
  after = @index || -1
  _unsynchronized_insert_at(after + 1, *tracks)
  @playlist.dup
end
insert_at(index, *tracks) click to toggle source

Insert one or more tracks anywhere in the playlist. A negative index may be given to refer to the end of the playlist.

# File lib/plllayer.rb, line 88
def insert_at(index, *tracks)
  index = @playlist.length + index + 1 if index < 0
  unless (0..@playlist.length).include? index
    raise IndexError, "index is out of range"
  end
  @playlist.insert(index, *tracks)
  @index += tracks.length if @index && index < @index
  @playlist.dup
end
mute() click to toggle source

Mute the volume.

# File lib/plllayer.rb, line 334
def mute
  @single_player.mute
end
muted?() click to toggle source

Check if volume is muted.

# File lib/plllayer.rb, line 344
def muted?
  @single_player.muted?
end
pause() click to toggle source

Pause playback.

# File lib/plllayer.rb, line 218
def pause
  if playing? && !paused?
    @paused = true
    @single_player.pause
  else
    false
  end
end
paused?() click to toggle source

Check if playback is paused.

# File lib/plllayer.rb, line 177
def paused?
  @paused
end
play() click to toggle source

Start playing the playlist from beginning to end.

# File lib/plllayer.rb, line 190
def play
  if !@playlist.empty? && !playing?
    @playing = true
    @paused = false
    @index = 0
    play_track
    true
  else
    false
  end
end
playing?() click to toggle source

Check if the playlist is being played. Whether playback is paused doesn’t affect this. False is only returned if either (1) play was never called, (2) stop has been called, or (3) playback stopped after finishing playing all the songs.

# File lib/plllayer.rb, line 185
def playing?
  @playing
end
playlist() click to toggle source

Returns a copy of the playlist.

# File lib/plllayer.rb, line 143
def playlist
  @playlist.dup
end
position() click to toggle source

Returns the current position of the currently-playing track, in milliseconds.

# File lib/plllayer.rb, line 386
def position
  @single_player.position || 0
end
remove(*tracks) click to toggle source

Remove one or more tracks from the playlist by value. If the currently-playing track is removed, it will start playing the next track in the playlist.

# File lib/plllayer.rb, line 101
def remove(*tracks)
  n = nil
  n = tracks.pop if tracks.last.is_a?(Fixnum)
  index = 0
  current_track_removed = false
  @playlist = @playlist.inject([]) do |playlist, track|
    if tracks.include?(track) && (n.nil? || n > 0)
      n &&= n - 1
      @index -= 1 if @index && index < @index
      current_track_removed = true if index == @index
    else
      playlist << track
    end
    index += 1
    playlist
  end
  _unsynchronized_change_track(0) if current_track_removed
  @playlist.dup
end
remove_at(index, n = 1) click to toggle source

Remove one or more tracks from the playlist at a particular index. A negative index may be given to refer to the end of the playlist. If the currently-playing track is removed, it will start playing the next track in the playlist.

# File lib/plllayer.rb, line 125
def remove_at(index, n = 1)
  index = @playlist.length + index if index < 0
  if @playlist.empty? || index < 0 || index + n > @playlist.length
    raise IndexError, "index is out of range"
  end
  @playlist.slice!(index, n)
  if @index && @index > index
    if @index < index + n
      _unsynchronized_change_track(index - @index)
    else
      @index -= n
    end
  end
  @playlist.dup
end
repeat(one_or_all_or_off) click to toggle source

Set the repeat behaviour of the playlist. There are three possible values:

:one    repeat a single track over and over
:all    repeat the whole playlist, treating it like a circular array
:off    play songs consecutively, stop playback when done

The default, of course, is :off.

# File lib/plllayer.rb, line 249
def repeat(one_or_all_or_off)
  case one_or_all_or_off
  when :one
    @repeat_mode = :one
  when :all
    @repeat_mode = :all
  when :off
    @repeat_mode = nil
  else
    raise ArgumentError
  end
  true
end
restart() click to toggle source

Play the currently-playing track from the beginning.

# File lib/plllayer.rb, line 264
def restart
  change_track(0)
end
resume() click to toggle source

Resume playback.

# File lib/plllayer.rb, line 228
def resume
  if playing? && paused?
    @paused = false
    if @single_player.playing?
      @single_player.resume
    else
      play_track @track
      true
    end
  else
    false
  end
end
seek(where) click to toggle source

Seek to a particular position within the currently-playing track. There are multiple ways to specify where to seek to:

seek 10000         # seek 10000 milliseconds forward, relative to the current position
seek -5000         # seek 5000 milliseconds backward
seek "3:45"        # seek to the absolute position 3 minutes and 45 seconds
seek "1:03:45.123" # seek to 1 hour, 3 minutes, 45 seconds, 123 milliseconds
seek 3..45         # syntax sugar for seeking to "3:45"
seek 3..45.123     # syntax sugar for seeking to "3:45.123"
seek abs: 150000   # seek to the absolute position 150000 milliseconds
seek percent: 80   # seek to 80% of the way through the track
# File lib/plllayer.rb, line 292
def seek(where)
  if paused? && !@single_player.playing?
    resume
    pause
  end

  case where
  when Integer
    @single_player.seek(where, :relative)
  when Range
    seconds = where.begin * 60 + where.end
    @single_player.seek(seconds * 1000, :absolute)
  when String
    @single_player.seek(Plllayer.parse_time(where), :absolute)
  when Hash
    if where[:abs]
      if where[:abs].is_a? Integer
        @single_player.seek(where[:abs], :absolute)
      else
        seek(where[:abs])
      end
    elsif where[:percent]
      @single_player.seek(where[:percent], :percent)
    end
  else
    raise ArgumentError, "seek doesn't take a #{where.class}"
  end
end
shuffle() click to toggle source

Shuffle the playlist. If this is done while the playlist is playing, the current song will go to the top of the playlist and the rest of the songs will be shuffled.

# File lib/plllayer.rb, line 361
def shuffle
  current_track = track
  @playlist.shuffle!
  if playing?
    index = @playlist.index(current_track)
    @playlist[0], @playlist[index] = @playlist[index], @playlist[0]
    @index = 0
  end
  true
end
skip(n = 1) click to toggle source

Play the next track. Pass a number to go forward that many tracks. Treats the playlist like a circular array if the repeat mode is :all.

# File lib/plllayer.rb, line 276
def skip(n = 1)
  change_track(n)
end
sort(&by) click to toggle source

Sorts the playlist. Delegates to Array#sort, so a block may be passed to specify what the tracks should be sorted by.

This method is safe to call while the playlist is playing.

# File lib/plllayer.rb, line 376
def sort(&by)
  current_track = track
  @playlist.sort! &by
  if playing?
    @index = @playlist.index(current_track)
  end
  true
end
speed() click to toggle source

Get the playback speed as a Float. 1.0 is normal speed, 2.0 is double speed, 0.5 is half-speed, and so on.

# File lib/plllayer.rb, line 323
def speed
  @single_player.speed || 1.0
end
speed=(new_speed) click to toggle source

Set the playback speed as a Float. 1.0 is normal speed, 2.0 is double speed, 0.5 is half-speed, and so on.

# File lib/plllayer.rb, line 329
def speed=(new_speed)
  @single_player.speed = new_speed
end
stop() click to toggle source

Stop playback.

# File lib/plllayer.rb, line 203
def stop
  if playing?
    @single_player.stop
    @track = nil
    @index = nil
    @paused = false
    @playing = false
    true
  else
    false
  end
end
track() click to toggle source

Get the currently-playing track, or the track that’s about to play if you’re paused between tracks. Returns nil if playback is stopped.

# File lib/plllayer.rb, line 149
def track
  @index ? @playlist[@index] : nil
end
track_index() click to toggle source

Get the index of the currently-playing track, or of the track that’s about to play if you’re paused between tracks. Returns nil if playback is stopped.

# File lib/plllayer.rb, line 156
def track_index
  @index
end
track_length() click to toggle source

Returns the length of the currently-playing track, in milliseconds.

# File lib/plllayer.rb, line 397
def track_length
  if paused? && !@single_player.playing?
    resume
    pause
  end
  @single_player.track_length
end
track_path() click to toggle source

Get the path to the audio file of the currently-playing track.

# File lib/plllayer.rb, line 161
def track_path
  if track.respond_to? :location
    track.location
  else
    track
  end
end
unmute() click to toggle source

Unmute the volume.

# File lib/plllayer.rb, line 339
def unmute
  @single_player.unmute
end
volume() click to toggle source

Get the volume, as a percentage.

# File lib/plllayer.rb, line 349
def volume
  @single_player.volume
end
volume=(new_volume) click to toggle source

Set the volume, as a percentage.

# File lib/plllayer.rb, line 354
def volume=(new_volume)
  @single_player.volume = new_volume
end

Private Instance Methods

change_track(by = 1, options = {}) click to toggle source
# File lib/plllayer.rb, line 414
def change_track(by = 1, options = {})
  if playing?
    @index += by
    if @repeat_mode
      case @repeat_mode
      when :one
        @index -= by if options[:auto]
      when :all
        @index %= @playlist.length
      end
    end
    if track && @index >= 0
      @single_player.stop if paused? && @single_player.active?
      play_track if not paused?
    else
      _unsynchronized_stop
    end
    true
  else
    false
  end
end
play_track() click to toggle source
# File lib/plllayer.rb, line 438
def play_track
  @single_player.play(track_path) do
    change_track(1, auto: true)
    ActiveRecord::Base.connection.close if defined?(ActiveRecord)
  end
  @paused = false
  true
end