You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
3.9 KiB
148 lines
3.9 KiB
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;
|
|
|