In my quest to create the MP3 daemon that can handle multiple song queues (and thus manage multiple instances of MPG321), I've ended up writing two modules to aid me. Audio::Play::MPG321 and Audio::Daemon::MPG321 are on CPAN and are needed for this script to work. Basically this program launches MPG321 every time a 'play' request comes in. Anyway, here's the code.
#!/usr/bin/perl ################### # Title # # By Da-Breegster # ################### use strict; use warnings; use Audio::Daemon::MPG321; use Config::Auto; use MP3::Info; use POSIX ":sys_wait_h"; use List::Util "shuffle"; use Cwd; our (@Players, $MixerPID, %Config, $Title); $| = 1; mkdir "/tmp/jmd" unless -d "/tmp/jmd"; open LOCK, "/var/lock/jmd" and do { kill 0, <LOCK> and die "JMD already running!"; }; open LOCK, ">/var/lock/jmd"; print LOCK $$; close LOCK; %Config = %{Config::Auto::parse("/etc/jmd")}; do { do { $MixerPID = fork() or exec("esd -nobeeps"); system("echo $MixerPID >> /root/esd"); system("esdctl unlock"); } if system("esdctl standbymode >> /dev/null") == 256; } if $Config{mixer} eq "esd"; $SIG{ALRM} = sub { return unless -s "/tmp/jmd/input"; open INPUT, "/tmp/jmd/input"; chomp(my $Input = <INPUT>); close INPUT; my ($Action, @Args); ($Action, @Args) = split(/\s+/, $Input); if ($Action eq "play" or $Action eq "add") { my @Songs = map { FullPath($_) } @Args; Play(@Songs) if $Action eq "play"; Add(@Songs) if $Action eq "add"; } elsif ($Action eq "rplay" or $Action eq "radd") { my @Songs = shuffle(@Args); @Songs = map { FullPath($_) } @Songs; Play(@Songs) if $Action eq "rplay"; Add(@Songs) if $Action eq "radd"; } else { Next() if $Action eq "next"; Prev() if $Action eq "prev"; Restart() if $Action eq "restart"; Forward(@Args) if $Action eq "forward"; Backward(@Args) if $Action eq "backward"; Pause() if $Action eq "pause"; Resume() if $Action eq "resume"; Toggle() if $Action eq "toggle"; Stop() if $Action eq "stop"; List() if $Action eq "ls"; $SIG{QUIT}->() if $Action eq "quit"; } }; $SIG{CHLD} = sub { my $Stiff; while (($Stiff = waitpid(-1, &WNOHANG)) > 0) { pop @Players; sleep($Config{lag}); Resume() if @Players; } }; $SIG{QUIT} = sub { unlink "/var/lock/jmd"; Stop() until (! @Players); kill "INT", $MixerPID if $MixerPID; open SONG, ">/tmp/jmd/song"; print SONG "[0]"; close SONG; open TIMES, ">/tmp/jmd/times"; print TIMES "0:00 (0:00)"; close TIMES; exit 0; }; $SIG{INT} = $SIG{QUIT}; sub FullPath { my $Song = shift; return $Song if $Song =~ m/^\//; my $CWD = getcwd; return "$CWD/$Song" if -f "$CWD/$Song"; return $Config{dir} . "/$Song" if -f $Config{dir} . "/$Song"; return; } sub Play { my @Songs = @_; Pause() if @Players; push @Players, new Audio::Daemon::MPG321 (@Songs); } sub Add { return unless @Players; $Players[-1]->add(@_); } sub Next { return unless @Players; $Players[-1]->next(); } sub Prev { return unless @Players; $Players[-1]->prev(); } sub Restart { return unless @Players; $Players[-1]->restart(); } sub Forward { return unless @Players; $Players[-1]->forward(shift); } sub Backward { return unless @Players; $Players[-1]->backward(shift); } sub Pause { return unless @Players; $Players[-1]->pause(); } sub Resume { return unless @Players; $Players[-1]->resume(); } sub Toggle { return unless @Players; $Players[-1]->toggle(); } sub Stop { return unless @Players; $Players[-1]->stop(); } sub List { open REPLY, ">/tmp/jmd/reply"; print REPLY "START\n"; foreach (@{$Players[-1]->{queue}}) { my $Prelude; if ($_ eq $Players[-1]->{queue}->[$Players[-1]->{pointer}]) { $Prelude = "> "; } else { $Prelude = " "; } print REPLY $Prelude . $_ . "\n"; } print REPLY "END\n"; close REPLY; } while (1) { select(undef, undef, undef, 0.5); next unless @Players; until ($Players[-1]->{player}->state() == 0) { $Players[-1]->{player}->poll(); if (my $Info = get_mp3tag($Players[-1]->{queue}->[$Players[-1]->{poi +nter}])) { $Title = $Info->{TITLE}; } else { $Title = $Players[-1]->{queue}->[$Players[-1]->{pointer}]; } open SONG, ">/tmp/jmd/song"; print SONG "[", $#Players + 1, "] ", $Title, "\n"; close SONG; open TIMES, ">/tmp/jmd/times"; print TIMES $Players[-1]->{player}->{sofar}, " (", $Players[-1]->{player}->{remains}, ")\n"; close TIMES; } $Players[-1]->{pointer}++; unless ($Players[-1]->{queue}->[$Players[-1]->{pointer}]) { Stop(); } else { $Players[-1]->load(); } }
Oh yeah, you'll also need mpg321, obviously. And set /etc/jmd to something like this: mixer: esd dir: /home/dabreegster/mp3 lag: 0 forward: 15 backward: 15 And you'll need esd to handle the whole /dev/dsp locking issue. Another mixer should work as long as mpg321 supports it. And here's the client script:
#!/usr/bin/perl ################### # Title # # By Da-Breegster # ################### use strict; use warnings; open PID, "/var/lock/jmd" or do { unless ($ARGV[0] eq "start") { print "JMD not running!\n"; exit 1; } }; no warnings; our $PID = <PID>; use warnings; close PID; my $Cmd = shift @ARGV; if ($Cmd eq "start") { system("jmd &"); exit 1; } elsif ($Cmd eq "ls") { # Dunno } elsif ($Cmd =~ m/(add|play)/) { my $Send = "$Cmd " . join(" ", @ARGV); $Send =~ s/\s$//; open CMD, ">/tmp/jmd/input"; print CMD $Send; close CMD; kill "ALRM", $PID; exit 1; } else { my $Send = "$Cmd " . join(" ", @ARGV); $Send =~ s/\s$//; open CMD, ">/tmp/jmd/input"; print CMD $Send; close CMD; kill "ALRM", $PID; exit 1; }
Just call it like this: 'jmc start; jmc play mp3/foo.mp3'. Anyway, it mostly works, except for some bugs, which I can't seem to locate the source of.

1) This is the major one. If you start up a new Player and let all the songs finish naturally, Stop() is called and it works fine. But if you call Stop() yourself, it'll pop @Players and stuff in $SIG{CHLD} and you can resume the queue you had playing before, but the while loop will totally freeze. I've inserted prints in there before and determined that it totally just stops. I have no clue what's going on.

2) When a queue finishes naturally or otherwise, Resume() is called on the appropriate element in @Players from $SIG{CHLD}, but it never actually resumes!

If I need to explain the code more, just tell me and I will. Thanks.

In reply to Yet another music-related queue question by dabreegster

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.