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.
@@ -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