Class: Plllayer

Inherits:
Object
  • Object
show all
Extended by:
TimeHelpers
Defined in:
lib/plllayer.rb,
lib/plllayer/time_helpers.rb,
lib/plllayer/single_player.rb,
lib/plllayer/single_players/nop.rb,
lib/plllayer/single_players/mplayer.rb

Overview

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.

Defined Under Namespace

Modules: SinglePlayers, TimeHelpers Classes: SinglePlayer

Constant Summary collapse

SINGLE_PLAYERS =
{
  mplayer: Plllayer::SinglePlayers::MPlayer,
  nop: Plllayer::SinglePlayers::Nop
}
FileNotFoundError =

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

Class.new(ArgumentError)
InvalidAudioFileError =

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

Class.new(ArgumentError)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from TimeHelpers

format_time, parse_time

Constructor Details

#initialize(*args) ⇒ Plllayer

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.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 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

Instance Attribute Details

#repeat_modeObject (readonly)

Returns the value of attribute repeat_mode.



24
25
26
# File 'lib/plllayer.rb', line 24

def repeat_mode
  @repeat_mode
end

Instance Method Details

#append(*tracks) ⇒ Object Also known as: <<

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.



64
65
66
67
68
69
70
71
72
73
# 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

#back(n = 1) ⇒ Object

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.



270
271
272
# File 'lib/plllayer.rb', line 270

def back(n = 1)
  change_track(-n)
end

#clearObject

Stop playback and empty the playlist.



170
171
172
173
174
# File 'lib/plllayer.rb', line 170

def clear
  stop
  @playlist.clear
  true
end

#formatted_position(options = {}) ⇒ Object

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



392
393
394
# File 'lib/plllayer.rb', line 392

def formatted_position(options = {})
  Plllayer.format_time(position, options)
end

#formatted_track_length(options = {}) ⇒ Object

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



406
407
408
409
410
# File 'lib/plllayer.rb', line 406

def formatted_track_length(options = {})
  if length = track_length
    Plllayer.format_time(length, options)
  end
end

#insert(*tracks) ⇒ Object

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.



79
80
81
82
83
# 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) ⇒ Object

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



88
89
90
91
92
93
94
95
96
# 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

#muteObject

Mute the volume.



334
335
336
# File 'lib/plllayer.rb', line 334

def mute
  @single_player.mute
end

#muted?Boolean

Check if volume is muted.

Returns:

  • (Boolean)


344
345
346
# File 'lib/plllayer.rb', line 344

def muted?
  @single_player.muted?
end

#pauseObject

Pause playback.



218
219
220
221
222
223
224
225
# File 'lib/plllayer.rb', line 218

def pause
  if playing? && !paused?
    @paused = true
    @single_player.pause
  else
    false
  end
end

#paused?Boolean

Check if playback is paused.

Returns:

  • (Boolean)


177
178
179
# File 'lib/plllayer.rb', line 177

def paused?
  @paused
end

#playObject

Start playing the playlist from beginning to end.



190
191
192
193
194
195
196
197
198
199
200
# 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?Boolean

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.

Returns:

  • (Boolean)


185
186
187
# File 'lib/plllayer.rb', line 185

def playing?
  @playing
end

#playlistObject

Returns a copy of the playlist.



143
144
145
# File 'lib/plllayer.rb', line 143

def playlist
  @playlist.dup
end

#positionObject

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



386
387
388
# File 'lib/plllayer.rb', line 386

def position
  @single_player.position || 0
end

#remove(*tracks) ⇒ Object

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.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# 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) ⇒ Object

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.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# 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) ⇒ Object

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.



249
250
251
252
253
254
255
256
257
258
259
260
261
# 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

#restartObject

Play the currently-playing track from the beginning.



264
265
266
# File 'lib/plllayer.rb', line 264

def restart
  change_track(0)
end

#resumeObject

Resume playback.



228
229
230
231
232
233
234
235
236
237
238
239
240
# 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) ⇒ Object

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


292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# 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

#shuffleObject

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.



361
362
363
364
365
366
367
368
369
370
# 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) ⇒ Object

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.



276
277
278
# File 'lib/plllayer.rb', line 276

def skip(n = 1)
  change_track(n)
end

#sort(&by) ⇒ Object

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.



376
377
378
379
380
381
382
383
# 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

#speedObject

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.



323
324
325
# File 'lib/plllayer.rb', line 323

def speed
  @single_player.speed || 1.0
end

#speed=(new_speed) ⇒ Object

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.



329
330
331
# File 'lib/plllayer.rb', line 329

def speed=(new_speed)
  @single_player.speed = new_speed
end

#stopObject

Stop playback.



203
204
205
206
207
208
209
210
211
212
213
214
# 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

#trackObject

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.



149
150
151
# File 'lib/plllayer.rb', line 149

def track
  @index ? @playlist[@index] : nil
end

#track_indexObject

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.



156
157
158
# File 'lib/plllayer.rb', line 156

def track_index
  @index
end

#track_lengthObject

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



397
398
399
400
401
402
403
# File 'lib/plllayer.rb', line 397

def track_length
  if paused? && !@single_player.playing?
    resume
    pause
  end
  @single_player.track_length
end

#track_pathObject

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



161
162
163
164
165
166
167
# File 'lib/plllayer.rb', line 161

def track_path
  if track.respond_to? :location
    track.location
  else
    track
  end
end

#unmuteObject

Unmute the volume.



339
340
341
# File 'lib/plllayer.rb', line 339

def unmute
  @single_player.unmute
end

#volumeObject

Get the volume, as a percentage.



349
350
351
# File 'lib/plllayer.rb', line 349

def volume
  @single_player.volume
end

#volume=(new_volume) ⇒ Object

Set the volume, as a percentage.



354
355
356
# File 'lib/plllayer.rb', line 354

def volume=(new_volume)
  @single_player.volume = new_volume
end