plllayer 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/plllayer/single_player.rb +16 -12
- data/lib/plllayer/single_players/mplayer.rb +35 -34
- data/lib/plllayer/time_helpers.rb +37 -0
- data/lib/plllayer.rb +126 -54
- data/plllayer.gemspec +2 -2
- metadata +3 -2
@@ -1,4 +1,7 @@
|
|
1
1
|
class Plllayer
|
2
|
+
# Raise this exception when the file at the given track path doesn't exist.
|
3
|
+
FileNotFoundError = Class.new(ArgumentError)
|
4
|
+
|
2
5
|
# A SinglePlayer takes care of playing a single track, and controlling the
|
3
6
|
# playback with commands like pause, resume, seek, and so on. It probably
|
4
7
|
# starts an external audio player process to do this job. This class is an
|
@@ -9,7 +12,7 @@ class Plllayer
|
|
9
12
|
# All methods that perform an action should return false if the action isn't
|
10
13
|
# applicable, and return a truthy value otherwise.
|
11
14
|
class SinglePlayer
|
12
|
-
#
|
15
|
+
# Return true if a track is currently loaded, i.e. either playing or
|
13
16
|
# paused.
|
14
17
|
def playing?
|
15
18
|
raise NotImplementedError
|
@@ -22,7 +25,8 @@ class Plllayer
|
|
22
25
|
|
23
26
|
# Begin playing a track. The track_path should be a String representing a
|
24
27
|
# path to an audio file. The &on_end callback should be called when the track
|
25
|
-
# is finished playing.
|
28
|
+
# is finished playing. Should raise FileNotFoundError if the audio file
|
29
|
+
# doesn't exist.
|
26
30
|
def play(track_path, &on_end)
|
27
31
|
raise NotImplementedError
|
28
32
|
end
|
@@ -32,12 +36,12 @@ class Plllayer
|
|
32
36
|
raise NotImplementedError
|
33
37
|
end
|
34
38
|
|
35
|
-
#
|
39
|
+
# Pause playback.
|
36
40
|
def pause
|
37
41
|
raise NotImplementedError
|
38
42
|
end
|
39
43
|
|
40
|
-
#
|
44
|
+
# Resume playback.
|
41
45
|
def resume
|
42
46
|
raise NotImplementedError
|
43
47
|
end
|
@@ -59,30 +63,30 @@ class Plllayer
|
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
62
|
-
#
|
66
|
+
# Get the current playback speed. The speed is a multiplier. For example,
|
63
67
|
# double speed is 2 and half-speed is 0.5. Normal speed is 1.
|
64
68
|
def speed
|
65
69
|
raise NotImplementedError
|
66
70
|
end
|
67
71
|
|
68
|
-
#
|
72
|
+
# Set the playback speed. The speed is a multiplier. For example, for
|
69
73
|
# double speed you'd set it to 2 and for half-speed you'd set it to 0.5. And
|
70
74
|
# for normal speed: 1.
|
71
75
|
def speed=(new_speed)
|
72
76
|
raise NotImplementedError
|
73
77
|
end
|
74
78
|
|
75
|
-
#
|
79
|
+
# Return true if audio is muted.
|
76
80
|
def muted?
|
77
81
|
raise NotImplementedError
|
78
82
|
end
|
79
83
|
|
80
|
-
#
|
84
|
+
# Mute the audio player.
|
81
85
|
def mute
|
82
86
|
raise NotImplementedError
|
83
87
|
end
|
84
88
|
|
85
|
-
#
|
89
|
+
# Unmute the audio player.
|
86
90
|
def unmute
|
87
91
|
raise NotImplementedError
|
88
92
|
end
|
@@ -92,17 +96,17 @@ class Plllayer
|
|
92
96
|
raise NotImplementedError
|
93
97
|
end
|
94
98
|
|
95
|
-
# Set the volume as a percentage. The player
|
99
|
+
# Set the volume as a percentage. The player may be automatically unmuted.
|
96
100
|
def volume=(new_volume)
|
97
101
|
raise NotImplementedError
|
98
102
|
end
|
99
103
|
|
100
|
-
#
|
104
|
+
# Return the current time into the song, in milliseconds.
|
101
105
|
def position
|
102
106
|
raise NotImplementedError
|
103
107
|
end
|
104
108
|
|
105
|
-
#
|
109
|
+
# Return the length of the current track, in milliseconds.
|
106
110
|
def track_length
|
107
111
|
raise NotImplementedError
|
108
112
|
end
|
@@ -27,42 +27,41 @@ class Plllayer
|
|
27
27
|
# Make sure we're only playing one song at any one time.
|
28
28
|
_quit
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
# the track's audio file.
|
33
|
-
cmd = ["mplayer", "-slave", "-quiet", track_path]
|
34
|
-
@pid, @stdin, @stdout, @stderr = Open4.popen4(*cmd)
|
35
|
-
|
36
|
-
# This should skip past mplayer's initial lines of output so we can
|
37
|
-
# start reading its replies to our commands.
|
38
|
-
until @stdout.gets["playback"]
|
39
|
-
end
|
30
|
+
# Make sure the audio file exists.
|
31
|
+
raise FileNotFoundError, "file '#{track_path}' doesn't exist" unless File.exists? track_path
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# process.
|
46
|
-
self.speed = @speed
|
47
|
-
self.volume = @volume
|
48
|
-
mute if @muted
|
49
|
-
|
50
|
-
# Start a thread that waits for the mplayer process to end, then calls
|
51
|
-
# the end of song callback. If the #quit method is called, this thread
|
52
|
-
# will be killed if it's still waiting for the process to end.
|
53
|
-
@quit_hook_active = false
|
54
|
-
@quit_hook = Thread.new do
|
55
|
-
Process.wait(@pid)
|
56
|
-
@quit_hook_active = true
|
57
|
-
@paused = false
|
58
|
-
@track_path = nil
|
59
|
-
on_end.call
|
60
|
-
end
|
33
|
+
# Run the mplayer process in slave mode, passing it the location of
|
34
|
+
# the track's audio file.
|
35
|
+
cmd = ["mplayer", "-slave", "-quiet", track_path]
|
36
|
+
@pid, @stdin, @stdout, @stderr = Open4.popen4(*cmd)
|
61
37
|
|
62
|
-
|
63
|
-
|
64
|
-
|
38
|
+
# This should skip past mplayer's initial lines of output so we can
|
39
|
+
# start reading its replies to our commands.
|
40
|
+
until @stdout.gets["playback"]
|
65
41
|
end
|
42
|
+
|
43
|
+
@paused = false
|
44
|
+
@track_path = track_path
|
45
|
+
|
46
|
+
# Persist the previous speed, volume, and mute properties into this
|
47
|
+
# process.
|
48
|
+
self.speed = @speed
|
49
|
+
self.volume = @volume
|
50
|
+
mute if @muted
|
51
|
+
|
52
|
+
# Start a thread that waits for the mplayer process to end, then calls
|
53
|
+
# the end of song callback. If the #quit method is called, this thread
|
54
|
+
# will be killed if it's still waiting for the process to end.
|
55
|
+
@quit_hook_active = false
|
56
|
+
@quit_hook = Thread.new do
|
57
|
+
Process.wait(@pid)
|
58
|
+
@quit_hook_active = true
|
59
|
+
@paused = false
|
60
|
+
@track_path = nil
|
61
|
+
on_end.call
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
66
65
|
end
|
67
66
|
|
68
67
|
def stop
|
@@ -201,8 +200,10 @@ class Plllayer
|
|
201
200
|
_command "quit"
|
202
201
|
@paused = false
|
203
202
|
@track_path = nil
|
203
|
+
true
|
204
|
+
else
|
205
|
+
false
|
204
206
|
end
|
205
|
-
true
|
206
207
|
end
|
207
208
|
|
208
209
|
# Check if the mplayer process is still around.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Plllayer
|
2
|
+
module TimeHelpers
|
3
|
+
# Helper method to format a number of milliseconds as a string like
|
4
|
+
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
|
5
|
+
# false, milliseconds won't be included in the formatted string.
|
6
|
+
def format_time(milliseconds, options = {})
|
7
|
+
ms = milliseconds % 1000
|
8
|
+
seconds = (milliseconds / 1000) % 60
|
9
|
+
minutes = (milliseconds / 60000) % 60
|
10
|
+
hours = milliseconds / 3600000
|
11
|
+
|
12
|
+
if ms.zero? || options[:include_milliseconds] == false
|
13
|
+
ms_string = ""
|
14
|
+
else
|
15
|
+
ms_string = ".%03d" % [ms]
|
16
|
+
end
|
17
|
+
|
18
|
+
if hours > 0
|
19
|
+
"%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
|
20
|
+
else
|
21
|
+
"%d:%02d%s" % [minutes, seconds, ms_string]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Helper method to parse a string like "1:03:56.555" and return the number of
|
26
|
+
# milliseconds that time length represents.
|
27
|
+
def parse_time(string)
|
28
|
+
parts = string.split(":").map(&:to_f)
|
29
|
+
parts = [0] + parts if parts.length == 2
|
30
|
+
hours, minutes, seconds = parts
|
31
|
+
seconds = hours * 3600 + minutes * 60 + seconds
|
32
|
+
milliseconds = seconds * 1000
|
33
|
+
milliseconds.to_i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/lib/plllayer.rb
CHANGED
@@ -1,16 +1,46 @@
|
|
1
1
|
require "open4"
|
2
2
|
|
3
|
+
require "plllayer/time_helpers.rb"
|
3
4
|
require "plllayer/single_player"
|
4
5
|
require "plllayer/single_players/mplayer"
|
5
6
|
|
7
|
+
# Plllayer provides an interface to an external media player, such as mplayer. It
|
8
|
+
# contains a playlist of tracks, which may be as simple as an Array of paths to
|
9
|
+
# some audio files. You can then control the playback of this playlist by calling
|
10
|
+
# various command-like methods, like play, pause, seek, skip, shuffle, and so on.
|
6
11
|
class Plllayer
|
12
|
+
# This pulls in the Plllayer.parse_time and Plllayer.format_time helper methods.
|
13
|
+
# See plllayer/time_helpers.rb.
|
14
|
+
extend TimeHelpers
|
15
|
+
|
16
|
+
SINGLE_PLAYERS = {
|
17
|
+
mplayer: Plllayer::SinglePlayers::MPlayer
|
18
|
+
}
|
19
|
+
|
7
20
|
attr_reader :repeat_mode
|
8
21
|
|
22
|
+
# Create a new Plllayer. Optionally, you can pass in an initial playlist to be
|
23
|
+
# loaded (won't start playing until you call #play). You can also pass the
|
24
|
+
# :external_player option to specify the preferred external player to use.
|
25
|
+
# Otherwise, it will try to figure out what external players are available, and
|
26
|
+
# attempt to use the best one available.
|
27
|
+
#
|
28
|
+
# However, only mplayer is supported at the moment, so this option isn't useful
|
29
|
+
# right now.
|
30
|
+
#
|
31
|
+
# TODO: check if external player is available before trying to use it.
|
9
32
|
def initialize(*args)
|
10
33
|
options = {}
|
11
34
|
options = args.pop if args.last.is_a? Hash
|
35
|
+
options[:external_player] ||= :mplayer
|
36
|
+
|
37
|
+
# Look up the single player class, raise error if it doesn't exist.
|
38
|
+
single_player_class = SINGLE_PLAYERS[options[:external_player].to_sym]
|
39
|
+
if single_player_class.nil?
|
40
|
+
raise NotImplementedError, "external player #{options[:external_player]} not supported"
|
41
|
+
end
|
12
42
|
|
13
|
-
@single_player =
|
43
|
+
@single_player = single_player_class.new
|
14
44
|
@playlist = []
|
15
45
|
append(args.first) unless args.empty?
|
16
46
|
@index = nil
|
@@ -18,34 +48,47 @@ class Plllayer
|
|
18
48
|
@playing = false
|
19
49
|
@repeat_mode = nil
|
20
50
|
|
51
|
+
# Make sure the music stops playing once the Ruby script is exited.
|
21
52
|
at_exit { stop }
|
22
53
|
end
|
23
54
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
55
|
+
# Append tracks to the playlist. Can be done while the playlist is playing.
|
56
|
+
# A track is either a String containing the path to the audio file, or an
|
57
|
+
# object with a #location method that returns the path to the audio file.
|
58
|
+
# An ArgumentError is raised when you try to pass a non-track.
|
59
|
+
#
|
60
|
+
# This method is aliased as the << operator.
|
29
61
|
def append(tracks)
|
30
|
-
tracks = Array(tracks)
|
31
|
-
tracks.
|
62
|
+
tracks = Array(tracks)
|
63
|
+
tracks.each do |track|
|
64
|
+
if !track.is_a?(String) && !track.respond_to?(:location)
|
65
|
+
raise ArgumentError, "a #{track.class} is not a track (try adding a #location method)"
|
66
|
+
end
|
67
|
+
end
|
32
68
|
@playlist += tracks
|
33
69
|
@playlist.dup
|
34
70
|
end
|
35
71
|
alias :<< :append
|
36
72
|
|
73
|
+
# Returns a copy of the playlist.
|
37
74
|
def playlist
|
38
75
|
@playlist.dup
|
39
76
|
end
|
40
77
|
|
78
|
+
# Get the currently-playing track, or the track that's about to play if
|
79
|
+
# you're paused between tracks. Returns nil if playback is stopped.
|
41
80
|
def track
|
42
81
|
@index ? @playlist[@index] : nil
|
43
82
|
end
|
44
83
|
|
84
|
+
# Get the index of the currently-playing track, or of the track that's
|
85
|
+
# about to play if you're paused between tracks. Returns nil if playback
|
86
|
+
# is stopped.
|
45
87
|
def track_index
|
46
88
|
@index
|
47
89
|
end
|
48
90
|
|
91
|
+
# Get the path to the audio file of the currently-playing track.
|
49
92
|
def track_path
|
50
93
|
if track.respond_to? :location
|
51
94
|
track.location
|
@@ -54,22 +97,29 @@ class Plllayer
|
|
54
97
|
end
|
55
98
|
end
|
56
99
|
|
100
|
+
# Stop playback and empty the playlist.
|
57
101
|
def clear
|
58
102
|
stop
|
59
103
|
@playlist.clear
|
60
104
|
true
|
61
105
|
end
|
62
106
|
|
107
|
+
# Check if playback is paused.
|
63
108
|
def paused?
|
64
109
|
@paused
|
65
110
|
end
|
66
111
|
|
112
|
+
# Check if the playlist is being played. Whether playback is paused doesn't
|
113
|
+
# affect this. False is only returned if either (1) #play was never called,
|
114
|
+
# (2) #stop has been called, or (3) playback stopped after finishing playing
|
115
|
+
# all the songs.
|
67
116
|
def playing?
|
68
117
|
@playing
|
69
118
|
end
|
70
119
|
|
120
|
+
# Start playing the playlist from beginning to end.
|
71
121
|
def play
|
72
|
-
|
122
|
+
if !@playlist.empty? && !playing?
|
73
123
|
@playing = true
|
74
124
|
@paused = false
|
75
125
|
@index = 0
|
@@ -80,17 +130,23 @@ class Plllayer
|
|
80
130
|
end
|
81
131
|
end
|
82
132
|
|
133
|
+
# Stop playback.
|
83
134
|
def stop
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
135
|
+
if playing?
|
136
|
+
@single_player.stop
|
137
|
+
@track = nil
|
138
|
+
@index = nil
|
139
|
+
@paused = false
|
140
|
+
@playing = false
|
141
|
+
true
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
90
145
|
end
|
91
146
|
|
147
|
+
# Pause playback.
|
92
148
|
def pause
|
93
|
-
if playing?
|
149
|
+
if playing? && !paused?
|
94
150
|
@paused = true
|
95
151
|
@single_player.pause
|
96
152
|
else
|
@@ -98,14 +154,14 @@ class Plllayer
|
|
98
154
|
end
|
99
155
|
end
|
100
156
|
|
157
|
+
# Resume playback.
|
101
158
|
def resume
|
102
|
-
if playing?
|
159
|
+
if playing? && paused?
|
103
160
|
@paused = false
|
104
161
|
if @single_player.playing?
|
105
162
|
@single_player.resume
|
106
163
|
else
|
107
164
|
play_track @track
|
108
|
-
@playlist_paused = false
|
109
165
|
true
|
110
166
|
end
|
111
167
|
else
|
@@ -113,6 +169,13 @@ class Plllayer
|
|
113
169
|
end
|
114
170
|
end
|
115
171
|
|
172
|
+
# Set the repeat behaviour of the playlist. There are three possible values:
|
173
|
+
#
|
174
|
+
# :one repeat a single track over and over
|
175
|
+
# :all repeat the whole playlist, treating it like a circular array
|
176
|
+
# :off play songs consecutively, stop playback when done
|
177
|
+
#
|
178
|
+
# The default, of course, is :off.
|
116
179
|
def repeat(one_or_all_or_off)
|
117
180
|
case one_or_all_or_off
|
118
181
|
when :one
|
@@ -121,22 +184,41 @@ class Plllayer
|
|
121
184
|
@repeat_mode = :all
|
122
185
|
when :off
|
123
186
|
@repeat_mode = nil
|
187
|
+
else
|
188
|
+
raise ArgumentError
|
124
189
|
end
|
125
190
|
true
|
126
191
|
end
|
127
192
|
|
193
|
+
# Play the currently-playing track from the beginning.
|
128
194
|
def restart
|
129
195
|
change_track(0)
|
130
196
|
end
|
131
197
|
|
198
|
+
# Play the previous track. Pass a number to go back that many tracks. Treats
|
199
|
+
# the playlist like a circular array if the repeat mode is :all.
|
132
200
|
def back(n = 1)
|
133
201
|
change_track(-n)
|
134
202
|
end
|
135
203
|
|
204
|
+
# Play the next track. Pass a number to go forward that many tracks. Treats
|
205
|
+
# the playlist like a circular array if the repeat mode is :all.
|
136
206
|
def skip(n = 1)
|
137
207
|
change_track(n)
|
138
208
|
end
|
139
209
|
|
210
|
+
# Seek to a particular position within the currently-playing track. There are
|
211
|
+
# multiple ways to specify where to seek to:
|
212
|
+
#
|
213
|
+
# seek 10000 # seek 10000 milliseconds forward, relative to the current position
|
214
|
+
# seek -5000 # seek 5000 milliseconds backward
|
215
|
+
# seek "3:45" # seek to the absolute position 3 minutes and 45 seconds
|
216
|
+
# seek "1:03:45.123" # seek to 1 hour, 3 minutes, 45 seconds, 123 milliseconds
|
217
|
+
# seek 3..45 # syntax sugar for seeking to "3:45"
|
218
|
+
# seek 3..45.123 # syntax sugar for seeking to "3:45.123"
|
219
|
+
# seek abs: 150000 # seek to the absolute position 150000 milliseconds
|
220
|
+
# seek percent: 80 # seek to 80% of the way through the track
|
221
|
+
#
|
140
222
|
def seek(where)
|
141
223
|
if paused? && !@single_player.playing?
|
142
224
|
resume
|
@@ -150,7 +232,7 @@ class Plllayer
|
|
150
232
|
seconds = where.begin * 60 + where.end
|
151
233
|
@single_player.seek(seconds * 1000, :absolute)
|
152
234
|
when String
|
153
|
-
@single_player.seek(parse_time(where), :absolute)
|
235
|
+
@single_player.seek(Plllayer.parse_time(where), :absolute)
|
154
236
|
when Hash
|
155
237
|
if where[:abs]
|
156
238
|
if where[:abs].is_a? Integer
|
@@ -161,37 +243,51 @@ class Plllayer
|
|
161
243
|
elsif where[:percent]
|
162
244
|
@single_player.seek(where[:percent], :percent)
|
163
245
|
end
|
246
|
+
else
|
247
|
+
raise ArgumentError, "seek doesn't take a #{where.class}"
|
164
248
|
end
|
165
249
|
end
|
166
250
|
|
251
|
+
# Get the playback speed as a Float. 1.0 is normal speed, 2.0 is double speed,
|
252
|
+
# 0.5 is half-speed, and so on.
|
167
253
|
def speed
|
168
254
|
@single_player.speed || 1.0
|
169
255
|
end
|
170
256
|
|
257
|
+
# Set the playback speed as a Float. 1.0 is normal speed, 2.0 is double speed,
|
258
|
+
# 0.5 is half-speed, and so on.
|
171
259
|
def speed=(new_speed)
|
172
260
|
@single_player.speed = new_speed
|
173
261
|
end
|
174
262
|
|
263
|
+
# Mute the volume.
|
175
264
|
def mute
|
176
265
|
@single_player.mute
|
177
266
|
end
|
178
267
|
|
268
|
+
# Unmute the volume.
|
179
269
|
def unmute
|
180
270
|
@single_player.unmute
|
181
271
|
end
|
182
272
|
|
273
|
+
# Check if volume is muted.
|
183
274
|
def muted?
|
184
275
|
@single_player.muted?
|
185
276
|
end
|
186
277
|
|
278
|
+
# Get the volume, as a percentage.
|
187
279
|
def volume
|
188
280
|
@single_player.volume
|
189
281
|
end
|
190
282
|
|
283
|
+
# Set the volume, as a percentage.
|
191
284
|
def volume=(new_volume)
|
192
285
|
@single_player.volume = new_volume
|
193
286
|
end
|
194
287
|
|
288
|
+
# Shuffle the playlist. If this is done while the playlist is playing, the
|
289
|
+
# current song will go to the top of the playlist and the rest of the songs
|
290
|
+
# will be shuffled.
|
195
291
|
def shuffle
|
196
292
|
current_track = track
|
197
293
|
@playlist.shuffle!
|
@@ -203,6 +299,10 @@ class Plllayer
|
|
203
299
|
true
|
204
300
|
end
|
205
301
|
|
302
|
+
# Sorts the playlist. Delegates to Array#sort, so a block may be passed to
|
303
|
+
# specify what the tracks should be sorted by.
|
304
|
+
#
|
305
|
+
# This method is safe to call while the playlist is playing.
|
206
306
|
def sort(&by)
|
207
307
|
current_track = track
|
208
308
|
@playlist.sort! &by
|
@@ -212,14 +312,18 @@ class Plllayer
|
|
212
312
|
true
|
213
313
|
end
|
214
314
|
|
315
|
+
# Returns the current position of the currently-playing track, in milliseconds.
|
215
316
|
def position
|
216
317
|
@single_player.position || 0
|
217
318
|
end
|
218
319
|
|
320
|
+
# Returns the current position of the currently-playing track, as a String
|
321
|
+
# like "1:23".
|
219
322
|
def formatted_position(options = {})
|
220
|
-
format_time(position, options)
|
323
|
+
Plllayer.format_time(position, options)
|
221
324
|
end
|
222
325
|
|
326
|
+
# Returns the length of the currently-playing track, in milliseconds.
|
223
327
|
def track_length
|
224
328
|
if paused? && !@single_player.playing?
|
225
329
|
resume
|
@@ -228,9 +332,10 @@ class Plllayer
|
|
228
332
|
@single_player.track_length
|
229
333
|
end
|
230
334
|
|
335
|
+
# Returns the length of the currently-playing track, as a String like "1:23".
|
231
336
|
def formatted_track_length(options = {})
|
232
337
|
if length = track_length
|
233
|
-
format_time(length, options)
|
338
|
+
Plllayer.format_time(length, options)
|
234
339
|
end
|
235
340
|
end
|
236
341
|
|
@@ -270,38 +375,5 @@ class Plllayer
|
|
270
375
|
@paused = false
|
271
376
|
true
|
272
377
|
end
|
273
|
-
|
274
|
-
# Helper method to format a number of milliseconds as a string like
|
275
|
-
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
|
276
|
-
# false, milliseconds won't be included in the formatted string.
|
277
|
-
def format_time(milliseconds, options = {})
|
278
|
-
ms = milliseconds % 1000
|
279
|
-
seconds = (milliseconds / 1000) % 60
|
280
|
-
minutes = (milliseconds / 60000) % 60
|
281
|
-
hours = milliseconds / 3600000
|
282
|
-
|
283
|
-
if ms.zero? || options[:include_milliseconds] == false
|
284
|
-
ms_string = ""
|
285
|
-
else
|
286
|
-
ms_string = ".%03d" % [ms]
|
287
|
-
end
|
288
|
-
|
289
|
-
if hours > 0
|
290
|
-
"%d:%02d:%02d%s" % [hours, minutes, seconds, ms_string]
|
291
|
-
else
|
292
|
-
"%d:%02d%s" % [minutes, seconds, ms_string]
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
# Helper method to parse a string like "1:03:56.555" and return the number of
|
297
|
-
# milliseconds that time length represents.
|
298
|
-
def parse_time(string)
|
299
|
-
parts = string.split(":").map(&:to_f)
|
300
|
-
parts = [0] + parts if parts.length == 2
|
301
|
-
hours, minutes, seconds = parts
|
302
|
-
seconds = hours * 3600 + minutes * 60 + seconds
|
303
|
-
milliseconds = seconds * 1000
|
304
|
-
milliseconds.to_i
|
305
|
-
end
|
306
378
|
end
|
307
379
|
|
data/plllayer.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "plllayer"
|
3
|
-
s.version = "0.0.
|
4
|
-
s.date = "
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.date = "2013-01-31"
|
5
5
|
s.summary = "An audio playback library for Ruby."
|
6
6
|
s.description = "plllayer is an audio playback library for Ruby. It is a Ruby interface to some external media player, such as mplayer."
|
7
7
|
s.author = "Jeremy Ruten"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plllayer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
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:
|
12
|
+
date: 2013-01-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -72,6 +72,7 @@ files:
|
|
72
72
|
- plllayer.gemspec
|
73
73
|
- lib/plllayer/single_player.rb
|
74
74
|
- lib/plllayer/single_players/mplayer.rb
|
75
|
+
- lib/plllayer/time_helpers.rb
|
75
76
|
- lib/plllayer.rb
|
76
77
|
homepage: http://github.com/yjerem/plllayer
|
77
78
|
licenses:
|