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.
149 lines
3.9 KiB
149 lines
3.9 KiB
10 years ago
|
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;
|