Class: JimmyJukebox::Song

Inherits:
Object show all
Includes:
DisplayOptions
Defined in:
lib/jimmy_jukebox/song.rb

Defined Under Namespace

Classes: CannotSpawnProcessException, InvalidSongFormatException, NoPlayingPidException, SongTerminatedPrematurelyException, UnsupportedSongFormatException

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DisplayOptions

#display_options, #display_options_after_delay

Constructor Details

#initialize(in_music_file) ⇒ Song

Returns a new instance of Song.



19
20
21
22
23
# File 'lib/jimmy_jukebox/song.rb', line 19

def initialize(in_music_file)
  self.music_file = in_music_file
  self.paused = false
  self.playing_pid = nil
end

Instance Attribute Details

#music_fileObject

Returns the value of attribute music_file.



15
16
17
# File 'lib/jimmy_jukebox/song.rb', line 15

def music_file
  @music_file
end

#paused=(value) ⇒ Object (writeonly)

Sets the attribute paused

Parameters:

  • value

    the value to set the attribute paused to.



16
17
18
# File 'lib/jimmy_jukebox/song.rb', line 16

def paused=(value)
  @paused = value
end

#playerObject

Returns the value of attribute player.



17
18
19
# File 'lib/jimmy_jukebox/song.rb', line 17

def player
  @player
end

#playing_pidObject

Returns the value of attribute playing_pid.



17
18
19
# File 'lib/jimmy_jukebox/song.rb', line 17

def playing_pid
  @playing_pid
end

Instance Method Details

#<=>(other) ⇒ Object



29
30
31
# File 'lib/jimmy_jukebox/song.rb', line 29

def <=>(other)
  music_file <=> other.music_file
end

#grandchild_pidObject



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/jimmy_jukebox/song.rb', line 45

def grandchild_pid
  # returns grandchild's pid if the child process spawns a grandchild
  # if so, the child is probably "/bin/sh" and the grandchild is "mpg123" or similar
  if JimmyJukebox::RUNNING_LINUX
    gpid_str = `ps --ppid #{playing_pid} -o pid`
    gpid_match = /\D*([0-9]*)\D*/m.match(gpid_str)[1]
    gpid = gpid_match ? gpid_match : nil
    gpid == 0 ? nil : gpid
  else
    nil
  end
end

#kill_playing_pid_and_childrenObject



88
89
90
91
92
93
# File 'lib/jimmy_jukebox/song.rb', line 88

def kill_playing_pid_and_children
  return nil unless playpid = playing_pid
  grandpid = grandchild_pid
  `kill #{grandpid}` if grandpid
  `kill #{playpid}`
end

#pauseObject



62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/jimmy_jukebox/song.rb', line 62

def pause
  self.paused = true
  if playing_pid
    if grandchild_pid
      `kill -s STOP #{grandchild_pid}`
    else
      `kill -s STOP #{playing_pid}`
    end
  else
    raise NoPlayingPidException, "*** Can't pause song because can't find playing_pid #{playing_pid} ***"
  end
end

#paused?Boolean

Returns:

  • (Boolean)


25
26
27
# File 'lib/jimmy_jukebox/song.rb', line 25

def paused?
  @paused
end

#play(user_config, jukebox) ⇒ Object



131
132
133
134
135
# File 'lib/jimmy_jukebox/song.rb', line 131

def play(user_config, jukebox)
  set_player(user_config)
  process_status = play_with_player
  process_status.exitstatus.to_i == 0 ? (self.playing_pid = nil) : (raise SongTerminatedPrematurelyException, "Experienced a problem playing a song")
end

#play_with_playerObject



137
138
139
140
141
142
143
144
# File 'lib/jimmy_jukebox/song.rb', line 137

def play_with_player
  escaped_music_file = escape_characters_in_string(music_file)
  run_command(player, escaped_music_file)
  puts "Now playing '#{music_file}'"
  display_options_after_delay
  Process.waitpid(playing_pid) # Waits for a child process to exit, returns its process id, and sets $? to a Process::Status object
  $? # return Process::Status object with instance methods .stopped?, .exited?, .exitstatus
end

#process_group_idObject



58
59
60
# File 'lib/jimmy_jukebox/song.rb', line 58

def process_group_id
  Process.getpgid(playing_pid)
end

#set_player(user_config) ⇒ Object



106
107
108
109
110
111
112
# File 'lib/jimmy_jukebox/song.rb', line 106

def set_player(user_config)
  if regex = AUDIO_FORMATS.keys.detect { |re| music_file =~ re }
    self.player = user_config.send(AUDIO_FORMATS[regex] + '_player')
  else
    raise UnsupportedSongFormatException, "Attempted to play a file format this program cannot play"
  end
end

#spawn_methodObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/jimmy_jukebox/song.rb', line 114

def spawn_method
  if JimmyJukebox::RUNNING_JRUBY
    lambda { |command, arg| Spoon.spawnp(command, arg) }
  else
    begin
      lambda { |command, arg| POSIX::Spawn::spawn(command + ' ' + arg) }

      # posix/spawn is much faster than fork-exec
      #pid = Process.fork do
      #  exec(command + ' ' + arg)
      #end
    rescue NotImplementedError
      raise CannotSpawnProcessException, "*** Cannot play music because we found neither Spoon.spawnp (for JRuby) nor Process.fork (for MRI) ***"
    end
  end
end

#terminateObject



95
96
97
98
99
100
101
102
103
104
# File 'lib/jimmy_jukebox/song.rb', line 95

def terminate
  self.paused = false
  self.player = nil
  if playing_pid
    kill_playing_pid_and_children
    self.playing_pid = nil
  else
    raise NoPlayingPidException, "*** Can't terminate song because can't find playing_pid #{playing_pid} ***"
  end
end

#unpauseObject



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/jimmy_jukebox/song.rb', line 75

def unpause
  self.paused = false
  if playing_pid
    if grandchild_pid
      `kill -s CONT #{grandchild_pid}`
    else playing_pid
      `kill -s CONT #{playing_pid}`
    end
  else
    raise NoPlayingPidException, "*** Can't unpause song because can't find playing_pid #{playing_pid} ***"
  end
end

#valid_audio_format?(music_file) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/jimmy_jukebox/song.rb', line 33

def valid_audio_format?(music_file)
  AUDIO_FORMATS.keys.any? { |re| re =~ music_file }
end