...
Chrome 66+ | Firefox 59+ | Safari 11.114+ | MS Cromium Edge | |
---|---|---|---|---|
Windows | + | + | + | |
Mac OS | + | + | + | |
Android | ++ | - | ||
iOS- | + (iOS 14.6+) | - | + |
Схема работы
- Браузер соединяется с сервером по протоколу Websocket и отправляет команду publish.
- Браузер захватывает изображение с элемента HTML5 Canvas и отправляет WebRTC поток на сервер.
- Второй браузер устанавливает соединение также по Websocket и отправляет команду play.
- Второй браузер получает WebRTC поток и воспроизводит этот поток на странице.
...
1. Для теста используем:
- демо-сервер demo.flashphoner.com
- веб-приложение Canvas Streaming в браузере Chrome
2. Нажмите кнопку "Start". Начнется трансляция изображения с HTML5 Canvas, на котором проигрывается тестовый ролик:
3. Убедитесь, что поток отправляется на сервер, откройте chrome://webrtc-internals
4. Графики воспроизведения chrome://webrtc-internals
Последовательность выполнения операций (Call Flow)
Ниже описана последовательность вызовов при использовании примера Canvas Streaming
1. Установка соединения с сервером.
Flashphoner.createSession(); code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ //session connected, start streaming startStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ setStatus(SESSION_STATUS.DISCONNECTED); onStopped(); }).on(SESSION_STATUS.FAILED, function(){ setStatus(SESSION_STATUS.FAILED); onStopped(); }); |
...
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ //session connected, start streaming startStreaming(session); ... }); |
2.1. Настройка захвата с элемента HTML5 Canvas
getConstraints(); code
Code Block | ||||
---|---|---|---|---|
| ||||
function getConstraints() { var constraints; var stream = createCanvasStream(); constraints = { audio: false, video: false, customStream: stream }; return constraints; } |
...
настройка захвата видео с элемента Canvas code
Code Block | ||||
---|---|---|---|---|
| ||||
function createCanvasStream() { var canvasContext = canvas.getContext("2d"); var canvasStream = canvas.captureStream(30); mockVideoElement = document.createElement("video"); mockVideoElement.setAttribute("playsinline", ""); mockVideoElement.setAttribute("webkit-playsinline", ""); mockVideoElement.src = '../../dependencies/media/test_movie.mp4'; mockVideoElement.loop = true; mockVideoElement.muted = true; ... return canvasStream; } |
отрисовка видео на элементе Canvas с частотой 30 fps использованием requestAnimationFrame() или setTimeout() code
Code Block | ||||
---|---|---|---|---|
| ||||
function createCanvasStream() { ... var useRequestAnimationFrame = $("#usedAnimFrame").is(':checked'); mockVideoElement.addEventListener("play", function () { var $this = this; (function loop() { if (!$this.paused && !$this.ended) { canvasContext.drawImage($this, 0, 0); if (useRequestAnimationFrame) { requestAnimationFrame(loop); } else { setTimeout(loop, 1000 / 30); // drawing at 30fps } } })(); }, 0); ... return canvasStream; } |
воспроизведение тестового ролика на Canvas code
Code Block | ||||
---|---|---|---|---|
| ||||
function createCanvasStream() { mockVideoElement ... mockVideoElement.play(); ... return canvasStream; } |
настройка публикации аудио с Canvas code
Code Block | ||||
---|---|---|---|---|
| ||||
function createCanvasStream() { ... if ($("#sendAudio").is(':checked')) { mockVideoElement.muted = false; try { var audioContext = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.warn("Failed to create audio context"); } var source = audioContext.createMediaElementSource(mockVideoElement); var destination = audioContext.createMediaStreamDestination(); source.connect(destination); canvasStream.addTrack(destination.stream.getAudioTracks()[0]); } return canvasStream; } |
3. Публикация потока.
stream.publish(); code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints }).on(STREAM_STATUS.PUBLISHING, function (stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function () { ... }).on(STREAM_STATUS.FAILED, function () { ... }).publish(); |
...
StreamStatusEvent, статус PUBLISHING code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function (stream) { setStatus("#publishStatus", STREAM_STATUS.PUBLISHING); playStream(); onPublishing(stream); }).on(STREAM_STATUS.UNPUBLISHED, function () { ... }).on(STREAM_STATUS.FAILED, function () { ... }).publish(); |
...
6. Остановка публикации потока.
stream.stop(); code
Code Block | ||||
---|---|---|---|---|
| ||||
function stopStreaming() { ... if (publishStream != null && publishStream.published()) { publishStream.stop(); } stopCanvasStream(); } |
stopCanvasStream() code
Code Block | ||||
---|---|---|---|---|
| ||||
function stopCanvasStream() { if(mockVideoElement) { mockVideoElement.pause(); mockVideoElement.removeEventListener('play', null); mockVideoElement = null; } } |
...
StreamStatusEvent, статус UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function (stream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function () { setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED); disconnect(); }).on(STREAM_STATUS.FAILED, function () { ... }).publish(); |
...
Code Block | ||||
---|---|---|---|---|
| ||||
constraints.customStream = canvas.captureStream(30); |
Отметим, что при использовании customStream
, параметр cacheLocalResources
игнорируется, кэширование локальных ресурсов не производится.
Использование requestAnimationFrame API
В сборке WebSDK 2.0.200 добавлен пример использование requestAnimationFrame API для отрисовки изображения на канвасе:
Code Block | ||||
---|---|---|---|---|
| ||||
function createCanvasStream() { ... var useRequestAnimationFrame = $("#usedAnimFrame").is(':checked'); mockVideoElement.addEventListener("play", function () { var $this = this; (function loop() { if (!$this.paused && !$this.ended) { canvasContext.drawImage($this, 0, 0); if (useRequestAnimationFrame) { requestAnimationFrame(loop); } else { setTimeout(loop, 1000 / 30); // drawing at 30fps } } })(); }, 0); ... return canvasStream; } |
Данный способ является более современным по отношению к отрисовке по таймеру, однако требует, чтобы вкладка браузера, в которой происходит захват видео потока с канваса, всегда была активной. Если переключиться на другую вкладку или свернуть окно браузера в фон, браузер останавливает работу requestAnimationFrame API. Отрисовка по таймеру в таких случаях не останавливается, за исключением мобильных браузеров.
Известные проблемы
1) . Захват с элемента HTML5 Video не работает в Firefox на определенных платформах и в старых версиях Safari.
Решение: использовать данную возможность только в браузере Chromeтех версиях/платформах, где она поддерживается.
2) . В примере Media Devices при захвате с HTML5 Canvas:
...
Решение: учитывать особенности поведения браузеров при разработке.
3) . Если веб-приложение расположено внутри iframe элемента, публикация видеопотока может не пройти.
Симптомы: ошибки IceServer error в консоли браузера.
Решение: вынести приложение из iframe на отдельную страницу.
4) . Если публикация потока идет с Windows 10 или Windows 8 и в браузере Google Chrome включено аппаратное ускорение, могут быть проблемы с битрейтом.
Симптомы: качество видео плохое, мутное, битрейт в chrome://webrtc-internals показывает меньше 100 kbps.
Решение: отключите отключить аппаратное ускорение в браузере , переключите установив флаг chrome://flags/#disable-accelerated-video-encode в Disable
, переключить браузер или сервер на использование кодека VP8 для публикации.
5. В браузерах на базе Chromium (Chrome, MS Edge) видео не публикуется с канваса при включенном аппаратном ускорении кодирования видео
codeСимптомы; в опубликованном с канваса потоке нет видео, но есть аудио, публикация завершается с сообщением Failed by Video RTP activity
Решение: отключить аппаратное ускорение кодирования видео в браузере, установив флаг chrome://flags/#disable-accelerated-video-encode в Disable
, переключить браузер или сервер на использование кодека VP8 для публикации, или использовать другой браузер
6. При переключении на другую вкладку или сворачивании браузера в фон трансляция с канваса может останавливаться
Симптомы: фриз при проигрывании потока, транслируемого с канваса, играющий клиент перестает получать видео и аудио пакеты
Решение: удерживать вкладку, с которой транслируется канвас, на переднем плане.
7. Разрешение публикуемого потока не превышает размеры канваса
Симптомы: размер картинки потока при публикации равен или меньше размера (ширина x высота) HTML5 Canvas элемента на странице
Решение:
a) для публикации картинки в нужном размере использовать HTML5 Canvas соответствующего размера на странице
b) транскодировать картинку к нужному разрешению на сервере