listlace 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/lib/listlace/commands/player_commands.rb +4 -4
- data/lib/listlace/library/database.rb +2 -3
- data/lib/listlace/library/selectors.rb +4 -12
- data/lib/listlace/library.rb +3 -1
- data/lib/listlace/models/track.rb +6 -22
- data/lib/listlace/player.rb +31 -17
- data/lib/listlace/simple_track.rb +13 -0
- data/lib/listlace/single_player.rb +63 -8
- data/lib/listlace/single_players/mplayer.rb +138 -27
- data/lib/listlace/time_helper.rb +34 -0
- data/lib/listlace.rb +2 -0
- data/listlace.gemspec +2 -2
- metadata +4 -2
data/Gemfile.lock
CHANGED
@@ -123,14 +123,14 @@ module Listlace
|
|
123
123
|
end
|
124
124
|
when :playing
|
125
125
|
if player.started?
|
126
|
-
|
126
|
+
title = player.current_track.title
|
127
127
|
artist = player.current_track.artist
|
128
|
-
time = player.
|
129
|
-
total_time = player.
|
128
|
+
time = Listlace.format_time(player.current_time, include_milliseconds: false)
|
129
|
+
total_time = Listlace.format_time(player.total_time, include_milliseconds: false)
|
130
130
|
paused = player.paused? ? "|| " : ""
|
131
131
|
speed = player.speed
|
132
132
|
speed = speed != 1 ? "#{TIMES_SYMBOL}#{speed} " : ""
|
133
|
-
puts "%s - %s (%s / %s) %s%s" % [
|
133
|
+
puts "%s - %s (%s / %s) %s%s" % [title, artist, time, total_time, paused, speed]
|
134
134
|
else
|
135
135
|
puts "Stopped."
|
136
136
|
end
|
@@ -41,7 +41,7 @@ module Listlace
|
|
41
41
|
ActiveRecord::Schema.define do
|
42
42
|
create_table :tracks do |t|
|
43
43
|
t.integer :original_id
|
44
|
-
t.string :
|
44
|
+
t.string :title
|
45
45
|
t.string :artist
|
46
46
|
t.string :composer
|
47
47
|
t.string :album
|
@@ -61,8 +61,7 @@ module Listlace
|
|
61
61
|
t.integer :sample_rate
|
62
62
|
t.text :comments
|
63
63
|
t.integer :play_count
|
64
|
-
t.
|
65
|
-
t.datetime :play_date_utc
|
64
|
+
t.datetime :play_date
|
66
65
|
t.integer :skip_count
|
67
66
|
t.datetime :skip_date
|
68
67
|
t.integer :rating
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Listlace
|
2
2
|
class Library
|
3
3
|
module Selectors
|
4
|
-
STRING_SELECTORS = %w(
|
4
|
+
STRING_SELECTORS = %w(title artist composer album album_artist genre comments location)
|
5
5
|
INTEGER_SELECTORS = %w(disc_number disc_count track_number track_count year bit_rate sample_rate play_count skip_count rating)
|
6
6
|
|
7
7
|
STRING_SELECTORS.each do |column|
|
@@ -20,10 +20,6 @@ module Listlace
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
# rename the "name" selector to "song"
|
24
|
-
alias_method :song, :name
|
25
|
-
remove_method :name
|
26
|
-
|
27
23
|
# The length selector is an integer selector for the length of a track. A
|
28
24
|
# plain integer given to it represents the number of seconds. It can also take
|
29
25
|
# a String in the format "1:23", to represent 83 seconds, for example. These
|
@@ -32,7 +28,7 @@ module Listlace
|
|
32
28
|
normalize = lambda do |value|
|
33
29
|
case value
|
34
30
|
when String
|
35
|
-
|
31
|
+
Listlace.parse_time(value)
|
36
32
|
when Integer
|
37
33
|
value * 1000
|
38
34
|
when Range
|
@@ -72,16 +68,12 @@ module Listlace
|
|
72
68
|
#
|
73
69
|
# This method shouldn't have to be used directly. Many convenient methods are
|
74
70
|
# generated for you, one for each string field you may want to select on.
|
75
|
-
# These are: artist, composer, album, album_artist, genre, comments,
|
76
|
-
# For example:
|
71
|
+
# These are: title, artist, composer, album, album_artist, genre, comments,
|
72
|
+
# location. For example:
|
77
73
|
#
|
78
74
|
# artist :muse, match: :exact #=> playlist (108 tracks)
|
79
75
|
# composer :rachmanino #=> playlist (33 tracks)
|
80
76
|
#
|
81
|
-
# To match the name of a track, use song:
|
82
|
-
#
|
83
|
-
# song "frontier psychiatrist" #=> playlist (1 track)
|
84
|
-
#
|
85
77
|
def string_selector(column, query, options = {})
|
86
78
|
options[:match] ||= :middle
|
87
79
|
|
data/lib/listlace/library.rb
CHANGED
@@ -70,9 +70,11 @@ module Listlace
|
|
70
70
|
# row already contains a hash of attributes almost ready to be passed to
|
71
71
|
# ActiveRecord. We just need to modify the keys, e.g. change "Play Count"
|
72
72
|
# to "play_count".
|
73
|
+
row["Title"] = row.delete("Name")
|
74
|
+
row["Play Date"] = row.delete("Play Date UTC")
|
75
|
+
row["Original ID"] = row.delete("Track ID")
|
73
76
|
attributes = row.inject({}) do |acc, (key, value)|
|
74
77
|
attribute = key.gsub(" ", "").underscore
|
75
|
-
attribute = "original_id" if attribute == "track_id"
|
76
78
|
acc[attribute] = value if whitelist.include? attribute
|
77
79
|
acc
|
78
80
|
end
|
@@ -3,30 +3,14 @@ module Listlace
|
|
3
3
|
has_many :playlist_items
|
4
4
|
has_many :playlists, through: :playlist_items
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def increment_skip_count
|
7
|
+
increment! :skip_count
|
8
|
+
update_column :skip_date, Time.now
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
seconds = total_seconds % 60
|
14
|
-
minutes = (total_seconds / 60) % 60
|
15
|
-
hours = total_seconds / 3600
|
16
|
-
|
17
|
-
if hours > 0
|
18
|
-
"%d:%02d:%02d" % [hours, minutes, seconds]
|
19
|
-
else
|
20
|
-
"%d:%02d" % [minutes, seconds]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.parse_time(string)
|
25
|
-
parts = string.split(":").map(&:to_i)
|
26
|
-
parts = [0] + parts if parts.length == 2
|
27
|
-
hours, minutes, seconds = parts
|
28
|
-
seconds = hours * 3600 + minutes * 60 + seconds
|
29
|
-
seconds * 1000
|
11
|
+
def increment_play_count
|
12
|
+
increment! :play_count
|
13
|
+
update_column :play_date, Time.now
|
30
14
|
end
|
31
15
|
end
|
32
16
|
end
|
data/lib/listlace/player.rb
CHANGED
@@ -18,7 +18,6 @@ module Listlace
|
|
18
18
|
def initialize
|
19
19
|
@single_player = DEFAULT_SINGLE_PLAYER.new
|
20
20
|
@queue = []
|
21
|
-
@queue.name = :queue
|
22
21
|
@current_track = nil
|
23
22
|
@current_track_index = nil
|
24
23
|
@playlist_paused = false
|
@@ -28,11 +27,13 @@ module Listlace
|
|
28
27
|
|
29
28
|
def queue(playlist = nil)
|
30
29
|
if playlist.is_a? Array
|
31
|
-
|
32
|
-
|
30
|
+
playlist = playlist.dup
|
31
|
+
playlist.map! { |track| track.is_a?(String) ? SimpleTrack.new(track) : track }
|
32
|
+
playlist.select! { |track| track.respond_to? :location }
|
33
|
+
if @queue.empty?
|
34
|
+
@queue = playlist
|
33
35
|
else
|
34
36
|
@queue += playlist
|
35
|
-
@queue.name = :queue
|
36
37
|
end
|
37
38
|
end
|
38
39
|
@queue.dup
|
@@ -41,7 +42,6 @@ module Listlace
|
|
41
42
|
def clear
|
42
43
|
stop
|
43
44
|
@queue.clear
|
44
|
-
@queue.name = :queue
|
45
45
|
true
|
46
46
|
end
|
47
47
|
|
@@ -118,9 +118,8 @@ module Listlace
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def skip(n = 1)
|
121
|
-
if @current_track
|
122
|
-
@current_track.
|
123
|
-
@current_track.update_column :skip_date, Time.now
|
121
|
+
if @current_track.respond_to?(:increment_skip_count)
|
122
|
+
@current_track.increment_skip_count
|
124
123
|
end
|
125
124
|
change_track(n)
|
126
125
|
end
|
@@ -138,7 +137,7 @@ module Listlace
|
|
138
137
|
seconds = where.begin * 60 + where.end
|
139
138
|
@single_player.seek(seconds * 1000, :absolute)
|
140
139
|
when String
|
141
|
-
@single_player.seek(
|
140
|
+
@single_player.seek(Listlace.parse_time(where), :absolute)
|
142
141
|
when Hash
|
143
142
|
if where[:abs]
|
144
143
|
if where[:abs].is_a? Integer
|
@@ -154,11 +153,27 @@ module Listlace
|
|
154
153
|
end
|
155
154
|
|
156
155
|
def speed
|
157
|
-
@single_player.
|
156
|
+
@single_player.speed || 1.0
|
158
157
|
end
|
159
158
|
|
160
159
|
def speed=(new_speed)
|
161
|
-
@single_player.speed
|
160
|
+
@single_player.speed = new_speed
|
161
|
+
end
|
162
|
+
|
163
|
+
def mute
|
164
|
+
@single_player.mute
|
165
|
+
end
|
166
|
+
|
167
|
+
def unmute
|
168
|
+
@single_player.unmute
|
169
|
+
end
|
170
|
+
|
171
|
+
def volume
|
172
|
+
@single_player.volume
|
173
|
+
end
|
174
|
+
|
175
|
+
def volume=(new_volume)
|
176
|
+
@single_player.volume = new_volume
|
162
177
|
end
|
163
178
|
|
164
179
|
def shuffle
|
@@ -180,20 +195,19 @@ module Listlace
|
|
180
195
|
end
|
181
196
|
|
182
197
|
def current_time
|
183
|
-
@single_player.
|
198
|
+
@single_player.current_time || 0
|
184
199
|
end
|
185
200
|
|
186
|
-
def
|
187
|
-
|
201
|
+
def total_time
|
202
|
+
@single_player.total_time
|
188
203
|
end
|
189
204
|
|
190
205
|
private
|
191
206
|
|
192
207
|
def change_track(by = 1, options = {})
|
193
208
|
if started?
|
194
|
-
if options[:auto]
|
195
|
-
@current_track.
|
196
|
-
@current_track.update_column :play_date_utc, Time.now
|
209
|
+
if options[:auto] && @current_track.respond_to?(:increment_play_count)
|
210
|
+
@current_track.increment_play_count
|
197
211
|
end
|
198
212
|
@current_track_index += by
|
199
213
|
if options[:auto] && @repeat_mode
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Listlace
|
2
|
+
# The bare minimum needed to represent a track. This is used by the Player and
|
3
|
+
# SinglePlayer, in case they get passed a String containing a path to an audio
|
4
|
+
# file. That way, users don't have to worry about creating track objects, if
|
5
|
+
# they want.
|
6
|
+
class SimpleTrack
|
7
|
+
attr_accessor :location
|
8
|
+
|
9
|
+
def initialize(location = nil)
|
10
|
+
@location = location
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -20,9 +20,21 @@ module Listlace
|
|
20
20
|
raise NotImplementedError
|
21
21
|
end
|
22
22
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
23
|
+
# Get the current track object which was passed to the play method.
|
24
|
+
def track
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get the title of the current track. First tries to call the title method on
|
29
|
+
# the current track, if that doesn't work it tries to get the title from the
|
30
|
+
# metadata of the audio file, and if that doesn't work it uses the filename.
|
31
|
+
def track_title
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Begin playing a track. The track should be either a String representing a
|
36
|
+
# path to an audio file, or an object responding to #location. The &on_end
|
37
|
+
# callback will be called when the track is finished playing.
|
26
38
|
def play(track, &on_end)
|
27
39
|
raise NotImplementedError
|
28
40
|
end
|
@@ -52,17 +64,48 @@ module Listlace
|
|
52
64
|
raise NotImplementedError
|
53
65
|
when :relative
|
54
66
|
raise NotImplementedError
|
55
|
-
when :
|
67
|
+
when :percent
|
56
68
|
raise NotImplementedError
|
57
69
|
else
|
58
70
|
raise NotImplementedError
|
59
71
|
end
|
60
72
|
end
|
61
73
|
|
62
|
-
# Gets
|
63
|
-
#
|
64
|
-
|
65
|
-
|
74
|
+
# Gets the current playback speed. The speed is a multiplier. For example,
|
75
|
+
# double speed is 2 and half-speed is 0.5. Normal speed is 1.
|
76
|
+
def speed
|
77
|
+
raise NotImplementedError
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sets the playback speed. The speed is a multiplier. For example, for
|
81
|
+
# double speed you'd set it to 2 and for half-speed you'd set it to 0.5. And
|
82
|
+
# for normal speed: 1.
|
83
|
+
def speed=(new_speed)
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns true if audio is muted.
|
88
|
+
def muted?
|
89
|
+
raise NotImplementedError
|
90
|
+
end
|
91
|
+
|
92
|
+
# Mutes the audio player.
|
93
|
+
def mute
|
94
|
+
raise NotImplementedError
|
95
|
+
end
|
96
|
+
|
97
|
+
# Unmutes the audio player.
|
98
|
+
def unmute
|
99
|
+
raise NotImplementedError
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get the current volume as a percentage.
|
103
|
+
def volume
|
104
|
+
raise NotImplementedError
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set the volume as a percentage. The player is automatically unmuted.
|
108
|
+
def volume=(new_volume)
|
66
109
|
raise NotImplementedError
|
67
110
|
end
|
68
111
|
|
@@ -70,5 +113,17 @@ module Listlace
|
|
70
113
|
def current_time
|
71
114
|
raise NotImplementedError
|
72
115
|
end
|
116
|
+
|
117
|
+
# Returns the length of the current track, in milliseconds.
|
118
|
+
def total_time
|
119
|
+
raise NotImplementedError
|
120
|
+
end
|
121
|
+
|
122
|
+
# Get metadata for the current track from the audio player. Returns a Hash
|
123
|
+
# with keys like :artist and :album. Values should be Strings, or nil if the
|
124
|
+
# value is blank.
|
125
|
+
def metadata
|
126
|
+
raise NotImplementedError
|
127
|
+
end
|
73
128
|
end
|
74
129
|
end
|
@@ -1,37 +1,89 @@
|
|
1
1
|
module Listlace
|
2
2
|
module SinglePlayers
|
3
|
+
# This is the SinglePlayer implementation for mplayer. It requires mplayer
|
4
|
+
# to be in your $PATH. It uses open4 to start up and communicate with the
|
5
|
+
# mplayer process, and mplayer's slave protocol to issue commands to mplayer.
|
3
6
|
class MPlayer < SinglePlayer
|
7
|
+
# Create a new MPlayer. The mplayer process is only started when the #play
|
8
|
+
# method is called to start playing a song. The process quits when the
|
9
|
+
# song ends. Even though a new process is started for each song, the
|
10
|
+
# MPlayer object keeps track of the volume, speed, and mute properties and
|
11
|
+
# sets these properties when a new song is played.
|
4
12
|
def initialize
|
5
|
-
@active = false
|
6
13
|
@paused = false
|
14
|
+
@muted = false
|
15
|
+
@volume = 50
|
16
|
+
@speed = 1.0
|
17
|
+
@track = nil
|
7
18
|
end
|
8
19
|
|
9
20
|
def active?
|
10
|
-
@
|
21
|
+
not @track.nil?
|
11
22
|
end
|
12
23
|
|
13
24
|
def paused?
|
14
25
|
@paused
|
15
26
|
end
|
16
27
|
|
28
|
+
def track
|
29
|
+
@track
|
30
|
+
end
|
31
|
+
|
32
|
+
def track_title
|
33
|
+
if active?
|
34
|
+
if @track.respond_to? :title
|
35
|
+
@track.title
|
36
|
+
elsif title = metadata[:title]
|
37
|
+
title
|
38
|
+
else
|
39
|
+
_command "get_file_name", expect_answer: /^ANS_FILENAME='(.+)'$/
|
40
|
+
end
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
17
46
|
def play(track, &on_end)
|
47
|
+
# Make sure we're only playing one song at any one time.
|
18
48
|
_quit
|
19
49
|
|
50
|
+
# If a path to an audio file passed as the track, wrap it in a SimpleTrack.
|
51
|
+
track = SimpleTrack.new(track) if track.is_a? String
|
52
|
+
|
53
|
+
# The track object must respond to #location to be a track.
|
54
|
+
if not track.respond_to? :location
|
55
|
+
raise ArgumentError, "got a #{track.class} instead of a track"
|
56
|
+
end
|
57
|
+
|
20
58
|
if File.exists? track.location
|
59
|
+
# Run the mplayer process in slave mode, passing it the location of
|
60
|
+
# the track's audio file.
|
21
61
|
cmd = ["mplayer", "-slave", "-quiet", track.location]
|
22
62
|
@pid, @stdin, @stdout, @stderr = Open4.popen4(*cmd)
|
23
63
|
|
64
|
+
# This should skip past mplayer's initial lines of output so we can
|
65
|
+
# start reading its replies to our commands.
|
24
66
|
until @stdout.gets["playback"]
|
25
67
|
end
|
26
68
|
|
27
|
-
@active = true
|
28
69
|
@paused = false
|
70
|
+
@track = track
|
71
|
+
|
72
|
+
# Persist the previous speed, volume, and mute properties into this
|
73
|
+
# process.
|
74
|
+
self.speed = @speed
|
75
|
+
self.volume = @volume
|
76
|
+
mute if @muted
|
29
77
|
|
78
|
+
# Start a thread that waits for the mplayer process to end, then calls
|
79
|
+
# the end of song callback. If the #quit method is called, this thread
|
80
|
+
# will be killed if it's still waiting for the process to end.
|
30
81
|
@quit_hook_active = false
|
31
82
|
@quit_hook = Thread.new do
|
32
83
|
Process.wait(@pid)
|
33
84
|
@quit_hook_active = true
|
34
|
-
@
|
85
|
+
@paused = false
|
86
|
+
@track = nil
|
35
87
|
on_end.call
|
36
88
|
end
|
37
89
|
|
@@ -47,6 +99,7 @@ module Listlace
|
|
47
99
|
|
48
100
|
def pause
|
49
101
|
if not @paused
|
102
|
+
@paused = true
|
50
103
|
_command "pause"
|
51
104
|
else
|
52
105
|
false
|
@@ -55,6 +108,7 @@ module Listlace
|
|
55
108
|
|
56
109
|
def resume
|
57
110
|
if @paused
|
111
|
+
@paused = false
|
58
112
|
_command "pause"
|
59
113
|
else
|
60
114
|
false
|
@@ -62,7 +116,8 @@ module Listlace
|
|
62
116
|
end
|
63
117
|
|
64
118
|
def seek(where, type = :absolute)
|
65
|
-
seconds
|
119
|
+
# mplayer talks seconds, not milliseconds.
|
120
|
+
seconds = where.to_f / 1_000
|
66
121
|
case type
|
67
122
|
when :absolute
|
68
123
|
_command "seek #{seconds} 2", expect_answer: true
|
@@ -75,44 +130,97 @@ module Listlace
|
|
75
130
|
end
|
76
131
|
end
|
77
132
|
|
78
|
-
def speed
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
133
|
+
def speed
|
134
|
+
@speed
|
135
|
+
end
|
136
|
+
|
137
|
+
def speed=(new_speed)
|
138
|
+
@speed = new_speed.to_f
|
139
|
+
answer = _command "speed_set #{@speed}", expect_answer: true
|
140
|
+
!!answer
|
141
|
+
end
|
142
|
+
|
143
|
+
def muted?
|
144
|
+
@muted
|
145
|
+
end
|
146
|
+
|
147
|
+
def mute
|
148
|
+
@muted = true
|
149
|
+
answer = _command "mute 1", expect_answer: true
|
150
|
+
!!answer
|
151
|
+
end
|
152
|
+
|
153
|
+
def unmute
|
154
|
+
@muted = false
|
155
|
+
answer = _command "mute 0", expect_answer: true
|
156
|
+
!!answer
|
157
|
+
end
|
158
|
+
|
159
|
+
def volume
|
160
|
+
@volume
|
161
|
+
end
|
162
|
+
|
163
|
+
def volume=(new_volume)
|
164
|
+
@muted = false
|
165
|
+
@volume = new_volume.to_f
|
166
|
+
answer = _command "volume #{@volume} 1", expect_answer: true
|
167
|
+
!!answer
|
90
168
|
end
|
91
169
|
|
92
170
|
def current_time
|
93
|
-
answer = _command "get_time_pos", expect_answer:
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
171
|
+
answer = _command "get_time_pos", expect_answer: /^ANS_TIME_POSITION=([0-9.]+)$/
|
172
|
+
answer ? (answer.to_f * 1000).to_i : false
|
173
|
+
end
|
174
|
+
|
175
|
+
def total_time
|
176
|
+
answer = _command "get_time_length", expect_answer: /^ANS_LENGTH=([0-9.]+)$/
|
177
|
+
answer ? (answer.to_f * 1000).to_i : false
|
178
|
+
end
|
179
|
+
|
180
|
+
def metadata
|
181
|
+
properties = %w(album artist comment genre title track year)
|
182
|
+
properties.inject({}) do |hash, property|
|
183
|
+
answer = _command "get_meta_#{property}", expect_answer: /^ANS_META_#{property.upcase}='(.+)'$/
|
184
|
+
hash[property.to_sym] = answer || nil
|
185
|
+
hash
|
98
186
|
end
|
99
187
|
end
|
100
188
|
|
101
189
|
private
|
102
190
|
|
191
|
+
# Issue a command to mplayer through the slave protocol. False is returned
|
192
|
+
# if the process is dead (not playing anything).
|
193
|
+
#
|
194
|
+
# If :expect_answer option is set to true, this will wait for a legible
|
195
|
+
# answer back from mplayer, and send it as a return value. If :expect_answer
|
196
|
+
# is set to a Regexp, the answer mplayer gives back will be matched to that
|
197
|
+
# Regexp and the first match will be returned. If there are no matches, nil
|
198
|
+
# will be returned.
|
103
199
|
def _command(cmd, options = {})
|
104
200
|
if _alive? and active?
|
105
|
-
|
106
|
-
|
107
|
-
|
201
|
+
# If the player is paused, prefix the command with "pausing ".
|
202
|
+
# Otherwise it unpauses when it runs a command. The only exception to
|
203
|
+
# this is when the "pause" command itself is issued.
|
204
|
+
if paused? and cmd != "pause"
|
108
205
|
cmd = "pausing #{cmd}"
|
109
206
|
end
|
110
207
|
|
208
|
+
# Send the command to mplayer.
|
111
209
|
@stdin.puts cmd
|
112
210
|
|
113
211
|
if options[:expect_answer]
|
212
|
+
# Read lines of output from mplayer until we get an actual message.
|
114
213
|
answer = "\n"
|
115
|
-
|
214
|
+
while answer == "\n"
|
215
|
+
answer = @stdout.gets.sub("\e[A\r\e[K", "")
|
216
|
+
answer = "\n" if options[:expect_answer].is_a?(Regexp) && answer !~ options[:expect_answer]
|
217
|
+
end
|
218
|
+
|
219
|
+
if options[:expect_answer].is_a? Regexp
|
220
|
+
matches = answer.match(options[:expect_answer])
|
221
|
+
answer = matches && matches[1]
|
222
|
+
end
|
223
|
+
|
116
224
|
answer
|
117
225
|
else
|
118
226
|
true
|
@@ -122,16 +230,19 @@ module Listlace
|
|
122
230
|
end
|
123
231
|
end
|
124
232
|
|
233
|
+
# Quit the mplayer process, stopping playback. The end of song callback
|
234
|
+
# will not be called if this method is called.
|
125
235
|
def _quit
|
126
236
|
if _alive?
|
127
237
|
@quit_hook.kill unless @quit_hook_active
|
128
238
|
_command "quit"
|
129
|
-
@active = false
|
130
239
|
@paused = false
|
240
|
+
@track = nil
|
131
241
|
end
|
132
242
|
true
|
133
243
|
end
|
134
244
|
|
245
|
+
# Check if the mplayer process is still around.
|
135
246
|
def _alive?
|
136
247
|
return false if @pid.nil?
|
137
248
|
Process.getpgid(@pid)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Listlace
|
2
|
+
# Helper method to format a number of milliseconds as a string like
|
3
|
+
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
|
4
|
+
# false, milliseconds won't be included in the formatted string.
|
5
|
+
def self.format_time(milliseconds, options = {})
|
6
|
+
ms = milliseconds % 1000
|
7
|
+
seconds = (milliseconds / 1000) % 60
|
8
|
+
minutes = (milliseconds / 60000) % 60
|
9
|
+
hours = milliseconds / 3600000
|
10
|
+
|
11
|
+
if ms.zero? || options[:include_milliseconds] == false
|
12
|
+
ms_string = ""
|
13
|
+
else
|
14
|
+
ms_string = ".%03d" % [ms]
|
15
|
+
end
|
16
|
+
|
17
|
+
if hours > 0
|
18
|
+
"%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
|
19
|
+
else
|
20
|
+
"%d:%02d%s" % [minutes, seconds, ms_string]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Helper method to parse a string like "1:03:56.555" and return the number of
|
25
|
+
# milliseconds that time length represents.
|
26
|
+
def self.parse_time(string)
|
27
|
+
parts = string.split(":").map(&:to_f)
|
28
|
+
parts = [0] + parts if parts.length == 2
|
29
|
+
hours, minutes, seconds = parts
|
30
|
+
seconds = hours * 3600 + minutes * 60 + seconds
|
31
|
+
milliseconds = seconds * 1000
|
32
|
+
milliseconds.to_i
|
33
|
+
end
|
34
|
+
end
|
data/lib/listlace.rb
CHANGED
@@ -5,6 +5,7 @@ require "plist"
|
|
5
5
|
require "active_support/core_ext/string"
|
6
6
|
|
7
7
|
require "listlace/core_ext/array"
|
8
|
+
require "listlace/time_helper"
|
8
9
|
|
9
10
|
require "listlace/models/track"
|
10
11
|
require "listlace/models/playlist"
|
@@ -14,6 +15,7 @@ require "listlace/library"
|
|
14
15
|
require "listlace/library/database"
|
15
16
|
require "listlace/library/selectors"
|
16
17
|
|
18
|
+
require "listlace/simple_track"
|
17
19
|
require "listlace/single_player"
|
18
20
|
require "listlace/single_players/mplayer"
|
19
21
|
require "listlace/player"
|
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-09-
|
3
|
+
s.version = "0.0.8"
|
4
|
+
s.date = "2012-09-07"
|
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.8
|
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-09-
|
12
|
+
date: 2012-09-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -147,8 +147,10 @@ files:
|
|
147
147
|
- lib/listlace/models/playlist_item.rb
|
148
148
|
- lib/listlace/models/track.rb
|
149
149
|
- lib/listlace/player.rb
|
150
|
+
- lib/listlace/simple_track.rb
|
150
151
|
- lib/listlace/single_player.rb
|
151
152
|
- lib/listlace/single_players/mplayer.rb
|
153
|
+
- lib/listlace/time_helper.rb
|
152
154
|
- lib/listlace.rb
|
153
155
|
homepage: http://github.com/yjerem/listlace
|
154
156
|
licenses:
|