Пример стримера с автоматическим восстановлением публикации
Данный пример показывает, как восстановить публикацию, автоматически изменив кодек на VP8, если не удается опубликовать H264 поток. Восстановление возможно при условии, что браузер не перезагрузил страницу, и Javascript на ней выполняется. Проблемы с публикацией фиксируются по факту устойчивого падения битрейта публикации до 0.
Код примера
Код данного примера находится на 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_filter/stream_filter.html
Здесь host - адрес WCS-сервера.
Работа с кодом примера
Для разбора кода возьмем версию файла stream-auto-restore.js с хешем f2862b9, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.207.
1. Инициализация API.
Flashphoner.init() code
Flashphoner.init();
2. Подключение к серверу.
Flashphoner.createSession() code
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. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED code
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function(session){ setStatus("#connectStatus", session.status()); onConnected(session); }).on(SESSION_STATUS.DISCONNECTED, function(){ ... }).on(SESSION_STATUS.FAILED, function(){ ... });
4. Публикация видеопотока.
session.createStream(), publish() code
При создании передаются:
- имя видеопотока streamName
- localVideo - div-элемент, в котором будет отображаться видео с камеры
- stripCodecs - кодек, который нужно исключить для успешной публикации
session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true receiveVideo: false, receiveAudio: false, stripCodecs: stripCodecs ... }).publish();
5. Получение от сервера события, подтверждающего успешную публикацию потока.
StreamStatusEvent PUBLISHING code
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function(stream){ setStatus("#publishStatus", STREAM_STATUS.PUBLISHING); onPublishing(stream); }).on(STREAM_STATUS.UNPUBLISHED, function(){ ... }).on(STREAM_STATUS.FAILED, function(){ ... }).publish();
6. Воспроизведение видеопотока.
session.createStream(), play() code.
При создании передается имя видеопотока streamName (в том числе, это может быть имя потока, опубликованного выше), а также remoteVideo - div-элемент, в котором будет отображаться видео.
session.createStream({ name: streamName, display: remoteVideo ... }).play();
7. Получение от сервера события, подтверждающего успешное воспроизведение потока.
StreamStatusEvent PLAYING code
session.createStream({ name: streamName, display: remoteVideo ... }).on(STREAM_STATUS.PLAYING, function(stream) { setStatus("#playStatus", stream.status()); onPlaying(stream); }).on(STREAM_STATUS.STOPPED, function() { ... }).on(STREAM_STATUS.FAILED, function() { ... }).play();
8. Остановка воспроизведения видеопотока.
stream.stop() code
function onPlaying(stream) { $("#playBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); $("#playInfo").text(""); }
9. Получение от сервера события, подтверждающего успешную остановку воспроизведения потока.
StreamStatusEvent STOPPED code
session.createStream({ ... }).on(STREAM_STATUS.PLAYING, function(stream) { ... }).on(STREAM_STATUS.STOPPED, function() { setStatus("#playStatus", STREAM_STATUS.STOPPED); onStopped(); }).on(STREAM_STATUS.FAILED, function() { ... }).play();
10. Остановка публикации видеопотока
stream.stop() code
function onPublishing(stream) { $("#publishBtn").text("Stop").off('click').click(function(){ $(this).prop('disabled', true); stream.stop(); }).prop('disabled', false); $("#publishInfo").text(""); ... }
11. Получение от сервера события, подтверждающего успешную остановку публикации потока.
StreamStatusEvent UNPUBLISHED code
session.createStream({ ... }).on(STREAM_STATUS.PUBLISHING, function(stream){ ... }).on(STREAM_STATUS.UNPUBLISHED, function(){ setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED); onUnpublished(); }).on(STREAM_STATUS.FAILED, function(){ ... }).publish();
12. Параметры проверки текущего битрейта публикации
var statPublishFailureDetector = { failed: false, codec: "", lastBytesSent: 0, counter: { value: 0, threshold: PUBLISH_FAILURE_DETECTOR_MAX_TRIES } };
13. Запуск процедуры контроля текущего битрейта публикации по событию STREAM_STATUS.PUBLISHING
function onPublishing(stream) { ... if(Browser.isChrome()) { detectPublishFailure(stream, PUBLISH_FAILURE_DETECTOR_INTERVAL, PUBLISH_FAILURE_DETECTOR_MAX_TRIES); } }
14. Получение WebRTC статистики от браузера, определение текущего кодека и битрейта публикации, остановка публикации при устойчивом падении битрейта до 0
stream.getStats(function(stat) { let videoStats = stat.outboundStream.video; if(!videoStats) { return; } let codec = videoStats.codec; let bytesSent = videoStats.bytesSent; let bitrate = (bytesSent - statPublishFailureDetector.lastBytesSent) * 8; if (bitrate == 0) { statPublishFailureDetector.counter.value++; console.log("Bitrate is 0 (" + statPublishFailureDetector.counter.value + ")"); if (statPublishFailureDetector.counter.value >= statPublishFailureDetector.counter.threshold) { statPublishFailureDetector.failed = true; console.log("Publishing seems to be failed, stop the stream"); stream.stop(); } } else { statPublishFailureDetector.counter.value = 0; } statPublishFailureDetector.lastBytesSent = bytesSent; statPublishFailureDetector.codec = codec; $("#publishInfo").text(statPublishFailureDetector.codec); });
15. Остановка таймера проверки битрейта и вызов функции перезапуска публикации по событию STREAM_STATUS.UNPUBLISHED
function onUnpublished() { ... if (publishFailureIntervalID) { clearInterval(publishFailureIntervalID); publishFailureIntervalID = null; } checkPublishFailureConditions(); }
16. Перезапуск публикации с кодеком VP8
function checkPublishFailureConditions() { if (statPublishFailureDetector.failed) { $("#publishInfo").text("Failed to publish " + statPublishFailureDetector.codec); if (statPublishFailureDetector.codec == "H264") { console.log("H264 publishing seems to be failed, trying VP8 by stripping H264"); let stripCodecs = "H264"; publishBtnClick(stripCodecs); } else if (statPublishFailureDetector.codec == "VP8") { console.log("VP8 publishing seems to be failed, giving up"); } } }