И снова мы возвращаемся к теме разработки системы для проведения вебинаров. Онлайн-семинары, web-конференции, онлайн-встречи, разнообразные презентации и web-туры — все это, так или иначе, относится к вебинарам.
Итак, ваш клиент проводит вебинар в процессе которого он демонстрирует некую презентацию из слайдов. И появляется необходимость что-то вручную рисовать поверх этих слайдов, создавать какие-то пометки. Вам, как разработчику, нужно дать клиенту инструмент, который позволит это делать. В этом случае как раз можно использовать стриминг с холста (Canvas стриминг).
В этой статье мы разберем что же такое Canvas стриминг и какие есть подводные камни.
Что за зверь?
Посмотрим, что нам на эту тему может сказать Википедия.
Canvas (англ. canvas — «холст», рус. канва́с) — элемент HTML5, предназначенный для создания растрового двухмерного изображения при помощи скриптов, обычно на языке JavaScript. Используется, как правило, для отрисовки графиков для статей.
Из плюсов использования Canvas можно выделить:
- имеет аппаратное ускорение;
- можно манипулировать каждым пикселем.
Минусы:
- чрезмерно нагружает процессор и оперативную память;
- из-за ограничения сборщика мусора нет возможности очистить память;
- необходимо самому обрабатывать события с объектами;
- плохая производительность при высоком разрешении;
- приходится отрисовывать отдельно каждый элемент.
Минусов получилось больше, чем плюсов. Но несмотря на это, у Canvas есть огромное преимущество в использовании — можно стримить поток со слайдами и делать пометки на этих слайдах в процессе вебинара.
Нюансы Canvas стриминга
При захвате потока из элемента Canvas браузер считает этот элемент черным. Поэтому текст, который будет написан на холсте черными буквами, при захвате потока сольется с фоном и в поток будет транслироваться черный экран. То же самое может произойти при отрисовке .png изображения с прозрачным фоном и черным рисунком.
Кроме этого, поток не может состоять из одного кадра. Поэтому, когда вы один раз что-то вывели на Canvas, и это изображение статично там висит, поток не может сформироваться, т.к. был всего один кадр.
Решение — отрисовывать статичный контент циклично.
Например, ниже функция, которая реализует обновление картинки, отрисованной на Сanvas с частотой 30 fps:
1 2 3 4 | function loop(){ canvasContext.drawImage(img, 10, 10); setTimeout(loop, 1000 / 30); // drawing at 30fps } |
Еще один нюанс напрямую связан с политикой безопасности браузеров — «Политикой одного источника».
Изображение, которое нужно нарисовать на холсте, необходимо разместить локально на веб-сервере или на веб-сервере в том же домене.
Если изображение, которое будет отрисовано на холсте загружается из Интернета, браузер выдаст исключение.
Обойти эту проблему можно используя технологию Cross-Origin Resource Sharing (CORS), но это выходит за рамки нашей статьи.
Больше кода!
По традиции, дадим готовые рецепты. Рецепта сегодня два
- Отрисовка текста на Canvas и стриминг под соусом WebRTC
- Отрисовка на Canvas изображения и дальнейший стриминг по WebRTC
HTML часть будет практически идентична. Создаем HTML файл canvas-streaming-min.html
В хэде страницы пропишем обращения к скриптам:
1 2 | <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 страницы:
1 2 3 4 5 | <body onload= "init_page()" > <canvas width= "480" height= "320" id = "canvas" >< /canvas > <div id = "localDisplay" hidden>< /div > <br> < /body > |
Для простоты примера в HTML файле для холста с изображением добавим картинку, которая и будет отрисована на холсте. Файл картинки мы разместили в той же папке на сервере, что и файлы примера:
1 | <img src= "2307589.png" alt= "logo" id = "myimage" > |
Полный код HTML страницы для канваса с текстом:
1 2 3 4 5 6 7 8 9 10 11 | <!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 > |
Для канваса с изображением:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!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 скриптов.
Отрисовка и стриминг холста с текстом
Определяем константы и глобальные переменные:
1 2 3 4 5 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 и публикуем поток с канваса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //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 скрипта для отрисовки холста с текстом и захвата его в поток:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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 и запустите воспроизведение. На скриншоте исходный канвас и вид потока в плеере:
Отрисовка и стриминг холста с изображением
Определяем константы и глобальные переменные:
1 2 3 4 5 | var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS; var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS; var session; var canvas; var localDisplay; |
Получаем картинку с web-страницы и циклично отрисовываем ее на канвасе, чтобы получить 30 кадров в секунду:
1 2 3 4 5 6 7 8 | 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 и публикуем поток с канваса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //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 скрипта для отрисовки холста с изображением и захвата его в поток:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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-страницу. Канвас будет отрисован сразу при инициализации страницы. Картинка (в нижней части страницы) отображается на холсте (в верхней части страницы):
2. Откройте демо-пример «Player» на вашем WCS сервере. Укажите имя потока, в который был захвачен canvas и запустите воспроизведение. На скриншоте исходный канвас и вид потока в плеере:
Заключение
В этой статье мы не ставили целью показать что-то принципиально новое. Но среди обращений в нашу техническую поддержку есть вопросы по стримингу с канваса, и в этой статье мы разобрали минимальные примеры кода, с помощью которых можно использовать этот функционал в своем проекте.
Хорошего стриминга!