Comet Video API - Вступление

Ссылка на онлайн демо для нетерпеливых


Можно выделить два подхода к организации видео чатов на базе webRTC. Первый – это передача сигнала от каждого участника конференции каждому.

В такой конфигурации нет нужды в центральном сервере и её просто реализовать, но в тех случаях, когда участники находятся за NAT, надо всё равно трафик проксировать через turn сервер. ( Ответ на toster о преодолении NAT )

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

В такой ситуации можно перейти к другому подходу. Когда все абоненты посылают свой сигнал на центральный сервер, а тот в свою очередь посылает каждому абоненту один результирующий видео поток. (Именно так и реализовано Video API в CppComet)

Минусом такой схемы будет необходимость наличия центрального сервера. И ощутимая на него нагрузка.

Но за то это открывает ряд возможностей.

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

Video API

В комет сервер встроена интеграция с FreeSwitch упрощающая создание видио конференций и видео чатов. (С возможностью использовать всё это в режиме кластера)

Есть возможность совершать:

  • Видео/Аудио звонки (По WebRTC)
  • Видео/Аудио конференции на несколько участников (По WebRTC)
  • Видео трансляции для стриминга один ко многим (По WebRTC отправка видео от того, кто видео поток публикует и доставка зрителям в форматах hls и mpeg-dash с задержкой от 5 секунд (теоретически минимально возможная задержка для hls потока) до 20 - 40 секунд в зависимости от конфигурации ретрансляторов и настроек качества)
Если обнаружатся проблемы в настройке или в работе или пробелы в документации, не стесняйтесь задавать вопросы и писать баг репорты.

Демо видео конференций

Ещё несколько более длинных gif анимаций

Онлайн демо

See the Pen CppComet video chat example by Trapenok Victor (@Levhav) on CodePen.

Концепция API видео чатов

Comet Video API

  • Для работы видео чатов комет сервер использует возможности FreeSwitch просто взаимодействуя с ним по АПИ.
  • Комет сервер в состоянии распределять видео и аудио звонки по множеству серверов с FreeSwitch.
  • Работа с FreeSwitch полностью скрывается за апи комет сервера.

CometQL API

В CometQL добавлена таблица conference, которая содержит информацию о активных конференциях.

Чтобы создать новую конференцию и добавить в неё участника, надо выполнить запрос

INSERT INTO conference (name, user_id, caller_id, message, profile)VALUES('1', 2, 2, "", "video");

Тут параметры:

  • name - имя конференции (состоит только из цифр)
  • user_id - идентификатор добавляемого в конференцию пользователя
  • caller_id - идентификатор того пользователя, который иницирует звонок (не обязательный параметр)
  • message - произвольное сообщение
  • profile - режим звонка возможны варианты «audio», «video» (В open source версии и по запросу в поддержку в saas версии можно создавать свои режимы видео конференций, чтобы добавлять водяные знаки, показывать не просто говорящего в данный момет участника конференции, а всех сразу или настраивать правила показа участников конференции на общем холсте. Все возможности перечислены в документации на mod_conference)

Чтобы добавить ещё одного участника, надо выполнить такой же запрос только передать новый user_id и старый name. Соответственно создание конференции из 5 участников потребует 5 запросов вставки в таблицу conference

Пример можно смотреть в файле call.php

Выборка данных из таблицы conference

SELECT * FROM `conference` WHERE name = "101";

Удаление данных из таблицы conference

DELETE FROM `conference` WHERE name = "101" AND user_id = 29130;
В запросах оператор `and` работает в ограниченном режиме. Он работает только для таблиц `conference` и `conference_members` только для запроса `delete` и только с полями `name` и `user_id`

Полноценная поддержка оператора `and` в синтаксисе запросов будет реализована позже, так как и без него есть куда более важные задачи и работе отсутствие оператора end не мешает.

Запрос выборки из таблицы `conference` только покажет, кому были отправлены приглашения в конференцию.

SELECT * FROM `conference` WHERE name = "101";

Вовсе не факт, что все эти люди до сих пор в этой конференции находятся.

Чтобы узнать, кто разговаривает в данный момент надо сделать запрос к таблице `conference_members`

SELECT * FROM `conference_members`

В данный момент таблица `conference_members` доступна только для чтения и содержит такие поля

  • name - имя конференции
  • user_id - идентификатор пользователя
  • join_time - количество секунд прошедшее с момента подключения пользователя
  • last_talking - количество секунд прошедшее с того момента, когда он что-то говорил в конференции.

JavaScript API

В js надо подключить файл cometVideoApi.js и библиотеку JsSIP v2.0.6

после чего для обработки событий входящего звонка, подключения и отключения надо выполнить следующий код

// Иницируем активацию cometVideoApi
cometVideoApi.start({
 
  // Колбек вызываемый перед началом подключения для звонка
  // Предполагается, что в нём будут заданы настройки для близжайшего звонка
  // Такие как и параметры audio_remote, video_local, video_remote и возможно ещё какие–то.
  // А потом будет вызвана функция cometVideoApi.acceptCall(event)
  // А если не будет вызвана то значит мы не взяли трубку.
  onCall:function(callEvent)
  {
    $(".status").html("Входящий звонок, нажмите ответить.");
    if(!confirm("Ответить на вызов?"))
    {
      // Решили не отвечать на звонок
      $(".status").html("Решили не отвечать на звонок.");
      $(".StartCallBtn").show();
      return
    }
 
    // Берём трубку, если хотим
    cometVideoApi.acceptCall({
      // Тип звонка 'audio' | 'video'
      type:'video',
 
      // Указываем целевой элемент для видео потока от собеседника
      video_remote: $("#video_remote")[0],
 
      // Указываем целевой элемент для аудио потока от собеседника
      audio_remote: $("#audio_remote")[0],
 
      // Указываем целевой элемент для видео потока от меня (моё собственное изображение из камеры)
      video_local: $("#video_local")[0],
    })
    $(".status").html("Ожидаем ответа от других участников конференции");
  },
 
  /**
   * Колбек завершения звонка
   * @param {object} event
   * {
   * action:"",       // Имя события
   * status:"",       // Статус причины завершения звонка
   * callInfo:{},     // Информация о звонке
   * time:1000        // Время длительность звонка
   * type:"audio"          // Тип вызова
   * }
   */
  onCallEnd:function(event)
  {
    $(".root").removeClass("incall")
    $(".StartCallBtn").show();
    $(".status").html("Вызов завершён");
    log("onCallEnd", event)
  },
 
  /**
   * Колбек поднятия трубки собеседником
   * @param {object} event
   * {
   * action:"accept",       // Имя события
   * callInfo:{},           // Информация о звонке
   * type:"audio"           // Тип вызова который выбран собеседником
   * }
   *
   * Колбек вызывается только один раз для первого ответившего собеседника
   */
  onCallAccept: function(event)
  { 
    $(".status").html("Получен ответ от другого участника конференции.");
    log("onAccept", event)
  },
 
  /**
   * Колбек, когда я и мой собеседник подключились к серверу
   * @param {object} event
   * {
   * action:"start",       // Имя события
   * callInfo:{},          // Информация о звонке
   * type:"audio"          // Тип вызова
   * }
   */
  onCallStart: function(event)
  {
    $(".status").html("Подключение к медиасерверу выполнено. Разговор начался.");
    $(".root").addClass("incall")
    log("onCallStart", event)
  },
  onOut: function(event)
  {
    // Выход участника из конференции
    $(".status").html("Один из собеседников покинул конференцию");
    log("onOut", event)
  },
  onIn: function(event)
  {
    // Вход участника в конференцию
    $(".status").html("К конференции присоединился ещё один человек.");
    log("onIn", event)
  },
})

Настройка бэкенда для видео звонков

Дальнейшие инструкции по настройке комет сервера и сопутствующего ПО нужны только для пользователей Open Source версии, в SaaS версии уже всё настроено

Настройка comet.ini

Секция [freeswitch] в comet.ini поднимает на указанном порту и ip адресе http интерфейс который отвечает за механизм единой с freeswitch авторизации пользователей.

[freeswitch]
ip = 0.0.0.0
thread_num = 3
statistics = 10
port = 84 
Важно что этот интерфейс не должен быть доступен из внешнй сети. На данный момент не реализована поддержка авторизации (планируется в следующих релизах) и соответственно во избежание утечки данных авторизации надо разрешить доступ к этому порту только доверенным ip адресам с вашими серверами FreeSwitch. Ограничить доступ можно средствами фаервола.

Секция [sip] содержит список адресов и портов серверов freeswitch которые готовы обрабатывать видео и аудио звонки.

[sip]
port = 7443
host = ecort-n1.comet.su ;  
pipesalt = pipeSecretSalt

Настройка FreeSwitch

Есть docker образ с нужными настройками для работы FreeSwitch с комет сервером.

Скачать образ

docker pull cppcomet/freeswitch-video
 

Запустить

docker run -v /root/FreeSwitch-in-docker/conf:/usr/local/freeswitch/conf -v /root/FreeSwitch-in-docker/certs:/usr/local/freeswitch/certs --net host -it cppcomet/freeswitch-video
 

Нужно указать доступ к ssl сертификату и папке с файлами конфигурации.

Сборка FreeSwitch из исходников с нужными модулями

Нужно собрать FreeSwitch по инструкции FreeSwitch Building from source

Затем включить в сборку и в автозагрузку следующие модули

  • mod_rtmp - позволит вести трансляции видео звонков и конференций
  • mod_xml_curl - позволит FreeSwitch серверу использовать те же данные авторизации что имеются в комет сервере в таблице users_auth
  • mod_av
  • mod_sndfile
  • mod_shell_stream
  • mod_ilbc
  • mod_h26x
  • mod_siren
  • mod_isac

Итоговый скрипт для сборки с нужными модулями

wget -O - https://files.freeswitch.org/repo/deb/debian/freeswitch_archive_g0.pub | apt-key add -
 
echo "deb http://files.freeswitch.org/repo/deb/freeswitch-1.6/ jessie main" > /etc/apt/sources.list.d/freeswitch.list
apt-get update
apt-get install -y --force-yes freeswitch-video-deps-most
 
# because we're in a branch that will go through many rebases it's
# better to set this one, or you'll get CONFLICTS when pulling (update)
git config --global pull.rebase true
 
# then let's get the source. Use the -b flag to get a specific branch
cd /usr/src/
git clone https://freeswitch.org/stash/scm/fs/freeswitch.git -bv1.6 freeswitch
cd freeswitch
./bootstrap.sh -j
 
sed -e "s/#codecs\/mod_isac/codecs\/mod_isac/g" /usr/src/freeswitch/modules.conf | sed -e "s/#applications\/mod_av/applications\/mod_av/g" | sed -e "s/#endpoints\/mod_rtmp/endpoints\/mod_rtmp/g"  | sed -e "s/#xml_int\/mod_xml_curl/xml_int\/mod_xml_curl/g"  | sed -e "s/#applications\/mod_av/applications\/mod_av/g"| sed -e "s/#formats\/mod_sndfile/formats\/mod_sndfile/g"  | sed -e "s/#formats\/mod_shell_stream/formats\/mod_shell_stream/g" | sed -e "s/#codecs\/mod_ilbc/codecs\/mod_ilbc/g"| sed -e "s/#codecs\/mod_h26x/codecs\/mod_h26x/g"  | sed -e "s/#codecs\/mod_siren/codecs\/mod_siren/g"| sed -e "s/#codecs\/mod_isac/codecs\/mod_isac/g"   > /usr/src/freeswitch/modules.tmp
 
cat /usr/src/freeswitch/modules.tmp > /usr/src/freeswitch/modules.conf
rm -rf /usr/src/freeswitch/modules.tmp
 
./configure
make
make install
 
# create user 'freeswitch'
# add it to group 'freeswitch'
# change owner and group of the freeswitch installation
cd /usr/local
groupadd freeswitch
adduser --quiet --system --home /usr/local/freeswitch --gecos "FreeSWITCH open source softswitch" --ingroup freeswitch freeswitch --disabled-password
chown -R freeswitch:freeswitch /usr/local/freeswitch/
chmod -R ug=rwX,o= /usr/local/freeswitch/
chmod -R u=rwx,g=rx /usr/local/freeswitch/bin/*
 
sed -e "s/EnvironmentFile=-\/etc\/default\/freeswitch/EnvironmentFile=-\/etc\/freeswitch/g" /usr/src/freeswitch/debian/freeswitch-systemd.freeswitch.service | sed -e "s/PIDFile=\/run\/freeswitch\/freeswitch.pid/PIDFile=\/usr\/local\/freeswitch\/run\/freeswitch.pid/g" > /etc/systemd/system/freeswitch.service
 
 
cp /usr/local/freeswitch/bin/freeswitch  /usr/bin/freeswitch
mkdir /run/freeswitch
 
systemctl daemon-reload
systemctl enable freeswitch

Потом надо выполнить инструкции Debian+Post-Install+Tasks

Включение модулей в автозагрузку

Для включения в автозагрузку модулей надо убедится, что в файле /usr/local/freeswitch/conf/autoload_configs/modules.conf.xml есть следующие строки

<load module="mod_av"/>
<load module="mod_sndfile"/>
<load module="mod_shell_stream"/>
<load module="mod_rtmp"/>
<load module="mod_xml_curl"/>
<load module="mod_ilbc"/>
<load module="mod_h26x"/>
<load module="mod_siren"/>
<load module="mod_isac"/>

Настройка xml_curl.conf на freeswitch

В файле

/usr/local/freeswitch/conf/autoload_configs/xml_curl.conf.xml

надо вписать порт и адрес для подключения к комет серверу с реквизитами, которые мы задали в секции [freeswitch]

<configuration name="xml_curl.conf" description="cURL XML Gateway">
  <bindings>    
    <binding name="directory"> 
      <param name="gateway-url" value="http://app.comet-server.ru:84/directory" bindings="directory"/>  
      <param name="method" value="GET" /> 
    </binding>  
  </bindings> 
</configuration>

Эта настройка отвечает за то, чтобы пользователи, которые авторизованы на комет сервере могли по тем же данным авторизации подключится к freeswitch для совершения звонка.

Подробности по работе модуля тут mod_xml_curl

Настройка conference.conf.xml на freeswitch

Для настройки разных профилей работы видео конференций надо отредактировать файл /usr/local/freeswitch/conf/autoload_configs/conference.conf.xml

Документация на mod_conference настройки по умолчанию годятся.

Если хотите транслировать конференцию зрителям по rtmp то в настройки профиля конференции можно вписать команду:

<param name="auto-record" value="rtmp://comet.su/cam1/${conference_name}"/>

С указанием вашего nginx c nginx-rtmp-module

Настройка dialplan/default.xml на freeswitch

Файл /usr/local/freeswitch/conf/dialplan/default.xml содержит планы набора номеров. Он используется для определения что надо делать при звонке на конкретные номера.

В секцию

<context name="default">

надо добавить extension для набора видео конференции

    <extension name="comet_conferences">
      <condition field="destination_number" expression="^([0-9]{1,9}\*[0-9A-z\-_]+)\*([0-9A-z\-_]+)$">
	    <!--
                    Если у вас только аудио звонок то можно добавить эту строку
                    <action application="set" data="absolute_codec_string=PCMU,GSM"/>
             -->
	    <action application="answer"/>
	    <action application="conference" data="$1-${domain_name}@$2"/>
      </condition>
    </extension>

Настройка vars.xml на freeswitch

Полезно в vars.xml задать стойкий пароль в строке, он будет использоваться комет сервером для api вызовов.

 <X-PRE-PROCESS cmd="set" data="default_password=1234"/>

И надо установить переменную domain=FSdefaultDomain

  <X-PRE-PROCESS cmd="set" data="domain=FSdefaultDomain"/>

Настройка event_socket.conf на freeswitch

В event_socket.conf настраиваем на каком порту и с каким паролем freeswitch будет позволять подключиться для выполнения api команд.

 <configuration name="event_socket.conf" description="Socket Client">
  <settings>
    <param name="nat-map" value="false"/>
    <param name="listen-ip" value="0.0.0.0"/>
    <param name="listen-port" value="8021"/>
    <param name="password" value="MyPassword"/> 
    <param name="apply-inbound-acl" value="0.0.0.0/32"/> 
  </settings> 
</configuration>

Настройка https на freeswitch

Обычно после установки freeswitch в папке

/usr/local/freeswitch/certs/wss.pem

лежит само-подписанный сертификат. Для работы видео и аудио чатов их надо заменить на валидные так как иначе с https сайта не получится подключится к freeswitch по websockets для обмена sip командами с freeswitch

Для получения сертификатов удобно использовать Let's Encrypt. Я использую эту инструкцию

А в качестве post-hook для обновления сертификата такую команду:

cat /etc/letsencrypt/live/fs-n3.elevenow.com/fullchain.pem > /usr/local/freeswitch/certs/wss.pem && cat /etc/letsencrypt/live/fs-n3.elevenow.com/privkey.pem >> /usr/local/freeswitch/certs/wss.pem && cat /etc/letsencrypt/live/fs-n3.elevenow.com/fullchain.pem > /usr/local/freeswitch/certs/dtls-srtp.pem && cat /etc/letsencrypt/live/fs-n3.elevenow.com/privkey.pem >> /usr/local/freeswitch/certs/dtls-srtp.pem  && systemctl stop freeswitch && rm -rf /var/lib/freeswitch/db && systemctl start freeswitch

Настройка трансляций в nginx

Для трансляций надо собрать nginx с модулем nginx-rtmp-module вот неплохая инструкция

Вот пример конфигурации nginx

  worker_processes  1;

    rtmp {

        server {
                    live on;
            listen 1935;
            chunk_size 512;
            buflen 1s;
            idle_streams off;

            application cam1 {
                live on;
                
                    record off;
                    
                            hls on;
                            hls_path /tmp/hls;
                            hls_fragment 2s;
                            hls_playlist_length 20s; 
                            hls_type live;

                            #hls_continuous on;

                            hls_fragment_naming sequential;

                            hls_nested on; 
                            
                            dash on;
                dash_path /tmp/dash;

                dash_fragment 2s;
                dash_playlist_length 10s;
                dash_nested on;
            }
        }
    }

    server {
            listen 80 default_server;
            listen [::]:80 default_server;

            root /var/www/html;

            index index.html index.htm index.nginx-debian.html;

            server_name _;

        include acme;

        location /dash {
            root /tmp;

            add_header Access-Control-Allow-Origin *;
            add_header Cache-Control no-cache;
        }

       location / {
            add_header Cache-Control no-cache;
            add_header Access-Control-Allow-Origin *;
            root /tmp;
        } 


    }


    server {
            listen 0.0.0.0:443;  
            server_name comet-server.com;

            ssl on;
            ssl_certificate /etc/letsencrypt/live/stream-n1.example.com/fullchain.pem;
            ssl_certificate_key /etc/letsencrypt/live/stream-n1.example.com/privkey.pem;

            ssl_session_timeout 70m;

            keepalive_disable none;
            lingering_close always;
            send_timeout 3600s;

            client_max_body_size 100m;

        include acme;

        location /dash {
            root /tmp;

                    add_header Access-Control-Allow-Origin *;
            add_header Cache-Control no-cache;
        }


            location / {
            add_header Cache-Control no-cache;
                    add_header Access-Control-Allow-Origin *;
            root /tmp;
        } 

    }

Полезными могут быть статьи

Обсуждение

Ваш комментарий. Вики-синтаксис разрешён:
T R N R W