Files
samskivert/projects/robodj/bin/musicd
T
mdb d28142f3fa Missed a call to play_next_tune().
git-svn-id: https://samskivert.googlecode.com/svn/trunk@635 6335cc39-0255-0410-8fd6-9bcaacd3b74c
2002-03-03 18:05:20 +00:00

367 lines
9.7 KiB
Perl
Executable File

#!/usr/bin/perl -w
#
# $Id: musicd,v 1.16 2002/03/03 18:05:20 mdb Exp $
#
# MP3 audio server. Manages playlists of mp3 files that live on a local
# filesystem. The playlists can be manipulated by connecting to the server
# on a port and issuing simple text commands.
use strict;
use IO::Socket;
use IO::Select;
use MPEG::MP3Play ':state';
use Data::Dumper;
use Getopt::Long;
my $usage = "Usage: $0 [--bind_addr x.x.x.x]\n";
# this is what's currently being played
my $playing;
# this is our primary playlist
my @playlist;
# this is our current index into the playlist
my $index = -1;
# this is our handle on the MP3 player object
my $mp3;
# whether or not we're currently paused
my $paused = 0;
# get our options
my $bind_addr; # bind address
GetOptions("bind_addr:s" => \$bind_addr);
# validate the bind address
die $usage if (defined $bind_addr && $bind_addr !~ /\d+\.\d+\.\d+\.\d+/);
main: {
# create the server part of things
my $sock;
if (defined $bind_addr) {
$sock = new IO::Socket::INET(LocalHost => "$bind_addr:2500",
Proto => "tcp",
Listen => SOMAXCONN,
Reuse => 1);
} else {
$sock = new IO::Socket::INET(LocalPort => 2500,
Proto => "tcp",
Listen => SOMAXCONN,
Reuse => 1);
}
$sock or die "Unable to create server socket: $!\n";
# create our MP3 interface
$mp3 = new MPEG::MP3Play;
$mp3->set_player_mode(&XA_PLAYER_MODE_OUTPUT_AUTO_CLOSE_ON_STOP);
# manage our various inputs
my $sel = new IO::Select($sock);
my $mph = $mp3->get_command_read_pipe();
$sel->add($mph);
# do our main loop thang
my (@ready, $fh);
while (@ready = $sel->can_read()) {
foreach $fh (@ready) {
if ($fh == $mph) {
$mp3->process_messages_nowait();
} elsif ($fh == $sock) {
$sel->add($sock->accept());
} else {
# if handle_client() returns false, we remove this client from
# the select list and close the socket
if (!handle_client($fh)) {
$sel->remove($fh);
$fh->close();
}
}
}
}
}
# handles input from a client
sub handle_client
{
my ($cfh) = @_;
# see what the client has to say
my $input = <$cfh>;
if (defined $input) {
# remove trailing cr/nl
$input =~ s:[\r\n]+$::g;
} else {
# bail out if the socket is closed
return 0;
}
# parse and process the command
if ($input =~ m/^APPEND (\d+) (\d+) (.*)/) {
# append this song to the playlist
my $eid = $1;
my $sid = $2;
my $path = $3;
# make sure the file they provided exists and is readable
# if (-r $path) {
push @playlist, [ $eid, $sid, $path ];
print $cfh "200 Appended '$eid:$sid:$path' to playlist.\n";
# } else {
# print $cfh "404 Unable to read playlist candidate: '$path'\n";
# }
# if we're not playing a song, start playing now that we have a
# playlist with songs in it
if (!defined $playing) {
play_next_tune(1);
}
} elsif ($input =~ m/^REMOVEGRP (\d+) (\d+)/) {
my $sid = $1;
my $count = $2;
# remove the requested number of songs from the playlist starting
# with the first song that matches the specified songid
my $sidx = -1;
my $i;
my $yanked = 0;
for ($i = 0; $i < @playlist; $i++) {
if ($playlist[$i]->[1] == $sid) {
$sidx = $i;
splice(@playlist, $i, $count);
# adjust the playing index if necessary
if ($index < $i) {
# nothing to do here
} elsif ($index >= $i && $index < $i + $count) {
# we removed the playing tune
$yanked = 1;
$index = $i-1;
} else {
# we removed tunes from before the currently playing
# tune so we adjust index
$index -= $count;
}
last;
}
}
# now check to see if we've removed the playing tune
if ($yanked) {
play_next_tune(1);
}
if ($sidx >= 0) {
print $cfh "200 Removed $count entries from position $sidx.\n";
} else {
print $cfh "404 Unable to locate $sid in playlist.\n";
}
} elsif ($input =~ m/^REMOVE (\d+)/) {
my $sid = $1;
# if we're playing the song to remove, skip to the next song
if ($sid == $playing->[1]) {
play_next_tune(1);
}
# remove this songid from the playlist
my $sidx = -1;
my $i;
for ($i = 0; $i < @playlist; $i++) {
if ($playlist[$i]->[1] == $sid) {
$sidx = $i;
splice(@playlist, $i, 1);
# adjust the playing index if necessary
$index -= 1 if ($i < $index);
last;
}
}
if ($sidx >= 0) {
print $cfh "200 Removed $sid from position $sidx.\n";
} else {
print $cfh "404 Unable to locate $sid in playlist.\n";
}
} elsif ($input =~ m/^CLEAR/) {
# clear out the current playlist and reset the playing index
@playlist = ();
$index = -1;
print $cfh "200 Playlist cleared.\n";
} elsif ($input =~ m/^PLAYLIST/) {
print $cfh "200 Playlist songs: " . @playlist . " current: " .
(defined $playing ? $playing->[1] : "<none>") . "\n";
my $entry;
foreach $entry (@playlist) {
print $cfh join("\t", @{$entry}) . "\n";
}
} elsif ($input =~ m/^PLAYING/) {
print $cfh "200 Currently " . ($paused ? "paused" : "playing") . ": " .
(defined $playing ? $playing->[1] : "<none>") . "\n";
} elsif ($input =~ m/^STOP/) {
$mp3->stop();
$paused = 0; # clear the paused flag
undef $playing;
print $cfh "200 Music stopped.\n";
} elsif ($input =~ m/^PLAY/) {
# if we've already queued up a song, play it
if (defined $playing) {
$mp3->play();
} else {
# otherwise queue one up to play
play_next_tune(1);
}
# clear the paused flag
$paused = 0;
# and report back based on whether or not we successfully queued
# up a song for playing
if (defined $playing) {
print $cfh "200 Music started.\n";
} else {
print $cfh "501 Can't play: playlist empty.\n";
}
} elsif ($input =~ m/^PAUSE/) {
$mp3->pause();
$paused = 1;
print $cfh "200 Music paused (use PLAY to unpause).\n";
} elsif ($input =~ m/^SKIPTO (\d+)/) {
my $sid = $1;
# stop this tune and skip to the specified tune
$mp3->stop();
if (play_this_tune($sid)) {
print $cfh "200 Skipped to $sid.\n";
} else {
print $cfh "404 Unable to locate song $sid.\n";
}
} elsif ($input =~ m/^BACK/) {
# stop this tune and skip to the previous one
$mp3->stop();
play_next_tune(-1);
print $cfh "200 Skipped current song.\n";
} elsif ($input =~ m/^SKIP/) {
# stop this tune and skip to the next one
$mp3->stop();
play_next_tune(1);
print $cfh "200 Skipped current song.\n";
} elsif ($input =~ m/^CLOSE/) {
print $cfh "200 Bye bye.\n";
return 0;
} elsif ($input =~ m/^EXIT/) {
print $cfh "200 Bye bye.\n";
exit(0);
} else {
print $cfh "500 Unknown command: '$input'\n";
}
return 1;
}
sub play_next_tune
{
my ($direction) = @_;
my $pcount = @playlist;
# bail if the playlist is empty
if ($pcount == 0) {
$index = -1;
undef $playing;
print "Empty playlist, twiddling.\n";
return;
}
# advance the playlist pointer either forward or backward (wrapping
# around at the end because we always loop)
$index = ($index + $pcount + $direction) % $pcount;
$playing = $playlist[$index];
# open it
$mp3->open($playing->[2]);
# and play it
$mp3->play();
}
sub play_this_tune
{
my ($sid) = @_;
my $i;
# bail if the playlist is empty
if (@playlist == 0) {
$index = -1;
undef $playing;
return 0;
}
# advance the playlist pointer (wrapping around at the end because we
# always loop)
for ($i = 0; $i < @playlist; $i++) {
$index = ($index + 1) % @playlist;
$playing = $playlist[$index];
if ($playing->[1] == $sid) {
last;
}
}
# open it
$mp3->open($playing->[2]);
# and play it
$mp3->play();
# let them know if we found the tune
return ($playing->[1] == $sid);
}
# message handlers
package MPEG::MP3Play;
sub msg_notify_player_state
{
my ($mp3, $msg) = @_;
# deeee-bug!
# print "state: " . Dumper($msg) . "\n";
# play the next tune if we hit the end of this one
if ($msg->{state} == &XA_PLAYER_STATE_EOF) {
main::play_next_tune(1);
}
return 1;
}
sub msg_notify_error
{
my ($mp3, $msg) = @_;
print "error: " . Dumper($msg) . "\n";
return 1;
}
sub msg_notify_debug
{
my ($mp3, $msg) = @_;
if ($msg->{code} == &XA_ERROR_NO_SUCH_FILE) {
print "unable to load: $playing\n";
} else {
print "debug: " . Dumper($msg) . "\n";
}
print "nsf: " . &XA_ERROR_NO_SUCH_FILE . "\n";
return 1;
}