Table of Contents |
---|
Описание
Поддерживаемые платформы и браузеры
| 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
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();
}); |
2. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){
//session connected, start streaming
startStreaming(session);
...
}); |
3. Настройка захвата с элемента HTML5 Canvas
getConstraints(); code
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.video) {
if (constraints.customStream) {
constraints.customStream = canvas.captureStream(30);
constraints.video = false;
} else {
...
}
} |
4. Публикация потока.
stream.publish(); code
Code Block | ||||
---|---|---|---|---|
| ||||
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
Code Block | ||||
---|---|---|---|---|
| ||||
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
Code Block | ||||
---|---|---|---|---|
| ||||
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
Code Block | ||||
---|---|---|---|---|
| ||||
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/.
Данную возможность можно использовать также для захвата собственного видеопотока, отрисовываемого в браузере, например:
Code Block | ||||
---|---|---|---|---|
| ||||
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:
Code Block | ||||
---|---|---|---|---|
| ||||
constraints.customStream = videoElement.captureStream(30); |
Захват с canvas-элемента работает в Chrome 66, Firefox 59 и Mac OS Safari 11.1:
Code Block | ||||
---|---|---|---|---|
| ||||
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.