| blib/lib/MP3/Icecast/Simple.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 18 | 79 | 22.7 |
| branch | 0 | 14 | 0.0 |
| condition | 0 | 17 | 0.0 |
| subroutine | 6 | 11 | 54.5 |
| pod | 5 | 5 | 100.0 |
| total | 29 | 126 | 23.0 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package MP3::Icecast::Simple; | ||||||
| 2 | |||||||
| 3 | =head1 NAME | ||||||
| 4 | |||||||
| 5 | MP3::Icecast::Simple - Simple MP3::Icecast wrapper | ||||||
| 6 | |||||||
| 7 | =head1 SYNOPSIS | ||||||
| 8 | |||||||
| 9 | use MP3::Icecast::Simple; | ||||||
| 10 | |||||||
| 11 | $icy = MP3::Icecast::Simple->new( | ||||||
| 12 | description => "Station", | ||||||
| 13 | server => '127.0.0.1:8000', | ||||||
| 14 | password => 'password', | ||||||
| 15 | local_port => 1234, | ||||||
| 16 | bitrate => 96 | ||||||
| 17 | ); | ||||||
| 18 | $icy->play("/path/to/files"); | ||||||
| 19 | |||||||
| 20 | =head1 ABSTRACT | ||||||
| 21 | |||||||
| 22 | MP3::Icecast::Simple is a simple MP3::Icecast wrapper, that can be | ||||||
| 23 | used to create a SHOUTcast/Icecast broadcast source easy. | ||||||
| 24 | |||||||
| 25 | =head1 SEE ALSO | ||||||
| 26 | |||||||
| 27 | MP3::Icecast module by Allen Day (MP3::Icecast) | ||||||
| 28 | |||||||
| 29 | Nullsoft SHOUTcast DNAS home | ||||||
| 30 | http://www.shoutcast.com | ||||||
| 31 | |||||||
| 32 | =cut | ||||||
| 33 | |||||||
| 34 | 1 | 1 | 33751 | use strict; | |||
| 1 | 2 | ||||||
| 1 | 37 | ||||||
| 35 | 1 | 1 | 4 | use base 'MP3::Icecast'; | |||
| 1 | 3 | ||||||
| 1 | 677 | ||||||
| 36 | 1 | 1 | 1245 | use Time::HiRes qw(sleep); | |||
| 1 | 2060 | ||||||
| 1 | 4 | ||||||
| 37 | 1 | 1 | 1343 | use IO::Socket; | |||
| 1 | 30171 | ||||||
| 1 | 5 | ||||||
| 38 | 1 | 1 | 2074 | use LWP::UserAgent; | |||
| 1 | 71972 | ||||||
| 1 | 39 | ||||||
| 39 | 1 | 1 | 12 | use vars qw(@ISA $VERSION); | |||
| 1 | 2 | ||||||
| 1 | 6982 | ||||||
| 40 | |||||||
| 41 | $VERSION = "0.2"; | ||||||
| 42 | |||||||
| 43 | =head1 METHODS | ||||||
| 44 | |||||||
| 45 | =head2 new | ||||||
| 46 | |||||||
| 47 | Title : new | ||||||
| 48 | Usage : $icy = MP3::Icecast::Simple->new(%arg) | ||||||
| 49 | Function: Create a new MP3::Icecast::Simple instance | ||||||
| 50 | Returns : MP3::Icecast::Simple object | ||||||
| 51 | Args : description Name of the radiostation | ||||||
| 52 | server Address and port of SHOUTcast server | ||||||
| 53 | password Password to SHOUTcast server | ||||||
| 54 | local_port Local port | ||||||
| 55 | bitrate Initial bitrate | ||||||
| 56 | |||||||
| 57 | =cut | ||||||
| 58 | |||||||
| 59 | sub new { | ||||||
| 60 | 0 | 0 | 1 | my ($class, %arg) = @_; | |||
| 61 | 0 | my $self = bless {%arg}, $class; | |||||
| 62 | |||||||
| 63 | 0 | return $self; | |||||
| 64 | } | ||||||
| 65 | |||||||
| 66 | =head2 play | ||||||
| 67 | |||||||
| 68 | Title : play | ||||||
| 69 | Usage : $icy->play($dir, $resursive); | ||||||
| 70 | Function: Play a directory of .mp3 files | ||||||
| 71 | Returns : | ||||||
| 72 | Args : dirname Path to direactory with .mp3 files | ||||||
| 73 | recursive Flag determining whether a directory is recursively searched for files (optional) | ||||||
| 74 | |||||||
| 75 | =cut | ||||||
| 76 | |||||||
| 77 | sub play { | ||||||
| 78 | 0 | 0 | 1 | my $self = shift; | |||
| 79 | 0 | my $dir = shift; | |||||
| 80 | 0 | 0 | my $recursive = shift || 0; | ||||
| 81 | |||||||
| 82 | 0 | my $listen_socket = IO::Socket::INET->new( | |||||
| 83 | LocalPort => $self->{local_port}, | ||||||
| 84 | Listen => 20, | ||||||
| 85 | Proto => 'tcp', | ||||||
| 86 | Reuse => 0, | ||||||
| 87 | Timeout => 3600 | ||||||
| 88 | ); | ||||||
| 89 | |||||||
| 90 | 0 | $self->recursive($recursive); | |||||
| 91 | 0 | $self->add_directory($dir); | |||||
| 92 | |||||||
| 93 | 0 | my @files = $self->files; | |||||
| 94 | 0 | while(1) { | |||||
| 95 | 0 | 0 | next unless my $connection = $listen_socket->accept; | ||||
| 96 | 0 | 0 | defined(my $child = fork()) or die "Can't fork: $!"; | ||||
| 97 | 0 | 0 | if($child == 0) { | ||||
| 98 | 0 | $listen_socket->close; | |||||
| 99 | 0 | $connection->print($self->header); | |||||
| 100 | 0 | 0 | $self->stream($_, $connection) || last for(@files); | ||||
| 101 | } | ||||||
| 102 | 0 | $connection->close; | |||||
| 103 | } | ||||||
| 104 | 0 | exit 0; | |||||
| 105 | } | ||||||
| 106 | |||||||
| 107 | =head2 stream | ||||||
| 108 | |||||||
| 109 | Title : stream (rewrited from original MP3::Icecast package with improvements) | ||||||
| 110 | Usage : $icy->stream($file, $handle); | ||||||
| 111 | Function: Play a file via socket | ||||||
| 112 | Returns : 1 if file was transmitted successfully, | ||||||
| 113 | undef if an error occured | ||||||
| 114 | Args : file File to stream | ||||||
| 115 | handle Socket handler | ||||||
| 116 | |||||||
| 117 | =cut | ||||||
| 118 | |||||||
| 119 | sub stream { | ||||||
| 120 | 0 | 0 | 1 | my ($self, $file, $handle) = @_; | |||
| 121 | 0 | 0 | return undef unless -f $file; | ||||
| 122 | |||||||
| 123 | 0 | my $info = $self->_get_info($file); | |||||
| 124 | 0 | 0 | return undef unless defined($info); | ||||
| 125 | |||||||
| 126 | 0 | 0 | my $size = -s $file || 0; | ||||
| 127 | 0 | 0 | my $bitrate = $info->bitrate || 1; | ||||
| 128 | 0 | 0 | my $description = $self->description($file) || 'unknown'; | ||||
| 129 | 0 | 0 | my $fh = $self->_open_file($file) || die "couldn't open file $file: $!"; | ||||
| 130 | |||||||
| 131 | 0 | binmode $fh; | |||||
| 132 | |||||||
| 133 | 0 | 0 | 0 | if(ref($handle) and $handle->can('print')) { | |||
| 134 | 0 | my $bytes = $size; | |||||
| 135 | 0 | print $description."\n"; | |||||
| 136 | 0 | $self->updinfo($description); | |||||
| 137 | 0 | while($bytes > 0) { | |||||
| 138 | 0 | my $data; | |||||
| 139 | 0 | 0 | my $b = read($fh, $data, $bitrate * 128) || last; | ||||
| 140 | 0 | $bytes -= $b; | |||||
| 141 | 0 | $handle->print($data); | |||||
| 142 | 0 | sleep $b / ($bitrate * 128); | |||||
| 143 | } | ||||||
| 144 | 0 | return 1; | |||||
| 145 | } | ||||||
| 146 | 0 | return undef; | |||||
| 147 | } | ||||||
| 148 | |||||||
| 149 | =head2 updinfo | ||||||
| 150 | |||||||
| 151 | Title : updinfo | ||||||
| 152 | Usage : Not a publick method | ||||||
| 153 | Function: Update current song title on the SHOUTcast server | ||||||
| 154 | Returns : 1 if song title updated successfully, | ||||||
| 155 | undef if an error occured | ||||||
| 156 | Args : description Name of current song | ||||||
| 157 | |||||||
| 158 | =cut | ||||||
| 159 | |||||||
| 160 | sub updinfo { | ||||||
| 161 | 0 | 0 | 1 | my ($self, $songname) = @_; | |||
| 162 | 0 | my $ua = LWP::UserAgent->new; | |||||
| 163 | 0 | $ua->timeout(10); | |||||
| 164 | 0 | $ua->env_proxy; | |||||
| 165 | 0 | $ua->agent('Mozilla/5.0'); | |||||
| 166 | 0 | my $response = $ua->get("http://$self->{server}/admin.cgi?mode=updinfo&pass=$self->{password}&song=" . $songname); | |||||
| 167 | 0 | 0 | return undef unless ($response->is_success); | ||||
| 168 | 0 | return 1; | |||||
| 169 | } | ||||||
| 170 | |||||||
| 171 | =head2 header | ||||||
| 172 | |||||||
| 173 | Title : header | ||||||
| 174 | Usage : Not a publick method | ||||||
| 175 | Function: Create a ICY response header | ||||||
| 176 | Returns : ICY response header | ||||||
| 177 | Args : none | ||||||
| 178 | |||||||
| 179 | =cut | ||||||
| 180 | |||||||
| 181 | sub header { | ||||||
| 182 | 0 | 0 | 1 | my $self = shift; | |||
| 183 | 0 | my $output = ''; | |||||
| 184 | 0 | my $CRLF = "\015\012"; | |||||
| 185 | |||||||
| 186 | 0 | $output .= "ICY 200 OK$CRLF"; | |||||
| 187 | 0 | $output .= "icy-notice1: This stream requires a shoutcast/icecast compatible player. $CRLF"; |
|||||
| 188 | 0 | $output .= "icy-notice2:MP3::Icecast::Simple $CRLF"; |
|||||
| 189 | 0 | $output .= "icy-name:" . $self->{description} . $CRLF; | |||||
| 190 | 0 | $output .= "icy-pub:1$CRLF"; | |||||
| 191 | 0 | $output .= "icy-br:" . $self->{bitrate} . $CRLF; | |||||
| 192 | 0 | $output .= "Accept-Ranges: bytes$CRLF"; | |||||
| 193 | 0 | $output .= "Content-Type: audio/x-mp3$CRLF"; | |||||
| 194 | 0 | $output .= "$CRLF"; | |||||
| 195 | |||||||
| 196 | 0 | return $output; | |||||
| 197 | } | ||||||
| 198 | |||||||
| 199 | 1; | ||||||
| 200 | |||||||
| 201 | =head1 AUTHOR | ||||||
| 202 | |||||||
| 203 | Gregory A. Rozanoff, rozanoff@gmail.com | ||||||
| 204 | |||||||
| 205 | =head1 COPYRIGHT AND LICENSE | ||||||
| 206 | |||||||
| 207 | Copyright 2006, Gregory A. Rozanoff | ||||||
| 208 | |||||||
| 209 | This library is free software; you can redistribute it and/or modify | ||||||
| 210 | it under the same terms as Perl itself. | ||||||
| 211 | |||||||
| 212 | =cut |