WebRTC позволяет реализовать real-time аудио/видео связь через браузер ( и ).
В этом топике я расскажу как реализовать простейшее WebRTC приложение.
1. getUserMedia — получение доступа к медиа устройствам (микрофон/вебкамера)
Ничего сложного, с помощью 10 строк javascript-кода вы сможете увидеть и услышать себя в браузере(демо).
Cоздайте index.html:
<video autoplay></video>
<script>
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
navigator.getUserMedia({ audio: true, video: true }, gotStream, streamError);
function gotStream(stream) {
document.querySelector('video').src = URL.createObjectURL(stream);
}
function streamError(error) {
console.log(error);
}
</script>
К элементу video вы можете применить css3-фильтры.
Огорчает здесь то, что на данном этапе развития WebRTC я не могу сказать браузеру «этому сайту я доверяю, всегда давай ему доступ к моей камере и микрофону» и нужно нажимать Allow после каждого открытия/обновления страницы.
Ну и не лишним будет напомнить, что если вы дали доступ к камере в одном браузере, другой при попытке получить доступ получит PERMISSION_DENIED.
2. Signaling server (сигнальный сервер)
Здесь я нарушаю последовательность большинства «webrtc getting started» инструкций потому как они вторым шагом демонструруют возможности webRTC на одном клиенте, что лично мне только добавило путаницы в объяснение.
Сигнальный сервер — это координирующий центр WebRTC, который обеспечивает коммуникацию между клиентами, инициализацию и закрытие соединения, отчеты об ошибках.
Сигнальный сервер в нашем случае это Node.js + socket.io + node-static, он будет слушать порт 1234.
Плюс ко всему node-static может отдать index.html, что сделает наше приложение максимально простым.
В папке приложения установим необходимое:
npm install socket.io
npm install node-static
Скачайте server.js в папку приложения. Серверную часть я не буду разбирать, она довольна проста для понимания, к тому же она не является непосредственной частью WebRTC, поэтому просто запустите сервер:
node server.js
3. WebRTC
Теперь у нас все готово для работы непосредственно с WebRTC. Скачайте рабочий index.html полностью, дальше я буду разбирать его по кускам.
3.0. Стандарты пока не приняты, поэтому пока что мы вынуждены использовать префиксы для различных браузеров:
var PeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
var IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
var SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
3.1. getUserMedia
navigator.getUserMedia(
{ audio: true, video: true },
gotStream,
function(error) { console.log(error) }
);
function gotStream(stream) {
document.getElementById("callButton").style.display = 'inline-block';
document.getElementById("localVideo").src = URL.createObjectURL(stream);
pc = new PeerConnection(null);
pc.addStream(stream);
pc.onicecandidate = gotIceCandidate;
pc.onaddstream = gotRemoteStream;
}
Эту часть мы уже разбирали в самом начале поста, за исключением того, что в функции gotStream мы создаем PeerConnection и добавляем два обработчика событий:
onicecandidate
отдает нам сгенерированные ICE Candidate, которые мы передадим сигнальному серверу, который в свою очередь передаст их собеседникуonaddstream
будет вызван когда мы получим медиапоток собеседника
3.2. Call Offer
Call Offer — это инициализация сессии WebRTC. Call Offer и Call Answer(следующий шаг) имеют формат SDP (Session Description Protocol) и служат для описания параметров(разрешение, кодек и т.д.) инициализации медиапотоков клиентов.
ВАЖНО: клиенты должны дать доступ к медиаустройствам ДО отправки Call Offer.
function createOffer() {
pc.createOffer(
gotLocalDescription,
function(error) { console.log(error) },
{ 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } }
);
}
3.3. Call Answer
Call Answer отправляет собеседник сразу после получения Call Offer. Отмечу что callback-функция у
createOffer()
и createAnswer()
одна и та же — gotLocalDescription, т.е. localDescription для одного клиента создан функцией createOffer()
, для другого — функцией createAnswer()
.function createAnswer() {
pc.createAnswer(
gotLocalDescription,
function(error) { console.log(error) },
{ 'mandatory': { 'OfferToReceiveAudio': true, 'OfferToReceiveVideo': true } }
);
}
3.4. ICE Candidate
ICE (Interactive Connectivity Establishment) Candidate выполняет функцию соединения клиентов, устанавливая путь между клиентами, по которому будут передаваться медиапотоки. Сгенерированные ICE Candidate мы также отправляем сигнальному серверу, описание обработки сообщений от сигнального сервера на следующем шаге.
function gotIceCandidate(event){
if (event.candidate) {
sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
});
}
}
3.5. Обработка сообщений от сигнального сервера
var socket = io.connect('', {port: 1234});
socket.on('message', function (message){
if (message.type === 'offer') {
pc.setRemoteDescription(new SessionDescription(message));
createAnswer();
}
else if (message.type === 'answer') {
pc.setRemoteDescription(new SessionDescription(message));
}
else if (message.type === 'candidate') {
var candidate = new IceCandidate({sdpMLineIndex: message.label, candidate: message.candidate});
pc.addIceCandidate(candidate);
}
});
Код простой, но поясню:
- как только получили Call Offer, вызываем createAnswer
- при получении как Call Offer так и Call Answer вызываем
setRemoteDescription
с полученным SDP - при получении ICE Candidate вызываем addIceCandidate
3.6. gotRemoteStream
Если все прошло успешно, то будет вызван обработчик
onaddstream
из шага 3.1, который будет содержать медиапоток от собеседника.function gotRemoteStream(event){
document.getElementById("remoteVideo").src = URL.createObjectURL(event.stream);
}
Напомню, что исходный код приложения целиком можно взять здесь.