-
-
Save dinkypumpkin/7dd022d20838dd4a840b32dd919ead9c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
# BBC HLS live stream test harness | |
# use with Safari or Native HLS Playback extension for Firefox/Chrome | |
# bbclive.pl daemon | |
# or | |
# morbo bbclive.pl | |
# open http://127.0.0.1:3000/ | |
use Mojolicious::Lite; | |
use Mojo::JSON qw(decode_json); | |
use Parse::M3U::Extended qw(m3u_parser); | |
my $channels = { | |
'tv' => { | |
'national' => { | |
'BBC Four' => 'bbc_four_hd', | |
'BBC News' => 'bbc_news24', | |
'BBC One' => 'bbc_one_hd', | |
'BBC Parliament' => 'bbc_parliament', | |
'BBC Two' => 'bbc_two_hd', | |
'CBBC' => 'cbbc_hd', | |
'CBeebies' => 'cbeebies_hd', | |
'BBC Radio 1 Live Video' => 'bbc_radio_one_video', | |
'BBC Radio 1Xtra Live Video' => 'bbc_1xtra_video', | |
'BBC Radio 5 Live Video' => 'bbc_radio_five_live_video', | |
}, | |
'regional' => { | |
'BBC Alba' => 'bbc_alba', | |
'BBC One Northern Ireland' => 'bbc_one_northern_ireland_hd', | |
'BBC One Scotland' => 'bbc_one_scotland_hd', | |
'BBC One Wales' => 'bbc_one_wales_hd', | |
'BBC Two England' => 'bbc_two_england', | |
'BBC Two Northern Ireland' => 'bbc_two_northern_ireland_digital', | |
'BBC Two Scotland' => 'bbc_two_scotland', | |
'BBC Two Wales' => 'bbc_two_wales_digital', | |
'S4C' => 's4cpbs', | |
}, | |
'local' => { | |
'BBC One Cambridge' => 'bbc_one_cambridge', | |
'BBC One Channel Islands' => 'bbc_one_channel_islands', | |
'BBC One East Midlands' => 'bbc_one_east_midlands', | |
'BBC One East Yorkshire' => 'bbc_one_east_yorkshire', | |
'BBC One East' => 'bbc_one_east', | |
'BBC One London' => 'bbc_one_london', | |
'BBC One North East' => 'bbc_one_north_east', | |
'BBC One North West' => 'bbc_one_north_west', | |
'BBC One Oxford' => 'bbc_one_oxford', | |
'BBC One South East' => 'bbc_one_south_east', | |
'BBC One South West' => 'bbc_one_south_west', | |
'BBC One South' => 'bbc_one_south', | |
'BBC One West Midlands' => 'bbc_one_west_midlands', | |
'BBC One West' => 'bbc_one_west', | |
'BBC One Yorks' => 'bbc_one_yorks', | |
}, | |
'red button' => { | |
'BBC Red Button 01' => 'sport_stream_01', | |
'BBC Red Button 02' => 'sport_stream_02', | |
'BBC Red Button 03' => 'sport_stream_03', | |
'BBC Red Button 04' => 'sport_stream_04', | |
'BBC Red Button 05' => 'sport_stream_05', | |
'BBC Red Button 06' => 'sport_stream_06', | |
'BBC Red Button 07' => 'sport_stream_07', | |
'BBC Red Button 08' => 'sport_stream_08', | |
'BBC Red Button 09' => 'sport_stream_09', | |
'BBC Red Button 10' => 'sport_stream_10', | |
'BBC Red Button 11' => 'sport_stream_11', | |
'BBC Red Button 12' => 'sport_stream_12', | |
'BBC Red Button 13' => 'sport_stream_13', | |
'BBC Red Button 14' => 'sport_stream_14', | |
'BBC Red Button 15' => 'sport_stream_15', | |
'BBC Red Button 16' => 'sport_stream_16', | |
'BBC Red Button 17' => 'sport_stream_17', | |
'BBC Red Button 18' => 'sport_stream_18', | |
'BBC Red Button 19' => 'sport_stream_19', | |
'BBC Red Button 20' => 'sport_stream_20', | |
'BBC Red Button 21' => 'sport_stream_21', | |
'BBC Red Button 22' => 'sport_stream_22', | |
'BBC Red Button 23' => 'sport_stream_23', | |
'BBC Red Button 24' => 'sport_stream_24', | |
'BBC Red Button 01b' => 'sport_stream_01b', | |
'BBC Red Button 02b' => 'sport_stream_02b', | |
'BBC Red Button 03b' => 'sport_stream_03b', | |
'BBC Red Button 04b' => 'sport_stream_04b', | |
'BBC Red Button 05b' => 'sport_stream_05b', | |
'BBC Red Button 06b' => 'sport_stream_06b', | |
'BBC Red Button 07b' => 'sport_stream_07b', | |
'BBC Red Button 08b' => 'sport_stream_08b', | |
'BBC Red Button 09b' => 'sport_stream_09b', | |
'BBC Red Button 10b' => 'sport_stream_10b', | |
'BBC Red Button 11b' => 'sport_stream_11b', | |
'BBC Red Button 12b' => 'sport_stream_12b', | |
'BBC Red Button 13b' => 'sport_stream_13b', | |
'BBC Red Button 14b' => 'sport_stream_14b', | |
'BBC Red Button 15b' => 'sport_stream_15b', | |
'BBC Red Button 16b' => 'sport_stream_16b', | |
'BBC Red Button 17b' => 'sport_stream_17b', | |
'BBC Red Button 18b' => 'sport_stream_18b', | |
'BBC Red Button 19b' => 'sport_stream_19b', | |
'BBC Red Button 20b' => 'sport_stream_20b', | |
'BBC Red Button 21b' => 'sport_stream_21b', | |
'BBC Red Button 22b' => 'sport_stream_22b', | |
'BBC Red Button 23b' => 'sport_stream_23b', | |
'BBC Red Button 24b' => 'sport_stream_24b', | |
'BBC Scotland Stream 01' => 'scotland_stream_01', | |
'BBC Scotland Stream 02' => 'scotland_stream_02', | |
'BBC Scotland Stream 03' => 'scotland_stream_03', | |
'BBC Scotland Stream 04' => 'scotland_stream_04', | |
} | |
}, | |
'radio' => { | |
'national' => { | |
'BBC Asian Network' => 'bbc_asian_network', | |
'BBC Radio 1' => 'bbc_radio_one', | |
'BBC Radio 1Xtra' => 'bbc_1xtra', | |
'BBC Radio 2' => 'bbc_radio_two', | |
'BBC Radio 3' => 'bbc_radio_three', | |
'BBC Radio 4 Extra' => 'bbc_radio_four_extra', | |
'BBC Radio 4 FM' => 'bbc_radio_fourfm', | |
'BBC Radio 4 LW' => 'bbc_radio_fourlw', | |
'BBC Radio 5 live sports extra' => 'bbc_radio_five_live_sports_extra', | |
'BBC Radio 5 live' => 'bbc_radio_five_live', | |
'BBC Radio 6 Music' => 'bbc_6music', | |
'BBC World Service' => 'bbc_world_service', | |
}, | |
'regional' => { | |
'BBC Radio Cymru' => 'bbc_radio_cymru', | |
'BBC Radio Foyle' => 'bbc_radio_foyle', | |
'BBC Radio Nan Gaidheal' => 'bbc_radio_nan_gaidheal', | |
'BBC Radio Scotland FM' => 'bbc_radio_scotland_fm', | |
'BBC Radio Scotland MW' => 'bbc_radio_scotland_mw', | |
'BBC Radio Ulster' => 'bbc_radio_ulster', | |
'BBC Radio Wales' => 'bbc_radio_wales_fm', | |
}, | |
'local' => { | |
'BBC Coventry & Warwickshire' => 'bbc_radio_coventry_warwickshire', | |
'BBC Essex' => 'bbc_radio_essex', | |
'BBC Hereford & Worcester' => 'bbc_radio_hereford_worcester', | |
'BBC Newcastle' => 'bbc_radio_newcastle', | |
'BBC Radio Berkshire' => 'bbc_radio_berkshire', | |
'BBC Radio Bristol' => 'bbc_radio_bristol', | |
'BBC Radio Cambridgeshire' => 'bbc_radio_cambridge', | |
'BBC Radio Cornwall' => 'bbc_radio_cornwall', | |
'BBC Radio Cumbria' => 'bbc_radio_cumbria', | |
'BBC Radio Derby' => 'bbc_radio_derby', | |
'BBC Radio Devon' => 'bbc_radio_devon', | |
'BBC Radio Gloucestershire' => 'bbc_radio_gloucestershire', | |
'BBC Radio Guernsey' => 'bbc_radio_guernsey', | |
'BBC Radio Humberside' => 'bbc_radio_humberside', | |
'BBC Radio Jersey' => 'bbc_radio_jersey', | |
'BBC Radio Kent' => 'bbc_radio_kent', | |
'BBC Radio Lancashire' => 'bbc_radio_lancashire', | |
'BBC Radio Leeds' => 'bbc_radio_leeds', | |
'BBC Radio Leicester' => 'bbc_radio_leicester', | |
'BBC Radio Lincolnshire' => 'bbc_radio_lincolnshire', | |
'BBC Radio London' => 'bbc_london', | |
'BBC Radio Manchester' => 'bbc_radio_manchester', | |
'BBC Radio Merseyside' => 'bbc_radio_merseyside', | |
'BBC Radio Norfolk' => 'bbc_radio_norfolk', | |
'BBC Radio Northampton' => 'bbc_radio_northampton', | |
'BBC Radio Nottingham' => 'bbc_radio_nottingham', | |
'BBC Radio Oxford' => 'bbc_radio_oxford', | |
'BBC Radio Sheffield' => 'bbc_radio_sheffield', | |
'BBC Radio Shropshire' => 'bbc_radio_shropshire', | |
'BBC Radio Solent' => 'bbc_radio_solent', | |
'BBC Radio Stoke' => 'bbc_radio_stoke', | |
'BBC Radio Suffolk' => 'bbc_radio_suffolk', | |
'BBC Radio York' => 'bbc_radio_york', | |
'BBC Somerset' => 'bbc_radio_somerset_sound', | |
'BBC Surrey' => 'bbc_radio_surrey', | |
'BBC Sussex' => 'bbc_radio_sussex', | |
'BBC Tees' => 'bbc_tees', | |
'BBC Three Counties Radio' => 'bbc_three_counties_radio', | |
'BBC Wiltshire' => 'bbc_radio_wiltshire', | |
'BBC WM 95.6' => 'bbc_wm', | |
} | |
} | |
}; | |
# munge channel name for desired sorting | |
sub channel_name_sortable { | |
my $s = shift; | |
my $type = shift; | |
if ( $type eq "tv" ) { | |
$s =~ s/^BBC Radio/X/i; | |
} else { | |
$s =~ s/^BBC( Radio)?//i; | |
} | |
$s =~ s/One/1/i; | |
$s =~ s/Two/2/i; | |
$s =~ s/Extra/Xtra/i; | |
$s =~ s/^(BBC Red Button \d{2}b)$/Y$1/i; | |
$s =~ s/^(BBC Scotland Stream \d{2})$/Z$1/i; | |
return $s; | |
} | |
# create ordered lists of channel names for display | |
my $channel_names; | |
for my $type ( keys %{$channels} ) { | |
for my $group ( keys %{$channels->{$type}} ) { | |
for my $name ( sort { channel_name_sortable($a, $type) cmp channel_name_sortable($b, $type) } keys %{$channels->{$type}->{$group}} ) { | |
push @{$channel_names->{$type}->{$group}}, $name; | |
} | |
} | |
} | |
# get playlists from mediaselector API | |
sub mediaselector_playlists { | |
my $ua = shift; | |
my $type = shift; | |
my $channel = shift; | |
my $playlists; | |
# lowest to highest quality | |
my @mediasets; | |
if ( $type eq "tv" ) { | |
@mediasets = ( "apple-ipad-hls", "iptv-all" ); | |
} else { | |
@mediasets = ( "apple-iphone4-ipad-hls-3g", "apple-ipad-hls"); | |
} | |
my %seen; | |
for my $i ( 0 .. $#mediasets ){ | |
my $mediaset = $mediasets[$i]; | |
my $mediaselector = "http://open.live.bbc.co.uk/mediaselector/5/select/version/2.0/mediaset/$mediaset/vpid/$channel/format/json"; | |
app->log->debug("mediaselector: $mediaselector"); | |
my $tx = $ua->get($mediaselector); | |
my $res = $tx->success; | |
if ( ! $res ) { | |
my $err = $tx->error; | |
if ( $err->{code} ) { | |
app->log->error("code: $err->{code} response: $err->{message}"); | |
} else { | |
app->log->error("connection error: $err->{message}"); | |
} | |
next; | |
} | |
my $json = decode_json($res->body); | |
for my $media ( @{$json->{media}} ) { | |
my $media_playlists; | |
for my $conn ( @{$media->{connection}} ) { | |
# skip other stream types | |
next unless $conn->{transferFormat} eq "hls"; | |
# skip https URLs | |
next if $conn->{protocol} eq "https"; | |
my $url = $conn->{href}; | |
# skip duplicates | |
next if $seen{$url}; | |
$seen{$url} = 1; | |
my $supplier = $conn->{supplier} =~ /^ll/i ? "Limelight" : $conn->{supplier} =~ /^ak/i ? "Akamai" : ucfirst($conn->{supplier} ); | |
my $priority = $conn->{priority}; | |
my $availability; | |
if ( $type eq "tv" ) { | |
$availability = "UK Only"; | |
} else { | |
$availability = ( ( $url =~ /[\/-]uk[\/-]/ && $channel ne "bbc_world_service" ) || $channel eq "bbc_radio_five_live_sports_extra" ) ? "UK Only" : "UK & Intl"; | |
} | |
my $key = sprintf("%02d-%03d", $i+1, $priority); | |
my $playlist = { | |
key => $key, | |
type => $type, | |
mediaset => $mediaset, | |
target => $mediaset eq "iptv-all" ? "IPTV" : "Mobile", | |
availability => $availability, | |
supplier => $supplier, | |
priority => $priority, | |
bitrate => $type eq "tv" ? 0 : $media->{bitrate}, | |
url => $url | |
}; | |
$media_playlists->{$key} = $playlist; | |
} | |
@{$playlists}{keys %{$media_playlists}} = values %{$media_playlists}; | |
# break after first media descriptor with HLS | |
last if keys %{$media_playlists} != 0; | |
} | |
} | |
return $playlists; | |
} | |
# generate tv playlists without mediaselector API | |
sub tv_playlists { | |
my $ua = shift; | |
my $channel = shift; | |
my $playlists; | |
# lowest to highest quality | |
# my @mediasets = ( "hls_mobile_3g_main", "hls_tablet_rw" ); | |
my @mediasets = ( "hls_tablet" ); | |
# HD only for national channels (except BBC Parliament) and regional BBC One | |
if ( ( grep(/^$channel$/, values %{$channels->{tv}->{national}}) && $channel !~ /bbc_parliament/ ) || | |
( grep(/^$channel$/, values %{$channels->{tv}->{regional}}) && $channel =~ /bbc_one/ ) || | |
( grep(/^$channel$/, values %{$channels->{tv}->{'red button'}}) ) ) { | |
push @mediasets, "abr_hdtv"; | |
} else { | |
push @mediasets, "abr_tv"; | |
} | |
my $broadcast = "simulcast"; | |
if ( grep(/^$channel$/, values %{$channels->{tv}->{'red button'}}) ) { | |
$broadcast = "webcast"; | |
} | |
# randomise CDNs | |
#my @rand_cdns = ( "ak", "llnw" ); | |
#my @cdns = $rand_cdns[rand @rand_cdns]; | |
#push @cdns, $cdns[0] eq "ak" ? "llnw" : "ak"; | |
# reverse alphabetical | |
my @cdns = ( "llnw", "ak" ); | |
for my $i ( 0 .. $#mediasets ) { | |
my $mediaset = $mediasets[$i]; | |
for my $j ( 0 .. $#cdns ) { | |
my $cdn = $cdns[$j]; | |
my $supplier = $cdn eq "ak" ? "Akamai" : "Limelight"; | |
my $priority = $j + 1; | |
my $key = sprintf("%02d-%02d", $i+1, $priority); | |
my $playlist = { | |
key => $key, | |
type => 'tv', | |
mediaset => $mediaset, | |
target => $mediaset =~ /abr/ ? "IPTV" : "Mobile", | |
availability => "UK Only", | |
supplier => $supplier, | |
priority => $priority, | |
bitrate => 0, | |
url => "http://a.files.bbci.co.uk/media/live/manifesto/audio_video/${broadcast}/hls/uk/${mediaset}/${cdn}/${channel}.m3u8" | |
}; | |
$playlists->{$key} = $playlist; | |
} | |
} | |
return $playlists; | |
} | |
# generate radio playlists without mediaselector API | |
sub radio_playlists { | |
my $ua = shift; | |
my $channel = shift; | |
my $playlists; | |
my $bitrates = { | |
'sbr_vlow' => 48, | |
'sbr_low' => 96, | |
'sbr_med' => 128, | |
'sbr_high' => 320, | |
}; | |
# non-UK before UK | |
my @mediasets = ( "nonuk", "uk" ); | |
# randomise CDNs | |
#my @rand_cdns = ( "ak", "llnw" ); | |
#my @cdns = $rand_cdns[rand @rand_cdns]; | |
#push @cdns, $cdns[0] eq "ak" ? "llnw" : "ak"; | |
# reverse alphabetical | |
my @cdns = ( "llnw", "ak" ); | |
for my $i ( 0 .. $#mediasets ) { | |
my $mediaset = $mediasets[$i]; | |
# no non-UK streams for bbc_radio_five_live_sports_extra | |
next if $channel eq "bbc_radio_five_live_sports_extra" and $mediaset eq "nonuk"; | |
# no UK streams for bbc_world_service | |
next if $channel eq "bbc_world_service" and $mediaset eq "uk"; | |
my @sbrs; | |
# lowest to highest quality | |
if ( $mediaset eq "uk" ) { | |
@sbrs = ( 'sbr_vlow', 'sbr_low', 'sbr_med', 'sbr_high' ); | |
} else { | |
@sbrs = ( 'sbr_vlow', 'sbr_low' ); | |
} | |
for my $j ( 0 .. $#sbrs ){ | |
my $sbr = $sbrs[$j]; | |
for my $k ( 0 .. $#cdns ) { | |
my $cdn = $cdns[$k]; | |
my $supplier = $cdn eq "ak" ? "Akamai" : "Limelight"; | |
my $priority = $k + 1; | |
my $availability = ( ( $mediaset eq "uk" && $channel ne "bbc_world_service" ) || $channel eq "bbc_radio_five_live_sports_extra" ) ? "UK Only" : "UK & Intl"; | |
my $key = sprintf("%02d-%02d-%02d", $i+1, $j+1, $priority); | |
my $playlist = { | |
key => $key, | |
type => 'radio', | |
mediaset => $mediaset, | |
target => "Any", | |
availability => $availability, | |
supplier => $supplier, | |
priority => $priority, | |
bitrate => $bitrates->{$sbr}, | |
url => "http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/${mediaset}/${sbr}/${cdn}/${channel}.m3u8" | |
}; | |
$playlists->{$key} = $playlist; | |
} | |
} | |
} | |
return $playlists; | |
} | |
# generate playlists without mediaselector API | |
sub generate_playlists { | |
my $ua = shift; | |
my $type = shift; | |
my $channel = shift; | |
if ( $type eq "tv" ) { | |
return tv_playlists($ua, $channel); | |
} else { | |
return radio_playlists($ua, $channel); | |
} | |
} | |
# get streams in single playlist | |
sub playlist_streams { | |
my $ua = shift; | |
my $playlist = shift; | |
my $type = $playlist->{type}; | |
my $streams; | |
app->log->debug("playlist: $playlist->{url}"); | |
my $tx = $ua->get($playlist->{url}); | |
my $res = $tx->success; | |
if ( ! $res ) { | |
my $err = $tx->error; | |
if ( $err->{code} ) { | |
app->log->error("code: $err->{code} response: $err->{message}"); | |
} else { | |
app->log->error("connection error: $err->{message}"); | |
} | |
return undef; | |
} | |
my @lines = m3u_parser($res->body); | |
my %seen; | |
for my $i ( 0 .. $#lines ) { | |
my $line = $lines[$i]; | |
if ( $line->{type} eq 'directive' && $line->{tag} eq 'EXT-X-STREAM-INF' ) { | |
my ($bitrate, $width, $height) = (0, 0, 0); | |
if ( $type eq "tv" ) { | |
($bitrate, $width, $height) = $line->{value} =~ /BANDWIDTH=(\d+).*?RESOLUTION=(\d+)x(\d+)/i; | |
} else { | |
($bitrate) = $line->{value} =~ /BANDWIDTH=(\d+)/i; | |
} | |
$bitrate = int($bitrate / 1000); | |
$line = $lines[++$i]; | |
if ( $line->{type} eq 'item' ) { | |
my $url = $line->{value}; | |
# skip duplicates | |
next if $seen{$url}; | |
$seen{$url} = 1; | |
my ($audio_bitrate, $video_bitrate) = (0, 0); | |
if ( $type eq "tv" ) { | |
($audio_bitrate, $video_bitrate) = $url =~ /%3d(\d+)-video%3d(\d+)/i; | |
} else { | |
($audio_bitrate) = $url =~ /-audio%3d(\d+)/i; | |
} | |
$audio_bitrate = int($audio_bitrate / 1000); | |
$video_bitrate = int($video_bitrate / 1000); | |
my $key; | |
if ( $type eq "tv" ) { | |
$key = sprintf("%04d-%05d-%03d", $height, $bitrate, $playlist->{priority}); | |
} else { | |
$key = $playlist->{key}; | |
} | |
my $stream = { | |
key => $key, | |
type => $playlist->{type}, | |
mediaset => $playlist->{mediaset}, | |
supplier => $playlist->{supplier}, | |
priority => $playlist->{priority}, | |
target => $playlist->{target}, | |
availability => $playlist->{availability}, | |
bitrate => $bitrate, | |
width => $width, | |
height => $height, | |
audio_bitrate => $audio_bitrate, | |
video_bitrate => $video_bitrate, | |
frame_rate => $type eq "tv" ? $bitrate > 3000 ? 50 : 25 : 0, | |
url => $url | |
}; | |
$streams->{$key} = $stream; | |
app->log->debug("stream: $stream->{url}"); | |
} | |
} | |
} | |
return $streams; | |
} | |
# get streams in multiple playlists | |
sub playlists_streams { | |
my $ua = shift; | |
my $playlists = shift; | |
my $streams; | |
for my $key ( keys %{$playlists} ) { | |
my $playlist = $playlists->{$key}; | |
my $playlist_streams = playlist_streams($ua, $playlist); | |
@{$streams}{keys %{$playlist_streams}} = values %{$playlist_streams}; | |
} | |
return $streams; | |
} | |
# routes | |
get '/' => {template => 'index'}; | |
get '/channels/:medsel/:type' => sub { | |
my $c = shift; | |
my $type = $c->param('type'); | |
$c->stash(channels => $channels->{$type}); | |
$c->stash(channel_names => $channel_names->{$type}); | |
$c->render(template => 'channels'); | |
}; | |
get '/playlists/:medsel/:type/:channel/:name' => sub { | |
my $c = shift; | |
my $type = $c->param('type'); | |
my $channel = $c->param('channel'); | |
my $playlists; | |
if ( $c->param('medsel') ) { | |
$playlists = mediaselector_playlists($c->ua, $type, $channel); | |
} else { | |
$playlists = generate_playlists($c->ua, $type, $channel); | |
} | |
my $streams = playlists_streams($c->ua, $playlists); | |
$c->stash(playlists => $playlists, streams => $streams); | |
$c->render(template => 'playlists', variant => $type); | |
}; | |
app->start; | |
__DATA__ | |
@@ layouts/bbclive.html.ep | |
<!DOCTYPE html> | |
<html> | |
<head><title>BBC Live</title></head> | |
<body><%= content %></body> | |
</html> | |
@@ index.html.ep | |
% layout 'bbclive'; | |
<h1>BBC Live</h1> | |
<h2>(Generated Playlists)</h2> | |
<a href="/channels/0/tv"><h2>TV</h2></a> | |
<a href="/channels/0/radio"><h2>Radio</h2></a> | |
<h2>(Mediaselector API)</h2> | |
<a href="/channels/1/tv"><h2>TV</h2></a> | |
<a href="/channels/1/radio"><h2>Radio</h2></a> | |
@@ channels.html.ep | |
% layout 'bbclive'; | |
% use Mojo::Util qw(url_escape); | |
<h1><a href="/">BBC Live</a> > <%= $type eq "tv" ? "TV" : "Radio" %></h1> | |
<h2>(<%= $medsel ? "Mediaselector API" : "Generated Playlists" %>)</h2> | |
% for my $group ( 'national', 'regional', 'local', 'red button' ) { | |
<h2><%= ucfirst $group %><br/></h2> | |
% for my $name ( @{$channel_names->{$group}} ) { | |
<a href="/playlists/<%= $medsel %>/<%= $type %>/<%= $channels->{$group}->{$name} %>/<%= url_escape $name %>"><%= $name %></a><br/> | |
% } | |
% } | |
@@ playlists.html+tv.ep | |
% layout 'bbclive'; | |
% my $header = begin | |
% my $title = shift; | |
<tr><td colspan="11"><h2><%= $title %></h2></td></tr> | |
<tr valign="top"> | |
<th>Geographic<br/>Availability</th> | |
<th>Target<br/>Platform</th> | |
<th>Video<br/>Size</th> | |
<th>Frame<br/>Rate</th> | |
<th>Video<br/>Quality</th> | |
<th>Video<br/>Format</th> | |
<th>Audio<br/>Quality</th> | |
<th>Audio<br/>Format</th> | |
<th>Stream<br/>Type</th> | |
<th>CDN<br/>Supplier</th> | |
<th></th> | |
</tr> | |
% end | |
% my $row = begin | |
% my $x = shift; | |
<tr> | |
<td><%= $x->{availability} %></td> | |
<td><%= $x->{target} %></td> | |
<td><%= $x->{height} ? $x->{width}."x".$x->{height} : "Adaptive" %></td> | |
<td><%= $x->{frame_rate} ? $x->{frame_rate}."fps" : "Adaptive" %></td> | |
<td><%= $x->{video_bitrate} ? $x->{video_bitrate}."k" : "Adaptive" %></td> | |
<td>H.264</td> | |
<td><%= $x->{audio_bitrate} ? $x->{audio_bitrate}."k" : "Adaptive" %></td> | |
<td>AAC</td> | |
<td>HLS</td> | |
<td><%= $x->{supplier} %></td> | |
<td><a target="_blank" href="<%= $x->{url} %>">Play</a></td> | |
</tr> | |
% end | |
<h1><a href="/">BBC Live</a> > <a href="/channels/<%= $medsel %>/tv">TV</a> > <%= $name %></h1> | |
<h2>(<%= $medsel ? "Mediaselector API" : "Generated Playlists" %>)</h2> | |
<table cellspacing="3"> | |
%= $header->('Variant Playlists') | |
% for my $key ( reverse sort keys %{$playlists} ) { | |
%= $row->($playlists->{$key}) | |
% } | |
%= $header->('Individual Streams') | |
% for my $key ( reverse sort keys %{$streams} ) { | |
%= $row->($streams->{$key}) | |
% } | |
</table> | |
@@ playlists.html+radio.ep | |
% layout 'bbclive'; | |
% my $header = begin | |
% my $title = shift; | |
<tr><td colspan="6"><h2><%= $title %></h2></td></tr> | |
<tr valign="top"> | |
<th>Geographic<br/>Availability</th> | |
<th>Audio<br/>Quality</th> | |
<th>Audio<br/>Format</th> | |
<th>Stream<br/>Type</th> | |
<th>CDN<br/>Supplier</th> | |
<th></th> | |
</tr> | |
% end | |
% my $row = begin | |
% my $x = shift; | |
<tr> | |
<td><%= $x->{availability} %></td> | |
<td><%= $x->{audio_bitrate} || $x->{bitrate} %>k</td> | |
<td>AAC</td> | |
<td>HLS</td> | |
<td><%= $x->{supplier} %></td> | |
<td><a target="_blank" href="<%= $x->{url} %>">Play</a></td> | |
</tr> | |
% end | |
<h1><a href="/">BBC Live</a> > <a href="/channels/<%= $medsel %>/radio">Radio</a> > <%= $name %></h1> | |
<h2>(<%= $medsel ? "Mediaselector API" : "Generated Playlists" %>)</h2> | |
<table cellspacing="3"> | |
%= $header->('Variant Playlists') | |
% for my $key ( reverse sort keys %{$playlists} ) { | |
%= $row->($playlists->{$key}) | |
% } | |
%= $header->('Individual Streams') | |
% for my $key ( reverse sort keys %{$streams} ) { | |
%= $row->($streams->{$key}) | |
% } | |
</table> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment