listlace 0.0.9 → 0.1.0
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.
- checksums.yaml +7 -0
- data/Gemfile +0 -0
- data/Gemfile.lock +9 -33
- data/LICENSE +1 -1
- data/README.md +10 -32
- data/bin/listlace +12 -13
- data/lib/listlace/commands.rb +18 -0
- data/lib/listlace.rb +12 -28
- data/listlace.gemspec +7 -8
- metadata +22 -127
- data/README.old +0 -86
- data/lib/listlace/commands/library_commands.rb +0 -90
- data/lib/listlace/commands/player_commands.rb +0 -194
- data/lib/listlace/core_ext/array.rb +0 -58
- data/lib/listlace/library/database.rb +0 -84
- data/lib/listlace/library/selectors.rb +0 -199
- data/lib/listlace/library.rb +0 -160
- data/lib/listlace/models/playlist.rb +0 -6
- data/lib/listlace/models/playlist_item.rb +0 -6
- data/lib/listlace/models/track.rb +0 -21
- data/lib/listlace/player.rb +0 -249
- data/lib/listlace/simple_track.rb +0 -13
- data/lib/listlace/single_player.rb +0 -129
- data/lib/listlace/single_players/mplayer.rb +0 -255
- data/lib/listlace/time_helper.rb +0 -34
@@ -1,21 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
class Track < ActiveRecord::Base
|
3
|
-
has_many :playlist_items
|
4
|
-
has_many :playlists, through: :playlist_items
|
5
|
-
|
6
|
-
validates :location, presence: true, uniqueness: true
|
7
|
-
|
8
|
-
before_create { |track| track.date_added = Time.now }
|
9
|
-
before_save { |track| track.date_modified = Time.now }
|
10
|
-
|
11
|
-
def increment_skip_count
|
12
|
-
increment! :skip_count
|
13
|
-
update_column :skip_date, Time.now
|
14
|
-
end
|
15
|
-
|
16
|
-
def increment_play_count
|
17
|
-
increment! :play_count
|
18
|
-
update_column :play_date, Time.now
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/listlace/player.rb
DELETED
@@ -1,249 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
# This is the music box. It plays playlists. It contains a queue, which is
|
3
|
-
# just a playlist. To tell it what to play, you add one or more playlists to
|
4
|
-
# the queue, then start playing using the start method.
|
5
|
-
#
|
6
|
-
# Playback commands like pause, resume, seek, and so on are delegated to the
|
7
|
-
# SinglePlayer, which takes care of playing each individual song.
|
8
|
-
#
|
9
|
-
# Each method that performs an action is like a button on a physical media
|
10
|
-
# player: you can press the buttons even if they aren't applicable to the
|
11
|
-
# current state of the player. If that's the case, the methods wil return
|
12
|
-
# false. Otherwise, they'll return a truthy value.
|
13
|
-
class Player
|
14
|
-
DEFAULT_SINGLE_PLAYER = SinglePlayers::MPlayer
|
15
|
-
|
16
|
-
attr_reader :current_track, :current_track_index, :repeat_mode
|
17
|
-
|
18
|
-
def initialize
|
19
|
-
@single_player = DEFAULT_SINGLE_PLAYER.new
|
20
|
-
@queue = []
|
21
|
-
@current_track = nil
|
22
|
-
@current_track_index = nil
|
23
|
-
@playlist_paused = false
|
24
|
-
@started = false
|
25
|
-
@repeat_mode = false
|
26
|
-
end
|
27
|
-
|
28
|
-
def queue(playlist = nil)
|
29
|
-
if playlist.is_a? Array
|
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
|
35
|
-
else
|
36
|
-
@queue += playlist
|
37
|
-
end
|
38
|
-
end
|
39
|
-
@queue.dup
|
40
|
-
end
|
41
|
-
|
42
|
-
def clear
|
43
|
-
stop
|
44
|
-
@queue.clear
|
45
|
-
true
|
46
|
-
end
|
47
|
-
|
48
|
-
def empty?
|
49
|
-
@queue.empty?
|
50
|
-
end
|
51
|
-
|
52
|
-
def playlist_paused?
|
53
|
-
@playlist_paused
|
54
|
-
end
|
55
|
-
|
56
|
-
def paused?
|
57
|
-
playlist_paused? or @single_player.paused?
|
58
|
-
end
|
59
|
-
|
60
|
-
def started?
|
61
|
-
@started
|
62
|
-
end
|
63
|
-
|
64
|
-
def start
|
65
|
-
unless empty?
|
66
|
-
@started = true
|
67
|
-
@playlist_paused = false
|
68
|
-
@current_track = @queue.first
|
69
|
-
@current_track_index = 0
|
70
|
-
play_track @current_track
|
71
|
-
true
|
72
|
-
else
|
73
|
-
false
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def stop
|
78
|
-
@single_player.stop
|
79
|
-
@current_track = nil
|
80
|
-
@current_track_index = nil
|
81
|
-
@playlist_paused = false
|
82
|
-
@started = false
|
83
|
-
true
|
84
|
-
end
|
85
|
-
|
86
|
-
def pause
|
87
|
-
@single_player.pause
|
88
|
-
end
|
89
|
-
|
90
|
-
def resume
|
91
|
-
if playlist_paused?
|
92
|
-
play_track @current_track
|
93
|
-
@playlist_paused = false
|
94
|
-
true
|
95
|
-
else
|
96
|
-
@single_player.resume
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def repeat(one_or_all_or_off)
|
101
|
-
case one_or_all_or_off
|
102
|
-
when :one
|
103
|
-
@repeat_mode = :one
|
104
|
-
when :all
|
105
|
-
@repeat_mode = :all
|
106
|
-
when :off
|
107
|
-
@repeat_mode = false
|
108
|
-
end
|
109
|
-
true
|
110
|
-
end
|
111
|
-
|
112
|
-
def restart
|
113
|
-
change_track(0)
|
114
|
-
end
|
115
|
-
|
116
|
-
def back(n = 1)
|
117
|
-
change_track(-n)
|
118
|
-
end
|
119
|
-
|
120
|
-
def skip(n = 1)
|
121
|
-
if @current_track.respond_to?(:increment_skip_count)
|
122
|
-
@current_track.increment_skip_count
|
123
|
-
end
|
124
|
-
change_track(n)
|
125
|
-
end
|
126
|
-
|
127
|
-
def seek(where)
|
128
|
-
if playlist_paused?
|
129
|
-
resume
|
130
|
-
pause
|
131
|
-
seek where
|
132
|
-
else
|
133
|
-
case where
|
134
|
-
when Integer
|
135
|
-
@single_player.seek(where, :relative)
|
136
|
-
when Range
|
137
|
-
seconds = where.begin * 60 + where.end
|
138
|
-
@single_player.seek(seconds * 1000, :absolute)
|
139
|
-
when String
|
140
|
-
@single_player.seek(Listlace.parse_time(where), :absolute)
|
141
|
-
when Hash
|
142
|
-
if where[:abs]
|
143
|
-
if where[:abs].is_a? Integer
|
144
|
-
@single_player.seek(where[:abs], :absolute)
|
145
|
-
else
|
146
|
-
seek(where[:abs])
|
147
|
-
end
|
148
|
-
elsif where[:percent]
|
149
|
-
@single_player.seek(where[:percent], :percent)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def speed
|
156
|
-
@single_player.speed || 1.0
|
157
|
-
end
|
158
|
-
|
159
|
-
def speed=(new_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
|
177
|
-
end
|
178
|
-
|
179
|
-
def shuffle
|
180
|
-
if started?
|
181
|
-
@queue.shuffle_except! @current_track
|
182
|
-
@current_track_index = 0
|
183
|
-
else
|
184
|
-
@queue.shuffle!
|
185
|
-
end
|
186
|
-
true
|
187
|
-
end
|
188
|
-
|
189
|
-
def sort(&by)
|
190
|
-
@queue.sort! &by
|
191
|
-
if started?
|
192
|
-
@current_track_index = @queue.index(@current_track)
|
193
|
-
end
|
194
|
-
true
|
195
|
-
end
|
196
|
-
|
197
|
-
def current_time
|
198
|
-
@single_player.current_time || 0
|
199
|
-
end
|
200
|
-
|
201
|
-
def total_time
|
202
|
-
@single_player.total_time
|
203
|
-
end
|
204
|
-
|
205
|
-
private
|
206
|
-
|
207
|
-
def change_track(by = 1, options = {})
|
208
|
-
if started?
|
209
|
-
if options[:auto] && @current_track.respond_to?(:increment_play_count)
|
210
|
-
@current_track.increment_play_count
|
211
|
-
end
|
212
|
-
@current_track_index += by
|
213
|
-
if options[:auto] && @repeat_mode
|
214
|
-
case @repeat_mode
|
215
|
-
when :one
|
216
|
-
@current_track_index -= by
|
217
|
-
when :all
|
218
|
-
if @current_track_index >= @queue.length
|
219
|
-
@current_track_index = 0
|
220
|
-
end
|
221
|
-
end
|
222
|
-
end
|
223
|
-
@current_track = @queue[@current_track_index]
|
224
|
-
if @current_track && @current_track_index >= 0
|
225
|
-
if @single_player.paused?
|
226
|
-
@single_player.stop
|
227
|
-
@playlist_paused = true
|
228
|
-
elsif not playlist_paused?
|
229
|
-
play_track @current_track
|
230
|
-
end
|
231
|
-
else
|
232
|
-
stop
|
233
|
-
end
|
234
|
-
true
|
235
|
-
else
|
236
|
-
false
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def play_track(track)
|
241
|
-
@single_player.play(track) do
|
242
|
-
change_track(1, auto: true)
|
243
|
-
ActiveRecord::Base.connection.close if defined?(ActiveRecord)
|
244
|
-
end
|
245
|
-
@playlist_paused = false
|
246
|
-
true
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
@@ -1,13 +0,0 @@
|
|
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
|
@@ -1,129 +0,0 @@
|
|
1
|
-
module Listlace
|
2
|
-
# A SinglePlayer takes care of playing a single track, and controlling the
|
3
|
-
# playback with commands like pause, resume, seek, and so on. It typically
|
4
|
-
# starts an external audio player process to do this job. This class is an
|
5
|
-
# interface that is to be implemented for different audio players. Then the
|
6
|
-
# user can choose which SinglePlayer to use based on what audio players they
|
7
|
-
# have installed.
|
8
|
-
#
|
9
|
-
# All methods that perform an action should return false if the action isn't
|
10
|
-
# applicable, and return a truthy value otherwise.
|
11
|
-
class SinglePlayer
|
12
|
-
# Returns true if a track is currently loaded, i.e. either playing or
|
13
|
-
# paused.
|
14
|
-
def active?
|
15
|
-
raise NotImplementedError
|
16
|
-
end
|
17
|
-
|
18
|
-
# Returns true if a track is loaded and is paused.
|
19
|
-
def paused?
|
20
|
-
raise NotImplementedError
|
21
|
-
end
|
22
|
-
|
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.
|
38
|
-
def play(track, &on_end)
|
39
|
-
raise NotImplementedError
|
40
|
-
end
|
41
|
-
|
42
|
-
# Stop playback. Typically quits the external audio player process.
|
43
|
-
def stop
|
44
|
-
raise NotImplementedError
|
45
|
-
end
|
46
|
-
|
47
|
-
# Pauses playback.
|
48
|
-
def pause
|
49
|
-
raise NotImplementedError
|
50
|
-
end
|
51
|
-
|
52
|
-
# Resumes playback.
|
53
|
-
def resume
|
54
|
-
raise NotImplementedError
|
55
|
-
end
|
56
|
-
|
57
|
-
# Seek to a particular position in the track. Different types can be
|
58
|
-
# supported, such as absolute, relative, or percent. All times are specified
|
59
|
-
# in milliseconds. A NotImplementedError is raised when a certain type isn't
|
60
|
-
# supported.
|
61
|
-
def seek(where, type = :absolute)
|
62
|
-
case type
|
63
|
-
when :absolute
|
64
|
-
raise NotImplementedError
|
65
|
-
when :relative
|
66
|
-
raise NotImplementedError
|
67
|
-
when :percent
|
68
|
-
raise NotImplementedError
|
69
|
-
else
|
70
|
-
raise NotImplementedError
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
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)
|
109
|
-
raise NotImplementedError
|
110
|
-
end
|
111
|
-
|
112
|
-
# Returns the current time into the song, in milliseconds.
|
113
|
-
def current_time
|
114
|
-
raise NotImplementedError
|
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
|
128
|
-
end
|
129
|
-
end
|
@@ -1,255 +0,0 @@
|
|
1
|
-
module Listlace
|
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.
|
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.
|
12
|
-
def initialize
|
13
|
-
@paused = false
|
14
|
-
@muted = false
|
15
|
-
@volume = 50
|
16
|
-
@speed = 1.0
|
17
|
-
@track = nil
|
18
|
-
end
|
19
|
-
|
20
|
-
def active?
|
21
|
-
not @track.nil?
|
22
|
-
end
|
23
|
-
|
24
|
-
def paused?
|
25
|
-
@paused
|
26
|
-
end
|
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
|
-
|
46
|
-
def play(track, &on_end)
|
47
|
-
# Make sure we're only playing one song at any one time.
|
48
|
-
_quit
|
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
|
-
|
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.
|
61
|
-
cmd = ["mplayer", "-slave", "-quiet", track.location]
|
62
|
-
@pid, @stdin, @stdout, @stderr = Open4.popen4(*cmd)
|
63
|
-
|
64
|
-
# This should skip past mplayer's initial lines of output so we can
|
65
|
-
# start reading its replies to our commands.
|
66
|
-
until @stdout.gets["playback"]
|
67
|
-
end
|
68
|
-
|
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
|
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.
|
81
|
-
@quit_hook_active = false
|
82
|
-
@quit_hook = Thread.new do
|
83
|
-
Process.wait(@pid)
|
84
|
-
@quit_hook_active = true
|
85
|
-
@paused = false
|
86
|
-
@track = nil
|
87
|
-
on_end.call
|
88
|
-
end
|
89
|
-
|
90
|
-
true
|
91
|
-
else
|
92
|
-
false
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def stop
|
97
|
-
_quit
|
98
|
-
end
|
99
|
-
|
100
|
-
def pause
|
101
|
-
if not @paused
|
102
|
-
@paused = true
|
103
|
-
_command "pause"
|
104
|
-
else
|
105
|
-
false
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
def resume
|
110
|
-
if @paused
|
111
|
-
@paused = false
|
112
|
-
_command "pause"
|
113
|
-
else
|
114
|
-
false
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def seek(where, type = :absolute)
|
119
|
-
# mplayer talks seconds, not milliseconds.
|
120
|
-
seconds = where.to_f / 1_000
|
121
|
-
case type
|
122
|
-
when :absolute
|
123
|
-
_command "seek #{seconds} 2", expect_answer: true
|
124
|
-
when :relative
|
125
|
-
_command "seek #{seconds} 0", expect_answer: true
|
126
|
-
when :percent
|
127
|
-
_command "seek #{where} 1", expect_answer: true
|
128
|
-
else
|
129
|
-
raise NotImplementedError
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
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
|
168
|
-
end
|
169
|
-
|
170
|
-
def current_time
|
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
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
private
|
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.
|
199
|
-
def _command(cmd, options = {})
|
200
|
-
if _alive? and active?
|
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"
|
205
|
-
cmd = "pausing #{cmd}"
|
206
|
-
end
|
207
|
-
|
208
|
-
# Send the command to mplayer.
|
209
|
-
@stdin.puts cmd
|
210
|
-
|
211
|
-
if options[:expect_answer]
|
212
|
-
# Read lines of output from mplayer until we get an actual message.
|
213
|
-
answer = "\n"
|
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
|
-
|
224
|
-
answer
|
225
|
-
else
|
226
|
-
true
|
227
|
-
end
|
228
|
-
else
|
229
|
-
false
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
# Quit the mplayer process, stopping playback. The end of song callback
|
234
|
-
# will not be called if this method is called.
|
235
|
-
def _quit
|
236
|
-
if _alive?
|
237
|
-
@quit_hook.kill unless @quit_hook_active
|
238
|
-
_command "quit"
|
239
|
-
@paused = false
|
240
|
-
@track = nil
|
241
|
-
end
|
242
|
-
true
|
243
|
-
end
|
244
|
-
|
245
|
-
# Check if the mplayer process is still around.
|
246
|
-
def _alive?
|
247
|
-
return false if @pid.nil?
|
248
|
-
Process.getpgid(@pid)
|
249
|
-
true
|
250
|
-
rescue Errno::ESRCH
|
251
|
-
false
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
data/lib/listlace/time_helper.rb
DELETED
@@ -1,34 +0,0 @@
|
|
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
|