plllayer 0.0.2 → 0.0.3
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 +3 -0
- data/lib/plllayer/single_players/mplayer.rb +24 -3
- data/lib/plllayer/single_players/nop.rb +149 -0
- data/lib/plllayer/time_helpers.rb +8 -2
- data/lib/plllayer.rb +3 -4
- data/plllayer.gemspec +3 -3
- metadata +19 -2
@@ -2,6 +2,9 @@ class Plllayer
|
|
2
2
|
# Raise this exception when the file at the given track path doesn't exist.
|
3
3
|
FileNotFoundError = Class.new(ArgumentError)
|
4
4
|
|
5
|
+
# Raise this one when the file can't be played for whatever reason
|
6
|
+
InvalidAudioFileError = Class.new(ArgumentError)
|
7
|
+
|
5
8
|
# A SinglePlayer takes care of playing a single track, and controlling the
|
6
9
|
# playback with commands like pause, resume, seek, and so on. It probably
|
7
10
|
# starts an external audio player process to do this job. This class is an
|
@@ -37,8 +37,10 @@ class Plllayer
|
|
37
37
|
|
38
38
|
# This should skip past mplayer's initial lines of output so we can
|
39
39
|
# start reading its replies to our commands.
|
40
|
-
|
41
|
-
|
40
|
+
begin
|
41
|
+
line = @stdout.gets
|
42
|
+
raise InvalidAudioFileError, "file '#{track_path}' isn't a valid audio file" if line.nil?
|
43
|
+
end until line.chomp == "Starting playback..."
|
42
44
|
|
43
45
|
@paused = false
|
44
46
|
@track_path = track_path
|
@@ -46,7 +48,9 @@ class Plllayer
|
|
46
48
|
# Persist the previous speed, volume, and mute properties into this
|
47
49
|
# process.
|
48
50
|
self.speed = @speed
|
51
|
+
muted = @muted
|
49
52
|
self.volume = @volume
|
53
|
+
@muted = muted
|
50
54
|
mute if @muted
|
51
55
|
|
52
56
|
# Start a thread that waits for the mplayer process to end, then calls
|
@@ -61,6 +65,8 @@ class Plllayer
|
|
61
65
|
on_end.call
|
62
66
|
end
|
63
67
|
|
68
|
+
at_exit { stop }
|
69
|
+
|
64
70
|
true
|
65
71
|
end
|
66
72
|
|
@@ -68,6 +74,10 @@ class Plllayer
|
|
68
74
|
_quit
|
69
75
|
end
|
70
76
|
|
77
|
+
def paused?
|
78
|
+
@paused
|
79
|
+
end
|
80
|
+
|
71
81
|
def pause
|
72
82
|
if not @paused
|
73
83
|
@paused = true
|
@@ -148,6 +158,14 @@ class Plllayer
|
|
148
158
|
answer ? (answer.to_f * 1000).to_i : false
|
149
159
|
end
|
150
160
|
|
161
|
+
def log_start!
|
162
|
+
@log = true
|
163
|
+
end
|
164
|
+
|
165
|
+
def log_stop!
|
166
|
+
@log = false
|
167
|
+
end
|
168
|
+
|
151
169
|
private
|
152
170
|
|
153
171
|
# Issue a command to mplayer through the slave protocol. False is returned
|
@@ -169,12 +187,15 @@ class Plllayer
|
|
169
187
|
|
170
188
|
# Send the command to mplayer.
|
171
189
|
@stdin.puts cmd
|
190
|
+
File.open("log", "a") { |f| f << cmd << "\n" } if @log
|
172
191
|
|
173
192
|
if options[:expect_answer]
|
174
193
|
# Read lines of output from mplayer until we get an actual message.
|
175
194
|
answer = "\n"
|
176
195
|
while answer == "\n"
|
177
|
-
answer = @stdout.gets
|
196
|
+
answer = @stdout.gets
|
197
|
+
File.open("log", "a") { |f| f << answer } if @log
|
198
|
+
answer.sub! "\e[A\r\e[K", ""
|
178
199
|
answer = "\n" if options[:expect_answer].is_a?(Regexp) && answer !~ options[:expect_answer]
|
179
200
|
end
|
180
201
|
|
@@ -0,0 +1,149 @@
|
|
1
|
+
class Plllayer
|
2
|
+
module SinglePlayers
|
3
|
+
# This is the SinglePlayer implentation used for testing. It doesn't actually
|
4
|
+
# do anything, it just pretends to play a music file using sleeps.
|
5
|
+
class Nop < Plllayer::SinglePlayer
|
6
|
+
attr_reader :track_path
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@paused = false
|
10
|
+
@muted = false
|
11
|
+
@volume = 50
|
12
|
+
@speed = 1.0
|
13
|
+
@track_path = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def playing?
|
17
|
+
not @track_path.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def play(track_path, &on_end)
|
21
|
+
_quit
|
22
|
+
|
23
|
+
# Make sure the audio file exists.
|
24
|
+
raise FileNotFoundError, "file '#{track_path}' doesn't exist" unless File.exists? track_path
|
25
|
+
|
26
|
+
@paused = false
|
27
|
+
@track_path = track_path
|
28
|
+
|
29
|
+
# Assume the filename starts with the song length in milliseconds, so we
|
30
|
+
# don't actually have to read the file.
|
31
|
+
@total_time = File.basename(@track_path, ".*").to_i
|
32
|
+
@time_left = @total_time
|
33
|
+
@last_tick = Time.now
|
34
|
+
|
35
|
+
@quit_hook_active = false
|
36
|
+
@quit_hook = Thread.new do
|
37
|
+
while @time_left > 0
|
38
|
+
unless @paused
|
39
|
+
@time_left -= ((Time.now - @last_tick) * 1000 * @speed).to_i
|
40
|
+
end
|
41
|
+
@last_tick = Time.now
|
42
|
+
sleep 0.01
|
43
|
+
end
|
44
|
+
@quit_hook_active = true
|
45
|
+
@paused = false
|
46
|
+
@track_path = nil
|
47
|
+
@started = nil
|
48
|
+
on_end.call
|
49
|
+
end
|
50
|
+
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop
|
55
|
+
_quit
|
56
|
+
end
|
57
|
+
|
58
|
+
def paused?
|
59
|
+
@paused
|
60
|
+
end
|
61
|
+
|
62
|
+
def pause
|
63
|
+
if not @paused and playing?
|
64
|
+
@paused = true
|
65
|
+
else
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def resume
|
71
|
+
if @paused
|
72
|
+
@paused = false
|
73
|
+
true
|
74
|
+
else
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def seek(where, type = :absolute)
|
80
|
+
if playing?
|
81
|
+
case type
|
82
|
+
when :absolute
|
83
|
+
@time_left = @total_time - where
|
84
|
+
when :relative
|
85
|
+
@time_left -= where
|
86
|
+
when :percent
|
87
|
+
@time_left = @total_time - (@total_time * where) / 100
|
88
|
+
end
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def speed
|
96
|
+
@speed
|
97
|
+
end
|
98
|
+
|
99
|
+
def speed=(new_speed)
|
100
|
+
@speed = new_speed.to_f
|
101
|
+
true
|
102
|
+
end
|
103
|
+
|
104
|
+
def muted?
|
105
|
+
@muted
|
106
|
+
end
|
107
|
+
|
108
|
+
def mute
|
109
|
+
@muted = true
|
110
|
+
end
|
111
|
+
|
112
|
+
def unmute
|
113
|
+
@muted = false
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
def volume
|
118
|
+
@volume
|
119
|
+
end
|
120
|
+
|
121
|
+
def volume=(new_volume)
|
122
|
+
@muted = false
|
123
|
+
@volume = new_volume.to_f
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
def position
|
128
|
+
playing? ? @total_time - @time_left : false
|
129
|
+
end
|
130
|
+
|
131
|
+
def track_length
|
132
|
+
playing? ? @total_time : false
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def _quit
|
138
|
+
if playing?
|
139
|
+
@quit_hook.kill unless @quit_hook_active
|
140
|
+
@paused = false
|
141
|
+
@track_path = nil
|
142
|
+
true
|
143
|
+
else
|
144
|
+
false
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -4,6 +4,8 @@ class Plllayer
|
|
4
4
|
# "1:03:56.555". The only option is :include_milliseconds, true by default. If
|
5
5
|
# false, milliseconds won't be included in the formatted string.
|
6
6
|
def format_time(milliseconds, options = {})
|
7
|
+
raise ArgumentError, "can't format negative time" if milliseconds < 0
|
8
|
+
|
7
9
|
ms = milliseconds % 1000
|
8
10
|
seconds = (milliseconds / 1000) % 60
|
9
11
|
minutes = (milliseconds / 60000) % 60
|
@@ -25,8 +27,12 @@ class Plllayer
|
|
25
27
|
# Helper method to parse a string like "1:03:56.555" and return the number of
|
26
28
|
# milliseconds that time length represents.
|
27
29
|
def parse_time(string)
|
28
|
-
parts = string.split(":").map(&:to_f)
|
29
|
-
|
30
|
+
parts = string.split(":", -1).map(&:to_f)
|
31
|
+
|
32
|
+
raise ArgumentError, "too many parts" if parts.length > 3
|
33
|
+
raise ArgumentError, "can't parse negative numbers" if parts.any? { |x| x < 0 }
|
34
|
+
|
35
|
+
parts.unshift(0) until parts.length == 3
|
30
36
|
hours, minutes, seconds = parts
|
31
37
|
seconds = hours * 3600 + minutes * 60 + seconds
|
32
38
|
milliseconds = seconds * 1000
|
data/lib/plllayer.rb
CHANGED
@@ -3,6 +3,7 @@ require "open4"
|
|
3
3
|
require "plllayer/time_helpers.rb"
|
4
4
|
require "plllayer/single_player"
|
5
5
|
require "plllayer/single_players/mplayer"
|
6
|
+
require "plllayer/single_players/nop"
|
6
7
|
|
7
8
|
# Plllayer provides an interface to an external media player, such as mplayer. It
|
8
9
|
# contains a playlist of tracks, which may be as simple as an Array of paths to
|
@@ -14,7 +15,8 @@ class Plllayer
|
|
14
15
|
extend TimeHelpers
|
15
16
|
|
16
17
|
SINGLE_PLAYERS = {
|
17
|
-
mplayer: Plllayer::SinglePlayers::MPlayer
|
18
|
+
mplayer: Plllayer::SinglePlayers::MPlayer,
|
19
|
+
nop: Plllayer::SinglePlayers::Nop
|
18
20
|
}
|
19
21
|
|
20
22
|
attr_reader :repeat_mode
|
@@ -47,9 +49,6 @@ class Plllayer
|
|
47
49
|
@paused = false
|
48
50
|
@playing = false
|
49
51
|
@repeat_mode = nil
|
50
|
-
|
51
|
-
# Make sure the music stops playing once the Ruby script is exited.
|
52
|
-
at_exit { stop }
|
53
52
|
end
|
54
53
|
|
55
54
|
# Append tracks to the playlist. Can be done while the playlist is playing.
|
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 = "2013-
|
3
|
+
s.version = "0.0.3"
|
4
|
+
s.date = "2013-02-05"
|
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"
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_runtime_dependency gem_name
|
18
18
|
end
|
19
19
|
|
20
|
-
%w(rake).each do |gem_name|
|
20
|
+
%w(rake rspec).each do |gem_name|
|
21
21
|
s.add_development_dependency gem_name
|
22
22
|
end
|
23
23
|
end
|
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.3
|
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: 2013-
|
12
|
+
date: 2013-02-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
62
78
|
description: plllayer is an audio playback library for Ruby. It is a Ruby interface
|
63
79
|
to some external media player, such as mplayer.
|
64
80
|
email: jeremy.ruten@gmail.com
|
@@ -72,6 +88,7 @@ files:
|
|
72
88
|
- plllayer.gemspec
|
73
89
|
- lib/plllayer/single_player.rb
|
74
90
|
- lib/plllayer/single_players/mplayer.rb
|
91
|
+
- lib/plllayer/single_players/nop.rb
|
75
92
|
- lib/plllayer/time_helpers.rb
|
76
93
|
- lib/plllayer.rb
|
77
94
|
homepage: http://github.com/yjerem/plllayer
|