Table of Contents |
---|
Описание
Поддерживаемые платформы и браузеры
...
Chrome
...
Firefox
...
Safari 11
...
Windows
...
+
...
+
...
Mac OS
...
+
...
+
...
+
...
Android
...
+
...
+
...
iOS
...
-
...
-
...
+
Схема работы
- Браузер соединяется с сервером по протоколу Websocket и отправляет команду publish.
- Браузер захватывает микрофон и камеру и отправляет WebRTC поток на сервер.
- Второй браузер устанавливает соединение также по Websocket и отправляет команду play.
- Второй браузер получает WebRTC поток и воспроизводит этот поток на странице.
Краткое руководство по тестированию
Захват видеопотока с веб-камеры и подготовка к его трансляции
1. Для теста используем демо-сервер demo.flashphoner.com и веб-приложение Two Way Streaming
2. Установите соединение с сервером по кнопке Connect
3. Нажмите Publish. Браузер захватывает камеру и отправляет поток на сервер.
4. Убедитесь, что поток отправляется на сервер и система работает нормально, откройте chrome://webrtc-internals
5. Откройте Two Way Streaming в отдельном окне, нажмите Connect и укажите идентификатор потока, затем нажмите Play.
6. Графики воспроизведения chrome://webrtc-internals
Последовательность выполнения операций (Call Flow)
Ниже описана последовательность вызовов при использовании примера Two Way Streaming
1. Установка соединения с сервером.
Flashphoner.createSession(); code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
setStatus("#connectStatus", session.status());
onConnected(session);
}).on(SESSION_STATUS.DISCONNECTED, function () {
setStatus("#connectStatus", SESSION_STATUS.DISCONNECTED);
onDisconnected();
}).on(SESSION_STATUS.FAILED, function () {
setStatus("#connectStatus", SESSION_STATUS.FAILED);
onDisconnected();
}); |
2. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
setStatus("#connectStatus", session.status());
onConnected(session);
}).on(SESSION_STATUS.DISCONNECTED, function () {
setStatus("#connectStatus", SESSION_STATUS.DISCONNECTED);
onDisconnected();
}).on(SESSION_STATUS.FAILED, function () {
setStatus("#connectStatus", SESSION_STATUS.FAILED);
onDisconnected();
}); |
3. Публикация потока.
stream.publish(); code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
receiveVideo: false,
receiveAudio: false
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
onPublishing(stream);
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
onUnpublished();
}).on(STREAM_STATUS.FAILED, function () {
setStatus("#publishStatus", STREAM_STATUS.FAILED);
onUnpublished();
}).publish(); |
4. Получение от сервера события, подтверждающего успешную публикацию потока.
StreamStatusEvent, статус PUBLISHING code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
receiveVideo: false,
receiveAudio: false
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
onPublishing(stream);
...
}).publish(); |
5. Отправка аудио-видео потока по WebRTC
6. Остановка публикации потока.
stream.stop(); code
Code Block | ||||
---|---|---|---|---|
| ||||
function onPublishing(stream) {
$("#publishBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
stream.stop();
}).prop('disabled', false);
$("#publishInfo").text("");
} |
7. Получение от сервера события, подтверждающего остановку публикации потока.
StreamStatusEvent, статус UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
receiveVideo: false,
receiveAudio: false
...
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
onUnpublished();
...
}).publish(); |
Публикация с отображением локального видео в Delight Player
При захвате потока с вебкамеры возможно отображение локального видео в браузерном VR-плеере, например, Delight Player. Таким образом можно проигрывать поток в устройствах виртуальной и смешанной реальности, если на этом устройстве работает один из поддерживаемых браузеров. Для интеграции стороннего плеера используются возможности JavaScript и HTML5.
Тестирование
1. Для теста возьмем:
- WCS сервер
- тестовую страницу с VR-плеером Delight для воспроизведения потока при публикации
2. Укажем имя потока test и нажмем Publish. Публикуемый поток отображается в плеере
Пример кода страницы с плеером
1. Объявление видеоэлемента для воспроизведения потока, поля ввода имени потока и кнопок запуска и остановки публикации
Code Block | ||||
---|---|---|---|---|
| ||||
<div style="width: 50%;">
<dl8-live-video id="remoteVideo" format="STEREO_TERPON" muted="true">
<source>
</dl8-live-video>
</div>
<input class="form-control" type="text" id="streamName" placeholder="Stream Name">
<button id="publishBtn" type="button" class="btn btn-default" disabled>Publish</button>
<button id="unpublishBtn" type="button" class="btn btn-default" disabled>UnPublish</button> |
2. Обработка события готовности плеера к воспроизведению
Code Block | ||||
---|---|---|---|---|
| ||||
document.addEventListener('x-dl8-evt-ready', function () {
dl8video = $('#remoteVideo').get(0);
$('#publishBtn').prop('disabled', false).click(function() {
publishStream();
});
}); |
3. Создание псевдоэлементов для воспроизведения потока
Code Block | ||||
---|---|---|---|---|
| ||||
var mockLocalDisplay = $('<div></div>');
var mockLocalVideo = $('<video></video>',{id:'mock-LOCAL_CACHED_VIDEO'});
mockLocalDisplay.append(mockLocalVideo); |
4. Установка соединения с сервером и создание потока
Code Block | ||||
---|---|---|---|---|
| ||||
var video = dl8video.contentElement;
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
var session = Flashphoner.getSessions()[0];
session.createStream({
name: $('#streamName').val(),
display: mockLocalDisplay.get(0)
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
...
}).publish();
}) |
5. Публикация потока, запуск воспроизведения в VR-плеере и обработка нажатия кнопки остановки публикации
Code Block | ||||
---|---|---|---|---|
| ||||
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
var srcObject = mockLocalVideo.get(0).srcObject;
video.srcObject = srcObject;
dl8video.start();
mockLocalVideo.get(0).pause();
mockLocalVideo.get(0).srcObject = null;
$('#unpublishBtn').prop('disabled', false).click(function() {
stream.stop();
$('#publishBtn').prop('disabled', false);
$('#unpublishBtn').prop('disabled', true);
dl8video.exit();
});
}).publish(); |
Полный код примера страницы с VR-плеером
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
<!DOCTYPE html>
<html>
<head>
<title>WebRTC Delight</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="../../../../flashphoner.js"></script>
<script type="text/javascript" src="../../dependencies/jquery/jquery-1.12.0.js"></script>
<script type="text/javascript" src="../../dependencies/js/utils.js"></script>
<script src="dl8-66b250447635476d123a44a391c80b09887e831e.js" async></script>
<meta name="dl8-custom-format" content='{"name": "STEREO_TERPON","base":"STEREO_MESH","params":{"uri": "03198702.json"}}'>
</head>
<body>
<div style="width: 50%;">
<dl8-live-video id="remoteVideo" format="STEREO_TERPON" muted="true">
<source>
</dl8-live-video>
</div>
<input class="form-control" type="text" id="streamName" placeholder="Stream Name">
<button id="publishBtn" type="button" class="btn btn-default" disabled>Publish</button>
<button id="unpublishBtn" type="button" class="btn btn-default" disabled>UnPublish</button>
<script>
Flashphoner.init({flashMediaProviderSwfLocation: '../../../../media-provider.swf'});
var SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
var STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
var STREAM_STATUS_INFO = Flashphoner.constants.STREAM_STATUS_INFO;
var publishBtn = $('#publishBtn').get(0);
var dl8video = null;
var url = setURL();
document.addEventListener('x-dl8-evt-ready', function () {
dl8video = $('#remoteVideo').get(0);
$('#publishBtn').prop('disabled', false).click(function() {
publishStream();
});
});
var mockLocalDisplay = $('<div></div>');
var mockLocalVideo = $('<video></video>',{id:'mock-LOCAL_CACHED_VIDEO'});
mockLocalDisplay.append(mockLocalVideo);
function publishStream() {
$('#publishBtn').prop('disabled', true);
$('#unpublishBtn').prop('disabled', false);
var video = dl8video.contentElement;
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {
var session = Flashphoner.getSessions()[0];
session.createStream({
name: $('#streamName').val(),
display: mockLocalDisplay.get(0)
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
var srcObject = mockLocalVideo.get(0).srcObject;
video.srcObject = srcObject;
dl8video.start();
mockLocalVideo.get(0).pause();
mockLocalVideo.get(0).srcObject = null;
$('#unpublishBtn').prop('disabled', false).click(function() {
stream.stop();
$('#publishBtn').prop('disabled', false);
$('#unpublishBtn').prop('disabled', true);
dl8video.exit();
});
}).publish();
})
}
</script>
</body>
</html> |
Если браузер Chrome публикует пустое видео при занятой веб-камере
Некоторые версии браузера Chrome не возвращают ошибку в случае, если веб-камера занята другим процессом, а публикуют поток с пустым видео (черный экран). Остановить публикацию в этом случае можно двумя способами: при помощи JavaScript и HTML5 на клиенте, или при помощи настройки на сервере.
Остановка публикации потока на стороне клиента
Видеодорожка, созданная браузером Chrome для занятой камеры, останавливается в пределах первой секунды публикации, затем поток публикуется уже без видео. При этом состояние видеодорожки (переменная readyState
) меняется на ended
, и генерируется соответствующее событие onended
, которое может быть перехвачено веб-приложением. Для того, чтобы использовать это событие:
1. Добавляем в скрипт веб-приложения функцию регистрации обработчика события onended, в котором завершаем публикацию при помощи stream.stop()
Code Block | ||||
---|---|---|---|---|
| ||||
function addVideoTrackEndedListener(localVideo, stream) {
var videoTrack = extractVideoTrack(localVideo);
if (videoTrack && videoTrack.readyState == 'ended') {
console.error("Video source error. Disconnect...");
stream.stop();
} else if (videoTrack) {
videoTrack.onended = function (event) {
console.error("Video source error. Disconnect...");
stream.stop();
};
}
} |
2. Добавляем функцию удаления обработчика события при завершении публикации
Code Block | ||||
---|---|---|---|---|
| ||||
function removeVideoTrackEndedListener(localVideo) {
var videoTrack = extractVideoTrack(localVideo);
if(videoTrack) {
videoTrack.onended = null;
}
} |
3. Добавляем функцию извлечения видеодорожки
Code Block | ||||
---|---|---|---|---|
| ||||
function extractVideoTrack(localVideo) {
return localVideo.firstChild.srcObject.getVideoTracks()[0];
} |
4. При публикации потока регистрируем обработчик события
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({
name: streamName,
display: localVideo,
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
addVideoTrackEndedListener(localVideo, stream);
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
onPublishing(stream);
...
}).publish(); |
5. При завершении публикации потока удаляем обработчик события
Code Block | ||||
---|---|---|---|---|
| ||||
function onPublishing(stream) {
$("#publishBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
removeVideoTrackEndedListener(localVideo);
stream.stop();
}).prop('disabled', false);
$("#publishInfo").text("");
} |
Контроль активности видеодорожки в потоке на стороне сервера
Контроль активности видео в публикуемых потоках на сервере включается при помощи следующих настроек в файле flashphoner.properties
Code Block | ||
---|---|---|
| ||
rtp_activity_detecting=true,60
rtp_activity_video=true |
В этом случае, если в публикуемом потоке нет видео, публикация остановится через 60 секунд.
Известные проблемы
1. Если веб-приложение расположено внутри iframe элемента, публикация видеопотока может не пройти.
Симптомы: ошибки IceServer error в консоли браузера.
Решение: вынести приложение из iframe на отдельную страницу.
2. Если публикация потока идет с Windows 10 или Windows 8 и в браузере Google Chrome включено аппаратное ускорение, могут быть проблемы с битрейтом.
Симптомы: качество видео плохое, мутное, битрейт в chrome://webrtc-internals показывает меньше 100 kbps.
Решение: отключите аппаратное ускорение в браузере, переключите браузер или сервер на использование кодека VP8.
3. Публикация потока с воспроизведением локального видео в Delight Player не работает в MS Edge
Симптомы: при публикации потока в MS Edge не запускается воспроизведение локального видео в Delight Player
Решение: использовать другой браузер для публикации
4. В некоторых случаях в браузере Chrome не работает микрофон при публикации WebRTC
Симптомы: не работает микрофон при публикации WebRTC, в том числе в примерах из комплекта поставки
...
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init({
flashMediaProviderSwfLocation: '../../../../media-provider.swf',
createMicGainNode: false
}); |
При этом не будет работать регулировка усиления микрофона.
5. Кодек G722 не работает в браузере Edge
Симптомы: поток со звуком G722 не публикуется в браузере Edge
Решение: использовать другой кодек или другой браузер. В случае, если использование другого браузера невозможно, исключить кодек G722 при помощи настройки
...
theme | RDark |
---|
...
Include Page | ||||
---|---|---|---|---|
|