File Coverage

blib/lib/Net/Icecast/Source.pm
Criterion Covered Total %
statement 18 90 20.0
branch 0 30 0.0
condition 0 10 0.0
subroutine 6 14 42.8
pod 5 5 100.0
total 29 149 19.4


line stmt bran cond sub pod time code
1             package Net::Icecast::Source;
2              
3 1     1   23579 use strict;
  1         3  
  1         38  
4 1     1   5 use warnings;
  1         1  
  1         34  
5              
6 1     1   5 use Carp qw/croak/;
  1         6  
  1         74  
7 1     1   1474 use IO::Socket::INET;
  1         38786  
  1         8  
8 1     1   711 use IO::Handle;
  1         2  
  1         45  
9 1     1   1040 use MIME::Base64;
  1         815  
  1         1085  
10              
11             ######################
12              
13             our $VERSION = '1.1';
14             our $BUF_SIZE = 1460; # how many bytes to read/transmit at a time
15              
16             ######################
17              
18             =head1 NAME
19              
20             Net::Icecast::Source - Icecast streaming source
21              
22             =head1 SYNOPSIS
23              
24             use Net::Icecast::Source;
25             my $source = new Net::Icecast::Source(
26             username => 'revmischa',
27             password => 'hackthegibson',
28             server => '128.128.64.64',
29             port => '8000',
30             mount_point => '/source',
31             mime_type => 'audio/mpeg',
32             meta => {
33             name => 'lol dongs radio fun land',
34             description => 'party time all day',
35             aim => 'lindenstacker',
36             url => 'http://icecast.org',
37             },
38             );
39              
40             # attempt to connect to the streaming server
41             $source->connect
42             or die "Unable to connect to server: $!\n";
43            
44             # attempt to log in to the specified mountpoint
45             $source->login
46             or die "Incorrect username/password\n";
47              
48             # stream mp3
49             my $sample;
50             open $sample, "sample.mp3" or die $!;
51             $source->stream_fh($sample);
52             close $sample;
53            
54             # done, clean up
55             $source->disconnect
56            
57             =head1 DESCRIPTION
58              
59             C is a simple module designed to make it easy to
60             build programs which stream audio data to an Icecast2 server to be relayed.
61              
62             =head1 CONSTRUCTOR
63              
64             =over 4
65              
66             =item new (%opts)
67              
68             Create a new source instance. Options are: username, password, server,
69             port, mount_point, meta, mime_type
70              
71             =cut
72              
73             sub new {
74 0     0 1   my ($class, %opts) = @_;
75            
76 0           my $self = \%opts;
77 0           return bless $self, $class;
78             }
79              
80              
81             =item connect
82              
83             Connect to the server, use this before logging in. Returns success/failure
84              
85             =cut
86              
87             sub connect {
88 0     0 1   my ($self) = @_;
89            
90 0 0         my $server = $self->{server} or croak "no server specified";
91 0   0       my $port = $self->{port} || 8000;
92              
93 0           my $sock = IO::Socket::INET->new(
94             PeerAddr => $server,
95             PeerPort => $port,
96             Proto => 'tcp',
97             Timeout => 10,
98             );
99            
100 0           $self->{sock} = $sock;
101 0           return $sock;
102             }
103              
104              
105             =item login
106              
107             Log in to the mount point and send metadata. Returns if login was successful or not
108              
109             =cut
110              
111             sub login {
112 0     0 1   my ($self) = @_;
113            
114 0 0         my $password = $self->{password}
115             or croak "no password specified";
116 0   0       my $username = $self->{username} || '';
117 0   0       my $mount_point = $self ->{mount_point} || '/';
118 0   0       my $mime_type = $self->{mime_type} || 'audio/mpeg';
119              
120 0           my $auth = "Authorization: Basic " . encode_base64("$username:$password");
121 0           chomp $auth;
122 0           my $meta = $self->_metadata_headers;
123 0           my $req_method = qq/SOURCE $mount_point ICE\/1.0/;
124 0           my $mime = "content-type: $mime_type";
125              
126 0           my @req = ($req_method, $auth, $mime);
127 0 0         push @req, $meta if $meta;
128            
129 0           my $req = join("\r\n", @req) . "\r\n\r\n";
130              
131 0           $self->_write($req);
132            
133 0           my $ok = 0;
134 0           while (my $line = $self->_read) {
135 0           my ($status) = $line =~ /HTTP\/1.0 (\d\d\d)/;
136            
137 0 0         if ($status) {
138 0 0         if ($status == 401) {
    0          
139 0           $ok = 0;
140             } elsif ($status == 200) {
141 0           $ok = 1;
142             }
143             }
144            
145 0 0         if ($line eq "\r\n") {
146 0           last;
147             }
148             }
149            
150 0           $self->{logged_in} = $ok;
151 0           return $ok;
152             }
153              
154              
155             =item stream_fh($filehandle)
156              
157             Read from $filehandle until EOF, passing through the raw data to the
158             icecast server.
159              
160             =cut
161              
162             sub stream_fh {
163 0     0 1   my ($self, $fh) = @_;
164            
165 0 0         my $sock = $self->{sock} or croak "Tried to stream while not connected to server";
166 0 0         croak "Tried to stream while not logged in" unless $self->{logged_in};
167            
168 0           my $input = IO::Handle->new_from_fd($fh, "r");
169 0 0         unless ($input) {
170 0           warn "unable to create IO::Handle for filehandle $fh: $!\n";
171 0           $sock->close;
172 0           return 0;
173             }
174            
175 0           my $buf;
176 0           while (! $input->eof) {
177 0           my $bytes = $input->sysread($buf, $BUF_SIZE);
178 0 0         unless ($bytes) {
179             # EOF
180 0           last;
181             }
182            
183 0           $sock->print($buf);
184             }
185            
186 0           $input->close;
187             }
188              
189              
190             =item disconnect
191              
192             Closes all sockets and disconnects
193              
194             =cut
195              
196             sub disconnect {
197 0     0 1   my ($self) = @_;
198            
199 0           $self->{connected} = 0;
200 0           $self->{logged_in} = 0;
201            
202 0 0         my $sock = $self->{sock} or return;
203            
204 0           $sock->shutdown(2); # done w socket
205 0           $sock->close;
206 0           delete $self->{sock};
207             }
208              
209             #########
210              
211              
212             sub _metadata_headers {
213 0     0     my $self = shift;
214            
215 0           my @headers;
216 0   0       my $meta = $self->{meta} || {};
217 0           foreach my $field (qw/name description url irc genre icq aim/) {
218 0 0         my $val = $meta->{$field} or next;
219 0           push @headers, "icy-$field: $val";
220             }
221            
222 0           return join("\r\n", @headers);
223             }
224              
225             sub _write {
226 0     0     my ($self, $data) = @_;
227            
228 0           my $sock = $self->{sock};
229 0 0         croak "Tried to write while not connected" unless $sock;
230            
231 0           $sock->syswrite($data);
232             }
233              
234             sub _read {
235 0     0     my ($self) = @_;
236            
237 0           my $sock = $self->{sock};
238 0 0         croak "Tried to read while not connected" unless $sock;
239              
240 0           my $r = <$sock>;
241 0           return $r;
242             }
243              
244             1;