Практическое применение WebRTC Canvas стриминга

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

На первый взгляд решение лежит на поверхности. Надо только добавить на страницу холст — элемент HTML 5 "Canvas", и на нем рисовать. Добавить-то добавили, а как завернуть то, что нарисовано, в WebRTC?

Итак, давайте разберем, что такое WebRTC стриминг с холста, или Canvas стриминг, и какие в нем могут быть подводные камни.

Что такое Canvas?

Если верить Википедии, то:

Canvas (англ. canvas — «холст», рус. канва́с) — элемент HTML5, предназначенный для создания растрового двухмерного изображения при помощи скриптов, обычно на языке JavaScript. Используется, как правило, для отрисовки графиков для статей.

Из плюсов использования Canvas можно выделить:

  • имеет аппаратное ускорение;

  • можно манипулировать каждым пикселем;

Минусы:

  • чрезмерно нагружает процессор и оперативную память;

  • из-за ограничения сборщика мусора нет возможности очистить память;

  • необходимо самому обрабатывать события с объектами;

  • плохая производительность при высоком разрешении;

  • приходится отрисовывать отдельно каждый элемент.

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

Особенности захвата потока

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

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

Например, ниже функция, которая реализует обновление картинки, отрисованной на Сanvas с частотой 30 fps:

function loop(){
        canvasContext.drawImage(img, 10, 10);
        setTimeout(loop, 1000 / 30); // drawing at 30fps
    }

Еще один нюанс напрямую связан с политикой безопасности браузеров — "Политикой одного источника".

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

Обойти эту проблему можно используя технологию Cross-Origin Resource Sharing (CORS), но это выходит за рамки нашей статьи.

Рецепты по приготовлению Canvas

По традиции, дадим готовые рецепты. Рецепта сегодня два:

  1. Отрисовка текста на Canvas и стриминг под соусом WebRTC;

  2. Отрисовка на Canvas изображения и дальнейший стриминг по WebRTC.

HTML часть будет практически идентична. Создаем HTML файл canvas-streaming-min.html

В хэде страницы пропишем обращения к скриптам:

<script type="text/javascript" src="../../../../flashphoner.js"></script>
<script type="text/javascript" src="canvas-streaming-min.js"></script>

В боди — размещаем HTML 5 элемент Canvas и div элемент для публикации потока. Инициализируем Flashphoner API при загрузке HTML страницы:

<body onload="init_page()">
        <canvas width="480" height="320" id="canvas"></canvas>
        <div id="localDisplay" hidden></div>
        <br>     
</body>

Для простоты примера в HTML файле для холста с изображением добавим картинку, которая и будет отрисована на холсте. Файл картинки мы разместили в той же папке на сервере, что и файлы примера:

<img src="2307589.png" alt="logo" id="myimage" >

Полный код HTML страницы для канваса с текстом:

<!DOCTYPE html>
<html lang="en">
<head>
        <script type="text/javascript" src="../../../../flashphoner.js"></script>
        <script type="text/javascript" src="canvas-streaming-min.js"></script>
</head>
<body onload="init_page()">
        <canvas width="480" height="320" id="canvas"></canvas>
        <div id="localDisplay" hidden></div>    
</body>
</html>

Для канваса с изображением:

<!DOCTYPE html>
<html lang="en">
<head>
        <script type="text/javascript" src="../../../../flashphoner.js"></script>
        <script type="text/javascript" src="canvas-streaming-min.js"></script>
</head>
<body onload="init_page()">
        <canvas width="480" height="320" id="canvas"></canvas>
        <div id="localDisplay" hidden></div>
        <br>
        <img src="2307589.png" alt="logo" id="myimage" >      
</body>
</html>

Теперь переходим к написанию JS скриптов.

Отрисовка текста на Canvas и стриминг под соусом WebRTC

Определяем константы и глобальные переменные:

var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var session;
var canvas;
var localDisplay;

Пишем на Canvas фразу "Hello World". Нарисуем желтый прямоугольник 320px на 176px, текст напишем шрифтом Arial, кегль 30px, цвет - синий. Используем функцию "loop()" которую уже упоминали выше, чтобы перерисовывать канвас с частотой 30 fps:

function createCanvas() {
    var canvasContext = canvas.getContext ("2d");
    canvasContext.font = "30px Arial";
    canvasContext.fillStyle = "yellow";
    canvasContext.fillRect(0,0,320,176);
    canvasContext.strokeStyle = 'black';
    canvasContext.fillStyle = "blue";
    (function loop(){
        canvasContext.fillText("Hello World!", 10, 50);
        setTimeout(loop, 1000 / 30); // drawing at 30fps
    })();    
}

Подключаемся к WCS серверу через WebSocket и публикуем поток с канваса:

//Connect to WCS server over webSockets
function connect() {
    session = Flashphoner.createSession({
        urlServer: "wss://demo.flashphoner.com:8443" //specify the address of your WCS
    }).on(SESSION_STATUS.ESTABLISHED, function(session) {
        publishStream(session);
    });
}

//Capturing stream of Canvas element
function publishStream(session) {
    publishStream = session.createStream({
        name: "test-canvas",
        display: localDisplay,
        constraints: {
            audio: false,
            video: false,
            customStream: canvas.captureStream(30)
        }
    });
    publishStream.publish();
}

Полный код JS скрипта для отрисовки холста с текстом и захвата его в поток:

var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var session;
var canvas;
var localDisplay;

 //init api
function init_page() {
    Flashphoner.init({});
    localDisplay = document.getElementById("localDisplay");
    canvas = document.getElementById("canvas");
    createCanvas();
    connect();    
}

//create Canvas
function createCanvas() {
    var canvasContext = canvas.getContext ("2d");
    canvasContext.font = "30px Arial";
    canvasContext.fillStyle = "yellow";
    canvasContext.fillRect(0,0,320,176);
    canvasContext.strokeStyle = 'black';
    canvasContext.fillStyle = "blue";
    (function loop(){
        canvasContext.fillText("Hello World!", 10, 50);
        setTimeout(loop, 1000 / 30); // drawing at 30fps
    })();    
}

//Connect to WCS server over webSockets
function connect() {
    session = Flashphoner.createSession({
        urlServer: "wss://demo.flashphoner.com:8443" //specify the address of your WCS
    }).on(SESSION_STATUS.ESTABLISHED, function(session) {
        publishStream(session);
    });
}

//Capturing stream of Canvas element
function publishStream(session) {
    publishStream = session.createStream({
        name: "test-canvas",
        display: localDisplay,
        constraints: {
            audio: false,
            video: false,
            customStream: canvas.captureStream(30)
        }
    });
    publishStream.publish();
}

Привет мир!

  1. Откройте созданную Web-страницу. В этом примере мы не используем кнопок, поэтому канвас отрисовывается сразу при инициализации страницы:

  2. В другой вкладке браузера откройте демо-пример "Player" на вашем WCS сервере. Укажите имя потока, в который был захвачен canvas и запустите воспроизведение. На скриншоте исходный канвас и вид потока в плеере:

Отрисовка на Canvas изображения и дальнейший стриминг по WebRTC

Определяем константы и глобальные переменные:

var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var session;
var canvas;
var localDisplay;

Получаем картинку с web-страницы и циклично отрисовываем ее на канвасе, чтобы получить 30 кадров в секунду:

function createCanvas() {
    var canvasContext = canvas.getContext ("2d");
    var img = document.getElementById ("myimage");
    (function loop(){
        canvasContext.drawImage(img, 10, 10);
        setTimeout(loop, 1000 / 30); // drawing at 30fps
    })();    
}

Подключаемся к WCS серверу через WebSocket и публикуем поток с канваса:

//Connect to WCS server over webSockets
function connect() {
    session = Flashphoner.createSession({
        urlServer: "wss://demo.flashphoner.com:8443" //specify the address of your WCS
    }).on(SESSION_STATUS.ESTABLISHED, function(session) {
        publishStream(session);
    });
}

//Capturing stream of Canvas element
function publishStream(session) {
    publishStream = session.createStream({
        name: "test-canvas",
        display: localDisplay,
        constraints: {
            audio: false,
            video: false,
            customStream: canvas.captureStream(30)
        }
    });
    publishStream.publish();
}

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

var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var session;
var canvas;
var localDisplay;

 //init api
function init_page() {
    Flashphoner.init({});
    localDisplay = document.getElementById("localDisplay");
    canvas = document.getElementById("canvas");
    createCanvas();
    connect();    
}

//create Canvas
function createCanvas() {
    var canvasContext = canvas.getContext ("2d");
    var img = document.getElementById ("myimage");
    (function loop(){
        canvasContext.drawImage(img, 10, 10);
        setTimeout(loop, 1000 / 30); // drawing at 30fps
    })();    
}

//Connect to WCS server over webSockets
function connect() {
    session = Flashphoner.createSession({
        urlServer: "wss://demo.flashphoner.com:8443" //specify the address of your WCS
    }).on(SESSION_STATUS.ESTABLISHED, function(session) {
        publishStream(session);
    });
}

//Capturing stream of Canvas element
function publishStream(session) {
    publishStream = session.createStream({
        name: "test-canvas",
        display: localDisplay,
        constraints: {
            audio: false,
            video: false,
            customStream: canvas.captureStream(30)
        }
    });
    publishStream.publish();
}

Привет мир! часть вторая

  1. Откройте созданную Web-страницу. Канвас будет отрисован сразу при инициализации страницы. Картинка (в нижней части страницы) отображается на холсте (в верхней части страницы):

  1. Откройте демо-пример "Player" на вашем WCS сервере. Укажите имя потока, в который был захвачен canvas и запустите воспроизведение. На скриншоте исходный канвас и вид потока в плеере:

Заключение

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

Хорошего стриминга!

Ссылки

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

WCS на Amazon EC2 - Быстрое развертывание WCS на базе Amazon

WCS на DigitalOcean - Быстрое развертывание WCS на базе DigitalOcean

WCS в Docker - Запуск WCS как Docker контейнера

Документация по быстрому развертыванию и тестированию WCS сервера

Документация по захвату и трансляции видеопотока с элемента HTML5 Canvas (whiteboard)

Источник: https://habr.com/ru/company/flashphoner/blog/568680/

Интересные статьи

Интересные статьи

В этой статье собраны мои размышления по поводу обустройства состояния View в Android. Так, одна View имеет несколько состояний. Вот так сложилось. View одна, а показывае...
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
Битрикс24 — популярная в малом бизнесе CRM c большими возможностями даже на бесплатном тарифе. Благодаря API Битрикс24 (даже в облачной редакции) можно легко интегрировать с другими системами.
Всем привет! В этом посте я расскажу, какие подходы мы в Поиске Mail.ru используем для сравнения текстов. Для чего это нужно? Как только мы научимся хорошо сравнивать разные тексты друг с дру...
В Челябинске проходят митапы системных администраторов Sysadminka, и на последнем из них я делал доклад о нашем решении для работы приложений на 1С-Битрикс в Kubernetes. Битрикс, Kubernetes, Сep...