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.
131 lines
8.6 KiB
131 lines
8.6 KiB
9 years ago
|
---
|
||
|
title: Небольшой проектик JFF на perl+mojo
|
||
|
tags: xbmc, perl, mojo
|
||
|
---
|
||
|
|
||
|
Сиё творение родилось из простенькой, казалось бы, задачи: прицепить подрядовские вебкамеры к медиацентру.
|
||
|
|
||
|
Что хотелось видеть изначально: плейлист с перечнем камер со ссылками вида ``http://.*\.jpeg``. Открываешь его, тыкаешь в нужную камеру м смотришь обстановку на дороге. В крайнем случае - пачку плейлистов, ровно с одним пунктом. Как показывает опыт, xbmc просто до жопы хочет обязательно постоить превьюшку к каждому пункту плейлиста, и начинает их дёргать все сразу, что неприемлемо и в данном случае - не по джентельменски. Из-за дикого количества запросов на сервер подряда, нас могут просто рубануть по ip или useragent'у.
|
||
|
|
||
|
---
|
||
|
|
||
|
Грабли с превьюшками - это полбеды. Как выяснилось в дальнейшем, xbmc прекрасно понимает http, и пришедший по нему jpeg. НО! обрабатывает их не как картинку, а как видео из одного кадра. Повезёт, если увидишь как там что-то мелькнуло в 1/25ю секунды[^fn1].
|
||
|
|
||
|
Поэтому, возникла гениальная идея - быстренько забацать аналог udpxy, который:
|
||
|
|
||
|
- можно насиловать запросами сколько угодно и с какой угодно частотой,
|
||
|
- может отдавать не обязательно jpeg.
|
||
|
|
||
|
Из требований - возможно меньше зависимостей, кроме perl'а.
|
||
|
|
||
|
Кодинг
|
||
|
-------
|
||
|
|
||
|
В репозиториях debian'а есть и perl и [mojolicious](https://metacpan.org/pod/Mojolicious), поэтому установку я расписывать не буду, перейдём сразу к коду.
|
||
|
|
||
|
$ mojo generate app Livecam
|
||
|
|
||
|
Структура проекта:
|
||
|
|
||
|
$ tree -A -S -n livecam
|
||
|
livecam/
|
||
|
├── cache # кэш списка каналов и изображений с камер
|
||
|
├── lib
|
||
|
│ ├── Livecam
|
||
|
│ │ └── Main.pm # контроллер
|
||
|
│ └── Livecam.pm # роутер
|
||
|
├── public
|
||
|
├── script
|
||
|
│ └── livecam # скрипт запуска
|
||
|
├── t # здесь должны быть тесты, но по факту - не используется
|
||
|
│ └── basic.t
|
||
|
└── templates # не используется, у нас все ответы - plain http
|
||
|
└── layouts
|
||
|
└── default.html.ep
|
||
|
|
||
|
В роутере нужно дописать 2 пути и повесить Mojo::UserAgent как ресурс приложения, чтобы не инициализировать его каждый раз.
|
||
|
|
||
|
$r->get('/playlist') -> to('main#playlist');
|
||
|
$r->get('/livecam') -> to('main#livecam');
|
||
|
|
||
|
$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});
|
||
|
return $ua;
|
||
|
});
|
||
|
|
||
|
В контроллере кода побольше. Перечень функций с их назначением [^fn2]:
|
||
|
|
||
|
# private, работа со списком камер
|
||
|
_pls_cache_path
|
||
|
_pls_cache_save
|
||
|
_pls_cache_load
|
||
|
# private, работа с изображениями камер
|
||
|
_cam_cache_path
|
||
|
_cam_cache_save
|
||
|
_cam_cache_load
|
||
|
# и 2 метода, вызываемых из роутера, которые мы рассмотрим подробнее
|
||
|
playlist
|
||
|
livecam
|
||
|
|
||
|
В любой непонятной ситуации - [смотрите код](https://www.linuxdv.org/git/?p=livecam.git;a=summary), там его совсем мало.
|
||
|
|
||
|
playlist()
|
||
|
----------
|
||
|
|
||
|
Назначение - получить полный список камер с сайта подряда (и закешировать его), отдать его в виде плейлиста m3u.
|
||
|
|
||
|
Камеры раскиданы по 4 страницам, поэтому получим каждую и пройдёмся по ним парсером. Здесь нам поможет Mojo::DOM, совершенно замечательная штука, этакое jquery для perl'а (селекторы и манипуляция элементами, без ajax'а, которым занимается уже Mojo::UserAgent с Mojo::IOLoop).
|
||
|
|
||
|
Итак, структура html'я каждой камеры довольно проста: три div'а, враппер, имя камеры и ссылка с превью.
|
||
|
|
||
|
# перебор камер на странице
|
||
|
$self->app->ua->get($url)->res->dom('div.cam-preview')->each(sub {<...>});
|
||
|
# ...и пример вытаскивания данных внутри обработчика в each();
|
||
|
# из такого: <a href="<...>" class="<...>">Title</a>
|
||
|
my $title = $_->at('div.title a')->text;
|
||
|
|
||
|
Всё остальное здесь не представляет особого интереса.
|
||
|
|
||
|
livecam()
|
||
|
---------
|
||
|
|
||
|
Назначение - получить изображение с камеры, отдать перекодированное и ограничить частоту запроса к внешнему серверу за счёт кеширования вывода.
|
||
|
|
||
|
Изначально, я хотел использовать для вывода gif, но в итоге отказался (+зависимость от gd2/im::m/g::m и всё равно - генерация заголовка gif'а руками). В итоге остановился на mjpeg over http. В отличие от просто mjpeg, с точки зрения браузера это выглядит так:
|
||
|
|
||
|
* в заголовках посылается 'Content-Type: multipart/x-mixed-replace;boundary=--Frame'
|
||
|
* далее, пока страница не закрыта/держится соединение, периодически посылаются порции данных такого вида:
|
||
|
|
||
|
--Frame\r\n
|
||
|
Content-Type: image/jpg\r\n
|
||
|
Content-Length: $size\r\n
|
||
|
\r\n
|
||
|
<binary jpeg data>\r\n
|
||
|
\r\n
|
||
|
|
||
|
С точки зрения mojo, это реализуется так:
|
||
|
|
||
|
* пишется обработчик с такой логикой:
|
||
|
* достать из кэша/получить извне кадр с внешней камеры с таким-то id
|
||
|
* сформировать "кадр" из примера выше, отдать его клиенту
|
||
|
* повесить обработчик на повторное выполнение каждые N секунд
|
||
|
* дёрнуть обработчик в первый раз, чтобы клиент не ждал до срабатывания периодического таймера
|
||
|
* висеть, пока таймер периодически кормит клиента кадрами
|
||
|
* при отвале клиента - аккуратно снять таймер, закрыть соединение
|
||
|
|
||
|
Всё, теперь мы можем дёргать наш сервер хоть 50 раз в секунду, запросы наружу пойдут только по мере устаревания картинки в кеше.
|
||
|
|
||
|
Эпилог
|
||
|
------
|
||
|
|
||
|
И всё таки она верт^W^W оно не работает. Потому что в xbmc не настолько продвинутый http-клиент. Картинку кажет, достаточно долго, но вот обновлять - хрен. Зато в браузере работает только так. Кроме того, обнаружился неприятный баг - плейлист самопроизвольно удаляется при закрытии через 'stop'.
|
||
|
|
||
|
Напишу в их багзиллу, пусть думают.
|
||
|
|
||
|
[^fn1]: 25fps - берётся по умолчанию
|
||
|
|
||
|
[^fn2]: их назначение примерно понятно из названий, но тем не менее
|