Как-то в из старинных и уже заброшенных статей я писал о том, как легко и непринужденно можно транслировать видео с canvas через websockets. В той статье поверхностно рассказывал о том, как захватить видео с камеры и звук с микрофона посредством , как полученный поток кодировать и отправлять через websockets на сервер. Однако в реальности так не делают, для трансляций используют либо специальный софт, который нужно устанавливать и настраивать: навскидку это может быть , либо задействуют WebRTC, который работает прямо из коробки, то есть не требует установки никаких плагинов аля flash player, который вот уже в декабре выпилят из Chromium браузера.
Сегодня поговорим о WebRTC.
Web Real-Time Communication (WebRTC) это не один протокол, это целая коллекция стандартов, протоколов и JavaScript API, которые все вместе обеспечивают peer-to-peer видео-аудио коммуникации в реальном времени, а также могут быть использованы для передачи любых бинарных данных. Обычно пирами выступают браузеры, но это может быть и мобильное приложение, например. Для того чтобы организовать p2p общение между клиентами требуется поддержка браузером различных видов кодирования видео и аудио, поддержка множества сетевых протоколов, обеспечение взаимодействия железа с браузером (через слои ОС): вебкамер, звуковых карт. Вся эта мешанина технологий скрыта за абстракцией JavaScript API для удобства разработчика.
Все сводится в итоге к трем API:
— разбирали в прошлый раз, сегодня еще немного напишу про него. Служит для получения видео/аудио потоков от «железа»
— обеспечивает коммуникации между двумя клиентами (p2p)
— служит для передачи произвольных данных между двумя клиентами
Подготовка аудио и видео потоков к передаче
Все начинается с «захвата» медиапотоков вебкамеры и микрофона. Сырые потоки конечно же не подходят для организации телеконференции, каждый поток необходимо обработать: улучшить качество, синхронизировать аудио с видео, расставить метки синхронизации в видеопотоке, обеспечить соответствующий постоянно меняющейся широте пропускания канала битрейт. Браузер все это берет на себя, разработчику даже не надо беспокоиться об обеспечении кодирования медиа-потоков. Внутри современного браузера уже присутствуют программные слои захвата, улучшения качества (убрать эхо и шум из звука, улучшить картинку), кодирования видео и аудио. Схема слоев показана рис. 1:
Рис. 1. Слои аудио и видео обработки в браузере
Вся обработка происходит прямо в самом браузере, никаких доп. плагинов не требуется. Однако все еще не столь радужно на 2020 год. Остались браузеры, которые пока еще не поддерживают полностью , вы можете перейти по ссылке и в самом низу посмотреть таблицу совместимости. В частности IE снова разочаровывает.
С полученными потоками можно делать очень интересные вещи: можно клонировать, менять разрешение видео, манипулировать качеством аудио, можно взять и «прицепить» Media Stream поток к <video> тегу и смотреть на себя любимого на страничке html. А можно поток и на canvas отрисовать, и натравить WebGL или CSS3, и накладывать различные фильтры на видео, захватывать обработанное видео с canvas и далее уже отправлять по сети на сервер, транскодить и публиковать всем желающим (привет bigo live, twitch и прочие). Здесь я не буду разбирать как делаются такие вещи, приведу пару примеров, найденных на просторах сети:
— ребята занимаются realtime CV на Javascript. У них есть целый различных js-библиотек для работы с видеопотоком на canvas: детектирование лиц, объектов, наложение фильтров (масок, как в инсте) и пр. Отличный пример того, как без дополнительных плагинов можно прямо в браузере обрабатывать видео в реальном времени.
— документация API по захвату видеопотока с canvas. Уже поддерживается в Chrome, Opera и Firefox
RTCPeerConnection
Вот мы и подошли к тому, а как собственно передать видео другому пользователю? На первый план выступает . Если говорить кратко, практически на этом шаге вам необходимо создать объект RTCPeerConnection:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Одной из опций указываем iceServers — это сервер, который помогает обеспечивать соединение между двумя браузерами, находящимися за NAT’ом. То есть здесь решается проблема: как узнать ip собеседника, если он находится за NAT его провайдера? На помощь приходит ICE протокол, на самом деле, ICE вообще не относится к WebRTC, но об этом позже.
Ранее мы получили Usermedia потоки:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// Usermedia-потоки, обычно это видео и аудио
const tracks = stream.getTracks();
for (const track of tracks) {
// каждый трек присоединяем к peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);Далее на peerConnection срабатывает событие onnegotiationneeded, в обработчике которого мы должны создать offer (в терминах SDP — Session Description Protocol) и назначить в peerConnection через метод setLocalDescription. Об SDP — что это такое и о форматах offer и answer — поговорим далее.
После назначения LocalDescription peerConnection, браузер «собирает» ice-кандидатов, то есть находит различные пути для коммуникации через NAT. Срабатывает событие onicegatheringstatechange. В обработчике onicegatheringstatechange разрешаем соединение с webrtc-signaling-сервером stream для обмена Session Description между пирами:
peerConnection.oniceconnectionstatechange = (event) => {
console.log('Connection state: ', peerConnection.iceConnectionState);
if (peerConnection.iceConnectionState === 'connected') {
// Можем активировать кнопку Start broadcast
setBroadcasting(true);
setBroadcastingBtnActive(true);
}
};
// Событие срабатывает сразу, как только добавился медаиапоток в peerConnection
peerConnection.onnegotiationneeded = (event) => {
// Создаем и назначаем SDP offer
peerConnection.createOffer().
then((offer) => peerConnection.setLocalDescription(offer)).
catch(console.error);
};
// Событие срабатывает каждый раз, как появляется ICE кандидат
peerConnection.onicegatheringstatechange = (ev) => {
let connection = ev.target;
// Now we can activate broadcast button
if (connection.iceGatheringState === 'complete') {
let delay = 50;
let tries = 0;
let maxTries = 3;
let timerId = setTimeout(function allowStreaming() {
if (isOnline) {
setBroadcastingBtnActive(true);
return;
}
if (tries < maxTries) {
tries += 1;
delay *= 2;
timerId = setTimeout(allowStreaming, delay);
} else {
// TODO: show user notification
console.error("Can't connect to server");
alert("Can't connect to server");
}
}, delay);
}
};webrtc-signaling-сервер — это сервер, необходимый для обеспечения обмена session description между двумя пирами, это может быть простейший websocket или xhr-сервер на любом ЯП. Его задача проста: принять session description от одного пира и передать другому.
После обмена Session descriptions обе стороны готовы транслировать и принимать видеопотоки, на стороне, которая принимает видеопоток срабатывает событие ontrack на peerConnection, в обработчике которого, получаемые треки можно назначить на <video> и смотреть на любимого собеседника. Далее теория и подробности.
Ссылки и литература:
— документация
— реализация протоколов WebRTC на go
— книжечка от создателей pion
— книга High Perfomance Browser Networking. В подробностях разбираются вопросы обеспечения высокой производительности web-приложений. В конце описывается WebRTC. Книжка конечно старая (2013), но не теряет своей актуальности.
В следующей части хочу дать еще немного порции теории и на практике разобрать прием и обработку видеопотока на сервере с помощью pion, транскодинг в HLS через ffmpeg для последующей трансляции зрителям в браузере.
Для нетерпеливых: (это просто эксперимент).
Источник: habr.com
