plllayer 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- until @stdout.gets["playback"]
41
- end
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.sub("\e[A\r\e[K", "")
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
- parts = [0] + parts if parts.length == 2
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.2"
4
- s.date = "2013-01-31"
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.2
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-01-31 00:00:00.000000000 Z
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