listlace 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|