Streaming Auto Restore¶
Пример стримера с автоматическим восстановлением публикации/проигрывания¶
Данный пример показывает различные способы восстановления публикации и проигрывания:
- при проблемах с публикацией H264 потока из браузера с заменой кодека на VP8
- при остановке отправки медиапакетов, с контролем по снижению битрейта публикации до 0
- при переключении с одной сети на другую (с Wi-Fi на LTE и обратно)
- разрыве и последующем восстановлении соединения (в том числе при остановке публикации играемого потока или при перезапуске сервера)
Параметры контроля битрейта
- Check bitrate - контролировать снижение битрейта публикации до 0
- Change codec - менять кодек с H264 на VP8 при остановке публикации по снижению битрейта до 0
- Bitrate check interval - интервал проверки текущего значения битрейта публикации
- Max tries - максимальное количество последовательных попыток проверки битрейта, при котором зафиксировано снижение до 0
Параметры восстановления соединения
- Restore connection - восстанавливать соединение при обрыве сессии или при остановке проигрывания потока с признаком ошибки
- Timeout - интервал между попытками восстановить соединение
- Max tries - максимальное количество попыток восстановить соединение
- Missing pings - максимальное количество ping пакетов, не полученных от сервера (0 отключает проверку)
- Pings check period - интервал проверки количества пропущенных ping пакетов
Код примера¶
Код данного примера находится на WCS-сервере по следующему пути:
/usr/local/FlashphonerWebCallServer/client2/examples/demo/streaming/stream-auto-restore
- stream-auto-restore.css - файл стилей
- stream-auto-restore.html - страница клиента
- stream-auto-restore.js - скрипт, обеспечивающий работу примера
Тестировать данный пример можно по следующему адресу:
https://host:8888/client2/examples/demo/streaming/stream-auto-restore/stream-auto-restore.html
Здесь host - адрес WCS-сервера.
Работа с кодом примера¶
Для разбора кода возьмем версию файла stream-auto-restore.js
с хешем 2035db9
, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.209.
1. Действия при открытии страницы¶
1.1. Инициализация API¶
Flashphoner.init()
code
1.2. Инициализация объектов для хранения текущего состояния сессии и публикуемого/играемого потоков¶
1.3. Инициализация объекта для контроля битрейта¶
1.4. Инициализация объекта для восстановления соединения¶
Объекту передается функция, которая должна выполниться при срабатывании интервального таймера восстановления соединения
streamingRestarter = streamRestarter(function() {
if (streamPublishing.wasActive) {
onPublishRestart();
}
if (streamPlaying.wasActive && streamPlaying.name != streamPublishing.name) {
onPlayRestart();
}
});
1.5. Запуск детектора изменения сети¶
2. Действия при подключении к серверу/отключении от сервера¶
2.1. Подключение к серверу¶
Flashphoner.createSession()
code
При создании сессии передаются следующие параметры:
url
- Websocket URL сервераreceiveProbes
- максимальное количество ping пакетов, не полученных от сервера (0 отключает проверку)probesInterval
- интервал проверки количества пропущенных ping пакетов (0 отключает проверку)
Flashphoner.createSession({
urlServer: url,
receiveProbes: receiveProbes,
probesInterval: probesInterval
}).on(SESSION_STATUS.ESTABLISHED, function (session) {
...
}).on(SESSION_STATUS.DISCONNECTED, function () {
...
}).on(SESSION_STATUS.FAILED, function () {
...
});
2.2. Получение от сервера события, подтверждающего успешное соединение¶
ConnectionStatusEvent ESTABLISHED
code
При успешной установке соединения:
- запоминаются параметры текущей сессии для последующего восстановления
- если в предыдущей сессии был опубликован или игрался поток, публикация/проигрывание перезапускается
Flashphoner.createSession({
urlServer: url,
receiveProbes: receiveProbes,
probesInterval: probesInterval
}).on(SESSION_STATUS.ESTABLISHED, function (session) {
setStatus("#connectStatus", session.status());
currentSession.set(url, session);
onConnected(session);
if(restoreConnection) {
if(streamPublishing.wasActive) {
console.log("A stream was published before disconnection, restart publishing");
onPublishRestart();
return;
}
if(streamPlaying.wasActive) {
console.log("A stream was played before disconnection, restart playback");
onPlayRestart();
}
}
}).on(SESSION_STATUS.DISCONNECTED, function () {
...
}).on(SESSION_STATUS.FAILED, function () {
...
});
2.3. Закрытие соединения при нажатии на кнопку Disconnect
¶
Session.disconnect()
code
function onConnected(session) {
$("#connectBtn").text("Disconnect").off('click').click(function () {
$(this).prop('disabled', true);
currentSession.isManuallyDisconnected = true;
session.disconnect();
}).prop('disabled', false);
...
}
2.4. Получение события при закрытии соединения¶
ConnectionStatusEvent DISCONNECTED
code
Если соединение было закрыто вручную при нажатии на кнопку Disconnect
:
- объекты состояния очищаются
- таймер восстановления соединения останавливается
Flashphoner.createSession({
urlServer: url,
receiveProbes: receiveProbes,
probesInterval: probesInterval
}).on(SESSION_STATUS.ESTABLISHED, function (session) {
...
}).on(SESSION_STATUS.DISCONNECTED, function () {
setStatus("#connectStatus", SESSION_STATUS.DISCONNECTED);
onDisconnected();
// Prevent streaming restart if session is manually disconnected
if (currentSession.isManuallyDisconnected) {
streamPublishing.clear();
streamPlaying.clear();
streamingRestarter.reset();
currentSession.clear();
}
}).on(SESSION_STATUS.FAILED, function () {
...
});
2.5. Получение события при разрыве соединения¶
ConnectionStatusEvent FAILED
code
Если на момент разрыва соединения публиковался или проигрывался поток, запускается таймер восстановления соединения
Flashphoner.createSession({
urlServer: url,
receiveProbes: receiveProbes,
probesInterval: probesInterval
}).on(SESSION_STATUS.ESTABLISHED, function (session) {
...
}).on(SESSION_STATUS.DISCONNECTED, function () {
...
}).on(SESSION_STATUS.FAILED, function () {
setStatus("#connectStatus", SESSION_STATUS.FAILED);
onDisconnected();
if(restoreConnection
&& (streamPublishing.wasActive || streamPlaying.wasActive)) {
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
}
});
3. Действия при публикации видеопотока¶
3.1 Публикация видеопотока¶
Session.createStream()
, Stream.publish()
code
При создании передаются:
- имя видеопотока
streamName
localVideo
-div
элемент, в котором будет отображаться видео с камерыstripCodecs
- кодек, который нужно исключить, если при предыдущей публикации битрейт упал до 0
session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
receiveVideo: false,
receiveAudio: false,
stripCodecs: stripCodecs
...
}).publish();
3.2. Получение от сервера события, подтверждающего успешную публикацию потока¶
StreamStatusEvent PUBLISHING
code
При успешной публикации:
- запускается контроль битрейта
- запоминаются текущие параметры публикуемого потока для последующего восстановления публикации
- останавливается таймер восстановления соединения
- если в предыдущей сессии игрался поток, запускается проигрывание этого потока
session.createStream({
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
onPublishing(stream);
streamPublishing.set(streamName, stream);
streamingRestarter.reset();
if ($("#restoreConnection").is(':checked')
&& streamPlaying.wasActive) {
console.log("A stream was played before, restart playback");
onPlayRestart();
}
}).on(STREAM_STATUS.UNPUBLISHED, function () {
...
}).on(STREAM_STATUS.FAILED, function (stream) {
...
}).publish();
3.3. Запуск контроля битрейта при успешной публикации¶
function onPublishing(stream) {
...
// Start publish failure detector by bitrate #WCS-3382
if($("#checkBitrate").is(':checked')) {
h264PublishFailureDetector.startDetection(stream, $("#bitrateInteval").val(), $("#bitrateMaxTries").val());
}
}
3.4. Остановка публикации при нажатии на Stop
¶
Stream.stop()
code
function onPublishing(stream) {
$("#publishBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
streamPublishing.isManuallyStopped = true;
stream.stop();
}).prop('disabled', false);
...
}
3.5. Получение от сервера события, подтверждающего успешную остановку публикации потока при нажатии на Stop
¶
StreamStatusEvent UNPUBLISHED
code
При успешной остановке публикации:
- останавливается контроль битрейта
- останавливается таймер восстановления соединения
- очищается объект состояния публикуемого потока
session.createStream({
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
...
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
onUnpublished();
if (!streamPlaying.wasActive) {
// No stream playback< we don't need restart any more
streamingRestarter.reset();
} else if (streamPlaying.wasActive && streamPlaying.name == streamPublishing.name) {
// Prevent playback restart for the same stream
streamingRestarter.reset();
}
streamPublishing.clear();
}).on(STREAM_STATUS.FAILED, function (stream) {
...
}).publish();
3.6. Получение от сервера события об остановке публикации в связи с ошибкой¶
StreamStatusEvent FAILED
code
При остановке публикации в связи с ошибкой
- останавливается контроль битрейта
- если не зафиксирована локальная ошибка в браузере (например, по недоступности устройств захвата), запускается таймер восстановления соединения
session.createStream({
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
...
}).on(STREAM_STATUS.UNPUBLISHED, function () {
...
}).on(STREAM_STATUS.FAILED, function (stream) {
setStatus("#publishStatus", STREAM_STATUS.FAILED, stream);
onUnpublished();
if ($("#restoreConnection").is(':checked') && stream.getInfo() != ERROR_INFO.LOCAL_ERROR) {
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
}
}).publish();
3.7. Остановка контроля битрейта при остановке публикации¶
function onUnpublished() {
...
h264PublishFailureDetector.stopDetection(streamPublishing.isManuallyStopped || currentSession.isManuallyDisconnected);
...
}
4. Действия при воспроизведении видеопотока¶
4.1. Воспроизведение видеопотока¶
Session.createStream()
, Stream.play()
code.
При создании передается имя видеопотока streamName (в том числе, это может быть имя потока, опубликованного выше), а также remoteVideo
- div
элемент, в котором будет отображаться видео.
4.2. Получение от сервера события, подтверждающего успешное воспроизведение потока¶
StreamStatusEvent PLAYING
code
При успешном воспроизведении потока:
- запоминаются текущие параметры потока для последующего восстановления
- останавливается таймер восстановления воспроизведения
session.createStream({
name: streamName,
display: remoteVideo
}).on(STREAM_STATUS.PENDING, function (stream) {
...
}).on(STREAM_STATUS.PLAYING, function (stream) {
setStatus("#playStatus", stream.status());
onPlaying(stream);
streamingRestarter.reset();
streamPlaying.set(streamName, stream);
}).on(STREAM_STATUS.STOPPED, function () {
...
}).on(STREAM_STATUS.FAILED, function (stream) {
...
}).play();
4.3 Остановка воспроизведения видеопотока при нажатии на Stop
¶
Stream.stop()
code
function onPlaying(stream) {
$("#playBtn").text("Stop").off('click').click(function(){
$(this).prop('disabled', true);
stream.stop();
}).prop('disabled', false);
$("#playInfo").text("");
}
4.4. Получение от сервера события, подтверждающего успешную остановку воспроизведения потока¶
StreamStatusEvent STOPPED
code
При успешной остановке воспроизведения:
- останавливается таймер восстановления соединения
- очищается объект состояния играемого потока
session.createStream({
name: streamName,
display: remoteVideo
}).on(STREAM_STATUS.PENDING, function (stream) {
...
}).on(STREAM_STATUS.PLAYING, function (stream) {
...
}).on(STREAM_STATUS.STOPPED, function () {
setStatus("#playStatus", STREAM_STATUS.STOPPED);
onStopped();
streamingRestarter.reset();
streamPlaying.clear();
}).on(STREAM_STATUS.FAILED, function (stream) {
...
}).play();
4.5. Получение события об ошибке воспроизведения потока¶
StreamStatusEvent FAILED
code
При остановке воспроизведения в связи с ошибкой запускается таймер восстановления соединения
session.createStream({
name: streamName,
display: remoteVideo
}).on(STREAM_STATUS.PENDING, function (stream) {
...
}).on(STREAM_STATUS.PLAYING, function (stream) {
...
}).on(STREAM_STATUS.STOPPED, function () {
...
}).on(STREAM_STATUS.FAILED, function (stream) {
setStatus("#playStatus", STREAM_STATUS.FAILED, stream);
onStopped();
if ($("#restoreConnection").is(':checked')) {
streamingRestarter.restart($("#restoreTimeout").val(), $("#restoreMaxTries").val());
}
}).play();
5. Контроль битрейта и возобновление публикации при снижении битрейта до 0¶
5.1. Получение WebRTC статистики от браузера, определение текущего кодека и битрейта публикации, остановка публикации при устойчивом падении битрейта до 0¶
stream.getStats(function(stat) {
let videoStats = stat.outboundStream.video;
if(!videoStats) {
return;
}
let stats_codec = videoStats.codec;
let bytesSent = videoStats.bytesSent;
let bitrate = (bytesSent - detector.lastBytesSent) * 8;
if (bitrate == 0) {
detector.counter.inc();
console.log("Bitrate is 0 (" + detector.counter.getCurrent() + ")");
if (detector.counter.exceeded()) {
detector.failed = true;
console.log("Publishing seems to be failed, stop the stream");
stream.stop();
}
} else {
detector.counter.reset();
}
detector.lastBytesSent = bytesSent;
detector.codec = stats_codec;
$("#publishInfo").text(detector.codec);
});
5.2. Остановка таймера проверки битрейта¶
if (detector.publishFailureIntervalID) {
clearInterval(detector.publishFailureIntervalID);
detector.publishFailureIntervalID = null;
}
5.3. Перезапуск публикации¶
if (detector.failed) {
$("#publishInfo").text("Failed to publish " + detector.codec);
if($("#changeCodec").is(':checked')) {
// Try to change codec from H264 to VP8 #WCS-3382
if (detector.codec == "H264") {
console.log("H264 publishing seems to be failed, trying VP8 by stripping H264");
let stripCodecs = "H264";
publishBtnClick(stripCodecs);
} else if (detector.codec == "VP8") {
console.log("VP8 publishing seems to be failed, giving up");
}
} else {
// Try to republish with the same codec #WCS-3410
publishBtnClick();
}
}
6. Восстановление соединения¶
6.1. Запуск таймера восстановления соединения¶
Таймер вызывает функцию, в которой выполняются необходимые действия по восстановлению
restarter.restartTimerId = setInterval(function(){
if (restarter.counter.exceeded()) {
logger.info("Tried to restart for " + restartMaxTimes + " times with " +restartTimeout + " ms interval, cancelled");
restarter.reset();
return;
}
onRestart();
restarter.counter.inc();
}, restartTimeout);
6.2. Остановка таймера восстановления соединения¶
if (restarter.restartTimerId) {
clearInterval(restarter.restartTimerId);
logger.info("Timer " + restarter.restartTimerId + " stopped");
restarter.restartTimerId = null;
}
restarter.counter.reset();
6.3. Создание новой сессии¶
let sessions = Flashphoner.getSessions();
if (!sessions.length || sessions[0].status() == SESSION_STATUS.FAILED) {
logger.info("Restart session to publish");
click("connectBtn");
} else {
...
}
6.4. Повторная публикация¶
let streams = sessions[0].getStreams();
let stream = null;
let clickButton = false;
if (streams.length == 0) {
// No streams in session, try to restart publishing
logger.info("No streams in session, restart publishing");
clickButton = true;
} else {
// If there is already a stream, check its state and restart publishing if needed
for (let i = 0; i < streams.length; i++) {
if (streams[i].name() === $('#publishStream').val()) {
stream = streams[i];
if (!isStreamPublishing(stream)) {
logger.info("Restart stream " + stream.name() + " publishing");
clickButton = true;
}
break;
}
}
if (!stream) {
logger.info("Restart stream publishing");
clickButton = true;
}
}
if (clickButton) {
click("publishBtn");
}
6.5. Повторное проигрывание¶
let streams = sessions[0].getStreams();
let stream = null;
let clickButton = false;
if (streams.length == 0) {
// No streams in session, try to restart playing
logger.info("No streams in session, restart playback");
clickButton = true;
} else {
// If there is already a stream, check its state and restart playing if needed
for (let i = 0; i < streams.length; i++) {
if (streams[i].name() === $('#playStream').val()) {
stream = streams[i];
if (!isStreamPlaying(stream)) {
logger.info("Restart stream " + stream.name() + " playback");
clickButton = true;
}
break;
}
}
if (!stream) {
logger.info("Restart stream playback");
clickButton = true;
}
}
if (clickButton) {
click("playBtn");
}
7. Контроль смены сети¶
7.1. Обработка события, сигнализирующего о смене сети на устройстве¶
NetworkInformation.onchange
code
if (Browser.isChrome() || (Browser.isFirefox() && Browser.isAndroid())) {
connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (connection) {
connectionType = connection.type;
if (Browser.isFirefox()) {
connection.ontypechange = onNetworkChange;
} else {
connection.onchange = onNetworkChange;
}
}
}
7.2. Закрытие соединения при смене сети¶
if (isNetworkConnected() && connection.type != connectionType) {
if (currentSession.getStatus() == SESSION_STATUS.ESTABLISHED) {
let logger = Flashphoner.getLogger();
logger.info("Close session due to network change from " + connectionType + " to " + connection.type);
currentSession.sdkSession.disconnect();
}
}