Category: Misc
Author/Contact Info hossman
Description: Recently, I said to myself: "Self, you really ought to learn more about this POE thing." So I did some reading and thought: "This is some dope stuff!" So I started telling some friends about it, and making up stuff about how easy I thought it would be to do a lot of cool stuff with POE, and then i thought to myself: "is it really that easy?" -- so I whiped this up in about 2 hours just to prove to myself (and my friends) that with the right POE Components, anything is possible.

You'll have to changed $mp3dir to something practicle for your system, but once you do start with http://localhost:8000/ and everything should be self explanitory.

#!/usr/bin/perl -w

use strict;

use CGI; # for escape
use Data::Dumper;
use HTTP::Response;
use POE qw(Component::Server::HTTP Component::MPG123);

$| = 1;
my $port = 8000;                    # HTTP server port
my $mp3dir = "/home/hossman/mp3/";  # where all your mp3s are
my @song_queue =
    # initial queue of songs to play (if any)
    (
#     $mp3dir . 'a.mp3',
#     $mp3dir . 'b.mp3',
    );
##################################################################

# we'll fill this up.
my $global_status = { state => 'stoped',
              total_frames => 0, total_time => 0,
              last_frame => 0, last_time => 0,
          };

##
#
# The MPG123 Component
#
# much of this was taken verbatim from the
# POE::Component::MPG123 test script
#
POE::Component::MPG123->spawn( alias => 'song_manager' );

 POE::Session->create
    ( inline_states => {
    _start => sub {
        my ($kernel, $heap) = @_[KERNEL, HEAP];
        $kernel->alias_set( 'song_manager' );
        
        # Start the first song.
        &play_next($kernel);
    },
    
    _stop => sub { },
    
    # Receive status events from mpg123.
    status => sub {
        my ( $kernel, $heap,
         $frames_played, $frames_left,
         $time_played, $time_left
         ) = @_[KERNEL, HEAP, ARG0..ARG3];
        
        $global_status->{state} = "playing";
        unless ($global_status->{total_frames}) {
        $global_status->{total_frames} = $frames_left;
        $global_status->{total_time}   = $time_left;
        }
        $global_status->{frame_count}++;
        $global_status->{last_frame} = $frames_played;
        $global_status->{last_time}  = $time_played;
        
        # Rather than wait for a specific frame and call
        # it the end of the song, I'll set a brief delay
        # here to time out if frames
        # stop playing.
        
        $kernel->delay( song_timeout => 0.25 );
    },
    
    song_info => sub {
        my ($kernel, $heap, $info) = @_[KERNEL, HEAP, ARG0];
        $global_status->{song_info} = $info;
    },
    
    file_info => sub {
        my ($kernel, $heap, $info) = @_[KERNEL, HEAP, ARG0];
        $global_status->{file_info} = $info;
    },
      
    # The pause succeeded.
    
    song_paused => sub {
        my ($kernel, $heap) = @_[KERNEL, HEAP];
        $global_status->{state} = "paused";
    },
    
    # The song resumed after we asked it to.
    
    song_resumed => sub {
        my ($kernel, $heap) = @_[KERNEL, HEAP];
        $global_status->{state} = "playing";
    },
    
    # The song stopped naturally.
    
    song_stopped => sub {
        my ($kernel, $heap) = @_[KERNEL, HEAP];
        $global_status->{state} = "stoped";
        $global_status->{file_info} = undef;
        $global_status->{song_info} = undef;
        $global_status->{total_time}   = 0;
        $global_status->{total_frames} = 0;
        $global_status->{last_time}   = 0;
        $global_status->{last_frame}   = 0;
        &play_next($kernel);
        },

    # The player has quit.
    
    player_quit => sub {
        my ($kernel, $heap) = @_[KERNEL, HEAP];
        $global_status->{state} = "player quit";
        
    },
    
    ### Miscellaneous event handlers.
    debug   => sub { },
    _child  => sub { },
    _signal => sub { 0 },
    }
      );

##
#
# The HTTP Component
# 
# The docs for this component are a little lacking ...
# but this seems to work
#
my $httpd = new POE::Component::Server::HTTP
    (
     Port => 8000,
     Headers => {
     'Server' => 'My Server',
     'Content-type' => 'text/html',
     },
     
     ContentHandler => {
     '/pause/' => \&httpd_pause_handler,
     '/req/' => \&httpd_req_handler,
     '/list/' => \&httpd_list_handler,
     '/next/' => \&httpd_next_handler,
     '/' => \&httpd_status_handler,
     },
     );
#
# HTTPD Handlers
#
#
sub httpd_req_handler {
    # the really cool one, we unshift a song on to the head
    # of the list and queue it
    
    my ($request, $response) = @_;

    my $song = CGI::unescape($request->uri->query());
    my $res = "";
    if (-f $song) {
    push @song_queue, $song;
    $res = "REQUEST ACCEPTED: $song\n";
    &play_next($poe_kernel);
    } else {
    $res = "what are you smoking? (no $song)\n";
    }
    $response->content
    (&make_html_response($res, &get_status_info));
    
    $response->code(RC_OK);
    return RC_OK;   
}
sub httpd_list_handler {
    my ($request, $response) = @_;

    my @songs = `find $mp3dir -follow -name \*.mp3`;
    my $links = join ' ', map {
    chomp;
    "<a href=\"/req/?" . CGI::escape($_) .
        "\">$_</a><br>" } @songs;
    
    $response->code(RC_OK);
    $response->content
    (&make_html_response("Pick a song from the list below",
                 $links));
    return RC_OK;   
}
sub httpd_pause_handler {
    my ($request, $response) = @_;
    
    $poe_kernel->post( mpg123 => 'pause' );
    
    $response->code(RC_OK);
    $response->content
    (&make_html_response("The current song has been (un)paused",
                 &get_status_info));
    return RC_OK;   
}
sub httpd_next_handler {
    my ($request, $response) = @_;
    
    $poe_kernel->post( mpg123 => 'stop');
    
    $response->code(RC_OK);
    $response->content
    (&make_html_response("The last song was skipped",
                 &get_status_info));
    return RC_OK;   
}
sub httpd_status_handler {
    my ($request, $response) = @_;
    
    $response->code(RC_OK);
    $response->content
    (&make_html_response(&get_status_info));
    
               
    return RC_OK;   
}

sub make_html_response {
    my ($r) = ('<html><body>' .
           '[ <a href="/next/">skip song</a> | ' .
           '<a href="/pause/">(un)pause</a> | ' .
           '<a href="/list/">Make a Request</a> | ' . 
           '<a href="/">Reload Status</a> ]' .
           '<hr>' .
           join('<hr>', @_) .
           '<p></body></html>');
    return $r;
}

sub play_next {
    # takes a kernel,
    # plays if the state is "stoped" and there's a song waiting
    return unless scalar(@song_queue);
    return unless 'stoped' eq $global_status->{state};
    $_[0]->post( mpg123 => play => shift(@song_queue) );
}

sub get_status_info {
    my $r = "<pre>";
    $r .= "\nCurrent State: " . $global_status->{state} . "\n";
    
    $r .= "Total Frames: " . $global_status->{total_frames} . "\n";
    $r .= "Total Time: " .   $global_status->{total_time} . "\n";
    
    $r .= "Elapsed Frames: " . $global_status->{last_frame} . "\n";
    $r .= "Elapsed Time: " .   $global_status->{last_time} . "\n";
    
    $r .= "\nSong...\n" . Dumper($global_status->{song_info});
    $r .= "\nFile...\n" . Dumper($global_status->{file_info});

    $r .= "\nQueue...\n" . join("\n", @song_queue);
    $r .= "</pre>";
    return $r
}


# Kick the kernel
$poe_kernel->run();
exit 0;
Replies are listed 'Best First'.
Re: POE MP3/HTTP player
by Matts (Deacon) on Apr 08, 2002 at 10:58 UTC
    Beautiful!

    I'm giving the POE tutorial at this year's TPC - do you mind if I use this in the tutorial? I might end up not using it, but I wouldn't want to just steal it without permission.

    Now I just have to figure out how I'm going to get so much stuff onto the slides ;-)

      feel free to use/abuse/modify/hack in anyway you want... as I said, I threw it together rather haphazardly in only a few hours. You may want to clean some things up before using it as an example.
Re: POE MP3/HTTP player
by drewbie (Chaplain) on Apr 08, 2002 at 20:51 UTC
    Very cool! ++. Anyone know when the "updated" POE documentation is going to be finished? I remember taking a peek some time ago at POE because it is so darn cool (>1 yr I think) but couldn't figure out where to start.