listlace 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +6 -2
- data/lib/listlace/commands/playback.rb +78 -48
- data/lib/listlace/commands/queue.rb +35 -0
- data/lib/listlace/models/track.rb +10 -6
- data/lib/listlace/mplayer.rb +21 -2
- data/lib/listlace/player.rb +65 -5
- data/lib/listlace.rb +1 -0
- data/listlace.gemspec +2 -2
- metadata +3 -2
data/README
CHANGED
@@ -17,7 +17,7 @@ appropriate, don't you think?
|
|
17
17
|
|
18
18
|
It's a gem. So do this:
|
19
19
|
|
20
|
-
gem install listlace
|
20
|
+
$ gem install listlace
|
21
21
|
|
22
22
|
But you also need mplayer. It has to be
|
23
23
|
at /usr/bin/mplayer, for now.
|
@@ -67,7 +67,11 @@ itself will start playing the queue.
|
|
67
67
|
- restart: seek back to the beginning of the current track
|
68
68
|
- back: go back a song
|
69
69
|
- skip: go to the next song
|
70
|
+
- seek: move to a different part of the track
|
71
|
+
- ff: fast-forward, or slow down
|
72
|
+
- repeat: choose the repeat mode, either :one or :all
|
73
|
+
- norepeat: turn off the repeat mode
|
70
74
|
- status: show what's currently playing, as well as other information
|
71
|
-
*
|
75
|
+
* queue
|
72
76
|
- q: append some tracks to the queue, or get the queue
|
73
77
|
- clear: clear the queue
|
@@ -1,4 +1,7 @@
|
|
1
1
|
module Listlace
|
2
|
+
REPEAT_SYMBOL = "\u221E"
|
3
|
+
TIMES_SYMBOL = "\u00D7"
|
4
|
+
|
2
5
|
# The play command. With no arguments, it either resumes playback or starts
|
3
6
|
# playing the queue. With arguments, it replaces the queue with the given
|
4
7
|
# tracks and starts playing.
|
@@ -6,22 +9,26 @@ module Listlace
|
|
6
9
|
if tracks.empty?
|
7
10
|
if $player.paused?
|
8
11
|
$player.resume
|
9
|
-
status
|
12
|
+
status
|
10
13
|
elsif $player.started?
|
11
|
-
$player.
|
12
|
-
|
14
|
+
if $player.speed == 1
|
15
|
+
$player.pause
|
16
|
+
status
|
17
|
+
else
|
18
|
+
$player.set_speed 1
|
19
|
+
status
|
20
|
+
end
|
13
21
|
else
|
14
22
|
if $player.empty?
|
15
23
|
puts "Nothing to play."
|
16
24
|
else
|
17
25
|
$player.start
|
18
|
-
|
19
|
-
status :playing
|
26
|
+
status
|
20
27
|
end
|
21
28
|
end
|
22
29
|
else
|
23
|
-
stop
|
24
|
-
clear
|
30
|
+
$player.stop
|
31
|
+
$player.clear
|
25
32
|
q *tracks
|
26
33
|
p
|
27
34
|
end
|
@@ -37,13 +44,13 @@ module Listlace
|
|
37
44
|
# Start the current track from the beginning.
|
38
45
|
def restart
|
39
46
|
$player.restart
|
40
|
-
status
|
47
|
+
status
|
41
48
|
end
|
42
49
|
|
43
50
|
# Go back one song in the queue.
|
44
51
|
def back
|
45
52
|
if $player.back
|
46
|
-
status
|
53
|
+
status
|
47
54
|
else
|
48
55
|
puts "End of queue."
|
49
56
|
end
|
@@ -52,54 +59,77 @@ module Listlace
|
|
52
59
|
# Go directly to the next song in the queue.
|
53
60
|
def skip
|
54
61
|
if $player.skip
|
55
|
-
status
|
62
|
+
status
|
56
63
|
else
|
57
64
|
puts "End of queue."
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
q *playlist_or_track.tracks
|
72
|
-
when Array
|
73
|
-
q *playlist_or_track
|
74
|
-
when ActiveRecord::Relation
|
75
|
-
q *playlist_or_track.all
|
76
|
-
end
|
77
|
-
end
|
78
|
-
$player.queue
|
68
|
+
# Seek to a particular position in the current track. If given an integer, it
|
69
|
+
# will seek that many seconds forward or backward. If given a Range, it will
|
70
|
+
# seek to that specific time, the first number in the Range representing the
|
71
|
+
# minutes, the second number representing the seconds. You can also pass a
|
72
|
+
# String like "1:23:45" to do the same thing. To seek to an absolute time in
|
73
|
+
# seconds, do it like "seek(abs: 40)". To seek to a percentage, do something
|
74
|
+
# like "seek(percent: 75)".
|
75
|
+
def seek(where)
|
76
|
+
$player.seek(where)
|
77
|
+
status
|
79
78
|
end
|
80
79
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
80
|
+
# Fast-forward at a particular speed. Induces the chipmunk effect, which I
|
81
|
+
# find agreeable. Call p to go back to normal. You can also pass a value
|
82
|
+
# smaller than one to slow down.
|
83
|
+
def ff(speed = 2)
|
84
|
+
$player.set_speed(speed)
|
85
|
+
status
|
85
86
|
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
88
|
+
# Pass :all to start playing from the top of the queue when it gets to the
|
89
|
+
# end. Pass :one to repeat the current track.
|
90
|
+
def repeat(one_or_all = :all)
|
91
|
+
$player.repeat one_or_all
|
92
|
+
status
|
93
|
+
end
|
94
|
+
|
95
|
+
# Turn off the repeat mode set by the repeat command.
|
96
|
+
def norepeat
|
97
|
+
$player.repeat :off
|
98
|
+
status
|
99
|
+
end
|
100
|
+
|
101
|
+
# Show various information about the status of the player. The information it
|
102
|
+
# shows depends on what status types you pass:
|
103
|
+
#
|
104
|
+
# :playlist - Shows the playlist that is currently playing
|
105
|
+
# :playing - Shows the current track
|
106
|
+
#
|
107
|
+
def status(*types)
|
108
|
+
types = [:playlist, :playing] if types.empty?
|
109
|
+
types.each do |type|
|
110
|
+
case type
|
111
|
+
when :playlist
|
112
|
+
if $player.started?
|
113
|
+
track_number = $player.current_track_index + 1
|
114
|
+
num_tracks = q.length
|
115
|
+
repeat_one = $player.repeat_mode == :one ? REPEAT_SYMBOL : ""
|
116
|
+
repeat_all = $player.repeat_mode == :all ? REPEAT_SYMBOL : ""
|
117
|
+
puts "Playlist: queue (%d%s / %d%s)" % [track_number, repeat_one, num_tracks, repeat_all]
|
118
|
+
else
|
119
|
+
puts "Playlist: queue (%d songs)" % [q.length]
|
120
|
+
end
|
121
|
+
when :playing
|
122
|
+
if $player.started?
|
123
|
+
name = $player.current_track.name
|
124
|
+
artist = $player.current_track.artist
|
125
|
+
time = $player.formatted_current_time
|
126
|
+
total_time = $player.current_track.formatted_total_time
|
127
|
+
paused = $player.paused? ? "|| " : ""
|
128
|
+
speed = $player.speed != 1 ? "#{TIMES_SYMBOL}#{$player.speed} " : ""
|
129
|
+
puts "%s - %s (%s / %s) %s%s" % [name, artist, time, total_time, paused, speed]
|
130
|
+
else
|
131
|
+
puts "Stopped."
|
132
|
+
end
|
103
133
|
end
|
104
134
|
end
|
105
135
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Listlace
|
2
|
+
# The queue command. Simply appends tracks to the queue. Tracks can be
|
3
|
+
# specified by a single Track, a Playlist, an ActiveRecord::Relation, or an
|
4
|
+
# Array containing any of the above. With or without arguments, it returns the
|
5
|
+
# queue as an Array of Tracks, so this can be used as an accessor method.
|
6
|
+
def q(*tracks)
|
7
|
+
tracks.each do |playlist_or_track|
|
8
|
+
case playlist_or_track
|
9
|
+
when Track
|
10
|
+
$player.queue playlist_or_track
|
11
|
+
when Playlist
|
12
|
+
q *playlist_or_track.tracks
|
13
|
+
when Array
|
14
|
+
q *playlist_or_track
|
15
|
+
when ActiveRecord::Relation
|
16
|
+
q *playlist_or_track.all
|
17
|
+
end
|
18
|
+
end
|
19
|
+
$player.queue
|
20
|
+
end
|
21
|
+
|
22
|
+
# Clears the queue.
|
23
|
+
def clear
|
24
|
+
$player.clear
|
25
|
+
puts "Queue cleared."
|
26
|
+
end
|
27
|
+
|
28
|
+
def shuffle
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def sort(by = :artist_asc_album_asc_track_number_asc)
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -4,7 +4,16 @@ module Listlace
|
|
4
4
|
has_many :playlists, through: :playlist_items
|
5
5
|
|
6
6
|
def formatted_total_time
|
7
|
-
|
7
|
+
Track.format_time(total_time)
|
8
|
+
end
|
9
|
+
|
10
|
+
def play
|
11
|
+
$player.queue = [self]
|
12
|
+
Listlace.play
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.format_time(milliseconds)
|
16
|
+
total_seconds = milliseconds / 1000
|
8
17
|
|
9
18
|
seconds = total_seconds % 60
|
10
19
|
minutes = (total_seconds / 60) % 60
|
@@ -16,10 +25,5 @@ module Listlace
|
|
16
25
|
"%d:%02d" % [minutes, seconds]
|
17
26
|
end
|
18
27
|
end
|
19
|
-
|
20
|
-
def play
|
21
|
-
$player.queue = [self]
|
22
|
-
Listlace.play
|
23
|
-
end
|
24
28
|
end
|
25
29
|
end
|
data/lib/listlace/mplayer.rb
CHANGED
@@ -6,19 +6,38 @@ module Listlace
|
|
6
6
|
def initialize(track, &on_quit)
|
7
7
|
cmd = "/usr/bin/mplayer -slave -quiet #{Shellwords.shellescape(track.location)}"
|
8
8
|
@pid, @stdin, @stdout, @stderr = Open4.popen4(cmd)
|
9
|
+
@paused = false
|
10
|
+
@extra_lines = 0
|
9
11
|
|
12
|
+
until @stdout.gets["playback"]
|
13
|
+
end
|
14
|
+
|
15
|
+
@quit_hook_active = false
|
10
16
|
@quit_hook = Thread.new do
|
11
17
|
Process.wait(@pid)
|
18
|
+
@quit_hook_active = true
|
12
19
|
on_quit.call
|
13
20
|
end
|
14
21
|
end
|
15
22
|
|
16
|
-
def command(cmd)
|
23
|
+
def command(cmd, options = {})
|
24
|
+
if cmd == "pause"
|
25
|
+
@paused = !@paused
|
26
|
+
elsif @paused
|
27
|
+
cmd = "pausing #{cmd}"
|
28
|
+
end
|
29
|
+
|
17
30
|
@stdin.puts cmd
|
31
|
+
|
32
|
+
if options[:expect_answer]
|
33
|
+
answer = "\n"
|
34
|
+
answer = @stdout.gets.sub("\e[A\r\e[K", "") while answer == "\n"
|
35
|
+
answer
|
36
|
+
end
|
18
37
|
end
|
19
38
|
|
20
39
|
def quit
|
21
|
-
@quit_hook.kill
|
40
|
+
@quit_hook.kill unless @quit_hook_active
|
22
41
|
command "quit" if alive?
|
23
42
|
end
|
24
43
|
|
data/lib/listlace/player.rb
CHANGED
@@ -3,7 +3,7 @@ module Listlace
|
|
3
3
|
# then plays these tracks sequentially. The buttons for play, pause, next,
|
4
4
|
# previous, etc. are all located here.
|
5
5
|
class Player
|
6
|
-
|
6
|
+
attr_reader :current_track, :current_track_index, :repeat_mode
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
@mplayer = nil
|
@@ -12,6 +12,7 @@ module Listlace
|
|
12
12
|
@current_track_index = nil
|
13
13
|
@paused = false
|
14
14
|
@started = false
|
15
|
+
@repeat_mode = false
|
15
16
|
end
|
16
17
|
|
17
18
|
def queue(track = nil)
|
@@ -72,6 +73,17 @@ module Listlace
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
76
|
+
def repeat(one_or_all_or_off)
|
77
|
+
case one_or_all_or_off
|
78
|
+
when :one
|
79
|
+
@repeat_mode = :one
|
80
|
+
when :all
|
81
|
+
@repeat_mode = :all
|
82
|
+
when :off
|
83
|
+
@repeat_mode = false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
75
87
|
def restart
|
76
88
|
change_track(0)
|
77
89
|
end
|
@@ -84,18 +96,66 @@ module Listlace
|
|
84
96
|
change_track(1)
|
85
97
|
end
|
86
98
|
|
99
|
+
def seek(where)
|
100
|
+
case where
|
101
|
+
when Integer
|
102
|
+
@mplayer.command("seek %d 0" % [where], expect_answer: true)
|
103
|
+
when Range
|
104
|
+
@mplayer.command("seek %d 2" % [where.begin * 60 + where.end], expect_answer: true)
|
105
|
+
when String
|
106
|
+
parts = where.split(":").map(&:to_i)
|
107
|
+
parts = [0] + parts if parts.length == 2
|
108
|
+
hours, minutes, seconds = parts
|
109
|
+
@mplayer.command("seek %d 2" % [hours * 3600 + minutes * 60 + seconds], expect_answer: true)
|
110
|
+
when Hash
|
111
|
+
if where[:abs]
|
112
|
+
if where[:abs].is_a? Integer
|
113
|
+
@mplayer.command("seek %d 2" % [where[:abs]], expect_answer: true)
|
114
|
+
else
|
115
|
+
seek(where[:abs])
|
116
|
+
end
|
117
|
+
elsif where[:percent]
|
118
|
+
@mplayer.command("seek %d 1" % [where[:percent]], expect_answer: true)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def speed
|
124
|
+
answer = @mplayer.command("get_property speed", expect_answer: true)
|
125
|
+
if answer =~ /^ANS_speed=([0-9.]+)$/
|
126
|
+
$1.to_f
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_speed(speed)
|
131
|
+
@mplayer.command("speed_set %f" % [speed], expect_answer: true)
|
132
|
+
end
|
133
|
+
|
87
134
|
def current_time
|
88
|
-
|
135
|
+
answer = @mplayer.command "get_time_pos", expect_answer: true
|
136
|
+
if answer =~ /^ANS_TIME_POSITION=([0-9.]+)$/
|
137
|
+
($1.to_f * 1000).to_i
|
138
|
+
end
|
89
139
|
end
|
90
140
|
|
91
141
|
def formatted_current_time
|
92
|
-
|
142
|
+
Track.format_time(current_time)
|
93
143
|
end
|
94
144
|
|
95
145
|
private
|
96
146
|
|
97
|
-
def change_track(by = 1)
|
147
|
+
def change_track(by = 1, options = {})
|
98
148
|
@current_track_index += by
|
149
|
+
if options[:auto] && @repeat_mode
|
150
|
+
case @repeat_mode
|
151
|
+
when :one
|
152
|
+
@current_track_index -= by
|
153
|
+
when :all
|
154
|
+
if @current_track_index >= @queue.length
|
155
|
+
@current_track_index = 0
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
99
159
|
@current_track = @queue[@current_track_index]
|
100
160
|
if @current_track && @current_track_index >= 0
|
101
161
|
if paused?
|
@@ -112,7 +172,7 @@ module Listlace
|
|
112
172
|
|
113
173
|
def load_track(track)
|
114
174
|
@mplayer.quit if @mplayer
|
115
|
-
@mplayer = MPlayer.new(track) { send
|
175
|
+
@mplayer = MPlayer.new(track) { send(:change_track, 1, auto: true) }
|
116
176
|
@paused = false
|
117
177
|
end
|
118
178
|
end
|
data/lib/listlace.rb
CHANGED
@@ -21,6 +21,7 @@ require "listlace/commands/library"
|
|
21
21
|
require "listlace/commands/playback"
|
22
22
|
require "listlace/commands/selectors"
|
23
23
|
require "listlace/commands/volume"
|
24
|
+
require "listlace/commands/queue"
|
24
25
|
|
25
26
|
# gotta ged rid of this global sometime
|
26
27
|
$player = Listlace::Player.new
|
data/listlace.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "listlace"
|
3
|
-
s.version = "0.0.
|
4
|
-
s.date = "2012-08-
|
3
|
+
s.version = "0.0.3"
|
4
|
+
s.date = "2012-08-24"
|
5
5
|
s.summary = "A music player in a REPL."
|
6
6
|
s.description = "Listlace is a music player which is interacted with through a Ruby REPL."
|
7
7
|
s.author = "Jeremy Ruten"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: listlace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- bin/listlace
|
139
139
|
- lib/listlace/commands/library.rb
|
140
140
|
- lib/listlace/commands/playback.rb
|
141
|
+
- lib/listlace/commands/queue.rb
|
141
142
|
- lib/listlace/commands/selectors.rb
|
142
143
|
- lib/listlace/commands/volume.rb
|
143
144
|
- lib/listlace/database.rb
|