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;