Jump to content
Станислав

Клиент HTML + JS для системы видеонаблюдения «Линия»

Recommended Posts

https://geektimes.ru/company/devline/blog/297701/

На Geektimes я часто встречаю и с удовольствием читаю посты из серии DIY. Решив сделать небольшой вклад в копилку ценного опыта, собранного здесь, я собираюсь подробно описать процесс создания клиента для веб, базирующегося на серверах «Линия».

Система видеонаблюдения «Линия» предоставляет открытое API, и разработчики заявляют, что на его основе можно написать собственный клиент для просмотра видеоархива и камер онлайн. Также при желании можно реализовать такие функции, как добавление событий в архив, наложение OSD поверх видео. Описание всех возможностей представлено в спецификации на официальном сайте.

Данная статья — это реальный пример, как я, пользователь с начальными знаниями JS, HTML, написал собственное приложение, реализующее базовые принципы работы с серверами «Линия» через встроенный web-сервер.

Вводные данные

Автор — новичок в разработке HTML-клиента, имею отношение к разработке системы видеонаблюдения «Линия».
Уровень знаний JS, HTML — начальный.
Задача — написать HTML-клиент для работы с устройствами на базе программного обеспечения «Линия» с помощью спецификации с сайта.

Главную интригу раскрою сразу – я пришел к двум выводам:
1. Спецификация реальная, описано достаточно понятно, написать клиент можно, используя C++, PHP.
2. Полноценный HTML-клиент, используя только лишь JS, написать нельзя — только онлайн-наблюдение по спецификации до RPC.

Первый вывод вполне закономерен, если учесть большое количество интеграций со сторонними программами. Все они описаны на сайте: тут есть СКУД, весовые, POS-системы, программы для определения автомобильных номеров и .

Второй вывод более интересен, рассмотрим его ниже.
 

Почему нельзя создать полноценный клиент на HTML + JS?


Ответ: кросс-доменные запросы.

На данный момент веб-сервер «Линии» ограничен, и путем простого копирования кода в папку www доступ получить нельзя. Однако разработчики обещают, что в новой версии для Linux и в «Линии 8.0» веб-сервер будет работать стандартно: в случае запроса, при наличии файла, будет его возвращать.

Сейчас же создаем новый проект и начинаем кодить. Как и все новички в программировании для веб, уточнив, что сервер «Линии» отвечает «*» в заголовке Access-Control-Allow-Origin, я начал усиленно трудиться над кодом, проверяя результат на Firefox 57.0.4 (64-бит). Запросы на сервер отправлял XMLHttpRequest.

Изначально было бы полезно изучить информацию на данном ресурсе. Там очень подробно все описано, но уж очень хотелось побыстрее выполнить задачу. И, к сожалению, из-за недостатка информации полдня были потеряны на битье головой о стену политики безопасности современных браузеров.

На момент написания статьи четыре основных современных браузера не дают прочитать заголовки, получаемые от сервера. Согласно спецификации необходимо реализовать Digest-авторизацию, что без заголовков невозможно.

К концу первого дня я понял, что без добавления в веб-сервер «Линии» обработки OPTIONS ничего не получится, так как для запросов с «непростым» методом или особыми заголовками браузер делает предзапрос OPTIONS, указывая их в Access-Control-Request-Method и Access-Control-Request-Headers. Поэтому я начал искать другие варианты авторизации, а настоящие Basic или Digest не взлетели.

Альтернативный способ уже был описан в спецификации, оставалось потратить какое-то время на переписку с программным отделом «Линии». Так как подобные трудности возникают не в первый раз, уже есть костыль для авторизации, и он даже упомянут в спецификации:

На клиентах, где невозможно авторизовать запрос стандартными средствами (HTTP Digest/Basic Authentication), возможна передача заголовка Authorization одним из параметров запроса, например
/kfd3ado1sdrms/streaming/main.flv?authorization=Basic%20d2ViOg==


После всех манипуляций стандартный кросс-доменный запрос стал корректно выполняться! Необходимо также добавить в запрос заголовок Accept c правильным типом — я решил использовать JSON.

Код запроса:
 

function get_request_url(method,current_server_data, resource, additional){
    var request = current_server_data.server_ip + ':' +current_server_data.port +resource+'?authorization=Basic '+ utf8_to_b64(current_server_data.user+':'+current_server_data.password);
    if (additional != '' && typeof additional  != "undefined") {
        request += '&' + additional;
    }
    return request;
}

function http_request_of_resource (server_index , resource, auth_attempt) {
    var request = get_request_url('GET', servers_array[server_index], resource,'');
    var req_ = new XMLHttpRequest();
    req_.open('GET', 'http://'+ request, true);
    //req_.timeout = 9000;
    // выполнить код, когда придёт ответ
    req_.onreadystatechange = function() {
        if (this.readyState == 2) {
           if (this.status == 401) {
                //console.log('---unauthorized');
                hideModal();
                update_nessecary_structure(resource, 'unauthorized', server_index);
            }
        } else if (this.readyState === 4) {
            if (this.status === 0) {
                hideModal();
                update_nessecary_structure(resource, 'server_down',server_index)
            }
            if (this.status == 200) {
                if (auth_attempt) hideModal();
                else resource =(resource =='/cameras') ? resource+'_update_info': resource;
                //console.log('200' + this.responseText);
                update_nessecary_structure(resource, this.responseText, server_index);
            } else if (this.status == 404) {
                //console.log('404');
                update_nessecary_structure(resource, '404', server_index);
            }
        }
    };
    
    // Оправка запроса
    req_.setRequestHeader('Content-type', 'text/plain; charset=utf-8');
    req_.setRequestHeader('Accept', 'application/json');
    req_.send();
        
}


Меняем resource на нужный нам согласно спецификации и получаем те или иные данные. Переменная additional содержит дополнительные параметры для запроса, если таковые необходимы. На этом освоение первой половины спецификации, а именно получение/отправки текстовых данных посредством GET-запросов, можно считать закрытым.

Далее я столкнулся с тем, что тэг IMG в IE не воспроизводит MJPEG-поток и нужно самостоятельно реализовать обновление изображений с камер. Код открыт, его можно посмотреть и при желании изменить. В текущей реализации доступно одновременное воспроизведение максимум шести MJPEG потоков, так что работу с видом, отображающим большее количеством камер, придется делать самим. Все это есть в примере, при желании можно найти и разобраться, но если возникнут вопросы, обязательно задавайте в комментариях.
 

Спецификация RPC


Нам предложено отправлять и получать данные либо в JSON (версия сервера «Линии 7.1.1» и выше) или MessagePack (версия «Линия 7.0» и выше). Упоминают, что MessagePack меньше весит и работает быстрее, но, если честно, я бы выбрал JSON (он уже встроен в JS), если бы не одно но в спецификации: получение кадров из архива возможно только в MessagePack. Пришлось идти на их официальный сайт и качать JS-файл, который имеет на борту методы encode и decode.

Функция отправки запроса готова! Но праздновать победу рано: при попытке поменять заголовок запроса Content-type браузер ругается и не отправляет данные серверу. Дело в том, что сервер «Линии» анализирует это поле и в зависимости от типа производит парсинг. Своими силами дальше обойтись не смог.

Отправил заявку в программный отдел, и после обсуждения мне добавили костыль, как и в случае с авторизацией, — Content-type будет передаваться в url-запросе:

function rpc_request_of_resource (current_server_data , rpc_method, rpc_request) {
    var request = get_request_url('POST', current_server_data, '/rpc','');
    //console.log("i'm here request = " + request + '  '+ current_server_data.user);
    request += "&content-type='application/x-msgpack'";
    var req_ = new XMLHttpRequest();
    req_.open('POST', 'http://'+ request, true);   
    // выполнить код, когда придёт ответ
    req_.responseType = 'arraybuffer';
    req_.onreadystatechange = function() {
        if (this.readyState == 2) {
           if (this.status == 401) {
                //console.log('401' + this.getAllResponseHeaders());
                console.log('unauthorized');
            }
        } else if (this.readyState == 4) {
           if (this.status == 200) {
                //if (auth_attempt) hideModal();
                //console.log('200' + this.responseText);
                rpc_update_nessecary_method(rpc_method, this.response);
            } else if (this.status == 404) {
                console.log('404');
            } else if (this.status == 500) {
                //console.log('500');
                rpc_update_nessecary_method(rpc_method, '500');
            }
        }

      };
    
    // Оправка запроса
    //req_.setRequestHeader('Content-type', 'text/plain; charset=utf-8');
    //req_.setRequestHeader('Content-type', 'application/x-msgpack');
    req_.setRequestHeader('Accept', 'application/x-msgpack');
    req_.send(rpc_request);
        
}


Данное изменение будет работать с версии «Линия 7.4.1» и выше. Для всех серверов ниже этой версии работа с ресурсом /rpc будет недоступна.

В конце хочу поблагодарить всех клиентов, которые нам присылали вопросы/пожелания, связанные с реализацией приложений на базе нашего API. Благодаря вам было проведено исследование, в рамках которого выявлены и исправлены некоторые недочеты.

Пример, который описан в данной статье, постепенно вырастет в полноценный HTML-клиент «Линии». Весь код будет читабельным, вы его сможете менять или использовать как основу для построения собственных решений. API же со временем наполнится еще большим количеством возможностей, о которых мы обязательно проинформируем.

Share this post


Link to post
Share on other sites

После внедрения изменения

Клиент HTML + JS

 можно будет зайти по веб-интерфейсу из любого браузера на MicroNVR 192.168.0.156:9786 c Raspberry Pi 3 или c Orange Pi Zero и смотреть реал-тайм с сервера и архив без всяких ранее установленных Adobe Flash ? Или останутся какие-то ограничения?

Share this post


Link to post
Share on other sites

Здравствуйте!
Adobe Flash  не используется.

Обратите внимание на следующие два абзаца:

Цитата

В конце хочу поблагодарить всех клиентов, которые нам присылали вопросы/пожелания, связанные с реализацией приложений на базе нашего API. Благодаря вам было проведено исследование, в рамках которого выявлены и исправлены некоторые недочеты.

Пример, который описан в данной статье, постепенно вырастет в полноценный HTML-клиент «Линии». Весь код будет читабельным, вы его сможете менять или использовать как основу для построения собственных решений. API же со временем наполнится еще большим количеством возможностей, о которых мы обязательно проинформируем.

 

Share this post


Link to post
Share on other sites

В 2000 году нам тоже обещали жить при коммунизме и в отдельной квартире... Adobe Flash никто и не хотел использовать. Дайте больше информации и куда конкретно присылали вопросы\пожелания? Или это опять конфеденциальная информация? И обратите внимание на конкретный вопрос, волнующих пользователей: Можно будет зайти по веб-интерфейсу из любого браузера на MicroNVR 192.168.0.156:9786 c Raspberry Pi 3 или c Orange Pi Zero и смотреть реал-тайм с сервера и архив ? (Надоело для этого держать PC c Windows, Андроид-планшет....)

Share this post


Link to post
Share on other sites

Здравствуйте!

Цитата

Дайте больше информации и куда конкретно присылали вопросы\пожелания?

Всё пожелания собираются и учитываются, в том числе и в рамках данного форума.

Цитата

Можно будет зайти по веб-интерфейсу из любого браузера на MicroNVR 192.168.0.156:9786 c Raspberry Pi 3 или c Orange Pi Zero и смотреть реал-тайм с сервера и архив ?

Это можно сделать и сейчас, существующий веб-клиент разработан на мультимедийной платформе Adobe Flash, это гарантирует запуск на любых браузерах, поддерживающих его. Пример, который описан в данной теме, постепенно вырастет в полноценный HTML-клиент «Линии», не требующий Adobe Flash.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Similar Content

    • By Павел М.
      Возникла задача организовать закрытую трансляцию камер на сайте. Ни один из стандартных вариантов (https://devline.ru/translation/) не подошёл, предложенные на форуме способы тоже не устроили. На скорую руку было набросано решение на nginx (нужен для корректной работы по HTTPS) и Video.js (собственно сам плеер).
      Плюсы:
      Полноценное HTML5 видео со всеми вытекающими. Поддерживается Internet Explorer (по меньшей мере Windows 7/IE11 или новее, на более старых не проверял). Поддержка работы по HTTPS без каких-либо предупреждений. Скрыт реальный адрес сервера с «Линия IP». Не используются сторонние сайты. Разграничение доступа по пользователям. Минусы:
      Весь трафик идёт через ваш сервер.  
      В конфиг nginx надо будет добавить:
      server { listen 443 ssl http2; root /usr/local/www/video; server_name video.example.com; ssl_certificate fullchain.pem; ssl_certificate_key privkey.pem; location / { auth_basic "My video server"; auth_basic_user_file htpasswd; } location /cameras/ { proxy_pass http://devline.server.local:9786/cameras/; } } Вместо devline.server.local указываем имя или IP-адрес вашего сервер «Линия IP». В файле htpasswd необходимо прописать имена пользователей и пароли (по паролям читаем документацию), которые совпадают с соответствующими пользователями на сервере «Линия IP» и которым необходимо дать доступ к трансляции.
      Для работы потокового вещания потребуется библиотека HLS для Video.js. Пример кода:
      <!doctype html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Видеонаблюдение</title> <link href="css/video-js.min.css" rel="stylesheet"> <script src="js/videojs-ie8.min.js"></script> <script src="js/video.js"></script> <script src="js/lang/ru.js"></script> <script src="js/videojs-flash.min.js"></script> <script src="js/videojs-http-streaming.min.js"></script> </head> <body> <video id="video" class="video-js vjs-default-skin" width="640" height="360" poster="//video.example.com/cameras/0/image?resolution=640x360" preload="none" controls data-setup='{"language": "ru"}'> <source src="//video.example.com/cameras/0/streaming/main.m3u8" type="application/x-mpegURL" /> <p class="vjs-no-js">Для просмотра этого видео, пожалуйста, включите JavaScript и убедитесь, что ваш браузер поддерживает HTML5-видео.</p> </video> </body> </html>  
    • By Tolick
      Я так понимаю html5 можно сделать код и вынести на сайт? чтобы apple устройства тоже могли смотреть трансляцию на сайте
×