Описание
Поддерживаемые платформы и браузеры
| Chrome 66+ | Firefox 59+ | Safari 11.1 |
---|---|---|---|
Windows | + | + | |
Mac OS | + | + | + |
Android | + | + | |
iOS | - | - | + |
Схема работы
- Браузер соединяется с сервером по протоколу Websocket и отправляет команду publish.
- Браузер захватывает изображение с элемента HTML5 Canvas и отправляет WebRTC поток на сервер.
- Второй браузер устанавливает соединение также по Websocket и отправляет команду play.
- Второй браузер получает WebRTC поток и воспроизводит этот поток на странице.
Краткое руководство по тестированию
Захват видеопотока с HTML5 Canvas и подготовка к его трансляции
1. Для теста используем демо-сервер demo.flashphoner.com и веб-приложение Media Devices в браузере Chrome
2. В разделе "Send Video" выберите вариант "Canvas"
3. Нажмите кнопку "Start". Начнется трансляция изображения с HTML5 Canvas (красный квадрат):
4. Убедитесь, что поток отправляется на сервер и система работает нормально, откройте chrome://webrtc-internals
5. Откройте Two Way Streaming в отдельном окне, нажмите Connect и укажите идентификатор потока, затем нажмите Play.
6. Графики воспроизведения chrome://webrtc-internals
Последовательность выполнения операций (Call Flow)
Ниже описана последовательность вызовов при использовании примера Media Devices
1. Установка соединения с сервером.
Flashphoner.createSession(); code
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(); });
2. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ //session connected, start streaming startStreaming(session); ... });
3. Настройка захвата с элемента HTML5 Canvas
getConstraints(); code
if (constraints.video) { if (constraints.customStream) { constraints.customStream = canvas.captureStream(30); constraints.video = false; } else { ... } }
4. Публикация потока.
stream.publish(); code
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function () { ... }).on(STREAM_STATUS.FAILED, function () { ... }); publishStream.publish();
5. Получение от сервера события, подтверждающего успешную публикацию потока.
StreamStatusEvent, статус PUBLISHING code
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.id()); //resize local if resolution is available if (video.videoWidth > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); } enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked")) { muteVideo(); } if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } //remove resize listener in case this video was cached earlier video.removeEventListener('resize', resizeLocalVideo); video.addEventListener('resize', resizeLocalVideo); setStatus(STREAM_STATUS.PUBLISHING); //play preview var constraints = { audio: $("#playAudio").is(':checked'), video: $("#playVideo").is(':checked') }; if (constraints.video) { constraints.video = { width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0, quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 }; } previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints ... }); previewStream.play(); }).on(STREAM_STATUS.UNPUBLISHED, function () { ... }).on(STREAM_STATUS.FAILED, function () { ... }); publishStream.publish();
6. Отправка аудио-видео потока по WebRTC
7. Остановка публикации потока.
stream.stop(); code
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { ... previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { ... }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play(); }).on(STREAM_STATUS.UNPUBLISHED, function () { ... }).on(STREAM_STATUS.FAILED, function () { ... }); publishStream.publish();
8. Получение от сервера события, подтверждающего остановку публикации потока.
StreamStatusEvent, статус UNPUBLISHED code
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { ... }).on(STREAM_STATUS.UNPUBLISHED, function () { setStatus(STREAM_STATUS.UNPUBLISHED); //enable start button onStopped(); }).on(STREAM_STATUS.FAILED, function () { ... }); publishStream.publish();
Разработчику
Возможность захвата видеопотока с элемента HTML5 Canvas доступна в WebSDK WCS, начиная с данной версии JavaScript API. Исходный код примера располагается в каталоге examples/demo/streaming/media_devices_manager/.
Данную возможность можно использовать также для захвата собственного видеопотока, отрисовываемого в браузере, например:
var audioStream = new window.MediaStream(); var videoStream = videoElement.captureStream(30); var audioTrack = videoStream.getAudioTracks()[0]; audioStream.addTrack(audioTrack); publishStream = session.createStream({ name: streamName, display: localVideo, constraints: { customStream: audioStream }, }); publishStream.publish();
Захват с video-элемента работает в Chrome:
constraints.customStream = videoElement.captureStream(30);
Захват с canvas-элемента работает в Chrome 66, Firefox 59 и Mac OS Safari 11.1:
constraints.customStream = canvas.captureStream(30);
Известные проблемы
1) Захват с элемента HTML5 Video не работает в Firefox и Safari.
Решение: использовать данную возможность только в браузере Chrome.
2) В примере Media Devices при захвате с HTML5 Canvas:
- в Firefox локальное видео не отображает то, что отрисовывается;
- в Chrome локальное видео не отображает черный фон.
Решение: учитывать особенности поведения браузеров при разработке.
3) Если веб-приложение расположено внутри iframe элемента, публикация видеопотока может не пройти.
Симптомы: ошибки IceServer error в консоли браузера.
Решение: вынести приложение из iframe на отдельную страницу.
4) Если публикация потока идет с Windows 10 или Windows 8 и в браузере Google Chrome включено аппаратное ускорение, могут быть проблемы с битрейтом.
Симптомы: качество видео плохое, мутное, битрейт в chrome://webrtc-internals показывает меньше 100 kbps.
Решение: отключите аппаратное ускорение в браузере, переключите браузер или сервер на использование кодека VP8.