File Coverage

blib/lib/Tunein/Streams.pm
Criterion Covered Total %
statement 27 135 20.0
branch 0 32 0.0
condition 0 7 0.0
subroutine 9 18 50.0
pod 9 9 100.0
total 45 201 22.3


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