Full stack PHP/JS developer
0,0
рейтинг
23 октября 2013 в 04:38

Разработка → Делаем свой персональный Skype, пошаговая инструкция создания WebRTC приложения

WebRTC

WebRTC позволяет реализовать real-time аудио/видео связь через браузер (firefox и chrome).

В этом топике я расскажу как реализовать простейшее 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);
}


Напомню, что исходный код приложения целиком можно взять здесь.

Литература:


@limonte
карма
68,7
рейтинг 0,0
Full stack PHP/JS developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (28)

  • +2
    Хабрателепатия в действии: Только вчера думал, что после весеннего интереса к теме прошло достаточно времени: чтобы появилось нечто новое.
  • +4
    Нового появилось много, только вот работает это пока что не очень уверенно. Сейчас как раз занимаюсь проектом в котором одна из составных частей это webrtc. Работает на последних opera/firefox/chrome. Только вот чтобы совершить звонок из firefox нужно «попотеть», если для chrome все работет прозрачно, то firefox работает через DTLS-SRTP и это на серверном оборудовании не так просто организовать с учетом того, что не всякое серверное оборудование DTLS-SRTP поддерживает. Приходитеся использовать всякие прокладки типа webrtc2sip или oversip, которые пока, что достаточно проблемны в инсталляции и корректной работе, поддерживают не все функции, умеют транслировать на сервер не все сообщения…
    В общем ограничений пока что хватает. радует то -что это движется и движется достаточно быстро.
    • 0
      Подтверждаю товарищи. Это еще все очень трудно.

      Кстати не знаю как у вас, а у меня даже chrome работает через webrtc2sip, ибо иначе часто не получается пробросить соединения через нат если астериск сервер делает originate
      • 0
        Если есть SIP сервер то все должно работать через webrtc2sip/oversip, пока что только asterisk реализовал у себя поддержку webrtc (по сути тот же webrtc2sip/oversip только от digium), но это крайне неудобно: приходится делать 2 разных пира для одного пользователя, поскольку настройки разные для пиров webrtc и для обычных пиров (при этом взаимоисключающие)+ DTLS-SRTP мне так и не удалось правильно настроить.
  • +2
    WebRTC позволит обмениваться текстом между браузерами? Или только видео и звук?
    • +1
      Да это уже возможно, в Chrome и Firefox, через DataChannel Api.
      • +1
        Посмотрите вот эту презентацию. Очень доступно о том как все это работает и какие есть проблемы.
  • +3
    Огорчает здесь то, что на данном этапе развития WebRTC я не могу сказать браузеру «этому сайту я доверяю, всегда давай ему доступ к моей камере и микрофону»

    «always allow» доступно, если ваш сайт обслуживается через https.
  • 0
    Хотелось бы узнать, насколько корректно работает webrtc4all? Стоит ли его предлагать всем кроме хрома и будет ли от этого прозрачная поддержка webrtc на одном и том же API?
    • 0
      С самой последней версией были проблемы, точно какие сказать не смогу- не помню. А вот предпоследняя вполне себе годная получилась
  • 0
    Кто-нибудь знает а этот проект живой rtckit.com/?
    Не отвечают и активности нет никакой с 2012
  • +1
    Добавьте к статье еще эту сслыку. Штука реально хорошая. sipml5.org/
    • +1
      тогда уж doubango.org/
    • +1
      Я рассмотрю webrtc2sip в следующей статье, куда и включу эту ссылку. Здесь все-таки основы, поэтому упоминания в комментариях будет достаточно.
  • +2
    Прошу прощения, могу тупить.

    «Скачайте server.js в папку приложения». Откуда скачать?

    так-то node-static сервер написал, запускаю, но сокет в клиенте не подключается, чую socket.io еще как-то надо туда вклинить.
    • 0
      «Скачайте server.js в папку приложения». Откуда скачать?

      Прошу прощения, ссылка добавлена в топик, дублирую здесь: server.js

      так-то node-static сервер написал, запускаю, но сокет в клиенте не подключается, чую socket.io еще как-то надо туда вклинить.

      Хороший вопрос :) Socket.io автоматечески отдаст client-side для socket.io после старта сервера по адресу http://127.0.0.1:1234/socket.io/socket.io.js
      • 0
        Спасибо!

        Все стартует, но сервер у меня не локальный, повесил на порт 8080, все запускается и пашет по адресу
        http :// блаблабла.io:8080/skype/index.html

        в клиенте прописываю:
        var socket = io.connect('http :// блаблабла.io', {port: 8080}); // по аналогии с документацией var socket = io.connect('http://localhost');

        но в консоле следующее:
        ReferenceError: require is not defined
        ...r socket = io.connect(' http :// блаблабла.io', {port: 8080…

        как гласят интернеты:
        You cannot source /socket-lib/socket.io.js;
        you must source «http :// yourwebsite.com:12345/socket.io/socket.io.js».

        это все учел, но все равно require is not defined.

        Судя по исходникам и модулям, socket.io пользует еще и редис, его тоже запустил.

        По теме пока плаваю, т.к. благодаря статье только начал пытаться разобраться.

        з.ы. кстати, кому нужен кусочек облочка для экспериментов, можете тут зарегистрироваться www.nitrous.io/join/ESDgbzo_fuQ и вам и мне будет небольшой бонус
        • 0
          Зарегистрировался по вашей реферральной ссылке :)

          Все отлично заработало и на nitrous.io (порт 8080):

          • загрузите оба файла index.html и server.js
          • измените в файле server.js порт 1234 на 8080
          • npm install socket.io
          • npm install node-static
          • node server.js
          • Preview -> port 8080
          • 0
            прям как есть скопировал, все отлично заработало.

            где-то я видимо перемудрил. Спасибо за терпение и статью ) Буду разбираться.
  • 0
    А что надо сделать, чтобы всё это работало для двух человек, находящихся за NAT'ом? Поднимать STUN/TURN серверы?
    • 0
      Да, или воспользоватьсяя одним из общедоступных: code.google.com/p/natvpn/source/browse/trunk/stun_server_list
      • 0
        Возможно, покажусь излишне ленивым, но не подскажите, как по быстрому указать в index.html, чтобы скрипт подхватывал STUN-сервер?
        • 0
          Заменить строку 39 на:
          var pc_config = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}]};
          pc = new PeerConnection(pc_config);
          
          • 0
            Печаль, NAT всё-равно не пробивает =/
            • 0
              Прошу Вас написать сюда решение проблемы в случае если его найдете.
              • +1
                Решение найдено — с помощью бесплатного тестового TURN сервера — пробивает двойной NAT:

                var pc_config = {"iceServers": [{"url": "turn:drakmail%40delta.pm@numb.viagenie.ca:3478", "credential": "PLACE_HERE_YOUR_PASSWORD"}, {"url": "stun:stun.l.google.com:19302"}]};
  • +1
    У WebRTC есть один большой минус — вещать на 20+ клиентов проблематично (не говоря уже о паре тысяч) — тупо не хватит канала у вещающего. Или есть какие-нибудь серверы, которые умеет ретранслировать webrtc?
  • 0
    Отредактировал демку jsfiddle.net/HRJP8/84/ для поддержки и классической Оперы 12

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.