File Coverage

blib/lib/Tunein/Streams.pm
Criterion Covered Total %
statement 27 123 21.9
branch 0 24 0.0
condition 0 7 0.0
subroutine 9 14 64.2
pod 5 5 100.0
total 41 173 23.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Tunein::Streams - Fetch actual raw streamable URLs from radio-station websites on Tunein.com
4              
5             =head1 AUTHOR
6              
7             This module is Copyright (C) 2017 by
8              
9             Jim Turner, C<< >>
10            
11             All rights reserved.
12              
13             You may distribute this module under the terms of either the GNU General
14             Public License or the Artistic License, as specified in the Perl README
15             file.
16              
17             =head1 SYNOPSIS
18              
19             use strict;
20              
21             use Tunein::Streams;
22              
23             my $station = new Tunein::Streams();
24              
25             die "Invalid URL or no streams found!\n" unless ($station);
26              
27             my @streams = $station->get();
28              
29             my $first = $station->get();
30              
31             my $best = $station->getBest();
32              
33             print "Best stream URL=".$best->{'Url'}."\n";
34              
35             my $besturl = $station->getBest('Url');
36              
37             print "Best stream URL=$besturl\n";
38              
39             my @allfields = $station->validFields();
40              
41             for (my $i=0; $i<$station->count(); $i++) {
42              
43             foreach my $field (@allfields) {
44              
45             print "--$field: ".$streams[$i]->{$field}."\n";
46              
47             }
48              
49             }
50            
51             =head1 DESCRIPTION
52              
53             Tunein::Streams accepts a valid radio station URL on http://tunein.com and
54             returns the urls and other information properties for the actual stream URLs
55             available for that station. The purpose is that one needs one of these URLs
56             in order to have the option to stream the station in one's own choice of
57             audio player software rather than using their web browser and accepting any /
58             all flash, ads, javascript, cookies, trackers, web-bugs, and other crapware
59             that can come with that method of playing. The author uses his own custom
60             all-purpose audio player called "fauxdacious" (his custom hacked version of
61             the open-source "audacious" media player. "fauxdacious" incorporates this
62             module to decode and play tunein.com streams.
63              
64             One or more streams can be returned for each station. The available
65             properties for each stream returned are normally: Bandwidth,
66             HasPlaylist (1|0), MediaType (ie. MP3, AAC, etc.), Reliability (1-100),
67             StreamId (numeric), Type (ie. Live) and Url.
68              
69             =head1 EXAMPLES
70              
71             use strict;
72              
73             use Tunein::Streams;
74              
75             my $kluv = new Tunein::Streams('http://tunein.com/radio/987-KLUV-s33892/');
76              
77             die "Invalid URL or no streams found!\n" unless ($kluv);
78              
79             my $besturl = $kluv->getBest('Url');
80              
81             print "Best stream URL=$besturl\n"; #RETURNS: "http://19273.live.streamtheworld.com/KLUVFM_SC"
82              
83             my $beststream = $kluv->getBest();
84              
85             my @allfields = $kluv->validFields();
86              
87             foreach my $field (@allfields) {
88              
89             print "--$field: ".$beststream->{$field}."\n";
90              
91             }
92              
93             This would print:
94              
95             --Bandwidth: 64
96              
97             --HasPlaylist: 0
98              
99             --MediaType: Windows
100              
101             --Reliability: 100
102              
103             --StreamId: 75549037
104              
105             --Type: Live
106              
107             --Url: http://19273.live.streamtheworld.com/KLUVFM_SC
108              
109             =head1 SUBROUTINES/METHODS
110              
111             =over 4
112              
113             =item B(I)
114              
115             Accepts a tunein.com URL and creates and returns a new station object, or
116             I if the URL is not a valid tunein station or no streams are found.
117              
118             =item $station->B(I<[property]>)
119              
120             Returns either a scalar or array of either values or hash references with
121             an element for each stream url found. If I is specified, then
122             the item(s) returned are scalars containing that property's value,
123             otherwise, the item(s) returned are hash references, each to a hash who's
124             elements represent the names and values for each I of the given
125             stream. If a scalar target is used, the first stream is returned, if an
126             array target is used, all streams are returned.
127              
128             =item $station->B(I<[property]>)
129              
130             Similar to B() except it only returns a single stream representing
131             the "best" stream found. "best" is determined as the one with the best
132             I with the best I, if more than one with the
133             same best I value. If I<[property]> is specified, only that
134             property value is returned as a scalar. Otherwise, a hash reference
135             to all the properties for that stream is returned.
136              
137             =item $station->B()
138              
139             Returns the number of streams found for the station.
140              
141             =item $station->B()
142              
143             Returns an array containing all the valid property names found. This
144             list is normally: (B, B, B, B,
145             B, B, B). These can be used in the I functions and
146             as the keys in the hash references returned to fetch the corresponding
147             property values.
148              
149             =back
150              
151             =head1 KEYWORDS
152              
153             tunein
154              
155             =head1 DEPENDENCIES
156              
157             LWP::Simple
158              
159             =head1 BUGS
160              
161             Please report any bugs or feature requests to C, or through
162             the web interface at L. I will be notified, and then you'll
163             automatically be notified of progress on your bug as I make changes.
164              
165             =head1 SUPPORT
166              
167             You can find documentation for this module with the perldoc command.
168              
169             perldoc Tunein::Streams
170              
171              
172             You can also look for information at:
173              
174             =over 4
175              
176             =item * RT: CPAN's request tracker (report bugs here)
177              
178             L
179              
180             =item * AnnoCPAN: Annotated CPAN documentation
181              
182             L
183              
184             =item * CPAN Ratings
185              
186             L
187              
188             =item * Search CPAN
189              
190             L
191              
192             =back
193              
194             =head1 ACKNOWLEDGEMENTS
195              
196             The idea for this module came from a Python script that does this same task named
197             "getstream", but I wanted a Perl module that could be called from within another
198             program! I do not know the author of getstream.py.
199              
200             =head1 LICENSE AND COPYRIGHT
201              
202             Copyright 2017 Jim Turner.
203              
204             This program is free software; you can redistribute it and/or modify it
205             under the terms of the the Artistic License (2.0). You may obtain a
206             copy of the full license at:
207              
208             L
209              
210             Any use, modification, and distribution of the Standard or Modified
211             Versions is governed by this Artistic License. By using, modifying or
212             distributing the Package, you accept this license. Do not use, modify,
213             or distribute the Package, if you do not accept this license.
214              
215             If your Modified Version has been derived from a Modified Version made
216             by someone other than you, you are nevertheless required to ensure that
217             your Modified Version complies with the requirements of this license.
218              
219             This license does not grant you the right to use any trademark, service
220             mark, tradename, or logo of the Copyright Holder.
221              
222             This license includes the non-exclusive, worldwide, free-of-charge
223             patent license to make, have made, use, offer to sell, sell, import and
224             otherwise transfer the Package with respect to any patent claims
225             licensable by the Copyright Holder that are necessarily infringed by the
226             Package. If you institute patent litigation (including a cross-claim or
227             counterclaim) against any party alleging that the Package constitutes
228             direct or contributory patent infringement, then this Artistic License
229             to you shall terminate on the date that such litigation is filed.
230              
231             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
232             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
233             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
234             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
235             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
236             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
237             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
238             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
239              
240             =cut
241              
242             package Tunein::Streams;
243              
244 1     1   13831 use strict;
  1         2  
  1         24  
245 1     1   3 use warnings;
  1         1  
  1         20  
246             #use Carp qw(croak);
247 1     1   456 use LWP::Simple qw();
  1         46538  
  1         26  
248 1     1   5 use vars qw(@ISA @EXPORT $VERSION);
  1         1  
  1         352  
249              
250             our $VERSION = '1.10';
251              
252             require Exporter;
253              
254             @ISA = qw(Exporter);
255             @EXPORT = qw(get getBest count validFields);
256             $Carp::Internal{ (__PACKAGE__) }++;
257              
258             sub new
259             {
260 0     0 1   my $class = shift;
261 0           my $url = shift;
262 0           my $attrs;
263              
264 0           my $self = {};
265              
266 0 0         return undef unless ($url);
267              
268             #THIS IS A TWO-STEP FETCH. WE FIRST FETCH THE HTML FOR THE STATION'S tunein.com WEBSITE,
269             #PARSE IT FOR 'StreamUrl":""', THEN, (STEP 2) APPEND "http:" TO IT AND FETCH
270             #THAT. THE 2ND FETCH RETURNS A FILE VERY SIMILAR TO A PERL "Data::Dumper" FILE CONTAINING
271             #A HASH TREE COMPOSED OF AN ARRAY OF ONE OR MORE STREAM URLS ALONG WITH THEIR OTHER PROPERTIES.
272             #WE THEN USE A FEW REGICES TO CONVERT IT TO A TRUE PERL-EVALABLE "Data::Dumper" STRING THAT
273             #WE CAN THEN EVAL INTO A PERL HASH, FROM WHICH OUR get() FUNCTIONS CAN RETURN THE DESIRED
274             #DATA!
275              
276 0           my $html = '';
277 0           my $wait = 1;
278 0           for (my $i=0; $i<=2; $i++) { #WE TRY THIS FETCH 3 TIMES SINCE FOR SOME REASON, DOESN'T ALWAYS RETURN RESULTS 1ST TIME?!:
279 0           $html = LWP::Simple::get($url);
280 0 0         last if ($html);
281 0           sleep $wait;
282 0           ++$wait;
283             }
284 0 0         return undef unless ($html); #STEP 1 FAILED, INVALID STATION URL, PUNT!
285              
286 0           my (@streams, @streamHash, $s, $html2);
287 0           $self->{'cnt'} = 0;
288 0           $self->{'total'} = '0';
289 0           while ($html =~ s/StreamUrl\"\:\s*\"([^\"]+)\"(\,\s*\"DirectStreams\"\:\s*\[([^\]]+)\])?//o) { #FIND ONE (OR MORE) STREAM URLS:
290 0           $s = $1;
291 0 0         if ($2) { #WE HAVE A DIRECT STREAM (EMBEDDED TREE), USE THAT:
292 0           $html2 = $2;
293 0           $html2 =~ s/^\,\s*\"Direct//io;
294 0           $html2 = '{ "' . $html2 . ' }';
295             } else { #WE HAVE A STREAM URL, FETCH TREE FROM THERE:
296 0 0 0       $s = 'http:' . $s if ($s && $s !~ m#^\w+\:\/\/#o);
297 0           $wait = 1;
298 0           for (my $i=0; $i<=2; $i++) { #WE TRY THIS FETCH 3 TIMES SINCE FOR SOME REASON, DOESN'T ALWAYS RETURN RESULTS 1ST TIME?!:
299 0           $html2 = LWP::Simple::get($s);
300 0 0         last if ($html);
301 0           sleep $wait;
302 0           ++$wait;
303             }
304             }
305 0           $html2 =~ s/\:\s*true\b/\:1/gio; #CONVERT "true" AND "false" STRING VALUES INTO 1 & 0 RESPECTIVELY.
306 0           $html2 =~ s/\:\s*false\b/\:0/gio;
307 0           $html2 =~ s/\"\s*\:/\" =\> /go; #FIXUP TO MAKE A VALID EVAL-ABLE HASH TREE OUT OF IT:
308 0           $html2 = "\$streamHash[$self->{'cnt'}] = " . $html2;
309 1     1   5 no strict;
  1         1  
  1         24  
310 0           eval $html2; #EVAL EACH STREAM URL'S CONTENT INTO A PERL HASH REF.
311 1     1   3 use strict;
  1         1  
  1         175  
312 0           $self->{'total'} += scalar @{$streamHash[$self->{'cnt'}]->{'Streams'}};
  0            
313 0           ++$self->{'cnt'}; #NUMBER OF StreamUrl's FOUND (NOT SAME AS # OF STREAMS!)
314             }
315 0           $self->{streams} = \@streamHash;
316 0 0         return undef unless ($self->{'cnt'}); #STEP 2 FAILED - NO PLAYABLE STREAMS FOUND, PUNT!
317              
318             #SAVE WHAT PROPERTY NAMES WE HAVE (FOR $station->validFields()):
319            
320 0           @{$self->{fields}} = ();
  0            
321 0           foreach my $field (sort keys %{${$self->{streams}}[0]->{'Streams'}[0]}) {
  0            
  0            
322 0           push @{$self->{fields}}, $field;
  0            
323             }
324              
325 0           bless $self, $class; #BLESS IT!
326              
327 0           return $self;
328             }
329              
330             sub get
331             {
332 0     0 1   my $self = shift;
333 0   0       my $field = shift || 0;
334              
335 0           my @streams = ();
336 0           my $subcnt;
337 0 0         if ($field) { #USER SUPPLIED A PROPERTY NAME, FETCH ONLY THAT PROPERTY, (ie. "Url"):
338 0 0         return ${$self->{streams}}[0]->{'Streams'}[0]->{$field} unless (wantarray); #USER ONLY WANTS 1ST STREAM.
  0            
339 0           for (my $i=0; $i<$self->{'cnt'}; $i++) {
340 1     1   3 no strict;
  1         1  
  1         105  
341 0           $subcnt = scalar @{${$self->{streams}}[$i]->{'Streams'}};
  0            
  0            
342 0           for (my $j=0; $j<$subcnt; $j++) {
343 0           push @streams, ${$self->{streams}}[$i]->{'Streams'}[$j]->{$field};
  0            
344             }
345             }
346             } else { #NO PROPERTY NAME, RETURN A HASH-REF TO ALL THE PROPERTIES:
347 0 0         return ${$self->{streams}}[0]->{'Streams'}[0] unless (wantarray); #USER ONLY WANTS 1ST STREAM.
  0            
348 0           for (my $i=0; $i<$self->{'cnt'}; $i++) {
349 1     1   5 no strict;
  1         1  
  1         127  
350 0           $subcnt = scalar @{${$self->{streams}}[$i]->{'Streams'}};
  0            
  0            
351 0           for (my $j=0; $j<$subcnt; $j++) {
352 0           push @streams, ${$self->{streams}}[$i]->{'Streams'}[$j];
  0            
353             }
354             }
355             }
356 0           return @streams; #USER WANTS ALL OF 'EM.
357             }
358              
359             sub getBest #LIKE GET, BUT ONLY RETURN THE SINGLE ONE W/BEST BANDWIDTH AND RELIABILITY:
360             {
361 0     0 1   my $self = shift;
362 0   0       my $field = shift || 0;
363              
364 0           my $bestStream;
365             my $subcnt;
366 0           my $bestReliableBandwidth = 0;
367 0           my $ReliableBandwidth;
368 0           for (my $i=0; $i<$self->{'cnt'}; $i++) {
369 1     1   3 no strict;
  1         1  
  1         154  
370 0           $subcnt = scalar @{${$self->{streams}}[$i]->{'Streams'}};
  0            
  0            
371 0           for (my $j=0; $j<$subcnt; $j++) {
372 0           $ReliableBandwidth = (${$self->{streams}}[$i]->{'Streams'}[$j]->{'Bandwidth'} * 1000)
373 0           + ${$self->{streams}}[$i]->{'Streams'}[$j]->{'Reliability'};
  0            
374 0 0         if ($ReliableBandwidth > $bestReliableBandwidth) {
375 0           $bestStream = $field ? ${$self->{streams}}[$i]->{'Streams'}[$j]->{$field}
376 0 0         : ${$self->{streams}}[$i]->{'Streams'}[$j];
  0            
377 0           $bestReliableBandwidth = $ReliableBandwidth;
378             }
379             }
380             }
381 0           return $bestStream;
382             }
383              
384             sub count
385             {
386 0     0 1   my $self = shift;
387 0           return $self->{'total'}; #TOTAL NUMBER OF PLAYABLE STREAM URLS FOUND.
388             }
389              
390             sub validFields
391             {
392 0     0 1   my $self = shift;
393 0           return @{$self->{fields}}; #LIST OF ALL VALID PROPERTY NAME FIELDS.
  0            
394             }
395              
396             1