plllayer 0.0.1 → 0.0.2
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/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:
|