jimmy_jukebox 0.2.6 → 0.3.6
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/bin/jload_jukebox +1 -27
- data/bin/jplay_jukebox +12 -40
- data/bin/load_jukebox +1 -27
- data/bin/play_jukebox +0 -41
- data/lib/jimmy_jukebox/jukebox.rb +83 -0
- data/lib/jimmy_jukebox/load_jukebox_code.rb +27 -0
- data/lib/jimmy_jukebox/song.rb +111 -0
- data/lib/jimmy_jukebox/user_interface.rb +41 -0
- data/lib/jimmy_jukebox/version.rb +1 -1
- data/lib/jimmy_jukebox.rb +5 -118
- data/spec/jimmy_jukebox_spec.rb +162 -101
- data/spec/song_loader_spec.rb +2 -2
- data/spec/song_spec.rb +143 -0
- data/spec/spec_helper.rb +12 -0
- metadata +13 -7
data/bin/jload_jukebox
CHANGED
@@ -1,29 +1,3 @@
|
|
1
1
|
#!/usr/bin/env jruby
|
2
2
|
|
3
|
-
|
4
|
-
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
-
require 'jimmy_jukebox/song_loader'
|
6
|
-
include JimmyJukebox
|
7
|
-
|
8
|
-
require 'jimmy_jukebox/artists'
|
9
|
-
include Artists
|
10
|
-
|
11
|
-
def no_argv0
|
12
|
-
puts "You must select an artist to use 'load_jukebox'."
|
13
|
-
puts "For example: 'load_jukebox at' to load Art Tatum"
|
14
|
-
puts "Another example: 'load_jukebox dr' to load Django Reinhardt"
|
15
|
-
exit
|
16
|
-
end
|
17
|
-
|
18
|
-
def invalid_artist
|
19
|
-
puts "No action taken in response to your command 'load_jukebox #{ARGV[0]}'."
|
20
|
-
puts "JimmyJukebox does not recognize '#{ARGV[0]}'. You must select a valid artist."
|
21
|
-
puts "For example, valid artists include: 'bh' for Billie Holiday, 'cb' for Count Basie, and 'lh' for Lionel Hampton"
|
22
|
-
puts "Please see the README for a complete list of valid artists."
|
23
|
-
exit
|
24
|
-
end
|
25
|
-
|
26
|
-
no_argv0 unless ARGV[0]
|
27
|
-
invalid_artist unless JAZZ_ARTISTS.has_key?(ARGV[0].to_sym)
|
28
|
-
JimmyJukebox::SongLoader.send(JAZZ_ARTISTS[ARGV[0].to_sym])
|
29
|
-
|
3
|
+
require File.dirname(__FILE__) + '/../lib/jimmy_jukebox/load_jukebox_code.rb'
|
data/bin/jplay_jukebox
CHANGED
@@ -1,49 +1,21 @@
|
|
1
1
|
#!/usr/bin/env jruby
|
2
|
-
require 'readline'
|
3
|
-
|
4
|
-
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
5
|
-
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
6
|
-
require 'jimmy_jukebox'
|
7
|
-
include JimmyJukebox
|
8
|
-
|
9
|
-
jj = Jukebox.new
|
10
2
|
|
11
|
-
|
12
|
-
jj.play_loop
|
13
|
-
end
|
3
|
+
require 'readline'
|
14
4
|
|
15
|
-
display_string = "Press 'p' to (un)pause, 'q' to quit, or 's' to skip the song"
|
16
5
|
begin
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
puts "Quit requested"
|
23
|
-
jj.quit
|
24
|
-
Thread.main.exit
|
25
|
-
when "p"
|
26
|
-
if play_loop_thread && jj.current_song_paused
|
27
|
-
puts "Unpause requested"
|
28
|
-
jj.unpause_current_song
|
29
|
-
elsif play_loop_thread
|
30
|
-
puts "Pause requested"
|
31
|
-
jj.pause_current_song
|
32
|
-
else
|
33
|
-
raise "Can't find play_loop_thread"
|
34
|
-
end
|
35
|
-
puts display_string
|
36
|
-
when "s"
|
37
|
-
puts "Skip song requested"
|
38
|
-
jj.skip_song
|
39
|
-
else
|
40
|
-
puts display_string
|
41
|
-
end
|
6
|
+
#Gem::Specification.find_by_name('spoon') # Gem.available?('spoon')
|
7
|
+
#gem 'spoon'
|
8
|
+
if (defined?(JRUBY_VERSION) || RUBY_PLATFORM == 'java')
|
9
|
+
require 'rubygems'
|
10
|
+
require 'spoon'
|
42
11
|
end
|
43
|
-
rescue
|
44
|
-
puts "
|
12
|
+
rescue LoadError
|
13
|
+
puts "*** You must install the 'spoon' gem to use JimmyJukebox on JRuby ***"
|
45
14
|
exit
|
46
15
|
end
|
47
16
|
|
48
|
-
|
17
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
18
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
19
|
+
require 'jimmy_jukebox'
|
20
|
+
include JimmyJukebox
|
49
21
|
|
data/bin/load_jukebox
CHANGED
@@ -1,29 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
4
|
-
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
-
require 'jimmy_jukebox/song_loader'
|
6
|
-
include JimmyJukebox
|
7
|
-
|
8
|
-
require 'jimmy_jukebox/artists'
|
9
|
-
include Artists
|
10
|
-
|
11
|
-
def no_argv0
|
12
|
-
puts "You must select an artist to use 'load_jukebox'."
|
13
|
-
puts "For example: 'load_jukebox at' to load Art Tatum"
|
14
|
-
puts "Another example: 'load_jukebox dr' to load Django Reinhardt"
|
15
|
-
exit
|
16
|
-
end
|
17
|
-
|
18
|
-
def invalid_artist
|
19
|
-
puts "No action taken in response to your command 'load_jukebox #{ARGV[0]}'."
|
20
|
-
puts "JimmyJukebox does not recognize '#{ARGV[0]}'. You must select a valid artist."
|
21
|
-
puts "For example, valid artists include: 'bh' for Billie Holiday, 'cb' for Count Basie, and 'lh' for Lionel Hampton"
|
22
|
-
puts "Please see the README for a complete list of valid artists."
|
23
|
-
exit
|
24
|
-
end
|
25
|
-
|
26
|
-
no_argv0 unless ARGV[0]
|
27
|
-
invalid_artist unless JAZZ_ARTISTS.has_key?(ARGV[0].to_sym)
|
28
|
-
JimmyJukebox::SongLoader.send(JAZZ_ARTISTS[ARGV[0].to_sym])
|
29
|
-
|
3
|
+
require File.dirname(__FILE__) + '/../lib/jimmy_jukebox/load_jukebox_code.rb'
|
data/bin/play_jukebox
CHANGED
@@ -6,44 +6,3 @@ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
|
6
6
|
require 'jimmy_jukebox'
|
7
7
|
include JimmyJukebox
|
8
8
|
|
9
|
-
jj = Jukebox.new
|
10
|
-
|
11
|
-
play_loop_thread = Thread.new do
|
12
|
-
jj.play_loop
|
13
|
-
end
|
14
|
-
|
15
|
-
display_string = "Press 'p' to (un)pause, 'q' to quit, or 's' to skip the song"
|
16
|
-
begin
|
17
|
-
while true do
|
18
|
-
puts display_string
|
19
|
-
line = Readline.readline('> ', true)
|
20
|
-
case line.strip
|
21
|
-
when "q"
|
22
|
-
puts "Quit requested"
|
23
|
-
jj.quit
|
24
|
-
Thread.main.exit
|
25
|
-
when "p"
|
26
|
-
if play_loop_thread && jj.current_song_paused
|
27
|
-
puts "Unpause requested"
|
28
|
-
jj.unpause_current_song
|
29
|
-
elsif play_loop_thread
|
30
|
-
puts "Pause requested"
|
31
|
-
jj.pause_current_song
|
32
|
-
else
|
33
|
-
raise "Can't find play_loop_thread"
|
34
|
-
end
|
35
|
-
puts display_string
|
36
|
-
when "s"
|
37
|
-
puts "Skip song requested"
|
38
|
-
jj.skip_song
|
39
|
-
else
|
40
|
-
puts display_string
|
41
|
-
end
|
42
|
-
end
|
43
|
-
rescue Interrupt => e
|
44
|
-
puts "\nMusic terminated by user"
|
45
|
-
exit
|
46
|
-
end
|
47
|
-
|
48
|
-
play_loop_thread.join
|
49
|
-
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module JimmyJukebox
|
2
|
+
|
3
|
+
module Jukebox
|
4
|
+
|
5
|
+
class << Jukebox
|
6
|
+
|
7
|
+
attr_accessor :current_song, :continuous_play
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.play_loop
|
12
|
+
@continuous_play = true
|
13
|
+
while @continuous_play do
|
14
|
+
play_once
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.play_once
|
19
|
+
begin
|
20
|
+
play_random_song
|
21
|
+
rescue SystemExit, Interrupt => e
|
22
|
+
terminate_current_song
|
23
|
+
puts "\nMusic terminated by user"
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.quit
|
29
|
+
stop_looping
|
30
|
+
terminate_current_song
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.skip_song
|
34
|
+
if @current_song
|
35
|
+
puts "Terminating #{@current_song.music_file}"
|
36
|
+
terminate_current_song
|
37
|
+
else
|
38
|
+
raise "No @current_song"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.pause_current_song
|
43
|
+
@current_song.pause
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.unpause_current_song
|
47
|
+
@current_song.unpause
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.stop_looping
|
51
|
+
@continuous_play = false
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.songs
|
55
|
+
user_config.songs
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.play_random_song
|
59
|
+
#terminate_current_song
|
60
|
+
raise "JimmyJukebox has no songs to play!" if songs.length == 0
|
61
|
+
@current_song = Song.new( songs[rand(songs.length)] )
|
62
|
+
@current_song.play(user_config)
|
63
|
+
@current_song = nil # ????
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.terminate_current_song
|
67
|
+
if @current_song
|
68
|
+
puts "Terminating #{@current_song.music_file}"
|
69
|
+
@current_song.terminate
|
70
|
+
#@current_song = nil
|
71
|
+
else
|
72
|
+
puts "No song is currently playing"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.user_config
|
77
|
+
@user_config = UserConfig.new unless @user_config
|
78
|
+
@user_config
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/..')
|
2
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
3
|
+
require 'jimmy_jukebox/song_loader'
|
4
|
+
include JimmyJukebox
|
5
|
+
|
6
|
+
require 'jimmy_jukebox/artists'
|
7
|
+
include Artists
|
8
|
+
|
9
|
+
def no_argv0
|
10
|
+
puts "You must select an artist to use 'load_jukebox'."
|
11
|
+
puts "For example: 'load_jukebox at' to load Art Tatum"
|
12
|
+
puts "Another example: 'load_jukebox dr' to load Django Reinhardt"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
def invalid_artist
|
17
|
+
puts "No action taken in response to your command 'load_jukebox #{ARGV[0]}'."
|
18
|
+
puts "JimmyJukebox does not recognize '#{ARGV[0]}'. You must select a valid artist."
|
19
|
+
puts "For example, valid artists include: 'bh' for Billie Holiday, 'cb' for Count Basie, and 'lh' for Lionel Hampton"
|
20
|
+
puts "Please see the README for a complete list of valid artists."
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
|
24
|
+
no_argv0 unless ARGV[0]
|
25
|
+
invalid_artist unless JAZZ_ARTISTS.has_key?(ARGV[0].to_sym)
|
26
|
+
JimmyJukebox::SongLoader.send(JAZZ_ARTISTS[ARGV[0].to_sym])
|
27
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module JimmyJukebox
|
2
|
+
|
3
|
+
class Song
|
4
|
+
|
5
|
+
attr_reader :paused, :music_file
|
6
|
+
attr_accessor :player, :playing_pid
|
7
|
+
|
8
|
+
def initialize(music_file)
|
9
|
+
set_music_file(music_file)
|
10
|
+
@paused = false
|
11
|
+
@playing_pid = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_music_file(music_file)
|
15
|
+
if music_file =~ /\.mp3$/i || music_file =~ /\.ogg$/i
|
16
|
+
@music_file = music_file
|
17
|
+
else
|
18
|
+
raise "You can create a song only with an .mp3 or .ogg file"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def pause
|
23
|
+
@paused = true
|
24
|
+
# jruby doesn't seem to handle system() correctly
|
25
|
+
# trying backticks
|
26
|
+
# system("kill -s STOP #{@playing_pid}") if @playing_pid
|
27
|
+
`kill -s STOP #{@playing_pid}` if @playing_pid
|
28
|
+
end
|
29
|
+
|
30
|
+
def unpause
|
31
|
+
@paused = false
|
32
|
+
# jruby doesn't seem to handle system() correctly
|
33
|
+
# trying backticks
|
34
|
+
#system("kill -s CONT #{@playing_pid}") if @playing_pid
|
35
|
+
`kill -s CONT #{@playing_pid}` if @playing_pid
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete
|
39
|
+
# without temp var, next song could start playing
|
40
|
+
# in another thread and wrong file could be deleted
|
41
|
+
file_to_delete = @music_file
|
42
|
+
terminate
|
43
|
+
File.delete(file_to_delete)
|
44
|
+
end
|
45
|
+
|
46
|
+
def terminate
|
47
|
+
@paused = false
|
48
|
+
#`killall #{@player}`
|
49
|
+
@player = nil
|
50
|
+
# killing processes seems problematic in JRuby
|
51
|
+
# I've tried several approaches, and nothing seems reliable
|
52
|
+
#Process.kill("SIGKILL",@playing_pid) if @playing_pid
|
53
|
+
#Process.kill("SIGTERM",@playing_pid) if @playing_pid
|
54
|
+
`kill #{@playing_pid}` if @playing_pid
|
55
|
+
@playing_pid = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_player(user_config)
|
59
|
+
if @music_file =~ /\.mp3$/i
|
60
|
+
@player = user_config.mp3_player
|
61
|
+
elsif @music_file =~ /\.ogg$/i
|
62
|
+
@player = user_config.ogg_player
|
63
|
+
end
|
64
|
+
raise "Attempted to play a file format this program cannot play" unless @player
|
65
|
+
end
|
66
|
+
|
67
|
+
def play(user_config)
|
68
|
+
set_player(user_config)
|
69
|
+
process_status = play_with_player
|
70
|
+
process_status.exitstatus.to_i == 0 ? (@playing_pid = nil) : (raise "Experienced a problem playing a song")
|
71
|
+
end
|
72
|
+
|
73
|
+
def play_with_player
|
74
|
+
puts "Press Ctrl-C to stop the music and exit this program"
|
75
|
+
puts "Now playing '#{@music_file}'"
|
76
|
+
puts "#{@player} \"#{File.expand_path(@music_file)}\""
|
77
|
+
system_yield_pid(@player, File.expand_path(@music_file)) do |pid|
|
78
|
+
@playing_pid = pid
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
# make system call and get pid so you can terminate process
|
85
|
+
def system_yield_pid(player, filename)
|
86
|
+
# would like to use Process.respond_to?(:fork) but JRuby mistakenly returns true
|
87
|
+
if (defined?(JRUBY_VERSION) || RUBY_PLATFORM == 'java')
|
88
|
+
pid = Spoon.spawnp("#{player}", "#{filename}")
|
89
|
+
else
|
90
|
+
begin
|
91
|
+
pid = fork do # creates and runs block in subprocess (which will terminate with status 0), capture subprocess pid
|
92
|
+
exec(player,filename) # replaces current process with system call
|
93
|
+
exit! 127 # exit process and return exit status 127; should never be reached
|
94
|
+
end
|
95
|
+
rescue NotImplementedError
|
96
|
+
raise "*** fork()...exec() not supported ***"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
yield pid if block_given? # call block, passing in the subprocess pid
|
100
|
+
#if pid
|
101
|
+
# puts "pid: #{pid}"
|
102
|
+
# puts "current_song: #{Jukebox.current_song.inspect}"
|
103
|
+
#else
|
104
|
+
# puts "No process id (pid)!"
|
105
|
+
# raise "@current_song: #{Jukebox.current_song.inspect}"
|
106
|
+
#end
|
107
|
+
Process.waitpid(pid) # Waits for a child process to exit, returns its process id, and sets $? to a Process::Status object
|
108
|
+
$? # return Process::Status object with instance methods .stopped?, .exited?, .exitstatus; see: http://www.ruby-doc.org/core/classes/Process/Status.html
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
jj = Jukebox
|
2
|
+
|
3
|
+
play_loop_thread = Thread.new do
|
4
|
+
jj.play_loop
|
5
|
+
end
|
6
|
+
|
7
|
+
display_string = "Press 'p' to (un)pause, 'q' to quit, or 's' to skip the song"
|
8
|
+
begin
|
9
|
+
while true do
|
10
|
+
puts display_string
|
11
|
+
line = Readline.readline('> ', true)
|
12
|
+
case line.strip
|
13
|
+
when "q"
|
14
|
+
puts "Quit requested"
|
15
|
+
jj.quit
|
16
|
+
Thread.main.exit
|
17
|
+
when "p"
|
18
|
+
if play_loop_thread && jj.current_song.paused
|
19
|
+
puts "Unpause requested"
|
20
|
+
jj.unpause_current_song
|
21
|
+
elsif play_loop_thread
|
22
|
+
puts "Pause requested"
|
23
|
+
jj.pause_current_song
|
24
|
+
else
|
25
|
+
raise "Can't find play_loop_thread"
|
26
|
+
end
|
27
|
+
puts display_string
|
28
|
+
when "s"
|
29
|
+
puts "Skip song requested"
|
30
|
+
jj.skip_song
|
31
|
+
else
|
32
|
+
puts display_string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue Interrupt => e
|
36
|
+
puts "\nMusic terminated by user"
|
37
|
+
exit
|
38
|
+
end
|
39
|
+
|
40
|
+
play_loop_thread.join
|
41
|
+
|
data/lib/jimmy_jukebox.rb
CHANGED
@@ -1,120 +1,7 @@
|
|
1
|
-
|
1
|
+
require 'jimmy_jukebox/user_config'
|
2
|
+
require 'jimmy_jukebox/song'
|
3
|
+
require 'jimmy_jukebox/jukebox'
|
2
4
|
|
3
|
-
|
4
|
-
def system_yield_pid(*cmd)
|
5
|
-
# would like to use Process.respond_to?(:fork) but JRuby mistakenly returns true
|
6
|
-
begin
|
7
|
-
pid = fork do # creates and runs block in subprocess (which will terminate with status 0), capture subprocess pid
|
8
|
-
exec(*cmd) # replaces current process with system call
|
9
|
-
exit! 127 # exit process and return exit status 127; should never be reached
|
10
|
-
end
|
11
|
-
rescue NotImplementedError
|
12
|
-
require 'rubygems'
|
13
|
-
require 'spoon'
|
14
|
-
pid = Spoon.spawnp(*cmd)
|
15
|
-
#raise "*** fork() not supported ***" unless Process.respond_to?(:fork)
|
16
|
-
end
|
17
|
-
yield pid if block_given? # call block, passing in the subprocess pid
|
18
|
-
Process.waitpid(pid) # Waits for a child process to exit, returns its process id, and sets $? to a Process::Status object
|
19
|
-
$? # return Process::Status object with instance methods .stopped?, .exited?, .exitstatus; see: http://www.ruby-doc.org/core/classes/Process/Status.html
|
20
|
-
end
|
5
|
+
include JimmyJukebox
|
21
6
|
|
22
|
-
|
23
|
-
|
24
|
-
require 'jimmy_jukebox/user_config'
|
25
|
-
|
26
|
-
attr_reader :loop, :current_song_paused, :playing_pid
|
27
|
-
|
28
|
-
def initialize
|
29
|
-
@user_config = UserConfig.new
|
30
|
-
end
|
31
|
-
|
32
|
-
def play_loop
|
33
|
-
@loop = true
|
34
|
-
while @loop do
|
35
|
-
play
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def play
|
40
|
-
begin
|
41
|
-
play_random_song(@user_config.songs)
|
42
|
-
rescue SystemExit, Interrupt => e
|
43
|
-
terminate_current_song
|
44
|
-
puts "\nMusic terminated by user"
|
45
|
-
exit
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def quit
|
50
|
-
stop_looping
|
51
|
-
terminate_current_song
|
52
|
-
end
|
53
|
-
|
54
|
-
def skip_song
|
55
|
-
terminate_current_song
|
56
|
-
end
|
57
|
-
|
58
|
-
def pause_current_song
|
59
|
-
@current_song_paused = true
|
60
|
-
# jruby doesn't seem to handle system() correctly
|
61
|
-
# trying backticks
|
62
|
-
# system("kill -s STOP #{@playing_pid}") if @playing_pid
|
63
|
-
`kill -s STOP #{@playing_pid}` if @playing_pid
|
64
|
-
end
|
65
|
-
|
66
|
-
def unpause_current_song
|
67
|
-
@current_song_paused = false
|
68
|
-
# jruby doesn't seem to handle system() correctly
|
69
|
-
# trying backticks
|
70
|
-
#system("kill -s CONT #{@playing_pid}") if @playing_pid
|
71
|
-
`kill -s CONT #{@playing_pid}` if @playing_pid
|
72
|
-
end
|
73
|
-
|
74
|
-
private
|
75
|
-
|
76
|
-
def stop_looping
|
77
|
-
@loop = false
|
78
|
-
end
|
79
|
-
|
80
|
-
def play_random_song(songs)
|
81
|
-
terminate_current_song
|
82
|
-
raise "JimmyJukebox has no songs to play!" if songs.length == 0
|
83
|
-
music_file = songs[rand(songs.length)]
|
84
|
-
play_file(music_file)
|
85
|
-
end
|
86
|
-
|
87
|
-
def terminate_current_song
|
88
|
-
if @playing_pid
|
89
|
-
@current_song_paused = false
|
90
|
-
# killing processes seems problematic in JRuby
|
91
|
-
# I've tried several approaches, and nothing seems reliable
|
92
|
-
Process.kill("SIGKILL",@playing_pid)
|
93
|
-
#Process.kill("SIGTERM",@playing_pid)
|
94
|
-
#`kill #{@playing_pid}` if @playing_pid
|
95
|
-
@playing_pid = nil
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def play_file(music_file)
|
100
|
-
# TODO: refactor the duplicate code below into a method
|
101
|
-
if music_file =~ /\.mp3$/i && @user_config.mp3_player
|
102
|
-
process_status = play_file_with(music_file, @user_config.mp3_player)
|
103
|
-
elsif music_file =~ /\.ogg$/i && @user_config.ogg_player
|
104
|
-
process_status = play_file_with(music_file, @user_config.ogg_player)
|
105
|
-
else
|
106
|
-
raise "Attempted to play a file format this program cannot play"
|
107
|
-
end
|
108
|
-
process_status.exitstatus.to_i == 0 ? (@playing_pid = nil) : (raise "Experienced a problem playing a song")
|
109
|
-
end
|
110
|
-
|
111
|
-
def play_file_with(music_file,player)
|
112
|
-
puts "Press Ctrl-C to stop the music and exit this program"
|
113
|
-
system_yield_pid(player, File.expand_path(music_file)) do |pid|
|
114
|
-
@playing_pid = pid
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
end
|
7
|
+
require 'jimmy_jukebox/user_interface'
|
data/spec/jimmy_jukebox_spec.rb
CHANGED
@@ -3,29 +3,20 @@ require 'fakefs/safe'
|
|
3
3
|
require 'jimmy_jukebox'
|
4
4
|
include JimmyJukebox
|
5
5
|
|
6
|
-
# Override exec() to prevent songs from actually playing
|
7
|
-
# Instead, start a brief sleep process
|
8
|
-
module Kernel
|
9
|
-
alias :real_exec :exec
|
10
|
-
|
11
|
-
def exec(*cmd)
|
12
|
-
real_exec("sleep 0.2")
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
6
|
describe Jukebox do
|
17
7
|
|
18
8
|
before(:all) do
|
19
|
-
|
20
|
-
ARGV.pop
|
9
|
+
ARGV.clear
|
10
|
+
#ARGV.pop
|
21
11
|
end
|
22
12
|
|
13
|
+
let(:uc) { double('user_config').as_null_object }
|
14
|
+
|
23
15
|
context "with no command line parameter" do
|
24
16
|
|
25
|
-
it "
|
26
|
-
|
27
|
-
|
28
|
-
jj.quit
|
17
|
+
it "exists" do
|
18
|
+
Jukebox.should_not be_nil
|
19
|
+
Jukebox.quit
|
29
20
|
end
|
30
21
|
|
31
22
|
#it "raises exception when no songs available"
|
@@ -34,102 +25,172 @@ describe Jukebox do
|
|
34
25
|
# end.should raise_error
|
35
26
|
#end
|
36
27
|
|
28
|
+
it "has a user_config method" do
|
29
|
+
Jukebox.user_config.is_a?(UserConfig)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has a @user_config instance variable" do
|
33
|
+
Jukebox.instance_variable_get(:@user_config).is_a?(UserConfig)
|
34
|
+
end
|
35
|
+
|
37
36
|
it "generates a non-empty song list" do
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
jj.instance_variable_get(:@user_config).songs.length.should be > 0
|
37
|
+
Jukebox.user_config.songs.should_not be_nil
|
38
|
+
Jukebox.user_config.songs.should_not be_empty
|
39
|
+
Jukebox.user_config.songs.length.should be > 0
|
42
40
|
end
|
43
41
|
|
44
42
|
it "generates a non-empty song list with only mp3 & ogg files" do
|
45
|
-
|
46
|
-
jj.instance_variable_get(:@user_config).songs.each do |song|
|
43
|
+
Jukebox.user_config.songs.each do |song|
|
47
44
|
song.should match(/.*\.mp3|.*\.ogg/i)
|
48
45
|
end
|
49
46
|
end
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
describe "#play_once" do
|
49
|
+
|
50
|
+
it "should call play_random_song" do
|
51
|
+
Jukebox.should_receive(:play_random_song)
|
52
|
+
Jukebox.play_once
|
55
53
|
end
|
56
|
-
sleep 0.2
|
57
|
-
jj.instance_variable_get(:@playing_pid).should_not be_nil
|
58
|
-
jj.should_receive(:terminate_current_song)
|
59
|
-
jj.quit
|
60
|
-
end
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
55
|
+
it "should have a current_song" do
|
56
|
+
jj = Jukebox
|
57
|
+
uc.stub(:mp3_player) {"play"}
|
58
|
+
uc.stub(:ogg_player) {"play"}
|
59
|
+
thread = Thread.new do
|
60
|
+
Jukebox.play_once
|
61
|
+
end
|
62
|
+
sleep 0.1
|
63
|
+
jj.current_song.is_a?(Song)
|
64
|
+
thread.exit
|
66
65
|
end
|
67
|
-
sleep 0.1
|
68
|
-
song1 = jj.playing_pid
|
69
|
-
song1.should_not be_nil
|
70
|
-
jj.loop.should be_true
|
71
|
-
sleep 0.2
|
72
|
-
song2 = jj.playing_pid
|
73
|
-
song2.should_not be_nil
|
74
|
-
song2.should_not == song1
|
75
|
-
jj.quit
|
76
|
-
end
|
77
66
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
67
|
+
it "should have a current_song with a music_file" do
|
68
|
+
jj = Jukebox
|
69
|
+
uc.stub(:mp3_player) {"play"}
|
70
|
+
uc.stub(:ogg_player) {"play"}
|
71
|
+
thread = Thread.new do
|
72
|
+
Jukebox.play_once
|
73
|
+
end
|
74
|
+
sleep 0.1
|
75
|
+
jj.current_song.music_file.should match /\.mp3$|\.ogg$/
|
76
|
+
thread.exit
|
82
77
|
end
|
83
|
-
sleep 0.2
|
84
|
-
song_1 = jj.playing_pid
|
85
|
-
jj.skip_song
|
86
|
-
sleep 0.2
|
87
|
-
song_2 = jj.playing_pid
|
88
|
-
jj.skip_song
|
89
|
-
sleep 0.2
|
90
|
-
song_3 = jj.playing_pid
|
91
|
-
song_1.should_not == song_2 || song_2.should_not == song_3
|
92
|
-
jj.quit
|
93
|
-
end
|
94
78
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
79
|
+
it "should have a player" do
|
80
|
+
jj = Jukebox
|
81
|
+
thread = Thread.new do
|
82
|
+
Jukebox.play_once
|
83
|
+
end
|
84
|
+
sleep 0.1
|
85
|
+
jj.current_song.player.should_not be_nil
|
86
|
+
thread.exit
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should not have a current_song after song finishes" do
|
90
|
+
jj = Jukebox
|
91
|
+
thread = Thread.new do
|
92
|
+
Jukebox.play_once
|
93
|
+
end
|
94
|
+
sleep 0.3
|
95
|
+
jj.current_song.should be_nil
|
96
|
+
thread.exit
|
99
97
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
98
|
+
|
99
|
+
it "should have a current_song with a playing_pid" do
|
100
|
+
jj = Jukebox
|
101
|
+
thread = Thread.new do
|
102
|
+
jj.play_once
|
103
|
+
end
|
104
|
+
sleep 0.1
|
105
|
+
jj.current_song.playing_pid.should_not be_nil
|
106
|
+
#jj.should_receive(:terminate_current_song)
|
107
|
+
jj.quit
|
108
|
+
thread.exit
|
109
|
+
end
|
110
|
+
|
111
|
+
it "triggers play_random_song" do
|
112
|
+
Jukebox.should_receive(:play_random_song)
|
113
|
+
Jukebox.play_once
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can pause the current song" do
|
117
|
+
thread = Thread.new do
|
118
|
+
Jukebox.play_once
|
119
|
+
end
|
120
|
+
sleep 0.1
|
121
|
+
song_1 = Jukebox.current_song.playing_pid
|
122
|
+
Jukebox.pause_current_song
|
123
|
+
song_2 = Jukebox.current_song.playing_pid
|
124
|
+
song_1.should == song_2
|
125
|
+
Jukebox.current_song.paused.should be_true
|
126
|
+
Jukebox.quit
|
127
|
+
thread.exit
|
128
|
+
end
|
129
|
+
|
130
|
+
it "can unpause a paused song" do
|
131
|
+
thread = Thread.new do
|
132
|
+
Jukebox.play_once
|
133
|
+
end
|
134
|
+
sleep 0.05
|
135
|
+
song_1 = Jukebox.current_song
|
136
|
+
Jukebox.current_song.paused.should be_false
|
137
|
+
Jukebox.pause_current_song
|
138
|
+
song_2 = Jukebox.current_song
|
139
|
+
Jukebox.current_song.paused.should be_true
|
140
|
+
song_2.should == song_1
|
141
|
+
Jukebox.unpause_current_song
|
142
|
+
Jukebox.current_song.paused.should be_false
|
143
|
+
song_3 = Jukebox.current_song
|
144
|
+
Jukebox.current_song.paused.should be_false
|
145
|
+
Jukebox.pause_current_song
|
146
|
+
song_4 = Jukebox.current_song
|
147
|
+
Jukebox.current_song.paused.should be_true
|
148
|
+
song_4.should == song_3
|
149
|
+
Jukebox.unpause_current_song
|
150
|
+
Jukebox.current_song.paused.should be_false
|
151
|
+
Jukebox.quit
|
152
|
+
thread.exit
|
153
|
+
end
|
154
|
+
|
107
155
|
end
|
108
156
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
157
|
+
describe "#play_loop" do
|
158
|
+
|
159
|
+
it "plays multiple songs" do
|
160
|
+
thread = Thread.new do
|
161
|
+
Jukebox.play_loop
|
162
|
+
end
|
163
|
+
sleep 0.1
|
164
|
+
song1 = Jukebox.current_song.playing_pid
|
165
|
+
song1.should_not be_nil
|
166
|
+
Jukebox.continuous_play.should be_true
|
167
|
+
sleep 0.2
|
168
|
+
song2 = Jukebox.current_song.playing_pid
|
169
|
+
song2.should_not be_nil
|
170
|
+
song2.should_not == song1
|
171
|
+
Jukebox.quit
|
172
|
+
thread.exit
|
113
173
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
174
|
+
|
175
|
+
it "can skip a song" do
|
176
|
+
thread = Thread.new do
|
177
|
+
Jukebox.play_loop
|
178
|
+
end
|
179
|
+
sleep 0.2
|
180
|
+
song_1 = Jukebox.current_song
|
181
|
+
Jukebox.skip_song
|
182
|
+
sleep 0.2
|
183
|
+
song_2 = Jukebox.current_song
|
184
|
+
Jukebox.skip_song
|
185
|
+
sleep 0.2
|
186
|
+
song_3 = Jukebox.current_song
|
187
|
+
song_1.should_not == song_2 || song_2.should_not == song_3
|
188
|
+
Jukebox.quit
|
189
|
+
thread.exit
|
190
|
+
end
|
191
|
+
|
132
192
|
end
|
193
|
+
|
133
194
|
end
|
134
195
|
|
135
196
|
context "with valid music directory as command line parameter" do
|
@@ -140,20 +201,20 @@ describe Jukebox do
|
|
140
201
|
end
|
141
202
|
|
142
203
|
it "can skip a song" do
|
143
|
-
jj = Jukebox.new
|
144
204
|
thread = Thread.new do
|
145
|
-
|
205
|
+
Jukebox.play_loop
|
146
206
|
end
|
147
207
|
sleep 0.2
|
148
|
-
song_1 =
|
149
|
-
|
208
|
+
song_1 = Jukebox.current_song
|
209
|
+
Jukebox.skip_song
|
150
210
|
sleep 0.2
|
151
|
-
song_2 =
|
152
|
-
|
211
|
+
song_2 = Jukebox.current_song
|
212
|
+
Jukebox.skip_song
|
153
213
|
sleep 0.2
|
154
|
-
song_3 =
|
214
|
+
song_3 = Jukebox.current_song
|
155
215
|
song_1.should_not == song_2 || song_2.should_not == song_3
|
156
|
-
|
216
|
+
Jukebox.quit
|
217
|
+
thread.exit
|
157
218
|
end
|
158
219
|
|
159
220
|
end
|
data/spec/song_loader_spec.rb
CHANGED
data/spec/song_spec.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jimmy_jukebox/song'
|
3
|
+
require 'jimmy_jukebox/user_config'
|
4
|
+
|
5
|
+
include JimmyJukebox
|
6
|
+
|
7
|
+
describe Song do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#initialize" do
|
13
|
+
|
14
|
+
it "requires a parameter" do
|
15
|
+
expect {Song.new}.to raise_error
|
16
|
+
end
|
17
|
+
|
18
|
+
it "fails if parameter does not end in .mp3 or .ogg" do
|
19
|
+
expect {Song.new("/home/bill/music_file")}.to raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it "accepts a parameter ending in .mp3" do
|
23
|
+
Song.new("/home/bill/music_file.mp3").is_a?(Song)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "accepts a parameter ending in .ogg" do
|
27
|
+
Song.new("/home/bill/music_file.ogg").is_a?(Song)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "sets a music file" do
|
31
|
+
mf = "/home/bill/Music/JAZZ/billie_holiday.ogg"
|
32
|
+
song = Song.new(mf)
|
33
|
+
song.music_file.should == mf
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#paused" do
|
39
|
+
|
40
|
+
before(:each) do
|
41
|
+
@song = Song.new("~/Music/JAZZ/art_tatum.mp3")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is initially not paused" do
|
45
|
+
@song.paused.should be_false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "is paused after calling #pause" do
|
49
|
+
@song.pause
|
50
|
+
@song.paused.should be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is unpaused after calling #pause and #unpause" do
|
54
|
+
@song.pause
|
55
|
+
@song.unpause
|
56
|
+
@song.paused.should be_false
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#play" do
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
@music_file = "~/Music/JAZZ/art_tatum.mp3"
|
65
|
+
@song = Song.new(@music_file)
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:uc) { double('user_config').as_null_object }
|
69
|
+
let(:ps) { double('process_status').as_null_object}
|
70
|
+
|
71
|
+
it "calls #play_with_player" do
|
72
|
+
ps.stub(:exitstatus).and_return(0)
|
73
|
+
@song.should_receive(:play_with_player).and_return(ps)
|
74
|
+
uc.stub(:mp3_player) {"play"}
|
75
|
+
uc.stub(:ogg_player) {"play"}
|
76
|
+
@song.play(uc)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises error when exitstatus != 0" do
|
80
|
+
ps.stub(:exitstatus).and_return(1)
|
81
|
+
@song.should_receive(:play_with_player).and_return(ps)
|
82
|
+
uc.stub(:mp3_player) {"play"}
|
83
|
+
uc.stub(:ogg_player) {"play"}
|
84
|
+
expect{@song.play(uc)}.to raise_error
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#play_with_player" do
|
90
|
+
|
91
|
+
before(:each) do
|
92
|
+
@music_file = "~/Music/JAZZ/art_tatum.mp3"
|
93
|
+
@song = Song.new(@music_file)
|
94
|
+
end
|
95
|
+
|
96
|
+
let(:uc) { double('user_config').as_null_object }
|
97
|
+
let(:ps) { double('process_status').as_null_object}
|
98
|
+
|
99
|
+
it "calls #system_yield_pid" do
|
100
|
+
uc.stub(:mp3_player) {"play"}
|
101
|
+
uc.stub(:ogg_player) {"play"}
|
102
|
+
@song.set_player(uc)
|
103
|
+
@song.should_receive(:system_yield_pid).with("play",File.expand_path(@music_file)).and_return(ps)
|
104
|
+
@song.play_with_player
|
105
|
+
end
|
106
|
+
|
107
|
+
it "calls #system_yield_pid and captures playing_pid" do
|
108
|
+
pending
|
109
|
+
uc.stub(:mp3_player) {"play"}
|
110
|
+
uc.stub(:ogg_player) {"play"}
|
111
|
+
@song.set_player(uc)
|
112
|
+
@song.should_receive(:system_yield_pid).with("play",File.expand_path(@music_file)).and_yield(1469)
|
113
|
+
@song.play_with_player
|
114
|
+
@song.playing_pid.should == 1469
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#playing_pid" do
|
120
|
+
|
121
|
+
before(:each) do
|
122
|
+
@song = Song.new("~/Music/JAZZ/art_tatum.mp3")
|
123
|
+
end
|
124
|
+
|
125
|
+
let(:uc) { double('user_config').as_null_object }
|
126
|
+
|
127
|
+
it "is initially nil" do
|
128
|
+
@song.playing_pid.should be_nil
|
129
|
+
end
|
130
|
+
|
131
|
+
it "is not nil after #play" do
|
132
|
+
uc.stub(:mp3_player) {"play"}
|
133
|
+
uc.stub(:ogg_player) {"play"}
|
134
|
+
thread = Thread.new do
|
135
|
+
@song.play(uc)
|
136
|
+
end
|
137
|
+
sleep 0.1
|
138
|
+
@song.playing_pid.should_not be_nil
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,3 +3,15 @@ RSpec.configure do |config|
|
|
3
3
|
ARGV.clear
|
4
4
|
end
|
5
5
|
end
|
6
|
+
|
7
|
+
# Override exec() to prevent songs from actually playing
|
8
|
+
# Instead, start a brief sleep process
|
9
|
+
module Kernel
|
10
|
+
alias :real_exec :exec
|
11
|
+
|
12
|
+
def exec(*cmd)
|
13
|
+
real_exec("sleep 0.2")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jimmy_jukebox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 6
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.6
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- James Lavin
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-09-02 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rspec
|
@@ -115,10 +115,14 @@ extra_rdoc_files: []
|
|
115
115
|
files:
|
116
116
|
- roadmap.txt
|
117
117
|
- LICENSE.txt
|
118
|
+
- lib/jimmy_jukebox/load_jukebox_code.rb
|
119
|
+
- lib/jimmy_jukebox/jukebox.rb
|
118
120
|
- lib/jimmy_jukebox/song_loader.rb
|
119
121
|
- lib/jimmy_jukebox/user_config.rb
|
120
122
|
- lib/jimmy_jukebox/version.rb
|
123
|
+
- lib/jimmy_jukebox/song.rb
|
121
124
|
- lib/jimmy_jukebox/artists.rb
|
125
|
+
- lib/jimmy_jukebox/user_interface.rb
|
122
126
|
- lib/jimmy_jukebox.rb
|
123
127
|
- lib/jimmy_jukebox/songs/BennieMoten.yml
|
124
128
|
- lib/jimmy_jukebox/songs/MilesDavis.yml
|
@@ -148,6 +152,7 @@ files:
|
|
148
152
|
- spec/user_config_spec.rb
|
149
153
|
- spec/jimmy_jukebox_spec.rb
|
150
154
|
- spec/song_loader_spec.rb
|
155
|
+
- spec/song_spec.rb
|
151
156
|
- bin/play_jukebox
|
152
157
|
- bin/load_jukebox
|
153
158
|
- bin/jplay_jukebox
|
@@ -178,10 +183,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
183
|
segments:
|
179
184
|
- 0
|
180
185
|
version: "0"
|
181
|
-
requirements:
|
182
|
-
|
186
|
+
requirements:
|
187
|
+
- spoon gem (JRuby only)
|
183
188
|
rubyforge_project: jimmy_jukebox
|
184
|
-
rubygems_version: 1.8.
|
189
|
+
rubygems_version: 1.8.7
|
185
190
|
signing_key:
|
186
191
|
specification_version: 3
|
187
192
|
summary: plays your MP3 & OGG files and lets you easily download music
|
@@ -190,3 +195,4 @@ test_files:
|
|
190
195
|
- spec/user_config_spec.rb
|
191
196
|
- spec/jimmy_jukebox_spec.rb
|
192
197
|
- spec/song_loader_spec.rb
|
198
|
+
- spec/song_spec.rb
|