http://qs1969.pair.com?node_id=444560

dabreegster has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to write a relatively simple music-playing server with queue features, similar to Moosic or MPD. Jigstar Music Daemon, or JMD, simply listens for an ALRM signal and executes the commands in a file. It's different because it can play a song, pause it, play another, then resume the first when the second is finished. It can do this in a pretty much infinite manner. Operations will only occur to the most recent version of MPG321. (the program I'm using to actually play the songs.) I know MPG321 has a remote interface and that there's a POE module for controlling it, but it'd take a good deal more work because it can only play one song at once. I'm not experienced enough yet to construct a program that can do a real music queue yet. So I'm sticking with my current design. Here's the code of JMD. Be warned, it's not pretty and it doesn't run under strict.
#!/usr/bin/perl ######################## # Jigstar Music Daemon # ######################## #> #> = Temporary comment. #> Check for lock file to see if another instance of JMD is running. die "Error: Lock file exists!" if -e "/var/lock/jmd"; open LOCK, ">/var/lock/jmd"; print LOCK $$; close LOCK; #> Check to see if ESD is running. If not, start it. #> Remember, esdctl returns 1 if ESD isn't running. system("esdctl serverinfo >> /dev/null") and system("esd -nobeeps &") +; #> Exit cleanly when told to do so. $SIG{INT} = sub { #> Do I need to force my children to die so they won't become zombie +s? unlink "/var/lock/jmd", "/tmp/jmd"; system("killall esd"); exit; }; $SIG{QUIT} = $SIG{INT}; #> Use CHLD to simulate the 'done' thing. #> Declare subroutines. sub Play ($); sub Next (); sub Pause (); sub Resume (); sub Stop (); sub Debug (); #> When woken up by JMC, read the input and execute the commands. $SIG{ALRM} = sub { return unless -s "/tmp/jmd"; open INPUT, "/tmp/jmd"; chomp($Cmd = <INPUT>); close INPUT; open INPUT, ">/tmp/jmd"; print INPUT undef; close INPUT; $Cmd =~ s/^play // and Play $Cmd if $Cmd =~ m/^play /; Next if $Cmd eq "next"; Status == 1 ? Pause : Resume if $Cmd eq "toggle"; Pause if $Cmd eq "pause"; Resume if $Cmd eq "resume"; Stop if $Cmd eq "stop"; Debug if $Cmd eq "status"; $SIG{QUIT}->() if $Cmd eq "quit"; }; our @PIDs; sub Play ($) { my $Args = shift; Pause; #> Have MPG321 get all files from /root/mp3. Make it configurable lat +er. #> Change STDERR receiver. if (my $PID = fork()) { push @PIDs, $PID; } else { chdir "/root/mp3/"; exec("mpg321 -v $Args 2> /dev/tty12 &"); } } sub Next () { #> If it's stopped, resume it temporarily to change tracks. if (Status() == 2) { Resume; kill "INT", $PIDs[-1] if @PIDs; } kill "INT", $PIDs[-1] if @PIDs; } sub Pause () { kill "STOP", $PIDs[-1] if @PIDs; } sub Resume () { kill "CONT", $PIDs[-1] if @PIDs; } sub Stop () { kill 9, $PIDs[-1] if @PIDs; pop @PIDs; } sub Debug () { my $Counter = 0; for my $PID (@PIDs) { #> Print the ID, the PID, and the status. Don't need to check if not +hing is #> there, because there has to be. print "$Counter: $PID = " . Status($PID) . "\n"; $Counter++; } } #> Return status of a PID. 0 = Isn't running, 1 = Playing, & 2 = Pause +d. sub Status ($) { my $PID = shift || $PIDs[-1]; my $Line = +(split(/\n/, `ps $PID`))[1]; if ($Line !~ m/$PID/) { return 0; } elsif ($Line =~ m/T</) { return 2; } else { return 1; } } sleep while 1;
To control it, echo a command to /tmp/jmd and send the ALRM signal to JMD. The commands should do the following: play ARGS: Pass args to a new MPG321 instance. If the current instance is running, pause it. Next: Advance the songs. Pause: Force the current song to pause. Resume: Force the current song to resume. Toggle: If paused, resume, and vice versa. Stop: Stop the current instance. Debug: Print the list of PIDs and their status. Usually pausing a song, playing another, then resuming the first won't work under Linux using OSS. The dsp device is locked. So I run esd to mix the silence of the paused songs and the music of the running song. The problem is simple: Pausing, resuming, stopping, and advancing to the next track won't work. Every function that sends a signal to the current MPG321 instance doesn't do what it should. There are no errors. I know the methods I am using are messy, but I also now that they work. An older version of JMD that didn't use fork() to start mpg321 did work. I cannot find the difference between the two. Any help, suggestions, or code is much appreciated. I can also be found on #perl on irc.freenode.net generally, under the alias 'dabreegster'. Thanks.

Replies are listed 'Best First'.
Re: Managing and sending signals to control MPG321
by valdez (Monsignor) on Apr 04, 2005 at 21:36 UTC

    I will not comment on your code, I like the idea of suspending jobs, but it would be better to use mpg321 remote interface, it's simpler and IMHO much more cleaner. If you really need to suspend execution, you could save the player status and restore it later, it's not difficult using one of the available modules on CPAN; I would start from Audio::Daemon::MPG123, Audio::Play::MPG123 or MP3::Daemon.

    Ciao, Valerio

      I found my bug. When I forked and exec()ed, a sh instance was created so the & 2> /dev/null bit could be done. The mpg321 instance lived in a totally different process. I fixed it to not use the shell and it works great. I do have a small problem reaping the zombie children, though. If I have $SIG{CHLD} set to 'IGNORE', like it says in the Camel Book, then it'll reap 'em automatically, but I can't put in my own code to pop @PIDs. I tried the two loops given in the documentation, but they don't seem to work. Any clues? I'll check out the remote interface.