Пока новостные каналы и сайты предсказывают вторую волну пандемии, нашу техническую поддержку наводнили желающие разработать свою систему для проведения вебинаров. Почти каждый вебинар требует использования функции демонстрации экрана ПК, реализация которой часто вызывает вопросы у разработчиков. Не меньше вопросов вызывает подбор сервера или виртуального инстанса. И, наверное, самый главный вопрос — как обезопасить информацию от несанкционированного доступа.

Мы решили собрать все ответы воедино, так получилась эта статья.

Выбор сервера и подготовка к внедрению в Production

Для запуска стриминга в эксплуатацию нужно оценить масштабы системы.

  • число пользователей стримеров;
  • число входящих стримов;
  • число пользователей подписчиков;
  • число исходящих стримов;
  • параметры видео стримов;
  • временные периоды;
  • география размещения серверов и подписчиков.

 

И на основе этих данных выбрать подходящую конфигурацию физического сервера или виртуального инстанса.

Примерные требования по характеристикам сервера для типовых задач указаны ниже:

Количество подписчиков CPUs RAM, GB Трафик, TB Пример использования
до 200 4 8 5 Система видеонаблюдения
до 500 8 16 6 Вебинары
до 1000 16 64 9 Видеочат
до 2000 20 96 10 Стриминг HD видео

 

Если в вашем проекте планируется большее число подписчиков, не имеет смысла увеличивать технические характеристики одного сервера т.к. это приведет к появлению единой точки отказа. В этом случае можно использовать технологии CDN из расчета 1 Edge сервер на 2000 подписчиков. Благодаря масштабированию, географическому и логическому разделению (с выделением функций транскодинга и доставки контента) можно гибко определить необходимый уровень производительности для каждого из серверов. Нагрузка на системные ресурсы сервера не должна превышать 80%. В этом случае, все подписчики получат видеопотоки с приемлемым качеством.

Достаточно часто CDN разворачивают и при небольшом числе пользователей, если планируется значительное количество трафика. Арифметика простая: 1 стрим это примерно 1 Mbps трафика, соответственно 1000 стримов — 1000 Mbps. Не всегда есть техническая и финансовая возможность подключить к серверу канал 10Gbps. Большая часть интернет провайдеров не могут предоставить канал более 400 Mbps, поэтому, чтобы транслировать потоки со скоростью 1000 Mbps понадобится три сервера.

Рассмотрим некоторые настройки WCS, от которых напрямую зависит конечное число зрителей и качество трансляции.

Медиапорты

По умолчанию, для WebRTC подключений доступно только 499 портов. Это ограничение задается в файле flashphoner.properties. При необходимости можно увеличить диапазон портов в строках

media_port_from = 31001
media_port_to = 32000

media_ports_Screen_sharing_WebRTC_browser_WCS_Amazon_Docker_HLS_CDN_Origin_Edge

Внимательный читатель, конечно, возразит, что в указанном диапазоне 999 портов. Да, так и есть, но для передачи медиатрафика используются только четные порты. Поэтому по умолчанию доступно всего 499 соединений по WebRTC.

При расширении диапазона медиапортов, проверьте, что диапазон не пересекается с другими портами, используемыми в работе сервера и диапазоном динамических портов Linux (при необходимости можно его изменить)

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

Нагрузка на оперативную память

При стриминге в памяти создается и уничтожается много объектов с данными. Эти объекты хранятся в Java heap, если не рассматривать случай транскодинга, при котором картинки хранятся в физической памяти сервера.

Heap (куча) – основной сегмент памяти, где Java VM хранит все ваши объекты. Когда вы хотите создать новый объект, но места в heap уже нет, JVM проводит garbage collection (сборку мусора), что значит, что JVM ищет в памяти все объекты, которые более не нужны, и избавляется от них. В момент работы garbage collection работа остальных процессов в JVM приостанавливается. Поэтому, для задач стриминга, крайне важно, чтобы эта пауза занимала как можно меньше времени.

Для этого в Java был разработан новый сборщик мусора — «Z Garbage Collector» (ZGC), позволяющий обеспечить низкую задержку при сборке мусора, не останавливая выполнения потоков приложения более чем на 10 миллисекунд, даже при работе с очень большой memory heap.

Рекомендуется выделять под Java memory heap не менее, чем 1/2 физической памяти сервера. Например, если объем оперативной памяти сервера составляет 32 Гб, рекомендуется выделить 16 Гб. Размер Java memory heap указывается в файле wcs-core.properties в строчках

### JVM OPTIONS ###
-Xmx1024M

Значение по умолчанию — 1024 Мб. Что бы выделить под Java memory heap 16 ГБ укажите

-Xmx16g
-Xms16g

java_memory_heap_Screen_sharing_WebRTC_browser_WCS_Amazon_Docker_HLS_CDN_Origin_Edge

Установим Z Garbage Collector в составе OpenJDK 12.

1. Скачиваем последнюю сборку OpenJDK 12 со страницы http://jdk.java.net/12/:

wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz

2. Распаковываемый полученный файл и перемещаем его содержимое в рабочую директорию:

tar xvf openjdk-12.0.2_linux-x64_bin.tar.gz
mv jdk-12.0.2 /usr/java/jdk-12.0.2

3. Создаем символические ссылки на OpenJDK 12:

ln -sfn /usr/java/jdk-12.0.2 /usr/java/default
ln -sfn /usr/java/default/bin/java /usr/bin/java
ln -sfn /usr/java/default/bin/jstack /usr/bin/jstack
ln -sfn /usr/java/default/bin/jcmd /usr/bin/jcmd
ln -sfn /usr/java/default/bin/jmap /usr/bin/jmap

4. Если WCS был установлен ранее, комментируем или удаляем следующие строки в файле wcs-core.properties:

-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=70
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails

и заменяем строку

-Xloggc:/usr/local/FlashphonerWebCallServer/logs/gc-core-

на

-Xlog:gc*:/usr/local/FlashphonerWebCallServer/logs/gc-core-:time

5. Добавляем настройки для ZGC и логирования:

# ZGC
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xms24g -Xmx24g -XX:+UseLargePages -XX:ZPath=/hugepages

# Log
-Xlog:gc*:/usr/local/FlashphonerWebCallServer/logs/gc-core.log
-XX:ErrorFile=/usr/local/FlashphonerWebCallServer/logs/error%p.log

settings_Z_Garbage_Collector_Screen_sharing_WebRTC_browser_WCS_Amazon_Docker_HLS_CDN_Origin_Edge

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

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

Программирование

Подготовка к разработке

Архитектура решения для этой статьи:

1. Фронтенд Web сервер — организует интерфейс со стримером и зрителями.
2. WCS — обрабатывает входящий видеопоток от стримера и отдает его для воспроизведения зрителям.

solution-architecture_Screen_sharing_WebRTC_browser_WCS_Amazon_Docker_HLS_CDN_Origin_Edge

Начнем развертывание с установки WCS. Следуем указаниям инструкции или запускаем виртуальный инстанс на Amazon, Google Cloud или DigitalOcean. WCS так же можно запустить как контейнер в Docker. Выполняем настройку сервера согласно предыдущей главе и рекомендациям инструкции по подготовке к промышленной эксплуатации.

Теперь запускаем web сервер. В этой статье мы используем Nginx на CentOS 7. На Apache все будет конфигурироваться сходным образом. Если у вас уже есть сайт, к которому планируется добавить функционал скриншаринга, тогда готовить web сервер отдельно не нужно. Или можно развернуть web сервер на той же машине, что и WCS.

На web сервере создаем два файла: страницу будущего интерфейса скриншаринга и скрипт, который будет реализовывать работу. У нас это файлы — «screen-sharing-min.html» и «screen-sharing-min.js». Рядом с этими файлами нужно разместить скрипт основного API — «flashphoner.js», который можно скачать здесь. Или укажите путь до этого файла, если работы проводятся на той же машине, где установлен WCS.

Код

Начнем с подготовки HTML страницы со всеми нужными элементами. Подключаем скрипты:

<script type="text/javascript" src="flashphoner.js"></script>
<script type="text/javascript" src="screen-sharing-min.js"></script>

На загрузку тела страницы навешиваем функцию запуска API:

<body onload="init_api()">

И добавляем div-элемент для превью потока скриншаринга и кнопку для запуска:

<div id="screen-sharing" style="width:320px;height:240px;border: solid 1px"></div>
<input type="button" onclick="connect()" value="Share Screen"/>

Кода HTML страницы совсем немного:

<!DOCTYPE html>
<html lang="en">
<head>
<script type="text/javascript" src="flashphoner.js"></script>
<script type="text/javascript" src="screen-sharing-min.js"></script>
</head>
<body onload="init_api()">
<div id="screen-sharing" style="width:320px;height:240px;border: solid 1px"></div>
<input type="button" onclick="connect()" value="Share Screen"/>
</body>
</html>

Теперь переходим к созданию JS скрипта для работы скриншаринга.

Функция «init_api()» которая вызывается при загрузке HTML страницы инициализирует основной API

function init_api() {
Flashphoner.init({});
}

При нажатии на кнопку «Share Screen» будут последовательно выполняться две функции нашего скрипта — функция «connect()», которая будет устанавливать подключение к WCS по WebSocket

function connect() {
session = Flashphoner.createSession({
urlServer: "wss://demo.flashphoner.com"
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
startStreaming(session);
});
}

и функция «startStreaming()», которая формирует и публикует поток скриншаринга на WCS

function startStreaming(session) {
var constraints = {
video: {}
};
constraints.video.type = "screen";
constraints.video.withoutExtension = true;
session.createStream({
name: "mystream",
display: document.getElementById("screensharing"),
constraints: constraints
}).publish();
}

Важное отличие от обычного стриминга с веб камеры – чтобы захватить экран, а не камеру, в constraints нужно явно указать два параметра:

constraints.video.type = "screen";
constraints.video.withoutExtension = true;

Файл скрипта тоже достаточно компактный. Всего 34 строчки с комментариями.

//Status constants
var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;

//Websocket session
var session;

//Init Flashphoner API on page load
function init_api() {
Flashphoner.init({});
}

//Connect to WCS server over websockets
function connect() {
session = Flashphoner.createSession({
urlServer: "wss://demo.flashphoner.com"
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
startStreaming(session);
});
}

//Publishing Share Screen
function startStreaming(session) {
var constraints = {
video: {}
};
constraints.video.type = "screen";
constraints.video.withoutExtension = true;
session.createStream({
name: "mystream",
display: document.getElementById("screensharing"),
constraints: constraints
}).publish();
}

Сохраняем, запускаем и проверяем работу.

Проверка работы скриптов скриншаринга

Нажимаем на кнопку «Share Screen» и сообщаем браузеру, что именно будем шарить: весь экран, приложение или определенную вкладку браузера. Превью потока скриншаринга будет отображаться в div-элементе на HTML странице.

page_after_clicking_Screen_sharing_WebRTC_browser_WCS

В коде скрипта мы жестко задали имя публикуемого потока — «mystream»

session.createStream({
        name: "mystream",

Этот поток в дальнейшем может быть может быть записан, воспроизведен, транскодирован или ретранслирован по любой из технологий, поддерживаемых WCS

player_playing_Screen_sharing_WebRTC_browser_WCS

Итак, все отлично работает. Теперь предлагаю рассмотреть, как можно реализовать простую меру защиты вашего контента — доступ к стриму с использованием Basic Auth.

Защита с помощью Basic Auth

Настраиваем web сервер на использование Basic Auth.

Создаем файл .htpasswd:

sudo yum install apache2-utils
sudo htpasswd -c /etc/nginx/conf.d/.htpasswd admin

Первая команда уставит в системе утилиту для генерации файла .htpasswd. Вторая команда создаст файл по указанному пути и запросит пароль для пользователя «admin». Ключ «-c» нужен только при первичной генерации файла. Для записи в файл .htpasswd других учетных записей он не нужен.

Далее переходим к конфигам Nginx. Мы не выделяли настройки сайта в отдельный конфиг, поэтому делаем настройки в основном конфигурационном файле /etc/nginx/nginx.conf

В секции http добавляем следующие строчки:

auth_basic "Enter password";
auth_basic_user_file /etc/nginx/.htpasswd;

Первая строка – строка приветствия, вторая путь к файлу .htpasswd

На этом этапе можно перезапустить Nginx и посмотреть, заработал ли запрос пароля при открытии web интерфейса.

password_request_Screen_sharing_WebRTC_browser_WCS_Amazon_Docker_HLS_CDN_Origin_Edge

Далее настраиваем web server на работу по протоколу HTTPS. Дописываем следующие строчки в секцию «Server» в файле nginx.conf

listen 443 ssl;
ssl_certificate /etc/pki/tls/yourdomain/yourdomain.crt;
ssl_certificate_key /etc/pki/tls/yourdomain/yourdomain.key;
server_name wcs.yourdomain.com;
server_tokens off;
client_max_body_size 500m;
proxy_read_timeout 10m;

Настраиваем реверсивное проксирование на WebSocket порт WCS

location /wss {
proxy_set_header Host $host;
proxy_pass http://IP:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}

Еще раз перезапустите Nginx.

Теперь немного модифицируем код, который мы написали и протестировали в предыдущей главе, чтобы передавать логин и пароль. В параметре «urlServer» функции «Connect» указываем обращение к серверу в виде «wss://login:password@wcs.yourdomain.com:443/wss»

function connect() {
session = Flashphoner.createSession({
urlServer: "wss://login:password@wcs.yourdomain.com:443/wss"
}).on(SESSION_STATUS.ESTABLISHED, function(session) {
startStreaming(session);
});
}

Теперь при обращении к HTML странице «screen-sharing-min.html» браузер будет запрашивать логин и пароль. И дальнейший доступ по WebSocket протоколу к WCS серверу также будет использовать логин и пароль, который мы задали в коде.

Таким образом мы развернули и протестировали пример скриншаринга и прикрутили к нему минимальную авторизацию по технологии Basic Auth. Для чего в 2020 году не нужно создавать и регистрировать свое расширение для Google Chrome и утруждать конечных пользователей установкой каких-то дополнительных компонентов — все работает «из коробки».

Удачного скриншаринга!

Ссылки

Наш демо сервер

Документация

Файл настроек flashphoner.properties

Файл настроек wcs-core.properties

Управление памятью в Java

Рекомендации по тонкой настройке сервера

Подготовка к промышленной эксплуатации

Screen Sharing в браузере по WebRTC

Screen Sharing Web SDK