File Coverage

blib/lib/Net/Shoutcast/Admin.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package Net::Shoutcast::Admin;
2             # $Id: Admin.pm 229 2008-02-13 00:10:09Z davidp $
3              
4 2     2   95334 use warnings;
  2         4  
  2         66  
5 2     2   11 use strict;
  2         4  
  2         63  
6 2     2   9 use Carp;
  2         9  
  2         178  
7 2     2   1309 use Net::Shoutcast::Admin::Song;
  2         5  
  2         55  
8 2     2   1184 use Net::Shoutcast::Admin::Listener;
  2         6  
  2         59  
9 2     2   1759 use URI::Escape;
  2         3079  
  2         210  
10 2     2   2378 use LWP::UserAgent;
  2         133598  
  2         74  
11 2     2   1666 use XML::Simple;
  0            
  0            
12              
13             use vars qw($VERSION);
14             $VERSION = '0.02';
15              
16              
17             =head1 NAME
18              
19             Net::Shoutcast::Admin - administration of Shoutcast servers
20              
21              
22             =head1 VERSION
23              
24             This document describes Net::Shoutcast::Admin version 0.0.2
25              
26              
27             =head1 SYNOPSIS
28              
29             use Net::Shoutcast::Admin;
30              
31             my $shoutcast = Net::Shoutcast::Admin->new(
32             host => 'server hostname',
33             port => 8000,
34             admin_password => 'mypassword',
35             );
36            
37             if ($shoutcast->source_connected) {
38             printf "%s is currently playing %s by %s",
39             $shoutcast->dj_name,
40             $shoutcast->currentsong->title,
41             $shoutcast->currentsong->artist
42             ;
43             } else {
44             print "No source is currently connected.";
45             }
46            
47            
48             =head1 DESCRIPTION
49              
50             A module to interact with Shoutcast servers to retrieve information about
51             their current status (and perhaps in later versions of the module, to also
52             control the server in various ways).
53              
54              
55             =head1 INTERFACE
56              
57             =over 4
58              
59             =item new
60              
61             $shoutcast = Net::Shoutcast::Admin->new( %params );
62              
63             Creates a new Net::Shoutcast::Admin object. Takes a hash of options
64             as follows:
65              
66             =over 4
67              
68             =item B
69              
70             The hostname of the Shoutcast server you wish to query.
71              
72             =item port
73              
74             The port on which Shoutcast is running. Defaults to 8000 if not specified.
75              
76             =item B
77              
78             The admin password for the Shoutcast server.
79              
80             =item timeout
81              
82             The number of seconds to wait for a response. Defaults to 10 seconds if
83             not specified.
84              
85             =item agent
86              
87             The HTTP User-Agent header which will be sent in HTTP requests to the Shoutcast
88             server. If not supplied, a suitable default will be used.
89              
90             =back
91              
92             =cut
93              
94             sub new {
95              
96             my ($class, %params) = @_;
97            
98             my $self = bless {}, $class;
99            
100             $self->{last_update} = 0;
101            
102             my %acceptable_params = map { $_ => 1 }
103             qw(host port admin_password timeout agent);
104            
105             # make sure we haven't been given any bogus parameters:
106             if (my @bad_params = grep { ! $acceptable_params{$_} } keys %params) {
107             carp "Net::Shoutcast::Admin does not recognise param(s) "
108             . join ',', @bad_params;
109             return;
110             }
111            
112             #
113             $self->{$_} = $params{$_} for keys %acceptable_params;
114            
115             # set decent defaults for optional params:
116             $self->{port} ||= 8000;
117             $self->{timeout} ||= 10;
118             # In my initial testing, it seems the Shoutcast server will not respond
119             # with the status XML if the User-Agent does not contain "Mozilla"
120             # (Why? That's just lame!)
121             $self->{agent} ||= "Mozilla";
122            
123             if (my @missing_params = grep { ! $self->{$_} } keys %acceptable_params) {
124             carp "Net::Shoutcast::Admin->new() must be supplied with params: "
125             . join ',', @missing_params;
126             return;
127             }
128            
129             # get an LWP::UserAgent object to make our requests with:
130             my $ua = new LWP::UserAgent;
131             if ($ua) {
132             $ua->agent( $self->{agent} );
133             $ua->timeout( $self->{timeout} );
134             $self->{ua} = $ua;
135             } else {
136             warn "Failed to create LWP::UserAgent object";
137             return;
138             }
139            
140             return $self;
141              
142             }
143              
144              
145             # if we haven't fetched the status XML recently, fetch it. Returns true if
146             # we either fetched it successfully or it was fresh enough to not need
147             # re-fetching, or false if we couldn't get it.
148             sub _update_if_necessary {
149             my $self = shift;
150             if ($self->{last_update} and (time - $self->{last_update}) < 5) {
151             # status was updated not long ago
152             return 1;
153             }
154            
155             my ($fetched, $msg) = $self->_fetch_status_xml;
156             if (!$fetched) {
157             warn "Failed to fetch status from Shoutcast server: $msg";
158             return;
159             }
160            
161             # all good.
162             return 1;
163             }
164              
165              
166             sub _fetch_status_xml {
167             my $self = shift;
168            
169             my ($host, $port) = @$self{qw(host port)};
170             my $pass = URI::Escape::uri_escape( $self->{admin_password} );
171            
172             my $url = "http://$host:$port/admin.cgi?pass=$pass&mode=viewxml";
173            
174             my $response = $self->{ua}->get($url);
175            
176             if (!$response->is_success) {
177             my $err = "Failed to fetch status XML - " . $response->status_line;
178             carp $err;
179             return wantarray? (0, $err) : 0;
180             }
181            
182             my $data = XML::Simple::XMLin($response->content,
183             forceArray => [qw(LISTENER SONG)]);
184            
185             if (!$data) {
186             return wantarray? (0, 'Failed to parse XML') : 0;
187             }
188            
189             $self->{data} = $data;
190             $self->{last_update} = time;
191            
192             return wantarray? (1, undef) : 1;
193             }
194              
195              
196              
197             =item currentsong
198              
199             Returns a Net::Shoutcast::Admin::Song object representing the current
200             song.
201              
202             =cut
203              
204             sub currentsong {
205             my $self = shift;
206             $self->_update_if_necessary or return;
207            
208             my $song = Net::Shoutcast::Admin::Song->new(
209             title => $self->{data}->{SONGTITLE}
210             );
211             return $song;
212             }
213              
214              
215             =item song_history
216              
217             Returns a list of Net::Shoutcast::Admin::Song objects representing
218             the the last few songs played
219              
220             =cut
221              
222             sub song_history {
223             my $self = shift;
224             my @song_objects;
225             $self->_fetch_status_xml;
226            
227             for my $song (@{ $self->{data}->{SONGHISTORY}->{SONG} }) {
228             push @song_objects, Net::Shoutcast::Admin::Song->new(
229             title => $song->{TITLE},
230             played_at => $song->{PLAYEDAT},
231             );
232             }
233            
234             return (@song_objects);
235             }
236              
237              
238             =item listeners
239              
240             In scalar context, returns the number of listeners currently connected.
241             In list context, returns a list of Net::Shoutcast::Admin::Listener
242             objects representing each listener.
243              
244             =cut
245              
246             sub listeners {
247             my $self = shift;
248             $self->_fetch_status_xml;
249            
250             if (!wantarray) {
251             # okay, it's nice and simple:
252             return $self->{data}->{CURRENTLISTENERS};
253             } else {
254             # okay, we need to return a list of N:S:A::Listener objects:
255             return if !$self->{data}->{CURRENTLISTENERS};
256            
257             my @listener_objects;
258             for my $listener (@{ $self->{data}->{LISTENERS}->{LISTENER} }) {
259             push @listener_objects, Net::Shoutcast::Admin::Listener->new(
260             host => $listener->{HOSTNAME},
261             connect_time => $listener->{CONNECTTIME},
262             underruns => $listener->{UNDERRUNS} || 5,
263             agent => $listener->{USERAGENT},
264             );
265             }
266            
267             return (@listener_objects);
268             }
269             }
270              
271              
272             =item source_connected
273              
274             Returns true if the stream is currently up (a source is connected and streaming
275             audio to the server)
276              
277             =cut
278              
279             sub source_connected {
280             my $self = shift;
281             $self->_fetch_status_xml;
282             return ($self->{data}->{STREAMSTATUS});
283             }
284              
285              
286             1; # Magic true value required at end of module
287             __END__