1
0
Fork 0
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

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;