Alex 'AdUser' Z
10 years ago
commit
61a16fa11d
8 changed files with 199 additions and 0 deletions
@ -0,0 +1,24 @@
|
||||
package Livecam; |
||||
use Mojo::Base 'Mojolicious'; |
||||
|
||||
sub startup { |
||||
my ($self) = @_; |
||||
|
||||
my $r = $self->routes; |
||||
|
||||
$r->get('/')->to(cb => sub { |
||||
shift->redirect_to('/playlist') |
||||
}); |
||||
|
||||
$self->app->attr(ua => sub { |
||||
require Mojo::UserAgent; |
||||
my $ua = Mojo::UserAgent->new; |
||||
$ua->name(qq{Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20140925 Firefox/24.0 Iceweasel/24.8.1}); |
||||
return $ua; |
||||
}); |
||||
|
||||
$r->route('/playlist') -> via('GET') -> to('main#playlist'); |
||||
$r->route('/livecam') -> via('GET') -> to('main#livecam'); |
||||
} |
||||
|
||||
1; |
@ -0,0 +1,148 @@
|
||||
package Livecam::Main; |
||||
|
||||
use Graphics::Magick; |
||||
use Mojo::Base 'Mojolicious::Controller'; |
||||
use Mojo::Asset::File; |
||||
use Mojo::JSON; |
||||
|
||||
sub _pls_cache_path { |
||||
my ($self, $name) = @_; |
||||
return $self->app->home->rel_file('cache/playlist.json'); |
||||
} |
||||
|
||||
sub _pls_cache_save { |
||||
my ($self, $data) = @_; |
||||
|
||||
my $asset = Mojo::Asset::File->new(path => $self->_pls_cache_path); |
||||
unlink $asset->path; |
||||
$asset->add_chunk(Mojo::JSON->new->encode($data)); |
||||
$asset->cleanup(0); |
||||
return 1; |
||||
} |
||||
|
||||
sub _pls_cache_load { |
||||
my ($self) = @_; |
||||
|
||||
my $asset = Mojo::Asset::File->new(path => $self->_pls_cache_path); |
||||
return Mojo::JSON->new->decode($asset->slurp); |
||||
}; |
||||
|
||||
sub _cam_cache_path { |
||||
my ($self, $name) = @_; |
||||
$name =~ y|/|+|; |
||||
return $self->app->home->rel_file("cache/cam-$name.jpg"); |
||||
} |
||||
|
||||
sub _cam_cache_save { |
||||
my ($self, $name, $asset) = @_; |
||||
|
||||
$asset->move_to($self->_cam_cache_path($name)); |
||||
return 1; |
||||
} |
||||
|
||||
sub _cam_cache_load { |
||||
my ($self, $name) = @_; |
||||
my $path = $self->_cam_cache_path($name); |
||||
return unless (-f $path); |
||||
my $mtime = (stat($path))[9]; |
||||
return if ($mtime + 20 < time()); # expire in 20 seconds |
||||
return Mojo::Asset::File->new(path => $path); |
||||
} |
||||
|
||||
sub playlist { |
||||
my ($self) = @_; |
||||
|
||||
# fetch cam list |
||||
my $domain = "http://live.podryad.tv"; |
||||
my @pages = ("", "page/2", "page/3", "page/4"); |
||||
my @cams; |
||||
foreach my $page (@pages) { |
||||
$self->app->ua->get("$domain/$page")->res->dom('div.cam-preview')->each(sub { |
||||
my $title = $_->at('div.title a')->text; |
||||
my ($id) = $_->at('div.image img')->{src} =~ m|/cams/snap-(.+)\.jpg|oi; |
||||
return unless ($title and $id); |
||||
my $url = sprintf "%s/camws/cam-image.php?cam=%s&r=%.15f", $domain, $id, rand(1); |
||||
push @cams, {id => $id, title => $title, url => $url}; |
||||
}); |
||||
} |
||||
|
||||
$self->_pls_cache_save(\@cams); |
||||
my $playlist = "#EXTM3U aspect-ratio=4:3\n"; |
||||
|
||||
# gen playlists |
||||
foreach my $cam (@cams) { |
||||
$playlist .= sprintf "#EXTINF:-1 %s,%s\n", $cam->{id}, $cam->{title}; |
||||
my $url = $self->req->url->clone; |
||||
$url->path('/livecam'); |
||||
$url->query->param(id => $cam->{id}); |
||||
$playlist .= sprintf "%s\n", $url->to_abs; |
||||
} |
||||
|
||||
$self->render_text($playlist); |
||||
} |
||||
|
||||
sub livecam { |
||||
my ($self) = @_; |
||||
my $id = $self->req->param('id'); |
||||
my $cache = $self->_pls_cache_load; |
||||
|
||||
unless ($id and $cache) { |
||||
$self->res->code(404); |
||||
$self->render_text('Cam id not set or cache is empty'); |
||||
return; |
||||
} |
||||
|
||||
my $cam; |
||||
foreach my $entry (@$cache) { |
||||
next unless $entry->{id} eq $id; |
||||
$cam = $entry; |
||||
} |
||||
|
||||
unless ($cam) { |
||||
$self->res->code(404); |
||||
$self->render_text('Cam not found'); |
||||
return; |
||||
} |
||||
|
||||
$self->res->headers->content_type('multipart/x-mixed-replace;boundary=--Frame'); |
||||
|
||||
srand(time()); |
||||
my $url = Mojo::URL->new; |
||||
$url->scheme('http'); |
||||
$url->host('live.podryad.tv'); |
||||
$self->app->ua->get($url->to_abs); # setup cookies |
||||
$url->path('/camws/cam-image.php'); |
||||
|
||||
my $send_image = sub { |
||||
my $seed = sprintf "%.15f", rand(1); |
||||
$url->query(cam => $cam->{id}, r => $seed); |
||||
my $asset = $self->_cam_cache_load($cam->{id}); |
||||
unless ($asset) { |
||||
$self->app->log->debug("fetch new frame from $cam->{id}"); |
||||
$asset = $self->app->ua->get($url->to_abs)->res->content->asset; |
||||
$self->_cam_cache_save($cam->{id}, $asset) if $asset; |
||||
} |
||||
$self->write_chunk("--Frame\r\n"); |
||||
$self->write_chunk("Content-Type: image/jpg\r\n"); |
||||
$self->write_chunk("Content-Length: " . $asset->size . "\r\n"); |
||||
$self->write_chunk("\r\n"); |
||||
$self->write_chunk($asset->slurp()); |
||||
$self->write_chunk("\r\n" x 2); |
||||
my $msg = sprintf "Send frame from cam %s with size %d", $cam->{id}, $asset->size; |
||||
$self->app->log->debug($msg); |
||||
}; |
||||
|
||||
$send_image->(); # initial send |
||||
|
||||
Mojo::IOLoop->stream($self->tx->connection)->timeout(30); |
||||
Mojo::IOLoop->singleton->reactor->on(error => sub { |
||||
my ($reactor, $err) = @_; |
||||
$self->app->log->error($err); |
||||
}); |
||||
|
||||
my $timer = Mojo::IOLoop->recurring(3 => \&$send_image); |
||||
|
||||
$self->on(finish => sub { Mojo::IOLoop->remove($timer) }); |
||||
} |
||||
|
||||
1; |
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env perl |
||||
use Mojo::Base -strict; |
||||
|
||||
use File::Basename 'dirname'; |
||||
use File::Spec::Functions qw(catdir splitdir); |
||||
|
||||
# Source directory has precedence |
||||
my @base = (splitdir(dirname(__FILE__)), '..'); |
||||
my $lib = join('/', @base, 'lib'); |
||||
-e catdir(@base, 't') ? unshift(@INC, $lib) : push(@INC, $lib); |
||||
|
||||
# Start commands for application |
||||
require Mojolicious::Commands; |
||||
Mojolicious::Commands->start_app('Livecam'); |
@ -0,0 +1,7 @@
|
||||
use Mojo::Base -strict; |
||||
|
||||
use Test::More tests => 3; |
||||
use Test::Mojo; |
||||
|
||||
my $t = Test::Mojo->new('Livecam'); |
||||
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i); |
Loading…
Reference in new issue